/*****************************************************************************/
/** Program: Stow        Version: 0.7ß                                      **/
/** Date: November 1993                                                     **/
/** Author: Leonard D Atkinson II                                           **/
/**                                                                         **/
/** Purpose:  Stow examines a given pathname for files and then copies      **/
/**           those files to a device, volume by volume, with the lease     **/
/**           ammount of waste of space.  It optionally outputs a list of   **/
/**           what volumes hold what files.                                 **/
/**                                                                         **/
/** Requires: LIST, COPY, FORMAT commands somewhere in the path.            **/
/**                                                                         **/
/** This source copyright 1993 by Leonard D Atkinson II                     **/
/*****************************************************************************/


/****************************/
/* INCLUDES                 */
/****************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>             /*so we can make unique filename*/
#include <libraries/dos.h>    /*for the checking disk space*/
#include <exec/memory.h>      /*for AllocMem()*/
#include "ansi.h"             /*My own ansi macros*/

/****************************/
/* Data Types               */
/****************************/
struct my_file_node {         /*For putting files into linked list*/
     char filename[40];
     long filesize;
     struct my_file_node *next;
     };

FILE *fp; /*Generic file point for reading temp file*/
          /*and writing to the output file          */
/****************************/
/* functions                */
/****************************/
long check_free_space(char *); /*Returns the free space on the given volume*/
void bad_format(); /*sends user format and exits*/
void set_num(); /*changes the integer into ABC form*/
void set_format();/*sets first arg to string to append to format call*/
void my_system();/*execute system call with error checking*/
void verify_file();/*checks given filesize to copied filesize*/
void cat_files();/*tack arg2 onto arg3*/

/*****************************************************************************/
/* Main                                                                      */
/*****************************************************************************/
main(int argc, char *argv[]) {

     /****************************/
     /* Variables                */
     /****************************/
     char filename[40],   /*global filename, for reading into*/
          dos_input[80],      /*Working strings*/
          temp_file_name[80];
     long filesize = 0;   /*global filesize, for reading into*/
     long total = 0;      /*total bytes tally*/

     struct my_file_node *head, /*Pointers to linked list*/
                         *temp, 
                         *temp2;

     char keep_going;  /*boolean for the main loop*/

     int volume_num = 0; /*counter of number of volumes used*/
     int count,i; /*generic count variable*/
     char list_output[40] = "";/*name of list of files copied.*/
     char input_dir[40] = "";/*input directory to be copied from*/

     int assumed_size = 842*1024;
     int current_disk;
     int check_size;
     char output_device[40] = "";
     char output_script[40] = "";
     char fixed_size = 0;
     int max_waste = 20000;
     char use_max_waste = 0;
     char archive_name[40] = "STOW_";
     char format_options[40] = "";
     char verify_copies = 0;
     char quiet_output = 0;
     char list_sort = 1;
     char current_diskname[4] = "";

     /***********************************************************/
     /* Parse command line arguments                            */
     /***********************************************************/
     for(count = 1; count < argc; count++) {
          if(argv[count][0] == '-') {
               switch(argv[count][1]) {
                    case 'i':/*input directory*/
                    case 'I':
                         strcpy(input_dir,&argv[count][2]);
                         break;
                    case 'o':
                    case 'O':
                         strcpy(list_output,&argv[count][2]);
                         break;
                    case 'a':
                    case 'A':
                         if(strlen(argv[count]) > 2){
                              assumed_size = atoi(&argv[count][2]);
                              assumed_size *= 1024;
                              }
                         fixed_size = 1;
                         current_disk = assumed_size;
                         break;
                    case 'd':
                    case 'D':
                         strcpy(output_device,&argv[count][2]);
                         break;
                    case 'm':
                    case 'M':
                         use_max_waste = 1;
                         if(strlen(argv[count]) > 2){
                              max_waste = atoi(&argv[count][2]);
                              max_waste *= 1024;
                              }
                         break;
                    case 's':
                    case 'S':
                         strcpy(output_script,&argv[count][2]);
                         break;
                    case 'n':
                    case 'N':
                         strncpy(archive_name,&argv[count][2],16);
                         strcat(archive_name,"_");
                         /*turn all non-printables into underscores*/
                         for(i = 0; i<strlen(archive_name); i++)
                              if(isspace(archive_name[i])) archive_name[i] = '_';
                         break;
                    case 'f':
                    case 'F':
                         /*if options are provided, do it, else just put*/
                         /*a space in there for signaling purposes*/
                         if(argv[count][2] != NULL) {
                              set_format(format_options,&(argv[count][2]));
                              }
                         else {
                              strcpy(format_options," ");
                              }
                         break;
                    case 'v':
                    case 'V':
                         verify_copies = 1;
                         break;
                    case 'q':
                    case 'Q':
                         quiet_output = 1;
                         break;
                    case 'l':
                    case 'L':
                         list_sort = 0;
                         break;
                    default:
                         bad_format();
                    }
               }
          else bad_format();
          }

     if((strlen(output_device) == 0) || (strlen(input_dir) == 0)) bad_format();

     if((strlen(output_script) > 0) && (fixed_size != 1)) {
          printf("Output scripts require an assumed size.\n");
          printf("Auto-enabling an assumed size of %d.\n",assumed_size);
          fixed_size = 1;
          current_disk = assumed_size;
          }

     if(quiet_output != 1) printf("Assembling the list of files to be stowed.\n");

     /***********************************************************/
     /*Call the LIST command to make a list of all the files    */
     /*name the file with current time afterwards to avoid      */
     /*two versions of the program from clobbering each other's */
     /*temp files.                                              */
     /***********************************************************/
     sprintf(temp_file_name, "t:stow%d", (unsigned int)time(NULL));
     sprintf(dos_input, "list %s %s >%s", input_dir, "lformat \"%S %L\"", temp_file_name );
     my_system(dos_input,"Can't create file list in temp!");     

     fp = fopen(temp_file_name,"r");  /*open the file just created*/
     if(fp == NULL) {
          ChangeColors(YELLOW,BLACK);
          printf("For some reason the temp file can't be opened!\n");
          ChangeColors(RED,BLACK);
          exit(1);
          }

     /*Make a head node*/
     head = (struct my_file_node *) AllocMem( sizeof(struct my_file_node),
          MEMF_PUBLIC | MEMF_CLEAR );
     if(head == NULL) {
          ChangeColors(YELLOW,BLACK);
          printf("Couldn't allocate memory!\n");
          ChangeColors(RED,BLACK);
          exit(1);
          }          
     head->next = NULL;

     /****************************/
     /* Build Linked List        */
     /****************************/
     while(fscanf(fp, "%s %d",filename,&filesize) != EOF) {/*while more*/
          if((filesize > 0) && (strlen(filename)>0)) {
                            /*only bother making nodes for non-empty files*/
                            /*with valid names                            */
               total += filesize;
               /*make a new node*/
               temp = (struct my_file_node *) 
                    AllocMem( sizeof(struct my_file_node),
                              MEMF_PUBLIC | MEMF_CLEAR );
               if(temp == NULL) {
                    ChangeColors(YELLOW,BLACK);
                    printf("Couldn't allocate memory!\n");
                    ChangeColors(RED,BLACK);
                    exit(1);
                    } 
               strcpy(temp->filename, filename);/*fill with information*/
               temp->filesize = filesize;

               temp2 = head;/*Find the right place to insert new node*/

               if(list_sort == 1) {
                    while((temp2->next != NULL) && 
                         (((temp2->next)->filesize) > (temp->filesize))) 
                              temp2 = temp2->next;
                    }
               else {
                    /*just add to end of list now*/
                    while(temp2->next != NULL) temp2 = temp2->next;
                    }

               /*Insert Node*/
               if(temp2->next == NULL) {
                    temp2->next = temp;
                    temp->next = NULL;
                    }
               else {
                    temp->next = temp2->next;
                    temp2->next = temp;
                    }
               }
          }
     fclose(fp); /*close temp file*/
     if(DeleteFile(temp_file_name) == TRUE) { /*delete temp file*/
          ChangeColors(YELLOW,BLACK);
          printf("For some reason the temp file can't be deleted!\n");
          ChangeColors(RED,BLACK);
          exit(1);
          }

      if(strlen(list_output) > 0) { /*if requested, open output file*/
          fp = fopen(list_output,"w");
          if(fp == NULL) {
               ChangeColors(YELLOW,BLACK);
               printf("Error--Cannot open file %s for output.\n",list_output);
               ChangeColors(RED,BLACK);
               exit(1);
               }
          fprintf(fp,"!!! Files Stowed !!!\n");
          fclose(fp);
          }    
     if(strlen(output_script) > 0) { /*if requested, open output script*/
          fp = fopen(output_script,"w");
          if(fp == NULL) {
               ChangeColors(YELLOW,BLACK);
               printf("Error--Cannot open file %s for output.\n",output_script);
               ChangeColors(RED,BLACK);
               exit(1);
               }
          fprintf(fp,"echo Stowing %s from script\n",input_dir);
          fclose(fp);
          }

     /***************************************************************/
     /*if the input directory doesn't end in / or : then assume it's*/
     /*a directory and put the / in for later catenation.           */
     /***************************************************************/
     if((input_dir[strlen(input_dir) - 1] != '/') && 
        (input_dir[strlen(input_dir) - 1] != ':')) 
          strcat(input_dir, "/");
          
     if(quiet_output != 1) {
          printf("%d total bytes to be packed.\n",total);
          printf("This will take about %d FFS floppies.\n",(total/890000)+1);
          }

     /****************************/
     /* Main Loop                */
     /****************************/
     do {
          set_num(current_diskname,volume_num);/*make new disk name*/
          if(strlen(temp_file_name) != 0) { /*if requested, open output file*/
               fp = fopen(temp_file_name,"a");
               if(fp == NULL) {
                    ChangeColors(YELLOW,BLACK);
                    printf("Error--Cannot open file %s for output.\n",temp_file_name);
                    ChangeColors(RED,BLACK);
                    exit(1);
                    }
               fprintf(fp, "ask \"next disk\"\n");
               fclose(fp);
               }

          if(strlen(format_options) != 0) {
               sprintf(dos_input,"FORMAT DRIVE %s NAME %s%s %s",
                         output_device,archive_name,current_diskname, format_options);
               if(strlen(output_script) != 0) { /*if requested, open output file*/
                    fp = fopen(output_script,"a");
                    if(fp == NULL) {
                         ChangeColors(YELLOW,BLACK);
                         printf("Error--Cannot open file %s for output.\n",temp_file_name);
                         ChangeColors(RED,BLACK);
                         exit(1);
                         }
                    fprintf(fp, "%s\n",dos_input);
                    fclose(fp);
                    }
               else {
                    my_system(dos_input,"Can't format disk!\n");
                    }
               }

          keep_going = FALSE;
          temp = head->next;
          temp2 = head;
          while(temp != NULL) {
               /*Scan the whole list*/
               if(fixed_size == 1){
                    check_size = current_disk;
                    }
               else {
                    check_size = (check_free_space(output_device) - 3076);
                    }
               if(temp->filesize < check_size) {
                    /**************************************************/
                    /*if the file will fit on the volume, put it there*/
                    /*3K may be a bit large, but 1K is too small      */
                    /*Better safe than sorry, I guess                 */
                    /**************************************************/

                    /*Inform user what's going on*/
                    if(quiet_output != 1) {
                         printf("%c                                                %c",13,13);
                         printf("Copying %s",temp->filename);
                         }

                    /*assemble dos call to COPY*/
                    sprintf(dos_input, "copy %s%s %s",input_dir,temp->filename,output_device);
                    if(fixed_size == 1) current_disk -= temp->filesize;
                    if(strlen(output_script) > 0) { /*if requested, open output file*/
                         fp = fopen(output_script,"a");
                         if(fp == NULL) {
                              ChangeColors(YELLOW,BLACK);
                              printf("Error--Cannot open file %s for output.\n",list_output);
                              ChangeColors(RED,BLACK);
                              exit(1);
                              }
                         fprintf(fp,"%s\n",dos_input);
                         fclose(fp);
                         }
                    else {
                         my_system(dos_input,"Copy Failed!\n");
                         }
                    if(verify_copies == 1) {
                         sprintf(filename,"%s%s",input_dir,temp->filename);
                         verify_file(filename,temp->filesize);
                         }
                    /*Add filename to output file*/
                    if(strlen(list_output) > 0) { /*if requested, open output file*/
                         fp = fopen(list_output,"a");
                         if(fp == NULL) {
                              ChangeColors(YELLOW,BLACK);
                              printf("Error--Cannot open file %s for output.\n",list_output);
                              ChangeColors(RED,BLACK);
                              exit(1);
                              }
                         fprintf(fp,"%-32.32s %6.6d %s%s\n",temp->filename,temp->filesize,archive_name,current_diskname);

                         fclose(fp);
                         }
                    temp2->next = temp->next;/*remove node from list*/
                    FreeMem(temp, sizeof(struct my_file_node));/*delete node*/
                    temp = temp2->next; /*move temp to next in list*/
                    }

               else {
                    /*if this file won't fit, try next smaller one*/
                    temp2 = temp;
                    temp = temp->next;
                    }
               }/*end of while list-pointer-not-null loop*/
          if(quiet_output != 1) putchar(13);

          if(((use_max_waste == 1) && (current_disk < max_waste)) || (use_max_waste == 0)) {
               if(strlen(output_script) > 0) {
                    cat_files(output_script,temp_file_name);
                    fp = fopen(output_script,"a");
                    if(fp == NULL) {
                         ChangeColors(YELLOW,BLACK);
                         printf("Error--Cannot open file %s for output.\n",list_output);
                         ChangeColors(RED,BLACK);
                         exit(1);
                         }
                    fprintf(fp,"echo disk finished\necho %d bytes free on disk\n",current_disk);
                    fclose(fp);
                    }
               else {
                    printf("Remove the volume in %s and put in another.\n",output_device);
                    printf("Press RETURN");
                    getchar();
                    }
               if(fixed_size == 1) current_disk = assumed_size;
               if(head->next != NULL) keep_going = TRUE;
               }
          volume_num++;/*advance to next volume number*/
          } while(keep_going);
          if(quiet_output != 1) printf("Finished                           \n");
     }/*end of main*/


/******************************************************************************/
/**check_free_space()                                                        **/
/**Takes the name of a device and returns the bytes free                     **/
/******************************************************************************/
long check_free_space(char *checked_device) {

     /*************************/
     /* Variables             */
     /*************************/
     struct FileLock *lock;
     struct InfoData *info_ptr;
     long return_value;

     /***********************************/
     /*Get a new InfoData structure     */
     /*AllocMem assures a 4 byte boundry*/
     /***********************************/
     info_ptr = (struct InfoData *) AllocMem( sizeof( struct InfoData ),
          MEMF_PUBLIC | MEMF_CLEAR );
     if( info_ptr == NULL ) {
          ChangeColors(YELLOW,BLACK);
          printf("Couldn't allocate memory!\n");
          ChangeColors(RED,BLACK);
          exit(1);
          }

     /*get a file lock*/
     lock = (struct FileLock *) Lock( checked_device, SHARED_LOCK );
     if( lock == NULL ) {
          ChangeColors(YELLOW,BLACK);
          printf("Could not lock your desination device!\n");
          ChangeColors(RED,BLACK);
          FreeMem( info_ptr, sizeof( struct InfoData ) );
          exit(1);
          }
     if( Info( lock, info_ptr ) == NULL ) {
          ChangeColors(YELLOW,BLACK);
          printf("Could not examine your destination device!\n");
          ChangeColors(RED,BLACK);
          FreeMem( info_ptr, sizeof( struct InfoData ) );
          UnLock( lock );  
          exit(1);
          }

     /*Set return value*/
     return_value = (info_ptr->id_NumBlocks - info_ptr->id_NumBlocksUsed) 
            * info_ptr->id_BytesPerBlock;

     /*Free up the file and memory*/
     UnLock( lock );  
     FreeMem( info_ptr, sizeof( struct InfoData ) );
     
     return(return_value);
     }/*end of check_free_space() */

/******************************************************************************/
/**  bad_format                                                              **/
/**  instructs user that a bad format of options was given                   **/
/******************************************************************************/
void bad_format(){
     printf("Usage: stow <-command> <-command> ... <-command>\n\n");
     printf("A[#]        Assume disk size      I<directory> Directory to copy files from\n");
     printf("O<filename> Output list of files  M[max waste] Set 'max waste'\n");
     printf("S<filename> Make script file      D<device>    Output device copied to\n");
     printf("N<name>     Name the archive      Q            Be quiet\n");
     printf("F<options>  Format disks          V            Verify all copies made\n");
     printf("L           Alternate list pack\n");
     exit(1);
     }


/******************************************************************************/
/**  set_num                                                                 **/
/**  converts int to letter form for naming disks                            **/
/******************************************************************************/
void set_num(char *output, int num) {

     output[0] = 'A' + (num/(26*26));
     num -= ((num/(26*26)) * (26*26));
     output[1] = 'A' + (num/(26));
     num -= ((num/(26)) * 26);
     output[2] = 'A' + num;

     return;
     }

/******************************************************************************/
/** set_format                                                               **/
/** parses the options to the format command                                 **/
/******************************************************************************/
void set_format(char *out, char *in) {

     int count = 0;

     while(in[count] != NULL) {
          switch(in[count++]) {
               case 'Q':
               case 'q':
                    strcat(out,"QUICK ");
                    break;
               case 'F':
               case 'f':
                    strcat(out,"FFS ");
                    break;
               case 'N':
               case 'n':
                    strcat(out,"NOICONS ");
                    break;
               default:
                    break;
               }
          }
     return;
     }

/******************************************************************************/
/** my_system                                                                **/
/** Gets rid of a bunch of repetitive calls to system()                      **/
/******************************************************************************/
void my_system(char *sys_call, char *error_string) {

     if(system(sys_call) != 0) {
          ChangeColors(YELLOW,BLACK);
          printf("%s\n",error_string);
          ChangeColors(RED,BLACK);
          exit(1);
          }
     return;
     }

/******************************************************************************/
/** Verify_File()                                                            **/
/** Checks actual filesize verses the original                               **/
/******************************************************************************/
void verify_file(char *checked_file, int check_size) {
     /*************************/
     /* Variables             */
     /*************************/
     struct FileLock *lock;
     struct FileInfoBlock *fib_ptr;

     /***********************************/
     /*Get a new FileInfoBlock structure*/
     /*AllocMem assures a 4 byte boundry*/
     /***********************************/
     fib_ptr = (struct FileInfoBlock *)
          AllocMem( sizeof( struct FileInfoBlock ),
          MEMF_PUBLIC | MEMF_CLEAR );
     /* Check if we have allocated the memory successfully: */
     if( fib_ptr == NULL ) {
          ChangeColors(YELLOW,BLACK);
          printf("Couldn't allocate memory!\n");
          ChangeColors(RED,BLACK);
          exit(1);
          }

     /*get a file lock*/
     lock = (struct FileLock *) Lock( checked_file, SHARED_LOCK );
     if( lock == NULL ) {
          ChangeColors(YELLOW,BLACK);
          printf("Could not lock %s!\n",checked_file);
          ChangeColors(RED,BLACK);
          FreeMem( fib_ptr, sizeof( struct FileInfoBlock ) );
          exit(1);
          }

     if( Examine( lock, fib_ptr ) == NULL ) {
          ChangeColors(YELLOW,BLACK);
          printf("Could not examine %s!\n",checked_file);
          ChangeColors(RED,BLACK);
          FreeMem( fib_ptr, sizeof( struct FileInfoBlock ) );
          UnLock( lock );  
          exit(1);
          }

     /*Set return value*/
     if(fib_ptr->fib_Size != check_size) {
          ChangeColors(YELLOW,BLACK);
          printf("%s did not verify!\n",checked_file);
          ChangeColors(RED,BLACK);
          FreeMem( fib_ptr, sizeof( struct FileInfoBlock ) );
          UnLock( lock );  
          exit(1);
          }

     /*Free up the file and memory*/
     UnLock( lock );  
     FreeMem( fib_ptr, sizeof( struct FileInfoBlock ) );
     
     return;
     }/*end of verify_file() */

/******************************************************************************/
/**cat_files()                                                               **/
/**stick the second file onto the end of the first and delete it             **/
/******************************************************************************/
void cat_files(char *start_file, char *end_file) {

     FILE *fp1;
     FILE *fp2;
     char c;

     fp1 = fopen(start_file,"a");
     if(fp1 == NULL) {
          ChangeColors(YELLOW,BLACK);
          printf("Error--Cannot open file %s for output.\n",start_file);
          ChangeColors(RED,BLACK);
          exit(1);
          }
     fp2 = fopen(end_file,"r");
     if(fp2 == NULL) {
          ChangeColors(YELLOW,BLACK);
          printf("Error--Cannot open file %s for input.\n",end_file);
          ChangeColors(RED,BLACK);
          exit(1);
          }
     c = fgetc(fp2);
     while(c != EOF) {
          fputc(c, fp1);
          c = fgetc(fp2);
          }
     fclose(fp1);
     fclose(fp2);
     if(DeleteFile(end_file) == TRUE) { /*delete temp file*/
          ChangeColors(YELLOW,BLACK);
          printf("For some reason the temp file can't be deleted!\n");
          ChangeColors(RED,BLACK);
          exit(1);
          }
     return;
     }

