/*
 * nasd_script_client.c
 *
 * Simple NASD client to allow scriptable long-lived drive connections
 * from Perl or some other scripting language.
 *
 * Author: Nat Lanza
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */

#include <nasd/nasd_options.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <nasd/nasd_getopt.h>
#include <nasd/nasd_pdrive.h>
#include <nasd/nasd_pdrive_client.h>
#include <nasd/nasd_pdrive_client_kpdev.h>
#include <nasd/nasd_security.h>
#include <nasd/nasd_types_marshall.h>
#include <nasd/nasd_mem.h>

#define VERSION "1.00"
#define WHITESPACE "\t\n "
#define CMD_TABLE_SIZE 32

/* types */
typedef struct nasd_sc_cmd_s {
  char name[64];
  int  (*cmd)(char *, char *);
} nasd_sc_cmd_t;


/* prototypes for actual commands */
int nasd_sc_cmd_unimplemented(char *cmd, char *arg_str);
int nasd_sc_cmd_bind(char *cmd, char *arg_str);
int nasd_sc_cmd_create(char *cmd, char *arg_str);
int nasd_sc_cmd_driveinfo(char *cmd, char *arg_str);
int nasd_sc_cmd_eject(char *cmd, char *arg_str);
int nasd_sc_cmd_flush(char *cmd, char *arg_str);
int nasd_sc_cmd_getattr(char *cmd, char *arg_str);
int nasd_sc_cmd_initialize(char *cmd, char *arg_str);
int nasd_sc_cmd_listpart(char *cmd, char *arg_str);
int nasd_sc_cmd_noop(char *cmd, char *arg_str);
int nasd_sc_cmd_null(char *cmd, char *arg_str);
int nasd_sc_cmd_partinfo(char *cmd, char *arg_str);
int nasd_sc_cmd_partition(char *cmd, char *arg_str);
int nasd_sc_cmd_rangeread(char *cmd, char *arg_str);
int nasd_sc_cmd_rangetread(char *cmd, char *arg_str);
int nasd_sc_cmd_rangewrite(char *cmd, char *arg_str);
int nasd_sc_cmd_read(char *cmd, char *arg_str);
int nasd_sc_cmd_remove(char *cmd, char *arg_str);
int nasd_sc_cmd_quit(char *cmd, char *arg_str);
int nasd_sc_cmd_setattr(char *cmd, char *arg_str);
int nasd_sc_cmd_sync(char *cmd, char *arg_str);
int nasd_sc_cmd_tread(char *cmd, char *arg_str);
int nasd_sc_cmd_unbind(char *cmd, char *arg_str);
int nasd_sc_cmd_write(char *cmd, char *arg_str);


/* utility prototypes */
void usage(void);
int nasd_sc_do_command(char *cmd, char *arg_str);
void nasd_sc_loop(void);
void nasd_sc_setup(void);
void nasd_sc_cleanup(void);

/* globals */
char                      *progname;
int                        binding_type = NASD_BIND_DEFAULT;
int                        binding_args_len = 0;
void                      *binding_args = NULL;
char                      *binding_port = NASD_PDRIVE_PORT;
nasd_drive_param_kpdev_t   kpdev_args;
int                        nondefault_binding = 0;
nasd_drive_handle_t        h;
char                       drive_name[512];
nasd_sc_cmd_t              cmd_table[CMD_TABLE_SIZE];

int                        datainfd, dataoutfd;
FILE                      *datain = NULL, *dataout = NULL;

/* state */
int drive_is_bound = 0;

int main(int argc, char **argv) {
  nasd_status_t rc;
  char c;

  if (argc == 3) {
    datainfd  = strtol(argv[1], NULL, 10);
    dataoutfd = strtol(argv[2], NULL, 10);
    datain  = fdopen(datainfd,  "r");
    dataout = fdopen(dataoutfd, "w");
  } else if (argc == 1) {
    datainfd = fileno(stdin);
    dataoutfd = fileno(stdout);
    datain = stdin;
    dataout = stdout;
  } else {
    printf("! 1\nusage: %d [<data input fd> <data output fd>]\n");
    exit(1);
  }

  setvbuf(stdout, NULL, _IONBF, 0);
  setvbuf(stdin, NULL, _IONBF, 0);

  progname = argv[0];

  while (nasd_getopt(argc, argv, "klM", &c)) {
    switch (c) {
      case 'k':
	if (nondefault_binding) { usage(); }
	nondefault_binding = 1;
	binding_type = NASD_BIND_KPDEV_DEFAULT;
        binding_args = &kpdev_args;
        binding_args_len = sizeof(kpdev_args);
        strcpy(kpdev_args.devname, "/dev/nasdkp0");
        break;
      case 'l':
	if (nondefault_binding) { usage(); }
	nondefault_binding = 1;
        binding_type = NASD_BIND_COLOCATE;
        binding_args = &kpdev_args;
        binding_args_len = sizeof(kpdev_args);
        strcpy(kpdev_args.devname, "/dev/nasdkp0");
      case 'M':
	if (nondefault_binding) { usage(); }
	nondefault_binding = 1;
	binding_type = NASD_BIND_MSGQ;
        break;
      default:
	printf("! 1\nUnknown option '%c'\n", nasd_optopt);
	fflush(stdout);
	usage();
    }
  }

  rc = nasd_cl_p_init();
  if (rc) {
    printf("! 1\ncannot initialize client library, err 0x%x (%s)\n",
	   rc, nasd_error_string(rc));
    fflush(stdout);
    exit(1);
  }

  nasd_sc_setup();
  nasd_sc_loop();
  nasd_sc_cleanup();

  nasd_cl_p_shutdown();

  exit(0);
}

/*
 * utility commands
 */

void usage(void) {
  printf("! 5\n");
  printf("Usage: %s [options]\n", progname);
  printf("Options:\n");
  printf(" -k use kernel device\n");
  printf(" -l use colocated drive\n");
  printf(" -M use message queues\n");
  fflush(stdout);
  exit(1);
}


void nasd_sc_setup(void) {
  int i = 0;

#define SETUP_COMMAND(_name_, _cmd_) { \
           if (i >= CMD_TABLE_SIZE) { \
           printf("! 1\nCommand table overflow\n"); fflush(stdout); exit(1); }\
           strcpy(cmd_table[i].name, _name_); \
           cmd_table[i].cmd = _cmd_; i++; }\

  SETUP_COMMAND("bind",       nasd_sc_cmd_bind);
  SETUP_COMMAND("create",     nasd_sc_cmd_create);
  SETUP_COMMAND("driveinfo",  nasd_sc_cmd_driveinfo);
  SETUP_COMMAND("eject",      nasd_sc_cmd_eject);
  SETUP_COMMAND("flush",      nasd_sc_cmd_flush);
  SETUP_COMMAND("getattr",    nasd_sc_cmd_getattr);
  SETUP_COMMAND("initialize", nasd_sc_cmd_initialize);
  SETUP_COMMAND("listpart",   nasd_sc_cmd_listpart);
  SETUP_COMMAND("noop",       nasd_sc_cmd_noop);
  SETUP_COMMAND("null",       nasd_sc_cmd_null);
  SETUP_COMMAND("partinfo",   nasd_sc_cmd_partinfo);
  SETUP_COMMAND("partition",  nasd_sc_cmd_partition);
  SETUP_COMMAND("quit",       nasd_sc_cmd_quit);
  SETUP_COMMAND("rangeread",  nasd_sc_cmd_rangeread);
  SETUP_COMMAND("rangetread", nasd_sc_cmd_rangetread);
  SETUP_COMMAND("rangewrite", nasd_sc_cmd_rangewrite);
  SETUP_COMMAND("read",       nasd_sc_cmd_read);
  SETUP_COMMAND("remove",     nasd_sc_cmd_remove);
  SETUP_COMMAND("setattr",    nasd_sc_cmd_setattr);
  SETUP_COMMAND("sync",       nasd_sc_cmd_sync);
  SETUP_COMMAND("tread",      nasd_sc_cmd_tread);
  SETUP_COMMAND("unbind",     nasd_sc_cmd_unbind);
  SETUP_COMMAND("write",      nasd_sc_cmd_write);

  while (i < CMD_TABLE_SIZE) { SETUP_COMMAND("", nasd_sc_cmd_unimplemented); }
#undef SETUP_COMMAND
}


void nasd_sc_cleanup(void) {
  if (drive_is_bound) { nasd_unbind_drive(&h); } /* clean up */
}


void nasd_sc_loop(void) {
  int err, done = 0, seg_len = 0;
  char buffer[2048], word[64];
  char *buf_ptr;

  int pid = getpid();

  err = printf(". 1\nVERSION %s\n", VERSION);

  fflush(stdout);

  while (!done && fgets(buffer, 2048, stdin)) {
    buf_ptr = buffer + strspn(buffer, WHITESPACE);
    seg_len = strcspn(buf_ptr, WHITESPACE);

    strncpy(word, buf_ptr, seg_len);
    word[seg_len] = '\0';

    buf_ptr += seg_len;
    seg_len = strspn(buf_ptr, WHITESPACE);
    buf_ptr += seg_len;

    done = nasd_sc_do_command(word, buf_ptr);
  }
}


int nasd_sc_do_command(char *cmd, char *arg_str) {
  int finished = 0, i;

  for (i = 0; i < CMD_TABLE_SIZE; i++) {
    if (!strcasecmp(cmd_table[i].name, cmd)) {
      finished = cmd_table[i].cmd(cmd, arg_str);
      goto done;
    }
  }

  printf("! 1\nUNKNOWN COMMAND \"%s\"\n", cmd);

 done:
  return finished;
}

char *get_arg_long(char *buf_ptr, unsigned long *val) {
  char *next_ptr;

  *val = strtol(buf_ptr, &next_ptr, 10);
  if (next_ptr == buf_ptr) { return NULL;     }
  else                     { return next_ptr; }
}


char *get_arg_str(char *buf_ptr, char *str, int max_len) {
  int seg_len;

  buf_ptr += strspn(buf_ptr, WHITESPACE);
  seg_len = strcspn(buf_ptr, WHITESPACE);
  if ((seg_len < 1) || (seg_len >= max_len)) { return NULL; }
  strncpy(str, buf_ptr, seg_len);
  str[seg_len] = '\0';
  buf_ptr += seg_len;

  return buf_ptr;
}


/*
 * actual commands
 */

int nasd_sc_cmd_unimplemented(char *cmd, char *arg_str) {
  printf("! 1\nCommand \"%s\" unimplemented\n", cmd);
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_bind(char *cmd, char *arg_str) {
  nasd_status_t rc;
  char *buf_ptr = arg_str;
  
  if (drive_is_bound) {
    printf("! 1\nAlready bound to drive \"%s\"\n", drive_name);
    goto done;
  }

  buf_ptr = get_arg_str(buf_ptr, drive_name, 512);
  if (!buf_ptr) { goto usage; }

  rc = nasd_bind_to_drive(drive_name, binding_port, binding_type,
			  binding_args, binding_args_len, &h);
  if (rc) {
    printf("! 1\nbind failed: error 0x%x (%s)\n", rc, nasd_error_string(rc));
    goto done;
  }

  drive_is_bound = 1;

  printf(". 1\nOK\n");

 done:
  fflush(stdout);
  return 0;
 usage:
  printf("! 1\nusage: BIND <drivename>\n");
  fflush(stdout);
  return 0;  
}


int nasd_sc_cmd_create(char *cmd, char *arg_str) {
  char password[64], *buf_ptr = arg_str;
  int partnum, sec_level;

  nasd_sec_keyring_t       keys;
  nasd_security_param_t    sec_param;
  nasd_p_create_dr_args_t  args;
  nasd_p_create_dr_res_t   res;
  nasd_uint16              protection;
  nasd_status_t            rc;
  nasd_error_string_t      err_str;
  nasd_rpc_status_t        op_status;

  if (!drive_is_bound) { printf("! 1\nNo drive bound\n"); goto done; }
  
  /* fetch the arguments */

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &partnum);
  if (!buf_ptr) { goto usage; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &sec_level);
  if (!buf_ptr) { goto usage; }
  rc = nasd_sec_seclevel_to_protection(sec_level, &protection);
  if (rc) { printf("! 1\nBad security level %d\n", sec_level); goto done; }
  
  buf_ptr = get_arg_str(buf_ptr, password, 64);
  if (!buf_ptr) { goto usage; }
  nasd_sec_password_to_keys(password, 0, &keys);

  sec_param.type = NASD_BLACK_KEY;
  sec_param.partnum = partnum;
  sec_param.actual_protection = protection;
  
  bzero(&args, sizeof(nasd_p_create_dr_args_t));
  args.in_partnum = partnum;

  nasd_cl_p_create_dr(h, keys.black_key, &sec_param, NULL,
		      &args, &res, &op_status);
  rc = res.nasd_status;

  if (rc || op_status) {
    printf("! 1\ncreate failed: nasd status 0x%x (%s), op status 0x%x (%s)\n",
	   rc, nasd_error_string(rc), op_status,
	   nasd_cl_error_string(h, op_status, err_str));
    goto done;
  }

  printf(". 2\nOK\n0x%" NASD_ID_FMT "\n", res.out_identifier);
  
 done:
  fflush(stdout);
  return 0;
 usage:
  printf("! 1\nusage: CREATE <partition> <protection> <password>\n");
  fflush(stdout);
  return 0;  
}


int nasd_sc_cmd_driveinfo(char *cmd, char *arg_str){ 
  char password[64], *buf_ptr = arg_str;
  int sec_level;

  nasd_sec_keyring_t      keys;
  nasd_security_param_t   sec_param;
  nasd_ctrl_drive_info_t  drinfo;
  nasd_uint16             protection;
  nasd_status_t           rc;

  if (!drive_is_bound) { printf("! 1\nNo drive bound\n"); goto done; }
  
  /* fetch the arguments */

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &sec_level);
  if (!buf_ptr) { goto usage; }
  rc = nasd_sec_seclevel_to_protection(sec_level, &protection);
  if (rc) { printf("! 1\nBad security level %d\n", sec_level); goto done; }
  
  buf_ptr = get_arg_str(buf_ptr, password, 64);
  if (!buf_ptr) { goto usage; }
  nasd_sec_password_to_keys(password, 0, &keys);
  
  sec_param.type = NASD_BLACK_KEY;
  sec_param.partnum = 1;
  sec_param.actual_protection = protection;

  rc = nasd_cl_p_ctrl_get_drive_info(h, keys.black_key, &sec_param,
				     NULL, &drinfo);

  if (rc) {
    printf("! 1\ndriveinfo failed: error 0x%x (%s)\n",
	   rc, nasd_error_string(rc));
    goto done;
  }

  if (drinfo.ctrl_id != NASD_CTRL_DRIVE_INFO) {
    printf("! 1\ndriveinfo got a corrupted control object!\n");
    goto done;
  }

  printf(". 6\nOK\n%" NASD_64u_FMT "\n"  /* max_parts        0 */
	          "%" NASD_64u_FMT "\n"  /* blocksize        1 */
      	          "%" NASD_64u_FMT "\n"  /* num_parts        2 */
	          "%" NASD_64u_FMT "\n"  /* num_blocks       3 */
	          "%" NASD_64u_FMT "\n", /* blocks_allocated 4 */
	 drinfo.max_parts, drinfo.blocksize, drinfo.num_parts,
	 drinfo.num_blocks, drinfo.blocks_allocated);

 done:
  fflush(stdout);
  return 0;

 usage:
  printf("! 1\nusage: DRIVEINFO <protection> <password>\n");
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_eject(char *cmd, char *arg_str) {
  char password[64], tmp[64], *buf_ptr = arg_str;
  int partnum, sec_level;

  nasd_identifier_t           nasdid;
  nasd_sec_keyring_t          keys;
  nasd_security_param_t       sec_param;
  nasd_p_eject_obj_dr_args_t  args;
  nasd_p_eject_obj_dr_res_t   res;
  nasd_uint16                 protection;
  nasd_status_t               rc;
  nasd_error_string_t         err_str;
  nasd_rpc_status_t           op_status;

  if (!drive_is_bound) { printf("! 1\nNo drive bound\n"); goto done; }
  
  /* fetch the arguments */
  
  buf_ptr = get_arg_str(buf_ptr, tmp, 64);
  if (!buf_ptr) { goto usage; }
  rc = nasd_str_to_nasd_id(tmp, &nasdid);
  if (rc) { printf("! 1\nBad nasdid '%s'\n", tmp); goto done; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &partnum);
  if (!buf_ptr) { goto usage; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &sec_level);
  if (!buf_ptr) { goto usage; }
  rc = nasd_sec_seclevel_to_protection(sec_level, &protection);
  if (rc) { printf("! 1\nBad security level %d\n", sec_level); goto done; }
  
  buf_ptr = get_arg_str(buf_ptr, password, 64);
  if (!buf_ptr) { goto usage; }
  nasd_sec_password_to_keys(password, 0, &keys);

  sec_param.type = NASD_BLACK_KEY;
  sec_param.partnum = partnum;
  sec_param.actual_protection = protection;
  
  bzero(&args, sizeof(nasd_p_eject_obj_dr_args_t));

  args.in_partnum    = partnum;
  args.in_identifier = nasdid;

  nasd_cl_p_eject_obj_dr(h, keys.black_key, &sec_param, NULL,
			 &args, &res, &op_status);
  rc = res.nasd_status;

  if (rc || op_status) {
    printf("! 1\neject failed: nasd status 0x%x (%s), op status 0x%x (%s)\n",
	   rc, nasd_error_string(rc), op_status,
	   nasd_cl_error_string(h, op_status, err_str));
    goto done;
  }

  printf(". 1\nOK\n");

 done:
  fflush(stdout);
  return 0;
 usage:
  printf("! 1\nusage: EJECT <identifier> <partition> <protection <password>\n");
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_flush(char *cmd, char *arg_str) {
  char password[64], tmp[64], *buf_ptr = arg_str;
  int partnum, sec_level;

  nasd_identifier_t           nasdid;
  nasd_sec_keyring_t          keys;
  nasd_security_param_t       sec_param;
  nasd_p_flush_obj_dr_args_t  args;
  nasd_p_flush_obj_dr_res_t   res;
  nasd_uint16                 protection;
  nasd_status_t               rc;
  nasd_error_string_t         err_str;
  nasd_rpc_status_t           op_status;

  if (!drive_is_bound) { printf("! 1\nNo drive bound\n"); goto done; }
  
  /* fetch the arguments */
  
  buf_ptr = get_arg_str(buf_ptr, tmp, 64);
  if (!buf_ptr) { goto usage; }
  rc = nasd_str_to_nasd_id(tmp, &nasdid);
  if (rc) { printf("! 1\nBad nasdid '%s'\n", tmp); goto done; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &partnum);
  if (!buf_ptr) { goto usage; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &sec_level);
  if (!buf_ptr) { goto usage; }
  rc = nasd_sec_seclevel_to_protection(sec_level, &protection);
  if (rc) { printf("! 1\nBad security level %d\n", sec_level); goto done; }
  
  buf_ptr = get_arg_str(buf_ptr, password, 64);
  if (!buf_ptr) { goto usage; }
  nasd_sec_password_to_keys(password, 0, &keys);

  sec_param.type = NASD_BLACK_KEY;
  sec_param.partnum = partnum;
  sec_param.actual_protection = protection;
  
  bzero(&args, sizeof(nasd_p_flush_obj_dr_args_t));

  args.in_partnum    = partnum;
  args.in_identifier = nasdid;

  nasd_cl_p_flush_obj_dr(h, keys.black_key, &sec_param, NULL,
			 &args, &res, &op_status);
  rc = res.nasd_status;

  if (rc || op_status) {
    printf("! 1\nflush failed: nasd status 0x%x (%s), op status 0x%x (%s)\n",
	   rc, nasd_error_string(rc), op_status,
	   nasd_cl_error_string(h, op_status, err_str));
    goto done;
  }

  printf(". 1\nOK\n");

 done:
  fflush(stdout);
  return 0;
 usage:
  printf("! 1\nusage: FLUSH <identifier> <partition> <protection <password>\n");
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_getattr(char *cmd, char *arg_str) {
  char password[64], tmp[64], *buf_ptr = arg_str;
  int partnum, sec_level, err;

  nasd_identifier_t           nasdid;
  nasd_sec_keyring_t          keys;
  nasd_security_param_t       sec_param;
  nasd_p_getattr_dr_args_t    args;
  nasd_p_getattr_dr_res_t     res;
  nasd_uint16                 protection;
  nasd_status_t               rc;
  nasd_error_string_t         err_str;
  nasd_rpc_status_t           op_status;

  nasd_attribute_t            *attr;

  if (!drive_is_bound) { printf("! 1\nNo drive bound\n"); goto done; }
  
  /* fetch the arguments */
  
  buf_ptr = get_arg_str(buf_ptr, tmp, 64);
  if (!buf_ptr) { goto usage; }
  rc = nasd_str_to_nasd_id(tmp, &nasdid);
  if (rc) { printf("! 1\nBad nasdid '%s'\n", tmp); goto done; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &partnum);
  if (!buf_ptr) { goto usage; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &sec_level);
  if (!buf_ptr) { goto usage; }
  rc = nasd_sec_seclevel_to_protection(sec_level, &protection);
  if (rc) { printf("! 1\nBad security level %d\n", sec_level); goto done; }
  
  buf_ptr = get_arg_str(buf_ptr, password, 64);
  if (!buf_ptr) { goto usage; }
  nasd_sec_password_to_keys(password, 0, &keys);

  sec_param.type = NASD_BLACK_KEY;
  sec_param.partnum = partnum;
  sec_param.actual_protection = protection;
  
  args.in_identifier = nasdid;
  args.in_partnum    = partnum;

  nasd_cl_p_getattr_dr(h, keys.black_key, &sec_param, NULL,
		       &args, &res, &op_status);
  rc = res.nasd_status;

  attr = &res.out_attribute;

  if (rc || op_status) {
    printf("! 1\ngetattr failed: nasd status 0x%x (%s), op status 0x%x (%s)\n",
	   rc, nasd_error_string(rc), op_status,
	   nasd_cl_error_string(h, op_status, err_str));
    goto done;
  }

  printf(". 12\nOK DATA %d\n"
	 "%" NASD_64u_FMT "\n"    /* block_preallocation     0 */
	 "%" NASD_64u_FMT "\n"    /* blocks_used             1 */
	 "%u\n"                   /* block_size              2 */
	 "%hd\n"                  /* av                      3 */
	 "%" NASD_64u_FMT "\n"    /* object_len              4 */
	 "%d:%d\n"                /* attr_modify_time        5 */
	 "%d:%d\n"                /* object_modify_time      6 */
	 "%d:%d\n"                /* object_create_time      7 */
	 "%d:%d\n"                /* fs_attr_modify_time     8 */
	 "%d:%d\n"                /* fs_object_modify_time   9 */
	 "0x%" NASD_ID_FMT  "\n", /* layout_hint            10 */
	 NASD_FS_SPECIFIC_INFO_SIZE, attr->block_preallocation,
	 attr->blocks_used, attr->block_size, attr->av, attr->object_len,
	 attr->attr_modify_time.ts_sec, attr->attr_modify_time.ts_nsec,
	 attr->object_modify_time.ts_sec, attr->object_modify_time.ts_nsec,
	 attr->object_create_time.ts_sec, attr->object_create_time.ts_nsec,
	 attr->fs_attr_modify_time.ts_sec, attr->fs_attr_modify_time.ts_nsec,
	 attr->fs_object_modify_time.ts_sec,
	 attr->fs_object_modify_time.ts_nsec, attr->layout_hint.lh_nid);

  err = write(dataoutfd, attr->fs_specific, NASD_FS_SPECIFIC_INFO_SIZE);

  if (err != NASD_FS_SPECIFIC_INFO_SIZE) {
    fprintf(stderr, "uh-oh. short data write of %d bytes, expected %d!\n",
	    err, NASD_FS_SPECIFIC_INFO_SIZE);
  }

 done:
  fflush(stdout);
  return 0;
 usage:
  printf("! 1\nusage: GETATTR <identifier> <partition> <protection <password>\n");
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_initialize(char *cmd, char *arg_str) {
  char password[64], *buf_ptr = arg_str;

  nasd_sec_keyring_t           keys;
  nasd_p_initialize_dr_args_t  args;
  nasd_p_initialize_dr_res_t   res;
  nasd_error_string_t          error_text;
  nasd_rpc_status_t            op_status;
  nasd_status_t                rc;

  if (!drive_is_bound) { printf("! 1\nNo drive bound\n"); goto done; }

  /* fetch the arguments */

  buf_ptr = get_arg_str(buf_ptr, password, 64);
  if (!buf_ptr) { goto usage; }
  nasd_sec_password_to_keys(password, 0, &keys);

  memcpy(args.in_master_key, keys.master_key, sizeof(nasd_key_t));
  memcpy(args.in_drive_key,  keys.drive_key,  sizeof(nasd_key_t));

  nasd_cl_p_initialize_dr(h, &args, &res, &op_status);
  rc = res.nasd_status;
  
  if (rc || op_status) {
    printf("! 1\ninitialize failed: nasd status 0x%x (%s), op status 0x%x (%s)\n",
	   rc, nasd_error_string(rc), op_status,
	   nasd_cl_error_string(h, op_status, error_text));
  } else {
    printf(". 1\nOK\n");
  }

 done:
  fflush(stdout);
  return 0;
 usage:
  printf("! 1\nusage: INITIALIZE <password>\n");
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_listpart(char *cmd, char *arg_str) {
  char password[64], *buf_ptr = arg_str;
  int partnum, sec_level, chunk_size;

  nasd_uint64                num_objects, obj_no, i;

  nasd_sec_keyring_t         keys;
  nasd_security_param_t      sec_param;
  nasd_ctrl_part_info_t      ptinfo;
  nasd_p_smpl_op_dr_args_t   args;
  nasd_p_fastread_dr_res_t   res;
  nasd_offset_t              offset;
  nasd_len_t                 len;
  nasd_uint16                protection;
  nasd_error_string_t        error_text;
  nasd_status_t              rc;
  nasd_rpc_status_t          op_status;
  nasd_identifier_otw_t     *objs_otw = NULL;
  nasd_identifier_t         *objs = NULL;

  if (!drive_is_bound) { printf("! 1\nNo drive bound\n"); goto done; }
  
  /* fetch the arguments */

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &partnum);
  if (!buf_ptr) { goto usage; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &sec_level);
  if (!buf_ptr) { goto usage; }
  rc = nasd_sec_seclevel_to_protection(sec_level, &protection);
  if (rc) { printf("! 1\nBad security level %d\n", sec_level); goto done; }
  
  buf_ptr = get_arg_str(buf_ptr, password, 64);
  if (!buf_ptr) { goto usage; }
  nasd_sec_password_to_keys(password, 0, &keys);
  

  sec_param.type = NASD_BLACK_KEY;
  sec_param.partnum = partnum;
  sec_param.actual_protection = protection;

  rc = nasd_cl_p_ctrl_get_part_info(h, keys.black_key, &sec_param,
				    NULL, partnum, &ptinfo);
  if (rc) {
    printf("! 1\nCouldn't get partition info: error 0x%x (%s)\n",
	   rc, nasd_error_string(rc));
    goto done;
  }
  if (ptinfo.ctrl_id != NASD_CTRL_PART_INFO) {
    printf("! 1\nGot a corrupted partinfo control object!\n");
    goto done;
  }

  num_objects = ptinfo.num_obj;
  chunk_size = sizeof(nasd_identifier_otw_t) * 8192;

  NASD_Malloc(objs, sizeof(nasd_identifier_t) * num_objects,
	      (nasd_identifier_t *));
  NASD_Malloc(objs_otw, chunk_size, (nasd_identifier_otw_t *));
  if (!objs || !objs_otw) {
    printf("! 1\nCouldn't allocate buffers!\n"); goto done; }

  sec_param.type = NASD_PARTITION_KEY;
  args.in_partnum    = partnum;
  args.in_identifier = (nasd_identifier_t) NASD_CTRL_PART_OBJS;

  offset = 0; obj_no = 0;

  do {
    args.in_offset  = offset;
    args.in_len     = chunk_size;
    nasd_cl_p_read_simple_dr(h, keys.part_key, &sec_param, NULL,
			     &args, objs_otw, &res, &op_status);    
    len = res.out_datalen;
    rc  = res.nasd_status;
    if (rc || op_status) {
      printf("! 1\ncontrol obj read failed: nasd status 0x%x (%s), op status 0x%x (%s)\n",
	     rc, nasd_error_string(rc), op_status,
	     nasd_cl_error_string(h, op_status, error_text));
      goto done;
    }

    for (i = 0; i < (len/sizeof(nasd_identifier_otw_t)); i++) {
      nasd_identifier_t_unmarshall(objs_otw[obj_no], &(objs[obj_no]));
      obj_no++;
    }

    offset += len;
  } while (len != 0);
  
  printf(". %" NASD_64u_FMT "\nOK\n", obj_no + 1);
  for (i = 0; i < obj_no; i++) { printf("0x%" NASD_ID_FMT "\n", objs[i]); }
  
 done:
  fflush(stdout);  
  return 0;

 usage:
  printf("! 1\nusage: LISTPART <partition> <protection> <password>\n");
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_noop(char *cmd, char *arg_str) {
  printf(". 1\nOK\n");
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_null(char *cmd, char *arg_str) {
  nasd_status_t        rc;
  nasd_rpc_status_t    op_status;
  nasd_error_string_t  err_str;

  if (!drive_is_bound) { printf("! 1\nNo drive bound\n"); goto done; }

  nasd_cl_p_null_dr(h, &rc, &op_status);

  if (rc || op_status) {
    printf("! 1\nnull failed: nasd status 0x%x (%s), op status 0x%x (%s)\n",
	   rc, nasd_error_string(rc), op_status,
	   nasd_cl_error_string(h, op_status, err_str));
    goto done;
  }

  printf(". 1\nOK\n");

 done:
  fflush(stdout);
  return 0;

#if 0
 usage:
  printf("! 1\nusage: NULL\n");
  fflush(stdout);
  return 0;
#endif
}


int nasd_sc_cmd_partinfo(char *cmd, char *arg_str) {
  char password[64], *buf_ptr = arg_str;
  int partnum, sec_level;

  nasd_sec_keyring_t      keys;
  nasd_security_param_t   sec_param;
  nasd_ctrl_part_info_t   ptinfo;
  nasd_uint16             protection;
  nasd_status_t           rc;

  if (!drive_is_bound) { printf("! 1\nNo drive bound\n"); goto done; }
  
  /* fetch the arguments */

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &partnum);
  if (!buf_ptr) { goto usage; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &sec_level);
  if (!buf_ptr) { goto usage; }
  rc = nasd_sec_seclevel_to_protection(sec_level, &protection);
  if (rc) { printf("! 1\nBad security level %d\n", sec_level); goto done; }
  
  buf_ptr = get_arg_str(buf_ptr, password, 64);
  if (!buf_ptr) { goto usage; }
  nasd_sec_password_to_keys(password, 0, &keys);
  
  sec_param.type = NASD_BLACK_KEY;
  sec_param.partnum = partnum;
  sec_param.actual_protection = protection;

  rc = nasd_cl_p_ctrl_get_part_info(h, keys.black_key, &sec_param,
				    NULL, partnum, &ptinfo);

  if (rc) {
    printf("! 1\npartinfo failed: error 0x%x (%s)\n",
	   rc, nasd_error_string(rc));
    goto done;
  }

  if (ptinfo.ctrl_id != NASD_CTRL_PART_INFO) {
    printf("! 1\npartinfo got a corrupted control object!\n");
    goto done;
  }

  printf(". 9\nOK\n0x%" NASD_ID_FMT  "\n" /* first_obj        0 */
	            "%" NASD_64u_FMT "\n" /* num_obj          1 */
	            "%" NASD_64u_FMT "\n" /* part_size        2 */
	            "%" NASD_64u_FMT "\n" /* blocks_used      3 */
	            "%" NASD_64u_FMT "\n" /* blocks_allocated 4 */
	            "%" NASD_64u_FMT "\n" /* max_objs         5 */
	            "%" NASD_64u_FMT "\n" /* blocksize        6 */
	            "%hu\n",              /* min_protection   7 */
	 ptinfo.first_obj, ptinfo.num_obj, ptinfo.part_size,
	 ptinfo.blocks_used, ptinfo.blocks_allocated, ptinfo.max_objs,
	 ptinfo.blocksize, ptinfo.min_protection);

 done:
  fflush(stdout);
  return 0;

 usage:
  printf("! 1\nusage: PARTINFO <partition> <protection> <password>\n");
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_partition(char *cmd, char *arg_str) {
  /* partnum blocks protection password */
  int partnum, blocks, prot_level;
  char password[64], *buf_ptr = arg_str;

  nasd_sec_keyring_t          keys;
  nasd_security_param_t       sec_param;
  nasd_p_part_creat_dr_args_t args;
  nasd_p_part_creat_dr_res_t  res;
  nasd_error_string_t         error_text;
  nasd_status_t               rc;
  nasd_rpc_status_t           op_status;
  nasd_uint16                 protection;

  if (!drive_is_bound) { printf("! 1\nNo drive bound\n"); goto done; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &partnum);
  if (!buf_ptr) { goto usage; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &blocks);
  if (!buf_ptr) { goto usage; }
  
  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &prot_level);
  if (!buf_ptr) { goto usage; }

  buf_ptr = get_arg_str(buf_ptr, password, 64);

  memset((char *)&args, 0, sizeof(nasd_p_part_creat_dr_args_t)); 
  
  /* handle protection level */
  rc = nasd_sec_seclevel_to_protection(prot_level, &args.in_min_protection);
  if (rc) { printf("! 1\nBad security level %d\n", prot_level); goto done; }

  /* create keys from password */
  nasd_sec_password_to_keys(password, partnum, &keys);

  /* initial keys for this partition */
  memcpy(args.in_partition_key, keys.part_key,  sizeof(nasd_key_t));
  memcpy(args.in_red_key,       keys.red_key,   sizeof(nasd_key_t));
  memcpy(args.in_black_key,     keys.black_key, sizeof(nasd_key_t));

  /* fill in capability and args */
  nasd_sec_seclevel_to_protection(NASD_MAX_SECURITY_LEVEL, &protection);
  sec_param.actual_protection = protection;
  sec_param.type              = NASD_DRIVE_KEY;
  sec_param.partnum           = partnum;

  args.in_partnum             = partnum;
  args.in_blkcnt              = blocks;

  nasd_cl_p_part_creat_dr(h, keys.drive_key, &sec_param, NULL,
			  &args, &res, &op_status);
  rc = res.nasd_status;

  if (rc || op_status) {
    printf("! 1\npartition failed: nasd status 0x%x (%s), op status 0x%x (%s)\n",
	   rc, nasd_error_string(rc), op_status,
	   nasd_cl_error_string(h, op_status, error_text));
  } else {
    printf(". 1\nOK\n");
  }

 done:
  fflush(stdout);
  return 0;
  
 usage:
  printf("! 1\nusage: PARTITION <partnum> <blocks> <protection> <password>\n");
  fflush(stdout);
  
  return 0;
}


int nasd_sc_cmd_rangeread(char *cmd, char *arg_str) {
  printf("! 1\nCommand \"%s\" unimplemented\n", cmd);
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_rangetread(char *cmd, char *arg_str) {
  printf("! 1\nCommand \"%s\" unimplemented\n", cmd);
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_rangewrite(char *cmd, char *arg_str) {
  printf("! 1\nCommand \"%s\" unimplemented\n", cmd);
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_read(char *cmd, char *arg_str) {
  /* partnum nasdid offset length protection password */
  int partnum, prot_level;
  size_t bytes_written;
  char password[64], tmp[64], *buf_ptr = arg_str, *buf;
  nasd_offset_t              offset;
  nasd_len_t                 length;
  nasd_identifier_t          nasdid;
  nasd_sec_keyring_t         keys;
  nasd_security_param_t      sec_param;
  nasd_cookie_t              cookie;
  nasd_timespec_t            tm;
  nasd_p_smpl_op_dr_args_t   args;
  nasd_p_fastread_dr_res_t   res;
  nasd_error_string_t        error_text;
  nasd_status_t              rc;
  nasd_rpc_status_t          op_status;
  nasd_uint16                protection;

  if (!drive_is_bound) { printf("! 1\nNo drive bound\n"); goto done; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &partnum);
  if (!buf_ptr) { goto usage; }

  buf_ptr = get_arg_str(buf_ptr, tmp, 64);
  if (!buf_ptr) { goto usage; }
  rc = nasd_str_to_nasd_id(tmp, &nasdid);
  if (rc) { printf("! 1\nBad nasdid '%s'\n", tmp); goto done; }
  
  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &offset);
  if (!buf_ptr) { goto usage; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &length);
  if (!buf_ptr) { goto usage; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &prot_level);
  if (!buf_ptr) { goto usage; }
  rc = nasd_sec_seclevel_to_protection(prot_level, &protection);
  if (rc) { printf("! 1\nBad security level %d\n", prot_level); goto done; }

  buf_ptr = get_arg_str(buf_ptr, password, 64);
  if (!buf_ptr) { goto usage; }
  nasd_sec_password_to_keys(password, 0, &keys);

  /* get buffer */
  NASD_Malloc(buf, length, (char *));
  if (!buf) { printf("! 1\nCouldn't allocate read buffer\n"); goto done; }

  args.in_identifier = nasdid;
  args.in_partnum    = partnum;
  args.in_len        = length;
  args.in_offset     = offset;

  /* security dance */
  nasd_drive_handle_get_time(h, &tm);
  tm.ts_sec += (60*60);

  nasd_sec_build_capability(partnum, nasdid, NASD_ACCESS_RIGHTS_READ,
			    0, tm.ts_sec, protection, NASD_BLACK_CAPABILITY,
			    0, 0, 7, keys.black_key, &cookie);
  
  sec_param.actual_protection = protection;
  sec_param.type              = NASD_DRIVE_KEY;
  sec_param.partnum           = partnum;

  nasd_cl_p_read_simple_dr(h, cookie.key, &sec_param, &cookie.capability,
			   &args, buf, &res, &op_status);
  if (rc || op_status) {
    printf("! 1\nread failed: nasd status 0x%x (%s), op status 0x%x (%s)\n",
	   rc, nasd_error_string(rc), op_status,
	   nasd_cl_error_string(h, op_status, error_text));
    goto done;
  }

  printf(". 1\nOK DATA %u\n", res.out_datalen);

  /* should we wait for an ACK before writing? */
  bytes_written = write(dataoutfd, buf, (size_t) res.out_datalen);

 done:
  fflush(stdout);
  return 0;

 usage:
  printf("! 1\nusage: READ <partnum> <identifier> <offset> <length> <protection> <password>\n");
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_remove(char *cmd, char *arg_str) {
  char password[64], tmp[64], *buf_ptr = arg_str;
  int partnum, sec_level;

  nasd_identifier_t        nasdid;
  nasd_sec_keyring_t       keys;
  nasd_security_param_t    sec_param;
  nasd_p_remove_dr_args_t  args;
  nasd_p_remove_dr_res_t   res;
  nasd_uint16              protection;
  nasd_status_t            rc;
  nasd_error_string_t      err_str;
  nasd_rpc_status_t        op_status;

  if (!drive_is_bound) { printf("! 1\nNo drive bound\n"); goto done; }
  
  /* fetch the arguments */
  
  buf_ptr = get_arg_str(buf_ptr, tmp, 64);
  if (!buf_ptr) { goto usage; }
  rc = nasd_str_to_nasd_id(tmp, &nasdid);
  if (rc) { printf("! 1\nBad nasdid '%s'\n", tmp); goto done; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &partnum);
  if (!buf_ptr) { goto usage; }

  buf_ptr = get_arg_long(buf_ptr, (unsigned long *) &sec_level);
  if (!buf_ptr) { goto usage; }
  rc = nasd_sec_seclevel_to_protection(sec_level, &protection);
  if (rc) { printf("! 1\nBad security level %d\n", sec_level); goto done; }
  
  buf_ptr = get_arg_str(buf_ptr, password, 64);
  if (!buf_ptr) { goto usage; }
  nasd_sec_password_to_keys(password, 0, &keys);

  sec_param.type = NASD_BLACK_KEY;
  sec_param.partnum = partnum;
  sec_param.actual_protection = protection;
  
  bzero(&args, sizeof(nasd_p_remove_dr_args_t));

  args.in_partnum    = partnum;
  args.in_identifier = nasdid;

  nasd_cl_p_remove_dr(h, keys.black_key, &sec_param, NULL,
		      &args, &res, &op_status);
  rc = res.nasd_status;

  if (rc || op_status) {
    printf("! 1\nremove failed: nasd status 0x%x (%s), op status 0x%x (%s)\n",
	   rc, nasd_error_string(rc), op_status,
	   nasd_cl_error_string(h, op_status, err_str));
    goto done;
  }

  printf(". 1\nOK\n");

 done:
  fflush(stdout);
  return 0;
 usage:
  printf("! 1\nusage: REMOVE <identifier> <partition> <protection <password>\n");
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_quit(char *cmd, char *arg_str) {
  printf(". 1\nBYE\n");
  fflush(stdout);
  return 1;
}


int nasd_sc_cmd_setattr(char *cmd, char *arg_str) {
  printf("! 1\nCommand \"%s\" unimplemented\n", cmd);
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_sync(char *cmd, char *arg_str) {
  nasd_status_t        rc;
  nasd_rpc_status_t    op_status;
  nasd_error_string_t  err_str;

  if (!drive_is_bound) { printf("! 1\nNo drive bound\n"); goto done; }

  nasd_cl_p_null_dr(h, &rc, &op_status);

  if (rc || op_status) {
    printf("! 1\nsync failed: nasd status 0x%x (%s), op status 0x%x (%s)\n",
	   rc, nasd_error_string(rc), op_status,
	   nasd_cl_error_string(h, op_status, err_str));
    goto done;
  }

  printf(". 1\nOK\n");

 done:
  fflush(stdout);
  return 0;
#if 0
 usage:
  printf("! 1\nusage: SYNC\n");
  fflush(stdout);
  return 0;
#endif
}


int nasd_sc_cmd_tread(char *cmd, char *arg_str) {
  printf("! 1\nCommand \"%s\" unimplemented\n", cmd);
  fflush(stdout);
  return 0;
}


int nasd_sc_cmd_unbind(char *cmd, char *arg_str) {

  if (!drive_is_bound) {
    printf("! 1\nNot bound to a drive\n");
    fflush(stdout);
  } else {
    nasd_unbind_drive(&h);
    drive_is_bound = 0;
    printf(". 1\nOK\n");
    fflush(stdout);
  }

  return 0;
}


int nasd_sc_cmd_write(char *cmd, char *arg_str) {
  printf("! 1\nCommand \"%s\" unimplemented\n", cmd);
  fflush(stdout);
  return 0;
}
