/* **************************************************************
 *								*
 *		ADD A FILE TO THE CONTROL AREA			*
 *		      (OR MODIFY ONE)				*
 *		 last modified 02/23/84 dsb			*
 *								*
 ************************************************************** */

#include "libc.h"
#include "syslib.h"
#include "hardio.h"

/* #define DBUG YES */

/* ***************** REVISION HISTORY OF THIS MODULE ***************   

    01/18/84	Turned module into an overlay
    01/23/84	Structured up the hex data entry routines   
		Installed "replace" feature for adding
    01/24/84	Fixed bugs in hex data entry calls
		Changed post-increment to pre-increment wherever
		 possible (generates less code)
    01/26/84	Fixed user interface bugs
		Close CP/M file before returning to menu
    02/10/84	Use unit_0_k as size of Control Area rather than
		 assuming it's 256k.
		Made some variables register-type and tried to declare
		 some little-used variables within {}'s where they are
		 used.
    02/16/84	Check value returned by set_up_disk_addr so that we don't
		 attempt an add after determining there's not enough room
		If replacing a station OS (actually if re-using an OS table
		 entry, which should be the same thing), the old load list
		 is modified rather than created from nothing.
    02/22/84	Fixed bug in unit 0 maximum track calculation

*/
/* *********************** MISCELLANEOUS EQUATES ******************** */

#define ADDING	  'A'
#define MODIFYING 'M'

long curpos(),endpos(),lseek(), get_new_hex();
struct SDentry *get_sd_slot();

/*page*/
ovmain(which)
char which;
{
    switch(which)
    {
	default:	printf("\07\n*BUG* bad argument to syslib1! (0x%x)",  
				which);
			break;
	case ADDING:	add();
			break;
	case MODIFYING: modify();
			break;
    }
    return(0);
}

add()
{
    char sectbuf[128];
    struct SDentry  sd_ent, *sd_ptr, *replacing;
    struct OSTentry ost_ent, *ost_ptr;
    int  is_os;
    char inname[15], *p;
    long filpos;
    int  track;
    char sector;
    unsigned length, *l;
    int exact_fit;
    register int infile;
    register int i;  
    register int reads_done;

    if((infile = get_fname(inname,15,ADDING)) == -1) /* get name, open file */
	return();

    chop_name(inname,sd_ent.name);	/* chop off drive, filetype */

    replacing = find_sd(sd_ent.name);

    reads_done = 0;
    posit(infile,0);
    read(infile,sectbuf,128);
    ++reads_done;
  
    if(!get_sd_addrs(&sd_ent,replacing))  /* get load addr, exec offset */
	goto CLOSE;

    /* Find out whether this is an OS file.  If it is, make an entry for
       the OS Table; if a previous entry has the same name, replace it;
       and consult the header for the number of records to copy.  Read a
       record so the record in the buffer will be real code, not header.

       If not an OS, determine how many records to copy by finding the
       end of the physical file. */

    if(is_os =its_an_os(sectbuf))  /* is it an OS file? (check first record) */
    {	    
	if((ost_ptr = find_os(sd_ent.name)) == NULL)
	{
	    if(ost_entries() >= 48)
	    {
		printf("\nThere is no room in the OS Table for the entry ");
		printf("that this new OS\nwould require.");
		goto CLOSE;
	    }
	    ost_ptr = ((struct OSTentry *)(ostable.buffer)) +ost_entries();
	    make_os_entry(&ost_ent,sectbuf,sd_ent.name);  /* make new OS ent */
	}			/* if making new OS entry goes in a new slot */
	else
	{
	    blockmv(&ost_ent,ost_ptr,sizeof(struct OSTentry)); /* old entry */
	    remake_os(&ost_ent,sd_ent.name);		    /* go modify it */
	}			       /* if re-using an old OS table entry */

	l = (unsigned *) (sectbuf + 23);
	length = *l;			/* number of records to copy */
	posit(infile,reads_done);
	read(infile,sectbuf,128);	/* get first one to transfer */
	++reads_done;
    } /* if an OS */
    else			       /* find length directly */
	length = (unsigned) (endpos(infile) / 128L);

    exact_fit = (replacing) && (replacing->length == length);
    if(!confirm_add(&sd_ent,&ost_ent,is_os))
	goto CLOSE;

    if(replacing)
    {
	if(exact_fit)
	    printf("\nThe new file will fit exactly on top of the old one.");
	else
	{
	    printf("\nErasing the old version of the file.\nWARNING!!! DO ");
	    printf("NOT TOUCH THE COMPUTER UNTIL THE ERASE IS COMPLETED.");
	    printf("\nDO NOT INTERRUPT THIS ERASE!!!\n\nErasing.....");
	    kill(replacing);		    /* physically remove it */
	    sd_delete(replacing);	    /* remove its sd entry */
					/* ost entry will be replaced anyhow */
	    printf("\nErase is complete.");
	}
    } /* if replacing (decide whether to erase old file) */

    if(!(set_up_disk_addr(&sd_ent,replacing,length,exact_fit)) ||
       !(sd_ptr = get_sd_slot(replacing,exact_fit)))
	goto CLOSE;
#ifdef DBUG
    printf("\n*DEBUG* sd_ptr gets %x (sysdir buffer starts at %x)",
	sd_ptr,sysdir.buffer);
#endif
    printf("\nCopying file %s to hard disk",inname);
    if(!add_to_disk(&sd_ent,infile,sectbuf,reads_done))
	goto CLOSE;

    /* write went ok, now update directory tables */
    /* and write those tables to the hard disk */

    printf("\nUpdating Control Area Directory");
    sd_ent.volume = sd_ent.unit = 0;
    sd_ent.prog_flag = sd_ent.reserved[0] = sd_ent.reserved[1] = 0;
#ifdef DBUG
    printf("\n*DEBUG moving entry into sd_ptr at %x",sd_ptr);
#endif
    blockmv(sd_ptr, &sd_ent, sizeof(struct SDentry));
    putfile(sysdir);

    if(is_os)
    {
	printf("\nUpdating Operating System Table");
	blockmv(ost_ptr, &ost_ent, sizeof(struct OSTentry));
	sort((char *)ostable.buffer,sizeof(struct OSTentry),87,/*len is at 87*/
							      2,ost_entries());
	putfile(ostable);
    }

CLOSE:			    /* This is the only exit point after the file */
    close(infile);	    /* is opened.  Even though we are only reading */
    return();		    /* it, it must be closed to free the channel so */
			    /* we don't run out. */
} /* add/replace */
       
modify()
{
    struct SDentry sd_ent, *sd_ptr;
    struct OSTentry ost_ent, *ost_ptr;
    int  sd_num, ost_num;
    int is_os;
    char inname[15], *p;
    register int valid;
    register int  i;  

    if(get_fname(inname,15,MODIFYING) == -1)	/* stuff name */
	return();

    chop_name(inname,sd_ent.name);	/* chop off drive, filetype */

    if((sd_ptr = find_sd(sd_ent.name)) == NULL)
    {
	printf("\n*SERIOUS BUG* in modify, can't find file in sd any more");
	return();
    }

    is_os = ((ost_ptr = find_os(sd_ent.name)) != NULL);

    ost_num = ((int)ost_ptr - (int)ostable.buffer) / sizeof(struct OSTentry);
    sd_num = ((int) sd_ptr - (int) sysdir.buffer) / sizeof(struct SDentry);

    printf("\nThis is the entry you have chosen to modify:\n");
    sd_header();
    printf("\n%02x\t",sd_num);
    prt_sd(sd_ptr);
    if(!its_correct())
	return();

    blockmv(&sd_ent,sd_ptr,sizeof(struct SDentry));  /* set up entry */

    printf("\n\nWhat is the new file name?");
    printf("\n(Press RETURN to keep current name of %s)",sd_ent.name);
    printf("\n(Press ESC to go back to Main Menu) --> ");

    do
    {
	valid = FALSE;
	clear(inname,8,' ');
	if((i=dgets(inname,8)) >8)    /* something was hit */
	    printf("\n8 characters max!");
	else if (i)
	{
	    if(inname[0] == ESC)
		return();
	    inname[8] = '\0';
	    for(p = inname; *p++ = toupper(*p); ) /* upper case */
		;
	    if(good_name(inname))
	    {
		blockmv(sd_ent.name,inname,8);
		valid = TRUE;
	    }
	    else
		printf("\nThat's not a valid filename.");
	} /* if anything was hit */
	else
	    valid = TRUE;
    } while(!valid);

    if(!get_sd_addrs(&sd_ent,&sd_ent))	    /* new=old as default */
	return();
    printf("\n\nYou have modified the entry to read:\n");
    sd_header();
    printf("\n%02x\t",sd_num);
    prt_sd(&sd_ent);
    if(!its_correct())
	return();

    /* Now if this is an OS file we must also give the opportunity to change */
    /* names in the OS Table entry. */

    valid = 0;
    while((is_os) && !valid)
    {
	blockmv(&ost_ent,ost_ptr,sizeof(struct OSTentry));
	printf("\nThis is the operating system entry for ");
	prtname(sd_ent.name);
	printf("\n");
	ost_header();
	printf("\n %x\t",ost_num);
	prt_os(&ost_ent);
	if(yorn("\n\nDo you wish to modify it "))
	{
	    remake_os(&ost_ent,sd_ent.name);
	    printf("\nThis is the operating system entry you have created:\n");
	    ost_header();
	    printf("\n %x\t",ost_num);
	    prt_os(&ost_ent);
	    valid = its_correct();
	}
	else
	    valid = TRUE;
	if(valid)
	    blockmv(ost_ptr, &ost_ent, sizeof(struct OSTentry));
    }  /* if os */

    /*	now update directory tables */
    /* and write those tables to the hard disk */

    printf("\nUpdating Control Area Directory");
    blockmv(sd_ptr->name, sd_ent.name, 8);
    sd_ptr->load_addr = sd_ent.load_addr;
    sd_ptr->exec_off = sd_ent.exec_off;

    putfile(sysdir);

    if(is_os)
    {
	printf("\nUpdating Operating System Table");
	ost_num = ost_entries();	   
	sort((char *)ostable.buffer,sizeof(struct OSTentry),87,2,ost_num);
	putfile(ostable);
    }

} /* modify */


get_fname(name,len,a_or_m)     /* get the input file name & open it */
char *name;			/* set abort_req if empty <cr> hit */
int len;
char a_or_m;
{
    char *p;
    char short_name[8];
    int i,fd;
    char adding;

    adding = (a_or_m == ADDING);

    do
    {
	clear(name,len,'\0');

	if(adding)
	{
	    printf("\nWhat is the name of the file to add to the Control ");
	    printf("Area Directory?\n(Use the full CP/M name in form ");
	    printf("filename.ext)");
	}
	else
	    printf("\nEnter the name of the file entry to modify.");
	printf("\n(Press RETURN to go back to main menu) --> ");
				   
	if(!(i=dgets(name,len-1)) || (name[0] == ESC))
	    return(-1); 	       /* illegal value shows return request */
		     
	trunc_name(name);		/* make sure name is not too long */
	chop_name(name,short_name);	/* knock off the drive & filetype */
	if(adding)
	{
	    if(find_sd(short_name))
	    {
		printf("\n\nThere is already a file named ");
		prtname(short_name);
		printf(" in the Control Area.");
		if(!yorn("\nDo you want to overwrite it"))
		    return(-1);
	    } /* if sd entry already present */
	    else if(find_os(short_name))
	    {
		printf("\n\nThat file is not in the Control Area but it does");
		printf("have an\nOS Table entry.  This is an unusual condi");
		printf("tion.\nIf you proceed, the new entry will replace ");
		if(!yorn("the old one.\nDo you wish to proceed"))
		    return(-1);
	    } /* if os entry present but not sd entry */
	    else
	    {
		printf("\n\nYou have asked to read in file %s.",name);
		if(!its_correct())
		    return(-1);
	    } /* if file is not currently in the tables at all */

	    if((fd = open(name,O_RDONLY)) == -1)
	    {
		printf("\n\n\07Can't find the file (%s) to be added.",name);
		return(-1);
	    }
	} /* if adding */

	else
	{
	    if(!find_sd(short_name))
	    {
		printf("\n\n\07There is no file in the Control Area ");
		printf("Directory named ");
		prtname(short_name);
		printf(".");
		fd = -1;
	    }
	    else
		fd = 0;
	} /* if not adding */

   } while (fd == -1);
    
    return(fd);
} /* get_fname (ask name of input file,  retry if no such file) */


make_os_entry(ptr,header,name)
struct OSTentry *ptr;
char header[];
char *name;
{
    char *p;
    int  *l;
    char tempname[15];
    register int i,j;
    int new_entry;

    for(i=0, p=(char*)ptr; i<23; ++i)	/* copy os type, prod map, opt map */
	*p++ = *header++;

    l = (int *)header;
    ptr->length = *l;

    clear(ptr->load_list,64,'\0');
    for(i=0, p = ptr->load_list[0]; (i<8) && (*name); ++i) /* copy file name */
	*p++ = *name++;

    printf("\nThis is an OS file and may require other files in order");
    printf(" to work.");
    printf("\nEnter the names of any support files this OS requires.");
    printf("\n(Press ESCAPE when all file names have been entered)\n");

    for(i=1; i<8; ++i)
    {
	printf("Support File #%d --> ",i);
	clear(tempname,15,' ');
	if((!dgets(tempname,14)) || (tempname[0] == ' '))
	{
	   printf("\n\07Enter file name or ESCAPE only!\n");
	   i--;
	   continue; 
	}
	if(tempname[0] == ESC)
	    break;
	chop_name(tempname,ptr->load_list[i]);
    }

} /* make a new OS table entry	*/


remake_os(ptr,name)
struct OSTentry *ptr;	    /* Old entry has been copied into this one.  */
char *name;
{
    char *p;
    int  *l;
    char tempname[15];
    register int i,j;
    char a;

    blockmv(ptr->load_list[0],name,8);	   /* name must agree with sys. dir. */

    for(i=1; i<8; ++i)
    {
	clear(tempname,14,' ');
	printf("\nPress RETURN to keep current setting.");
	printf("\nPress SPACE then RETURN to erase entry.");
	printf("\nPress ESC to stop entering names.");
	printf("\n\nCurrent support file #%d is ",i);
	if((a=ptr->load_list[i][0]) && (a != 0xE5))
	    print8(ptr->load_list[i]);
	else
	    printf("not used.");
	printf("\nEnter new name for support file #%d --> ",i);

	if(!(j=dgets(tempname,14)))	    /* CR: skip this entry */
	    continue;
	if(tempname[0] == ESC)		    /* ESC: break from loop */
	    break;
	if((j==1) && (tempname[0] = ' '))   /* SPACE: kill entry */
	{
	    blockmv(ptr->load_list[i],ptr->load_list[i+1],(8-i)*8);
	    clear(ptr->load_list[7],8,'\0');
	    i--;
	    continue;
	}

	chop_name(tempname,ptr->load_list[i]);	/* new name: use it */
    } /* loop until all 7 names done or until break requested */
} /* remake os table entry */


confirm_add(sd,ost,os)
struct SDentry *sd;
struct OSTentry *ost;
int os;
{
    dump_ent(sd,ost,os);
    return(its_correct());
} /* confirm add */

long curpos(fd)
int fd;
{
    long pos;

    errno = 0;
    if((pos = lseek(fd,0L,1)) == -1L)
	lseek_err();
    return(pos);
} /* get current position  */

long endpos(fd)
int fd;
{
    long cur,end;

    errno = 0;
    if(((cur = lseek(fd,0L,1)) == -1L) ||
       ((end = lseek(fd,0L,2)) == -1L) ||
       (lseek(fd,cur,0) == -1L))
	  lseek_err();
    return(end);
} /* get current position  */


lseek_err()
{
    printf("\n*BUG* ");
    if(errno == EBADF)
	printf("bad fd to lseek");
    else if(errno == EINVAL)
	printf("invalid param to lseek");
    else
	printf("errno = %d from lseek",errno);
} /* handle error in lseek */


good_name(p)		/* check for invalid characters in a file name */
char *p;
{
    register int i;

    for(i=0; (i<8) && (*p != ' '); ++i,++p)  /* search to first blank or end */
      if(index(".:\t",*p))		     /* look for invalid characters */
	    return(FALSE);
    for( ; i<8; ++i,++p)	    /* must be all blanks after first blank */
	if(*p != ' ')
	    return(FALSE);
    return(TRUE);
} /* good_name */

trunc_name(p)		/* chop filename & ext to max 8.3 chars */
char *p;		/* mainly to avoid confusion in user interface */
{
    register int i;
    register char *q;

    for(i=0, q=p; (*q=toupper(*p)) && (*p != '.') && (i<8);  ++i,++p,++q)
	;							/* copy name */
    for( ; *p && (*p != '.'); ++p)			/* eat until the dot */
	;
    if(*q++ = *p++)				/* copy dot or terminating 0 */
	for(i=0; (*q=toupper(*p)) && (i<3); ++i,++p,++q)	 /* copy ext */
	    ;
    *q = '\0';					/* make sure it's terminated */
    return();
} /* trunc_name */


get_sd_addrs(new,old)		/* get load address & entry offset */
struct SDentry *new, *old;	/* return TRUE iff successful */
{
    char *cp;

    if(old)
	cp = "the new";
    else
	cp = "its";
    do
    {
	error = FALSE;
	printf("\nWhat is %s Memory Load Address (0-FFFFFFFF)?",cp);
	if(old)
	    printf("\n(Press RETURN to keep current value of %lx)",
			old->load_addr);
	printf("\n(Press ESC to go back to main menu) --> ");
	if(old)
	    new->load_addr = get_new_hex(old->load_addr,8);
	else
	    if(!(new->load_addr = gethex(1,8)) && (abort_req))
	    {
		abort_req = FALSE;	    /* gethex returns value 0 and */
		error = TRUE;		    /* sets abort_req for <cr>;   */
	    }				    /* this is not ok in ADD mode */

	if(abort_req)
	    return(FALSE);
	if(error)
	    printf("\n\07Invalid entry");
    } while(error);

    do
    {
	printf("\nWhat is %s Entry Point Offset (0-FFFF)?",cp);
	if(old)
	    printf("\n(Press RETURN to keep current value of %x)",
			old->exec_off);
	printf("\n(Press ESC to go back to main menu) --> ");
	if(old)
	    new->exec_off = (int)get_new_hex((long)old->exec_off,4);
	else
	    if(!(new->exec_off = gethex(1,4)) && (abort_req))
	    {
		abort_req = FALSE;	    /* gethex returns value 0 and */
		error = TRUE;		    /* sets abort_req for <cr>;   */
	    }				    /* this is not ok in ADD mode */

	if(abort_req)
	    return(FALSE);
	if(error)
	    printf("\n\07Invalid entry");
    } while(error);

    return(TRUE);
} /* get_sd_addrs (get load address and exec off for an SD entry) */


long get_new_hex(curval,len)
long curval;
int len;
{
    long tempval;

    tempval = gethex(1,len);
    if(abort_req && (!tempval)) 	    /* In case of an empty CR, */
    {					    /* give back the current value */
	abort_req = FALSE;		    /* and cancel abort_req.  In  */
	return(curval); 		    /* case of ESC, return the ESC */
    }					    /* and leave abort_req set */
    else
	return(tempval);
} /* get_new_hex */

add_to_disk(ent,fd,buf,reads_done)	/* Do physical add.  Return TRUE */
struct SDentry *ent;			/* if successful, else FALSE */
int fd,reads_done;
char *buf;
{
    int track;
    char sector;
    int i;

    track = ent->track;
    sector = ent->sector;
#ifdef DBUG
    printf("\nStarting at track %x sector %x",track,sector);
#endif
    printf("\nSectors copied:\n");
    for(i=0; i<ent->length; ++i)     /* write file to hard disk */
    {
	hd_xfer(WRITE,track,sector,buf,1);  /* write one sector */
	if(++sector > SECTS_PER_TRACK)
	{
	    if(++track > 0x0F)
	    {
		printf("\n*YIKES* attempt to increment to track 0x%x in add",
			  track);
		return(FALSE);
	    }
	    sector = 1;
	}
	printf("%c%02x",CR,i+1);
	if((i+1) < ent->length)
	{
	    posit(fd,reads_done);
	    if(read(fd,buf,128) != 128)        /* read another */
	    {
		printf("\n*BUG* read != 128, errno = %d",errno);
		return(FALSE);
	    }
	    else
		++reads_done;
	}
    } /* loop until every sector in file is written */

#ifdef DBUG
    printf("\nFinished; next available loc. is track %x, sector %x",
	     track,sector);
#endif
    return(TRUE);
} /* add_to_disk */


struct SDentry *
get_sd_slot(old,exact_fit)	    /* Find a place in the table for the  */
struct SDentry *old;		    /* new SD entry.  Re-use the old one */
int exact_fit;			    /* if doing a same-size replacement. */
{				    /* Return NULL if something wrong. */
    int sd_num;

    if(exact_fit)
	return(old);
    else if ((sd_num = sd_entries()) >= 128)
    {
	printf("\nThere is no room in the Control Area Directory for the");
	printf(" new entry that\nthis file would require.");
	return(NULL);
    }
    else
	return(((struct SDentry *)(sysdir.buffer)) +sd_num); /* next slot */

} /* get sd slot (find place in SD to put new entry) */


set_up_disk_addr(new,old,length,exact_fit)   /* set up track, sector, length */
struct SDentry *new,*old;		     /* fields in the new SD entry; */
unsigned length;			     /* return TRUE if ok, FALSE if */
int exact_fit;				     /* error (e.g. file won't fit  */
{					     /* in Unit 0)  */
    int  sd_num;
    int  replacing;

    replacing = (old != NULL);
    new->length = length;

    if(exact_fit)
    {
	new->track = old->track;
	new->sector = old->sector;
    }
    else
    {
	int  end_track;
	char end_sector;
	int  max_track;

	sd_num = sd_entries();
	old = ((struct SDentry *)(sysdir.buffer)) +sd_num-1; /* last used */
				       /* now figure beginning of NEXT entry */
	if(sd_num)
	{
	    new->track = old->track +
			    (old->sector+old->length-1)/SECTS_PER_TRACK;
	    new->sector = ((old->sector+old->length-1) %SECTS_PER_TRACK)+1;
	}
	else
	{
	    new->track = 3;
	    new->sector = 1;
	}

	end_track = new->track + (new->sector+length-1)/SECTS_PER_TRACK;
	end_sector = ((new->sector+length-1) % SECTS_PER_TRACK) +1;
#ifdef DBUG
	printf("\n*DBUG* unit_0_k = %dd,SECTS_PER_TRACK * (1024/128) = %d",
		    unit_0_k,SECTS_PER_TRACK *(1024/128));
#endif
	max_track = (unit_0_k / (SECTS_PER_TRACK / (1024/128)))-1;
#ifdef DBUG
	printf("\n*DBUG* so max_track works out to 0x%x",max_track);
#endif
	if(end_track > max_track)
	{
	    printf("\nThere is no room in the  Control Area for this file.");
	    printf("\nIf written, the file would end at track %x, sector %x.", 
			end_track, end_sector);
	    printf("\n(The Control Area ends at track %x, sector 80.)",
			max_track);
	    return(FALSE);
	}
    }  /* if not exact replacement */

    return(TRUE);	       /* return success flag */

} /* set up disk addr */

	}
    }  /* if not exact replacement */

    return(TRUE);	       /*