#include <conf.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef	HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include <utils.h>
#include <fileutil.h>
#include "backup.h"

UChar		*configfile = NULL;

UChar		*programdir = NULL;
UChar		*bindir = NULL;
UChar		*libdir = NULL;
UChar		*vardir = NULL;

Uns32		devicename_changed = 0;

UChar		*devicename = (UChar *) DEFAULT_TAPE_DEVICE;

UChar		*lockfile = NULL;

UChar		*setfilecmd = (UChar *) DEFAULT_SETFILECMD;

Int32		tapeblocksize = DEFAULT_TAPEBLOCKSIZE;

Int32		num_cartridges = 1;

Int32		label = -1;

UChar		*infobuffer;
Int32		infobuffersize;

UChar		*default_configfilenames[] = {	\
			DEFAULT_SERVER_CONFIGFILES, NULL, NULL };

ParamFileEntry	entries[] = {
	{ &devicename, &devicename_changed,
	(UChar *) "[Bb]ackup[-_ \t]*[Dd]evi?c?e?:?[ \t]*",
		TypeUCharPTR	},
	{ &lockfile, NULL,
	(UChar *) "[Ll]ocki?n?g?[-_ \t]*[Ff]ile:?[ \t]*",
		TypeUCharPTR	},
	{ &tapeblocksize, NULL,
	(UChar *) "[Tt]ape[-_ \t]*[Bb]lock[-_ \t]*[Ss]ize:?",
		TypeInt32	},
	{ &setfilecmd, NULL,
	(UChar *) "[Ss]et[-_ \t]*[Ff]ile[-_ \t]*[Cc]o?mm?a?n?d:?[ \t]*",
		TypeUCharPTR	},
	{ &num_cartridges, NULL,
	(UChar *) "[Nn]umb?e?r?[-_ \t]*[Oo]f[-_ \t]*[Cc]artr?i?d?g?e?s[-_ \t]*:?",
		TypeInt32	},
	};

UChar	*lockdirs[] = {
		"/var/locks", "/var/lock", "/var/spool/locks",
		"/var/spool/lock", "/var/tmp", "/tmp", NULL,
};

#define	BU_NO_LOCK		0
#define	BU_LOCKED		1
#define	BU_GOT_LOCK		2
#define	BU_CANT_LOCK		99

int		lockfd;
UChar		locked = BU_NO_LOCK;
struct flock	lockb;

void
do_exit(int exitst)
{
  if(locked == BU_GOT_LOCK){
    lockb.l_type = F_UNLCK;
    fcntl(lockfd, F_SETLK, &lockb);

    close(lockfd);

    unlink(lockfile);
  }

  exit(exitst);
}

void
fatal(UChar * msg)
{
  fprintf(stderr, "%s", msg);

  do_exit(1);
}

void
nomemfatal()
{
  fatal("Error: Cannot allocate memory.\n");
}

Int32
set_lock()
{
  struct stat	statb;
  int		i;
  char		buf[20];

  if(locked == BU_GOT_LOCK)
    return((Int32) locked);

  i = lstat(lockfile, &statb);
  if(!i && !IS_REGFILE(statb)){
    if(unlink(lockfile)){
	fprintf(stderr, "Error: Cannot remove lock file entry \"%s\", that is not a file.\n", lockfile);
	return( (Int32) (locked = BU_CANT_LOCK) );
    }

    i = 1;
  }

  if(!i){
    lockfd = open(lockfile, O_WRONLY | O_SYNC);
    if(lockfd < 0){
      fprintf(stderr, "Warning: Lock file \"%s\" exists, but can't open it.\n",
			lockfile);
      return( (Int32) (locked = BU_CANT_LOCK) );
    }
  }
  else{
    lockfd = open(lockfile, O_WRONLY | O_CREAT | O_SYNC, 0644);
    if(lockfd < 0){
      fprintf(stderr, "Error: Cannot create lock file \"%s\".\n", lockfile);
      return( (Int32) (locked = BU_CANT_LOCK) );
    }
  }

  lockb.l_type = F_WRLCK;
  i = fcntl(lockfd, F_SETLK, &lockb);
  if(i){
    fprintf(stderr, "Error: Cannot lock file \"%s\".\n", lockfile);
    return( (Int32) (locked = BU_LOCKED) );
  }

  sprintf(buf, "%d\n", (int) getpid());
  write(lockfd, buf, strlen(buf));

  return( (Int32) (locked = BU_GOT_LOCK) );
}

static void
usage(char * pnam)
{
  pnam = FN_BASENAME((UChar *) pnam);

  fprintf(stderr, "usage: %s <label-number> \\\n", pnam);
  fprintf(stderr, "                  [ -c <configfile> ] \\\n");
  fprintf(stderr, "                  [ -d <devicename> ] \\\n");
  fprintf(stderr, "                  [ -b <blocksize> ] \\\n");
  fprintf(stderr, "                  [ -s <set-file-cmd> ] \\\n");
  fprintf(stderr, "                  [ -C <num-cartridges> ]\n");
  fprintf(stderr, "       %s -q \\\n", pnam);
  fprintf(stderr, "                  [ -c <configfile> ] \\\n");
  fprintf(stderr, "                  [ -d <devicename> ] \\\n");
  fprintf(stderr, "                  [ -b <blocksize> ] \\\n");
  fprintf(stderr, "                  [ -s <set-file-cmd> ] \\\n");

  exit(1);
}

main(int argc, char ** argv)
{
  int		i, fd;
  Int32		have_label;
  FILE		*fp;
  struct stat	statb;
  UChar		*backuphome, **cpptr, *line = NULL, *cptr, cbuf[30];
  UChar		query = 0;

  i = goptions(-argc, (UChar **) argv, "s:c;i-1:;s:d;i:b;s:s;i:C;b:q",
		&configfile, &have_label, &label, &devicename, &tapeblocksize,
		&setfilecmd, &num_cartridges, &query);
  if(i || (!have_label && !query) || (have_label && query))
    usage(argv[0]);

  /* determine home directory */
  backuphome = getenv("BACKUP_HOME");
  if(!backuphome){
     /* construct file- and dirnames */

#ifdef	ORIG_DEFAULTS

    programdir = find_program(argv[0]);
    cptr = mkabspath(programdir, NULL);
    free(programdir);
    programdir = resolvepath__(cptr, NULL);
    free(cptr);

    *(FN_LASTDIRDELIM(programdir)) = '\0';
    *(FN_LASTDIRDELIM(programdir)) = '\0';
    bindir = strapp(programdir, FN_DIRSEPSTR "bin");
    libdir = strapp(programdir, FN_DIRSEPSTR "lib");
    vardir = strapp(programdir, FN_DIRSEPSTR "var");

#else	/* defined(ORIG_DEFAULTS) */

     bindir = DEFSERVBINDIR;
     vardir = DEFSERVVARDIR;
     libdir = DEFSERVLIBDIR;

#endif	/* if else defined(ORIG_DEFAULTS) */

  }
  else {
     backuphome = strdup(backuphome);
     /* construct file- and dirnames */
     bindir = strapp(backuphome, FN_DIRSEPSTR "bin");
     vardir = strapp(backuphome, FN_DIRSEPSTR "var");
     libdir = strapp(backuphome, FN_DIRSEPSTR "lib");
  }

  if(!configfile){
    for(cpptr = default_configfilenames; *cpptr; cpptr++);
    *cpptr = strapp(libdir, FN_DIRSEPSTR DEFSERVERCONF);

    for(cpptr = default_configfilenames; *cpptr; cpptr++){
      configfile = *cpptr;
      if(!stat(*cpptr, &statb) && access(*cpptr, R_OK)){
	while(*cpptr) cpptr++;
	break;
      }
      if(!stat(*cpptr, &statb))
	break;
    }

    configfile = *cpptr;
  }

  if(!configfile){
    fp = fopen("/etc/inetd.conf", "r");
    if(fp){
      forever{
	if(line)
	  ZFREE(line);

	line = fget_alloc_str(fp);
	if(!line)
	  break;

	if(strstr(line, bindir)){
	  cptr = line;
	  while(isspace(*cptr) && *cptr)
	    cptr++;
	  if(*cptr == '#')
	    continue;

	  cptr = line + strlen(line) - 1;
	  while(isspace(*cptr) && cptr > line)
	    cptr--;
	  *(cptr + 1) = '\0';
	  while(!isspace(*cptr) && cptr > line)
	    cptr--;
	  cptr++;

	  if(!access(cptr, R_OK))
	    configfile = strdup(cptr);

	  break;
	}
      }
      fclose(fp);
    }
  }

  if(!configfile){
    fprintf(stderr, "Warning: Cannot determine server side configuration file, using defaults.\n");
  }
  else{
    i = read_param_file(configfile, entries,
		sizeof(entries) / sizeof(entries[0]), NULL, NULL);
    if(i){
      fprintf(stderr, "Error: Cannot read configuration file \"%s\".\n",
				configfile);
      exit(3);
    }
  }

  goptions(-argc, (UChar **) argv, "s:c;i-1:;s:d;i:b;s:s;i:C;b:q",
		&configfile, &have_label, &label, &devicename, &tapeblocksize,
		&setfilecmd, &num_cartridges, &query);

  if(!query && (label < 1 || label > num_cartridges)){
    fprintf(stderr,
	"Error: Illegal cartridge label (must be in the range 1 - %d).\n",
		(int) num_cartridges);
    exit(7);
  }

  if(lockfile)
    if(empty_string(lockfile))
      lockfile = NULL;
  if(lockfile)
    massage_string(lockfile);
  if(devicename_changed)
    massage_string(devicename);

  if(!lockfile){
    for(cpptr = lockdirs; *cpptr; cpptr++){
      i = stat(*cpptr, &statb);
      if(!i){
	if(! access(*cpptr, W_OK)){
	  lockfile = strchain(*cpptr, FN_DIRSEPSTR,
					DEFAULT_SERVERLOCKFILE, NULL);
	  break;
	}
      }
    }
  }

  cptr = repl_substring(setfilecmd, "%d", devicename);
  free(setfilecmd);
  setfilecmd = repl_substring(cptr, "%n", "1");
  free(cptr);
  cptr = setfilecmd;
  setfilecmd = repl_substring(cptr, "%m", "0");
  free(cptr);

  infobuffersize = (INFOBLOCKSIZE / tapeblocksize + 1) * tapeblocksize;
  infobuffer = (UChar *) malloc(infobuffersize * sizeof(UChar));
  if(!infobuffer){
    nomemfatal();
  }
  memset(infobuffer, 0, sizeof(UChar) * infobuffersize);

  if(!query){
    printf("You are about to write a label at the beginning of the tape.\n");
    printf("All data on this tape will be lost and it will become very\n");
    printf("expensive to get anything back, possibly nothing at all.\n");
    printf("\nDo you want to continue ? [y/n] ");
    fflush(stdout);
    cbuf[0] = '\0';
    while(!fgets(cbuf, 20, stdin));
    if(cbuf[0] != 'y' && cbuf[0] != 'Y')
	exit(0);
  }

  if(set_lock() == BU_LOCKED){
    fprintf(stderr, "Error: An application using the device seems to be running.\n");
    exit(5);
  }
  if(locked != BU_GOT_LOCK){
    fprintf(stderr, "Error: Cannot set lock on lockfile. Device seems to be already in use by the afbackup service.\n");
    exit(9);
  }

  i = system(setfilecmd);
  if(i){
    fprintf(stderr, "Warning: Cannot rewind tape.\n");
  }

  if(query){
    fd = open(devicename, O_RDONLY | O_BINARY);
    if(fd < 0){
	fprintf(stderr, "Error: Cannot open tape device \"%s\".\n",
			devicename);
	do_exit(8);
    }

    have_label = 1;
    if(read(fd, infobuffer, infobuffersize) < infobuffersize)
	have_label = 0;
    else{
	if(!strstr(infobuffer, INFOHEADER))
	  have_label = 0;
    }

    infobuffer[infobuffersize - 1] = '\0';

    if(have_label)
	fprintf(stdout, "%s\n", infobuffer);
    else
	fprintf(stderr, "<Cannot read label>\n");

  }
  else{
    fd = open(devicename, O_WRONLY | O_BINARY, 0600);
    if(fd < 0){
	fprintf(stderr, "Error: Cannot open tape device \"%s\".\n",
			devicename);
	do_exit(4);
    }

    sprintf(infobuffer, "\n%s\n\n%s%d\n\n%s%s\n\n%s\n",
		INFOHEADER, CARTNOTEXT, (int) label,
		DATETEXT, actimestr(), VERSIONTEXT);

    if(write(fd, infobuffer, infobuffersize) < infobuffersize){
	fprintf(stderr, "Error: The write to tape failed.\n");
	do_exit(6);
    }
  }

  close(fd);

  i = system(setfilecmd);
  if(i){
    fprintf(stderr, "Warning: Cannot rewind tape.\n");
  }

  do_exit(0);
}
