/*
 * edrfs_script_client.c
 *
 * Simple NASD EDRFS client to allow scriptable long-lived drive
 * connections from Perl or some other scripting language.
 *
 * Author: Nat Lanza
 */
/*
 * Copyright (c) of Carnegie Mellon University, 2000.
 *
 * 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 <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <nasd/nasd_edrfs_types.h>
#include <nasd/nasd_edrfs_client.h>
#include <nasd/nasd_edrfs_server.h>
#include <nasd/nasd_edrfs_error.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 edrfs_sc_cmd_s {
  char name[64];
  int  (*cmd)(char *, char *);
} edrfs_sc_cmd_t;


/* prototypes for actual commands */
int edrfs_sc_cmd_unimplemented(char *cmd, char *arg_str);
int edrfs_sc_cmd_access(char *cmd, char *arg_str);
int edrfs_sc_cmd_bind(char *cmd, char *arg_str);
int edrfs_sc_cmd_create(char *cmd, char *arg_str);
int edrfs_sc_cmd_fsinfo(char *cmd, char *arg_str);
int edrfs_sc_cmd_fsstat(char *cmd, char *arg_str);
int edrfs_sc_cmd_getstats(char *cmd, char *arg_str);
int edrfs_sc_cmd_lookup(char *cmd, char *arg_str);
int edrfs_sc_cmd_mkdir(char *cmd, char *arg_str);
int edrfs_sc_cmd_mount(char *cmd, char *arg_str);
int edrfs_sc_cmd_newcookie(char *cmd, char *arg_str);
int edrfs_sc_cmd_noop(char *cmd, char *arg_str);
int edrfs_sc_cmd_null(char *cmd, char *arg_str);
int edrfs_sc_cmd_quit(char *cmd, char *arg_str);
int edrfs_sc_cmd_readdir(char *cmd, char *arg_str);
int edrfs_sc_cmd_remove(char *cmd, char *arg_str);
int edrfs_sc_cmd_rename(char *cmd, char *arg_str);
int edrfs_sc_cmd_resetstats(char *cmd, char *arg_str);
int edrfs_sc_cmd_rmdir(char *cmd, char *arg_str);
int edrfs_sc_cmd_setattr(char *cmd, char *arg_str);
int edrfs_sc_cmd_symlink(char *cmd, char *arg_str);
int edrfs_sc_cmd_unbind(char *cmd, char *arg_str);


/* utility prototypes */
void usage(void);
int edrfs_sc_do_command(char *cmd, char *arg_str);
void edrfs_sc_loop(void);
void edrfs_sc_setup(void);
void edrfs_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_EDRFS_SERVER_PORT;
int                           nondefault_binding = 0;
nasd_edrfs_handle_t           h;
char                          fm_name[512];
edrfs_sc_cmd_t                cmd_table[CMD_TABLE_SIZE];

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

/* state */
int fm_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, "M", &c)) {
    switch (c) {
      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 drive client library, err 0x%x (%s)\n",
	   rc, nasd_error_string(rc));
    fflush(stdout);
    exit(1);
  }

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

  edrfs_sc_setup();
  edrfs_sc_loop();
  edrfs_sc_cleanup();

  nasd_edrfscli_shutdown();

  exit(0);
}

/*
 * utility commands
 */

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


void edrfs_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("access",      edrfs_sc_cmd_access);
  SETUP_COMMAND("bind",        edrfs_sc_cmd_bind);
  SETUP_COMMAND("create",      edrfs_sc_cmd_create);
  SETUP_COMMAND("fsinfo",      edrfs_sc_cmd_fsinfo);
  SETUP_COMMAND("fsstat",      edrfs_sc_cmd_fsstat);
  SETUP_COMMAND("getstats",    edrfs_sc_cmd_getstats);
  SETUP_COMMAND("lookup",      edrfs_sc_cmd_lookup);
  SETUP_COMMAND("mkdir",       edrfs_sc_cmd_mkdir);
  SETUP_COMMAND("mount",       edrfs_sc_cmd_mount);
  SETUP_COMMAND("newcookie",   edrfs_sc_cmd_newcookie);
  SETUP_COMMAND("noop",        edrfs_sc_cmd_noop);
  SETUP_COMMAND("null",        edrfs_sc_cmd_null);
  SETUP_COMMAND("quit",        edrfs_sc_cmd_quit);
  SETUP_COMMAND("readdir",     edrfs_sc_cmd_readdir);
  SETUP_COMMAND("remove",      edrfs_sc_cmd_remove);
  SETUP_COMMAND("rename",      edrfs_sc_cmd_rename);
  SETUP_COMMAND("resetstats",  edrfs_sc_cmd_resetstats);
  SETUP_COMMAND("rmdir",       edrfs_sc_cmd_rmdir);
  SETUP_COMMAND("setattr",     edrfs_sc_cmd_setattr);
  SETUP_COMMAND("symlink",     edrfs_sc_cmd_symlink);
  SETUP_COMMAND("unbind",      edrfs_sc_cmd_unbind);

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


void edrfs_sc_cleanup(void) {
  if (fm_is_bound) { nasd_unbind_edrfs_server(&h); } /* clean up */
}


void edrfs_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 = edrfs_sc_do_command(word, buf_ptr);
  }
}


int edrfs_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 edrfs_sc_cmd_unimplemented(char *cmd, char *arg_str) {
  printf("! 1\nCommand \"%s\" unimplemented\n", cmd);
  fflush(stdout);
  return 0;
}

int edrfs_sc_cmd_access(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}

int edrfs_sc_cmd_bind(char *cmd, char *arg_str) {
  nasd_status_t rc;
  char *buf_ptr = arg_str;
  
  if (fm_is_bound) {
    printf("! 1\nAlready bound to file manager \"%s\"\n", fm_name);
    goto done;
  }

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

  rc = nasd_bind_to_edrfs_server(fm_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;
  }

  fm_is_bound = 1;

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

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


int edrfs_sc_cmd_create(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


int edrfs_sc_cmd_fsinfo(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


int edrfs_sc_cmd_fsstat(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


int edrfs_sc_cmd_getstats(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


int edrfs_sc_cmd_lookup(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


int edrfs_sc_cmd_mkdir(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


int edrfs_sc_cmd_mount(char *cmd, char *arg_str) {
  char *buf_ptr = arg_str;

  nasd_edrfs_mount_args_t        args;
  nasd_edrfs_mount_res_t         res;
  nasd_status_t                  rc;
  nasd_edrfscli_error_string_t   err_str;
  nasd_rpc_status_t              op_status;
  
  struct in_addr drive_addr;

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

  buf_ptr = get_arg_str(buf_ptr, args.in_dirpath, NASD_EDRFS_MAX_NAME_LEN);

  /* @@@ we don't do security here yet. */
  memset(&args.in_credential, 0, sizeof(nasd_edrfs_credential_t));

  nasd_edrfscli_mount(h, &args, &res, &op_status);
  rc = res.nasd_status;

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

  drive_addr.s_addr = res.out_drivelist[0].network_address;

  printf(". 4\nOK\n%s\n%u\n0x%" NASD_ID_FMT "\n",
	 inet_ntoa(drive_addr),
	 res.out_drivelist[0].port_number,
	 res.out_identifier);

 done:
  fflush(stdout);
  return 0;

 usage:
  printf("! 1\nusage: MOUNT <mountspec>\n");
  fflush(stdout);
  return 0;

}


int edrfs_sc_cmd_newcookie(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


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


int edrfs_sc_cmd_null(char *cmd, char *arg_str) {
  nasd_status_t        rc;
  nasd_rpc_status_t    op_status;
  nasd_edrfscli_error_string_t  err_str;

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

  nasd_edrfscli_null(h, &rc, &op_status);

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

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

 done:
  fflush(stdout);
  return 0;
}


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


int edrfs_sc_cmd_readdir(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


int edrfs_sc_cmd_remove(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


int edrfs_sc_cmd_rename(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


int edrfs_sc_cmd_resetstats(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


int edrfs_sc_cmd_rmdir(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


int edrfs_sc_cmd_setattr(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


int edrfs_sc_cmd_symlink(char *cmd, char *arg_str) {
  return edrfs_sc_cmd_unimplemented(cmd,arg_str);
}


int edrfs_sc_cmd_unbind(char *cmd, char *arg_str) {
  nasd_status_t rc;

  if (!fm_is_bound) {
    printf("! 1\nNot bound to a file manager!\n");
    goto done;
  } else {
    rc = nasd_unbind_edrfs_server(&h);

    if (rc) {
      printf("! 1\nunbind failed: error 0x%x (%s)\n",
	     rc, nasd_error_string(rc));
      goto done;
    } else {
      fm_is_bound = 0;
      printf(". 1\nOK\n");
      goto done;
    }
  }

 done:
  fflush(stdout);
  return 0;
}
