/*
 * nasd_edrfs_cat.c
 *
 * Read from a file, and write to standard output.
 *
 * Author: Ted Wong */
/*
 * 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 <errno.h>
#include <string.h>
#include <libgen.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <nasd/nasd_options.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_edrfs_client.h>
#include <nasd/nasd_mem.h>

#define CAT_BUFFER_LEN 4096

typedef struct {
  nasd_edrfs_handle_t server_h;
  char *server_name;

  nasd_drive_handle_t drive_h;
  char drive_name[64];
  char drive_port[16];

  int binding_type;
  void *binding_param;
  int binding_param_len;

  char *root_path;
  nasd_edrfs_identifier_t root_id;
  nasd_edrfs_credential_t root_credential;
  nasd_cookie_t root_cookie;
} cat_mount_info_t;

typedef struct {
  char *fullname;
  char *name;
  nasd_edrfs_identifier_t dir_id;
  nasd_edrfs_identifier_t id;
  nasd_attribute_t attr;
  nasd_cookie_t cookie;
} cat_file_info_t;

char *progname;

nasd_drive_param_kpdev_t   kpdev_args;

void
cat_shutdown(cat_mount_info_t *m_p)
{
  nasd_status_t rc;

  /* Clean up and exit. */

  if (m_p->drive_h != NULL && (rc = nasd_unbind_drive(&m_p->drive_h))) {
    fprintf(stderr, "ERROR: failed unbinding from drive %s, rc=0x%x (%s)\n",
            m_p->drive_name, rc, nasd_error_string(rc));
  }
  if (m_p->server_h != NULL &&
      (rc = nasd_unbind_edrfs_server(&m_p->server_h))) {
    fprintf(stderr, "ERROR: failed unbinding from server %s, rc=0x%x (%s)\n",
            m_p->server_name, rc, nasd_error_string(rc));
  }

  nasd_edrfscli_shutdown();
  nasd_cl_p_shutdown();
}

void
usage()
{
  int i;
  fprintf(stderr, "USAGE: %s [options] servername filename\n", progname);
  fprintf(stderr, "Options:\n");
  fprintf(stderr, "  -k : use kernel device\n");
  fprintf(stderr, "  -v : be verbose\n");
#if NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE
  fprintf(stderr, "  -T use DCE-TCP\n");
#endif /* NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE */
  fflush(stderr);
  exit(1);
}

int
main(
     int     argc,
     char  **argv)
{
  int nondefault_binding = 0;
  int verbose_flag = 0;
  char name[1024], c;
  char *p;
  nasd_edrfscli_error_string_t edrfserr_str;
  nasd_rpc_status_t status;
  nasd_status_t rc;
  cat_file_info_t finfo;
  cat_mount_info_t minfo;

  progname = basename(argv[0]);

  minfo.server_h = NULL;
  minfo.drive_h = NULL;

  minfo.binding_type = NASD_BIND_DEFAULT;
  minfo.binding_param = NULL;
  minfo.binding_param_len = 0;

  minfo.root_credential.uid = 0;
  minfo.root_credential.gid = 0;

  while (nasd_getopt(argc, argv, "kTv", &c)) {
    switch(c) {
    case 'k':
      if (nondefault_binding) {
        usage();
      }
      nondefault_binding = 1;
      minfo.binding_type = NASD_BIND_KPDEV_DEFAULT;
      minfo.binding_param = &kpdev_args;
      minfo.binding_param_len = sizeof(kpdev_args);
      strcpy(kpdev_args.devname, "/dev/nasdkp0");
      break;
    case 'v':
      verbose_flag = 1;
      break;
#if NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE
    case 'T':
      if (nondefault_binding) {
        usage();
      }
      nondefault_binding = 1;
      minfo.binding_type = NASD_BIND_DCE_DIRECT_TCP;
      break;
#endif /* NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE */
    default:
      fprintf(stderr, "Unknown option '%c'\n", c);
      usage();
    }
  }

  if (nasd_optind >= argc) {
    usage();
  }
  minfo.server_name = argv[nasd_optind];
  nasd_optind++;

  if (nasd_optind >= argc) {
    usage();
  }
  strncpy(name, argv[nasd_optind], 1024);
  finfo.fullname = argv[nasd_optind];
  nasd_optind++;

  if (nasd_optind < argc) {
    usage();
  }

  /* Initialize drive and file system clients. */

  rc = nasd_cl_p_init();
  if (rc) {
    fprintf(stderr, "ERROR: cannot init client library, rc=0x%x (%s)\n",
            rc, nasd_error_string(rc));
    exit(1);
  }

  rc = nasd_edrfscli_init();
  if (rc) {
    fprintf(stderr, "ERROR: cannot init EDRFS client library, rc=0x%x (%s)\n",
            rc, nasd_error_string(rc));
    exit(1);
  }

  rc = nasd_bind_to_edrfs_server(minfo.server_name,
                                 NASD_EDRFS_SERVER_PORT,
                                 minfo.binding_type,
                                 minfo.binding_param,
                                 minfo.binding_param_len,
                                 &minfo.server_h);
  if (rc) {
    fprintf(stderr, "ERROR: failed binding to EDRFS server %s, rc=0x%x (%s)\n",
            minfo.server_name, rc, nasd_error_string(rc));
    exit(1);
  }

  /* Mount the file system. */

  p = name;
  if (*p != '/') {
    fprintf(stderr, "ERROR: file name must have a fully-specified path\n");
    cat_shutdown(&minfo);
    exit(1);
  }
  p = strtok(p + 1, "/");
  if (p == NULL) {
    fprintf(stderr, "ERROR: file name must include the filesystem name\n");
    cat_shutdown(&minfo);
    exit(1);
  }
  p--;
  minfo.root_path = p;
  {
    struct in_addr drive_addr;
    nasd_edrfs_mount_args_t args;
    nasd_edrfs_mount_res_t res;

    args.in_credential = minfo.root_credential;
    strncpy(args.in_dirpath, minfo.root_path, NASD_EDRFS_MAX_NAME_LEN);
    nasd_edrfscli_mount(minfo.server_h, &args, &res, &status);
    if (status || res.nasd_status) {
      fprintf(stderr,
              "ERROR: failed mounting EDRFS, "
              "rc=0x%x (%s) nasd_status=0x%x (%s)\n",
              status, nasd_error_string(status),
              res.nasd_status, nasd_error_string(res.nasd_status));
      cat_shutdown(&minfo);
      exit(1);
    }
    minfo.root_id = res.out_identifier;
    minfo.root_cookie = res.out_cookie;

    if (verbose_flag) {
      printf("Mounted root dir %s, ID is 0x%" NASD_ID_FMT "\n",
             minfo.root_path, minfo.root_id);
    }

    /* Get a drive handle to do reads. */

    drive_addr.s_addr = res.out_drivelist[0].network_address;
    strncpy(minfo.drive_name, inet_ntoa(drive_addr), sizeof(minfo.drive_name));
    sprintf(minfo.drive_port, "%u", res.out_drivelist[0].port_number);
    rc = nasd_bind_to_drive(minfo.drive_name,
                            minfo.drive_port,
                            minfo.binding_type,
                            minfo.binding_param,
                            minfo.binding_param_len,
                            &minfo.drive_h);
    if (rc) {
      fprintf(stderr,
              "ERROR: failed binding to drive %s port %s, rc=0x%x (%s)\n",
               minfo.drive_name, minfo.drive_port, rc, nasd_error_string(rc));
      cat_shutdown(&minfo);
      exit(1);
    }

    if (verbose_flag) {
      printf("Mounted drive %s port %s\n", minfo.drive_name, minfo.drive_port);
    }
  }

  /* Walk the directory tree. */

  {
    char *last_path = NULL;
    nasd_cookie_t cookie = minfo.root_cookie;
    nasd_edrfs_identifier_t dir_id = minfo.root_id;
    nasd_cookie_t last_cookie;
    nasd_edrfs_identifier_t last_dir_id;

    while((p = strtok(NULL, "/"))) {
      nasd_edrfs_lookup_args_t args;
      nasd_edrfs_lookup_res_t res;

      args.in_cookie = cookie;
      args.in_identifier = dir_id;
      args.in_credential = minfo.root_credential;
      strcpy(args.in_dirpath, p);
      nasd_edrfscli_lookup(minfo.server_h, &args, &res, &status);

      if (status || res.nasd_status) {
        fprintf(stderr, "ERROR: lookup %s in dir 0x%" NASD_ID_FMT
                ": status=0x%x (%s) nasd_status=0x%x (%s)\n",
                p, dir_id.nasd_identifier,
                status, nasd_edrfscli_error_string(minfo.server_h, status, edrfserr_str),
                res.nasd_status, nasd_error_string(res.nasd_status));
        cat_shutdown(&minfo);
        exit(1);
      }
      if(verbose_flag) {
        printf("lookup %s in dir 0x%" NASD_ID_FMT " successful\n", p,
               dir_id.nasd_identifier);
      }
      last_path = p;
      last_dir_id = dir_id;
      last_cookie = cookie;
      cookie = res.out_cookie;
      dir_id = res.out_identifier;
    }
    if (last_path == NULL) {
      fprintf(stderr, "ERROR: unable to parse path \"%s\" for last element\n",
              finfo.fullname);
      fflush(stderr);
      exit(1);
    }
    finfo.name = last_path;
    finfo.id = dir_id;
    finfo.cookie = last_cookie;
  }

  /* OK, we now have a valid file. Start copying input from the file to
     stdout. */

  if (verbose_flag) {
    printf("File name is %s, ID is 0x%" NASD_ID_FMT "\n",
           finfo.name, finfo.id);
  }

  {
    int eof_flag = 0;
    off_t off = 0;

    while (!eof_flag) {
      char buffer[CAT_BUFFER_LEN];
      nasd_len_t len_rd;
      size_t len_wrt;

      {
        nasd_p_smpl_op_dr_args_t args;
        nasd_p_fastread_dr_res_t res;
        nasd_security_param_t sp;

        sp.type = finfo.cookie.capability.type;
        sp.partnum = finfo.id.partnum;
        sp.actual_protection = finfo.cookie.capability.min_protection;

        args.in_identifier = finfo.id.nasd_identifier;
        args.in_offset = off;
        args.in_len = sizeof(buffer);
        args.in_partnum = finfo.id.partnum;

        nasd_cl_p_read_simple_dr(minfo.drive_h,
                                 finfo.cookie.key,
                                 &sp,
                                 &finfo.cookie.capability,
                                 &args,
                                 buffer,
                                 &res,
                                 &status);
        if (status || res.nasd_status) {
          fprintf(stderr,
                  "ERROR: failed reading from file %s obj 0x%" NASD_ID_FMT ", "
                  "rc=0x%x (%s) nasd_status=0x%x (%s)\n",
                  finfo.fullname, finfo.id.nasd_identifier,
                  status, nasd_error_string(status),
                  res.nasd_status, nasd_error_string(res.nasd_status));
          cat_shutdown(&minfo);
          exit(1);
        }
        len_rd = res.out_datalen;
        off += len_rd;
      }

      len_wrt = fwrite(buffer, sizeof(char), len_rd, stdout);
      if (len_wrt != len_rd) {
        fprintf(stderr,
                "ERROR: failed writing to stdout, errno=0x%x (%s) \n",
                errno, strerror(errno));
        cat_shutdown(&minfo);
        exit(1);
      }

      if (len_rd < sizeof(buffer)) {
        eof_flag = 1;
      }
    }
  }

  cat_shutdown(&minfo);

  exit(0);
}

/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */
