/*
 * physclone.c
 *
 * clone a physical drive
 *
 * Author: Jim Zelenka
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1998,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 <nasd/nasd_getopt.h>
#include <nasd/nasd_timer.h>
#include <nasd/nasd_common.h>
#include <nasd/nasd_threadstuff.h>
#include <nasd/nasd_mem.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef DEC_OSF
#include <sys/ioctl.h>
#include <sys/disklabel.h>
#endif /* DEC_OSF */

char *progname;

char *src_dev_name;
char *tgt_dev_name;
int src_dev, tgt_dev;

nasd_uint64 bytes_moved;
nasd_uint64 cur_off;
nasd_uint64 src_disk_len;
nasd_uint64 tgt_disk_len;
nasd_uint64 move_len;
nasd_uint64 last_pcg_done;

#define DEFAULT_THREADS    10
#define DEFAULT_BLOCKSIZE  1024 * 1024
#define DEFAULT_MS_OFFSET  2

int nthreads  = DEFAULT_THREADS;
int blocksize = DEFAULT_BLOCKSIZE;
int ms_offset = DEFAULT_MS_OFFSET;
int verbose   = 0;

int err_occurred = 0;

#define MAX_THREADS 1024

nasd_thread_t threads[MAX_THREADS];
char *bufs[MAX_THREADS];
nasd_threadgroup_t group;

NASD_DECLARE_MUTEX(assignment_lock)
#define LOCK_ASSIGNMENT()   NASD_LOCK_MUTEX(assignment_lock)
#define UNLOCK_ASSIGNMENT() NASD_UNLOCK_MUTEX(assignment_lock)

#ifdef DEC_OSF
void
zero_target_disklabel()
{
  struct disklabel label;
  int ret, val;
  off_t off;

  bzero((char *)&label, sizeof(label));

  off = lseek(tgt_dev, (off_t)0, SEEK_SET);
  if (off) {
    fprintf(stderr, "ERROR: could not seek to position 0 to zero disklabel\n");
    exit(1);
  }

  val = 1;
  ret = ioctl(tgt_dev, DIOCWLABEL, &val);
  if (ret < 0) {
    perror("DIOCWLABEL");
    fprintf(stderr, "ERROR: could not make target disklabel writeable\n");
    exit(1);
  }

  ret = write(tgt_dev, &label, sizeof(label));
  if (ret != sizeof(label)) {
    if (ret < 0) {
      perror("write");
      fprintf(stderr, "ERROR: could not write target disklabel\n");
      exit(1);
    }
    else {
      fprintf(stderr, "ERROR: wrote only %d/%d disklabel bytes\n",
        ret, sizeof(label));
      exit(1);
    }
  }
}
#endif /* DEC_OSF */

void
copy_thread(
  nasd_threadarg_t  arg)
{
  int threadnum, move, src_fd, tgt_fd, got, ret;
  nasd_delaycounter_t delayer;
  nasd_uint64 do_off;
  off_t off, goff;
  char *buf;

  threadnum = (int)arg;

  NASD_THREADGROUP_RUNNING(&group);

  NASD_BEGIN_DELAYCNT(&delayer);

  buf = bufs[threadnum];

  src_fd = open(src_dev_name, O_RDWR);
  if (src_dev < 0) {
    perror("open");
    fprintf(stderr, "ERROR: thread #%d could not open %s\n",
      threadnum, src_dev_name);
    fflush(stderr);
    err_occurred = 1;
    goto die_thread_die;
  }

  tgt_fd = open(tgt_dev_name, O_RDWR);
  if (tgt_dev < 0) {
    perror("open");
    fprintf(stderr, "ERROR: thread #%d could not open %s\n",
      threadnum, tgt_dev_name);
    fflush(stderr);
    err_occurred = 1;
    close(tgt_fd);
    goto die_thread_die;
  }

  NASD_DELAY_FROM(&delayer, ms_offset * 1000);

  LOCK_ASSIGNMENT();

  while (cur_off < move_len) {
    do_off = cur_off;
    move = NASD_MIN(move_len-cur_off, blocksize);

    cur_off += move;

    UNLOCK_ASSIGNMENT();

    off = (off_t)do_off;
    goff = lseek(src_fd, off, SEEK_SET);
    if (goff != off) {
      fprintf(stderr, "ERROR: thread #%d could not seek to %" NASD_64u_FMT
        " on %s\n", threadnum, do_off, src_dev_name);
      fflush(stderr);
      err_occurred = 1;
      break;
    }

    off = (off_t)do_off;
    goff = lseek(tgt_fd, off, SEEK_SET);
    if (goff != off) {
      fprintf(stderr, "ERROR: thread #%d could not seek to %" NASD_64u_FMT
        " on %s\n", threadnum, do_off, tgt_dev_name);
      fflush(stderr);
      err_occurred = 1;
      break;
    }

    for(got=0;got<move;) {
      ret = read(src_fd, &buf[got], move-got);
      if (ret <= 0) {
        fprintf(stderr, "ERROR: thread #%d got ret=%d (errno %d) "
          "reading %d bytes at %" NASD_64u_FMT " from %s\n",
          threadnum, ret, errno, move-got, do_off+got,
          src_dev_name);
        fflush(stderr);
        err_occurred = 1;
        break;
      }
      got += ret;
    }
    if (err_occurred)
      break;

    for(got=0;got<move;) {
      ret = write(tgt_fd, &buf[got], move-got);
      if (ret <= 0) {
        fprintf(stderr, "ERROR: thread #%d got ret=%d (errno %d) "
          "writing %d bytes at %" NASD_64u_FMT " to %s\n",
          threadnum, ret, errno, move-got, do_off+got,
          tgt_dev_name);
        fflush(stderr);
        err_occurred = 1;
        break;
      }
      got += ret;
    }
    if (err_occurred)
      break;

    LOCK_ASSIGNMENT();

    bytes_moved += move;

    if (verbose) {
      nasd_uint64 pcg_done;
      char del = (char)8;
      pcg_done = (bytes_moved * 100) / move_len;
      if (pcg_done != last_pcg_done) {
        printf("%c%c%c%c%c%c%c%c%02"NASD_64u_FMT"%% done",
          del, del, del, del, del, del, del, del,
          pcg_done);
        fflush(stdout);
        last_pcg_done = pcg_done;
      }
    }
  }

  UNLOCK_ASSIGNMENT();

  close(src_fd);
  close(tgt_fd);

die_thread_die:
  NASD_THREADGROUP_DONE(&group);
  NASD_THREAD_KILL_SELF();
}

void
usage()
{
  fprintf(stderr, "USAGE: %s [options] src_dev target_dev\n", progname);
  fprintf(stderr, "Options:\n");
  fprintf(stderr, "  -b blocksize [default %d]\n", DEFAULT_BLOCKSIZE);
  fprintf(stderr, "  -m thread offset (milliseconds) [default %d]\n",
    DEFAULT_MS_OFFSET);
  fprintf(stderr, "  -t number of threads [default %d]\n", DEFAULT_THREADS);
  fprintf(stderr, "  -v verbose\n");
  fflush(stderr);
  exit(1);
}

int
main(
  int     argc,
  char  **argv)
{
  nasd_timespec_t ts1, ts2, diff;
  double d, tm;
  nasd_status_t rc;
  int show_idle, i;
  char c;
#if NASD_IDLE_SUPPORT > 0
  nasd_timespec_t idle_ts1, idle_ts2, idle_diff;
  double itm;
#endif /* NASD_IDLE_SUPPORT > 0 */

  progname = argv[0];

  rc = nasd_threads_init();
  if (rc) {
    fprintf(stderr, "ERROR: could not init nasd threads, error 0x%x (%s)\n",
      rc, nasd_error_string(rc));
    exit(1);
  }

  rc = nasd_mem_init();
  if (rc) {
    fprintf(stderr, "ERROR: could not init nasd memory, error 0x%x (%s)\n",
      rc, nasd_error_string(rc));
    exit(1);
  }

  nasd_gettime(&ts1);
#if NASD_IDLE_SUPPORT > 0
  rc = nasd_get_total_idle_time(&idle_ts1);
  if (rc)
    show_idle = 0;
  else
    show_idle = 1;
#else /* NASD_IDLE_SUPPORT > 0 */
  show_idle = 0;
#endif /* NASD_IDLE_SUPPORT > 0 */

  while (nasd_getopt(argc, argv, "b:m:t:v", &c)) {
    switch(c) {
      case 'b':
        if (sscanf(nasd_optarg, "%d", &blocksize) != 1)
          usage();
        break;
      case 'm':
        if (sscanf(nasd_optarg, "%d", &ms_offset) != 1)
          usage();
        break;
      case 't':
        if (sscanf(nasd_optarg, "%d", &nthreads) != 1)
          usage();
        break;
      case 'v':
        verbose = 1;
        break;
      default:
        fprintf(stderr, "Unknown option '%c'\n", nasd_optopt);
        usage();
    }
  }

  if (ms_offset < 0)
    usage();
  if (nthreads < 1)
    usage();
  if (nthreads > MAX_THREADS) {
    fprintf(stderr, "ERROR: max allowed threads is %d\n", MAX_THREADS);
    exit(1);
  }
  if (blocksize < 1)
    usage();

  if (nasd_optind >= argc)
    usage();
  src_dev_name = argv[nasd_optind];
  nasd_optind++;

  if (nasd_optind >= argc)
    usage();
  tgt_dev_name = argv[nasd_optind];
  nasd_optind++;

  if (nasd_optind < argc)
    usage();

  src_dev = open(src_dev_name, O_RDWR);
  if (src_dev < 0) {
    perror("open");
    fprintf(stderr, "ERROR: could not open %s\n", src_dev_name);
    exit(1);
  }

  tgt_dev = open(tgt_dev_name, O_RDWR);
  if (tgt_dev < 0) {
    perror("open");
    fprintf(stderr, "ERROR: could not open %s\n", tgt_dev_name);
    exit(1);
  }

  rc = nasd_mutex_init(&assignment_lock);
  if (rc) {
    fprintf(stderr, "ERROR: could not init assignment lock, error 0x%x (%s)\n",
      rc, nasd_error_string(rc));
    exit(1);
  }

  rc = nasd_init_threadgroup(&group);
  if (rc) {
    fprintf(stderr, "ERROR: could not init thread group, error 0x%x (%s)\n",
      rc, nasd_error_string(rc));
    exit(1);
  }

  for(i=0;i<MAX_THREADS;i++) {
    bufs[i] = NULL;
  }

#ifdef DEC_OSF
  zero_target_disklabel();
#endif /* DEC_OSF */

  bytes_moved = 0;
  cur_off = 0;
  last_pcg_done = 0;

  rc = nasd_raw_disk_len(src_dev, &src_disk_len);
  if (rc) {
    printf("ERROR: Could not get length of source disk, error 0x%x (%s)\n",
      rc, nasd_error_string(rc));
    exit(1);
  }

  rc = nasd_raw_disk_len(tgt_dev, &tgt_disk_len);
  if (rc) {
    printf("ERROR: Could not get length of target disk, error 0x%x (%s)\n",
      rc, nasd_error_string(rc));
    exit(1);
  }

  move_len = NASD_MIN(src_disk_len, tgt_disk_len);
  if (src_disk_len != tgt_disk_len) {
    fprintf(stderr, "WARNING: source disk length %" NASD_64u_FMT
      " target disk length %" NASD_64u_FMT "\n",
      src_disk_len, tgt_disk_len);
    fflush(stderr);
  }

  printf("Moving %" NASD_64u_FMT " bytes from %s to %s\n",
    move_len, src_dev_name, tgt_dev_name);

  if (verbose) {
    printf("00%% done");
    fflush(stdout);
  }

  for(i=0;i<nthreads;i++) {
    NASD_Malloc(bufs[i], blocksize, (char *));
    if (bufs[i] == NULL)
      break;
    rc = nasd_thread_create(&threads[i],
      copy_thread, (nasd_threadarg_t)i);
    if (rc) {
      fprintf(stderr, "WARNING: failed creating thread #%d, error 0x%x (%s)\n",
        i, rc, nasd_error_string(rc));
      break;
    }
    NASD_THREADGROUP_STARTED(&group);
  }
  if (i == 0) {
    fprintf(stderr, "ERROR: no threads successfully created\n");
    exit(1);
  }

  NASD_THREADGROUP_WAIT_START(&group);
  NASD_THREADGROUP_WAIT_STOP(&group);

  nasd_gettime(&ts2);
#if NASD_IDLE_SUPPORT > 0
  if (show_idle) {
    rc = nasd_get_total_idle_time(&idle_ts2);
    if (rc)
      show_idle = 0;
    idle_diff = idle_ts2;
    NASD_TIMESPEC_SUB(idle_diff, idle_ts1);
  }
#endif /* NASD_IDLE_SUPPORT > 0 */
  diff = ts2;
  NASD_TIMESPEC_SUB(diff, ts1);
  tm = (double)diff.ts_nsec;
  tm /= (double)NASD_NSEC_PER_SEC;
  tm += (double)diff.ts_sec;
  d = (double)bytes_moved;
  d /= tm;
  d /= (double)(1024*1024);
  if (verbose)
    printf("\n");
  printf("%" NASD_64u_FMT " bytes moved in %d:%09d (%f Mbytes/sec)\n",
    bytes_moved, diff.ts_sec, diff.ts_nsec, d);
#if NASD_IDLE_SUPPORT > 0
  if (show_idle) {
    itm = (double)idle_diff.ts_nsec;
    itm /= (double)NASD_NSEC_PER_SEC;
    itm += (double)idle_diff.ts_sec;
    d = itm / tm;
    d *= (double)100.0;
    printf("%d:%09d / %d:%09d idle (%f %%)\n",
      idle_diff.ts_sec, idle_diff.ts_nsec,
      diff.ts_sec, diff.ts_nsec, d);
  }
#endif /* NASD_IDLE_SUPPORT > 0 */

  close(src_dev);
  close(tgt_dev);

  if (err_occurred) {
    fprintf(stderr, "ERROR: not all regions successfully copied\n");
    fflush(stderr);
  }

  for(i=0;i<nthreads;i++) {
    if (bufs[i]) {
      NASD_Free(bufs[i], blocksize);
    }
  }

  nasd_destroy_threadgroup(&group);
  nasd_mutex_destroy(&assignment_lock);
  nasd_mem_shutdown();
  nasd_threads_shutdown();

  exit(err_occurred);
}

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