/* Ln: create a hard link to a file (os9/68k)
 * by Robert A. Larson (blarson@skat.usc.edu)
 * This is placed in the public domain for all to enjoy.
 *
 ****** USE AT YOUR OWN RISK *****
 *
 * 01/14/89	Version 1.0
 *
 * Warning: this does potentialy dangerous writing to directories
 * and to the raw disk.	 Although some attempt is made to lock out
 * other users from interfearing, I'm not sure the locking works.
 * Moving or links to directories should be used with EXTREME caution
 * if at all.  Note that moving a directory can make a directory tree
 * inaccessable, and 'deldir'ing a linked directory will delete its
 * contents and change the remaining links to normal files.
 */
 
/*
 * Version 1.1
 *
 * Modified by Mike Haaland
 *
 * December 8, 1992 Tuesday 9:55:17 am          - MSH
 * 
 * o added sector size checking for OSK 2.4
 * o added closing path to raw disk after successful link
 * o Added FROM_DISK || FROM_DRIVER as methods of checking sector size
 * o Added TESTING define for debuggin info
 *
 */

#include <stdio.h>
#include <ctype.h>
#include <modes.h>
#include <direct.h>
#include <errno.h>
#include <sgstat.h>

struct sgbuf sgopts;

extern char *rindex();
extern int  open();
extern int  close();
extern int  read();
extern int  write();
extern int  lseek();
extern int  access();
extern int  getuid();
extern int  strcmp();
extern char *strcpy();
extern char *strcat();
extern char *_prgname();
extern int  _gs_devn();
extern int  _ss_lock();
extern long _gs_pos();

static int  dncmp();		/* directory name comparison	*/
static int  do_link();		/* does all the real work		*/

/* keys for the do_link subroutine */
#define DO_LINK		0
#define DO_RENAME	1
#define TO_DIR		128		/* orred with one of the above	*/

/*
 * Define one of the following ONLY
 *
 * FROM_DISK   - will read LSN0 for the sector size info, byte 0x68
 *               works on ALL OSK systems that I've encountered.
 * FROM_DRIVER - uses the path descriptor sg_ssize field
 *               BEWARE: some OSK 2.3 drivers LIE and say 512 bytes
 *                       when the disk actually uses 256 byte sectors!!!
 * MSH
 */
 
#define FROM_DISK

/* Get sector size info from driver - Hazelwoods HD Driver lies on the TC70
#define FROM_DRIVER
*/

#ifndef LINK
main(argc, argv)
int argc;
char **argv;
{
    register int i;
    char *todir = NULL;
    int skip, what;

    for(i = 1; i < argc; i++) {
        if(argv[i][0]=='-') {
            switch(argv[i][1]) {
            case '?':
                usage();
                exit(0);
            case 'w':
                if(todir==NULL && argv[i][2]=='=') {
                    todir = &argv[i][3];
                    skip = i;
                    continue;
                }
                /*FALLTHROUGH*/
            default:
                usage();
                exit(1);
            }
        }
    }
    what = strcmp(argv[0],"ln")==0 ? DO_LINK : DO_RENAME;
    if(todir==NULL) {
        if(argc != 3) {
            usage();
            exit(1);
        }
        exit(do_link(argv[1], argv[2], what) < 0);
    }
    what |= TO_DIR;
    for(i=1; i < argc; i++) {
        if(i!=skip) {
            if(do_link(argv[i], todir, what) < 0) exit(1);
        }
    }
    exit(0);
}

usage() {
    register char *prg = _prgname();

    fprintf(stderr,"Usage: %s -?\n",prg);
    fprintf(stderr,"       %s <from> <to>\n",prg);
    fprintf(stderr,"       %s -w=<todir> <from> [<from>]...\n", prg);
}


#define problem(x)	fprintf x
#else
#define problem(x)	/* ignore */

link(old, new)
register char *old, *new;
{
    return do_link(old, new, DO_LINK);
}

rename(old, new)
register char *old, *new;
{
    return do_link(old, new, DO_RENAME);
}
#endif

static int do_link(old, new, action)
char *old, *new;
int action;
{
    char *olddir, *oldname, *newdir, *newname;
    int olddirp, newdirp;
    char olddevn[32], newdevn[32+2];
    struct dirent de;
    unsigned char fdl;
    int disk;
    long empty_slot;
    long newdir_addr;
    long entrydir_addr;
    long fdladdr;
    int same;
    int i;
    int sector_size;
    long old_slot;
    char namebuf[256];
    char nn[128];
    char ssz;
    int uid;

    if((oldname = rindex(old, '/')) == NULL) {
        olddir = ".";
        oldname = old;
    } 
    else {
        olddir = namebuf;
        strncpy(namebuf, old, oldname - old);
        namebuf[oldname - old] = '\0';
        oldname++;
    }
    if(action & TO_DIR) {
        newdir = new;
        newname = oldname;
        action &= ~TO_DIR;
    } 
    else {
        if((newname = rindex(new, '/')) == NULL) {
            newdir = ".";
            newname = new;
        } 
        else {
            newdir = namebuf;
            if(olddir==namebuf) newdir += oldname - old;
            strncpy(newdir, new, newname - new);
            newdir[newname - new] = '\0';
            newname++;
        }
    }
    if(_prsnam(newname) <= 0) {
        problem((stderr, "%s: Invalid file name \"%s\"\n", _prgname(), newname));
        return -1;
    }
    if((olddirp = open(olddir, S_IREAD | S_IWRITE | S_IFDIR)) < 0) {
        problem((stderr, "%s: Can't open old directory \"%s\"\n", _prgname(),
        olddir));
        return -1;
    }
    if((newdirp = open(newdir, S_IREAD | S_IWRITE | S_IFDIR)) < 0) {
        problem((stderr, "%s: Can't open new directory \"%s\"\n", _prgname(),
        newdir));
        close(olddirp);
        return -1;
    }
    _gs_devn(olddirp, olddevn);
    _gs_devn(newdirp, newdevn);
    if(strcmp(olddevn, newdevn) != 0) {
        problem((stderr, "%s: Not on same device \"%s\" \"%s\"\n",
        _prgname(), olddir, newdir));
        errno = E_DIFFER;
        close(olddirp);
        close(newdirp);
        return -1;
    }
    if((lseek(newdirp, (long)sizeof de, 0) < 0)				/* skip .. */
        || (read(newdirp, (char *)&de, sizeof de) != sizeof de)) {	/* read . */
        problem((stderr, "%s: Problem reading \"%s\"\n", _prgname(), newdir));
        close(olddirp);
        close(newdirp);
        return -1;
    }
    newdir_addr = de.dir_addr;
    if((lseek(olddirp, (long)sizeof de, 0) < 0)				/* skip .. */
        || (read(olddirp, (char *)&de, sizeof de) != sizeof de)) {	/* read . */
        problem((stderr, "%s: Problem reading \"%s\"\n", _prgname(), olddir));
        close(olddirp);
        close(newdirp);
        return -1;
    }
    if((same = (de.dir_addr == newdir_addr)) != 0) {
        /* link to same directory, scan only once */
        close(olddirp);
        olddirp = newdirp;
        /* rename to same name allowed, but not link */
        if(action == DO_LINK && dncmp(oldname, newname) == 0) {
            problem((stderr, "%s: Can't link to same name \"%s\" \"%s\"\n",
            _prgname(), oldname, newname));
            errno = E_CEF;
            close(newdirp);
            return -1;
        }
        if(_ss_lock(newdirp, 0xfffffffff) < 0) {
            problem((stderr, "%s: Could not lock directory \"%s\"\n",
            _prgname(), newdir));
            close(newdirp);
            return -1;
        }
    }
    empty_slot = 0;
    for(;;) {
        if(read(olddirp, (char *)&de, sizeof de) != sizeof de) {
            problem((stderr, "%s: Could not find \"%s\" in \"%s\"\n",
            _prgname(), oldname, olddir));
            errno = E_PNNF;
            if(!same) close(olddirp);
            close(newdirp);
            return -1;
        }
        if(dncmp(de.dir_name, oldname) == 0) break;	/* found it */
        if(same) {
            if(dncmp(de.dir_name, newname) == 0) {
                problem((stderr, "%s: Already exists \"%s/%s\"\n",
                _prgname(), newdir, newname));
                errno = E_CEF;
                close(newdirp);
                return -1;
            }
            if(empty_slot == 0 && de.dir_name[0] == '\0')
                empty_slot = _gs_pos(olddirp);
        }
    }
    if(action != DO_LINK) old_slot = _gs_pos(olddirp);
    entrydir_addr = de.dir_addr;
    if(!same) {
        if(_ss_lock(newdirp, 0xffffffff) < 0) {
            problem((stderr, "%s: Can't lock directory \"%s\"\n", _prgname(),
            newdir));
            close(olddirp);
            close(newdirp);
            return -1;
        }
    }
    if(action != DO_LINK && same) empty_slot = old_slot;
    while(read(newdirp, (char *)&de, sizeof de) == sizeof de) {
        if(dncmp(de.dir_name, newname) == 0) {
            problem((stderr, "%s: Already exists \"%s/%s\"\n", _prgname(),
            newdir, newname));
            errno = E_CEF;
            if(!same) close(olddirp);
            close(newdirp);
            return -1;
        }
        if(empty_slot == 0 && de.dir_name[0] == '\0') {
            empty_slot = _gs_pos(newdirp);
        }
    }
    strcpy(de.dir_name, newname);
    i = strlen(de.dir_name);
    de.dir_name[i-1] |= 0x80;
    while(i < sizeof de.dir_name) de.dir_name[i++] = '\0';
    de.dir_addr = entrydir_addr;
    if(empty_slot != 0) {
        if(lseek(newdirp, empty_slot - sizeof de, 0) < 0) {
            problem((stderr, "%s: Can't position in \"%s\"\n", _prgname(),
            newdir));
            close(disk);
            if(!same) close(olddirp);
            close(newdirp);
            return -1;
        }
    }
    if(action == DO_LINK) {		/* link: increase the link count */
	    /* Sector size info for OSK 2.4 added 12/07/1992 by MSH */
#ifdef FROM_DRIVER
	    if (_gs_opt(newdirp,&sgopts) == -1) {
        	problem((stderr, "%s: Can't get device sector size info.\n",
	        _prgname()));
        	close(olddirp);
	        close(newdirp);
        	return -1;
	    }
	    sector_size = sgopts.sg_ssize;
	    
	    if (sector_size == 0) 
	       sector_size = 256;
#endif
	    uid = getuid();
	    if (uid)
        	setuid(0);
        newdevn[0] = '/';			/* form /<device>@ string */
        strcat(strcpy(newdevn+1, olddevn), "@");
#ifdef TESTING
        fprintf(stderr,"Opening '%s' for link\n",newdevn);
#endif
        if((disk = open(newdevn, S_IREAD | S_IWRITE)) < 0) {
            problem((stderr, "%s: Can't open \"%s\"\n", _prgname(), newdevn));
            if(!same) close(olddirp);
            close(newdirp);
            setuid(uid);
            return -1;
        }
#ifdef FROM_MEDIA
	    /* Sector size info for OSK 2.4 added 12/07/1992 by MSH */
	    /* seek to byte 0x68 - LSNSize - media logical sector size */
        if (lseek(disk,0x68,0) != 0x68) {
        	problem((stderr,"%s: Can't seek to LSN0 on \"%s\"\n",_prgname(),newdevn));
            if(!same) close(olddirp);
            close(newdirp);
            setuid(uid);
            return -1;
        }
        /* Read LSNSize byte */	
        if(read(disk, &ssz, sizeof ssz) != sizeof ssz) {
            problem((stderr, "%s: Can't get sector size of \"%s\"\n", _prgname(), newdevn));
            close(disk);
            if(!same) close(olddirp);
            setuid(uid);
            close(newdirp);
            return -1;
        }
        /* If it's 0 sector size is 256 bytes - Pre-OSK 2.4 */
        if (ssz == 0)
            ssz = 1;
        /* value * 256 == sector size in bytes */
        sector_size = ssz * 256;
#endif /* FROM_MEDIA */
#ifdef TESTING
        fprintf(stderr,"Media sector size %d bytes\n",sector_size);
#endif
        if((fdladdr = lseek(disk, (entrydir_addr * sector_size) +
            ((char *)(&(((struct fildes *)0)->fd_link)) - (char *)0),
            0)) < 0) {
            problem((stderr, "%s: Can't seek to sector 0x%x on \"%s\"\n",
            _prgname(), entrydir_addr, newdevn));
            close(disk);
            if(!same) close(olddirp);
            setuid(uid);
            close(newdirp);
            return -1;
        }
#ifdef TESTING
        fprintf(stderr,"Linkbyte addr (%d) for file '%s'\n",fdladdr,old);
#endif
        if(read(disk, (char *)&fdl, sizeof fdl) != sizeof fdl) {
            problem((stderr, "%s: Can't read \"%s\"\n", _prgname(), newdevn));
            close(disk);
            if(!same) close(olddirp);
            setuid(uid);
            close(newdirp);
            return -1;
        }
#ifdef TESTING
        fprintf(stderr,"Linkbyte = (%d) for file '%s'\n",fdl,old);
#endif
        if(fdl == 0) {
            problem((stderr, "%s: Bad file descriptor \"%s/%s\"\n", _prgname(),
            olddir, oldname));
            errno = E_BMHP;		/* no appropriate error number */
            close(disk);
            if(!same) close(olddirp);
            setuid(uid);
            close(newdirp);
            return -1;
        }
        if(++fdl == 0) {
            problem((stderr, "%s: Too many links \"%s/%s\"\n", _prgname(),
            olddir, oldname));
            errno = E_BMHP;		/* no appropriate error number */
            close(disk);
            if(!same) close(olddirp);
            setuid(uid);
            close(newdirp);
            return -1;
        }
#ifdef TESTING
        fprintf(stderr,"Inc. Linkbyte = (%d) to link '%s'\n",fdl,new);
#endif
        if(lseek(disk, fdladdr, 0) != fdladdr) {
            problem((stderr, "%s: Can't lseek on \"%s\"\n", _prgname(),
            newdevn));
            close(disk);
            if(!same) close(olddirp);
            setuid(uid);
            close(newdirp);
            return -1;
        }
#ifdef TESTING
        fprintf(stderr,"ReSeek to (%d) to link '%s'\n",fdladdr,new);
#endif
        if(write(disk, &fdl, sizeof fdl) < 0) {
            problem((stderr, "%s: Can't write to \"%s\"\n", _prgname(),
            newdevn));
            close(disk);
            if(!same) close(olddirp);
            setuid(uid);
            close(newdirp);
            return -1;
        }
        /* This wasn't here!!! - might have been my link problem, yes? */
        close(disk);
#ifdef TESTING
        fprintf(stderr,"Success %s linked to %s - (%d) links\n",old,new,fdl);
#endif
        setuid(uid);
    } 
    else {
        strcpy(nn, newdir);
        strcat(nn, "/");
        strcat(nn, newname);
        /* Don't let non-superuser move directories, it is dangerous!
       	 * (artificial restriction)
       	 */
        if(!same && access(nn, 0) != 0 && getuid() != 0) {
            problem((stderr, "%s: Directory moving can only be done by superuser\n",
            _prgname()));
            errno = E_FNA;
            close(olddirp);
            close(newdirp);
            return -1;
        }
    }
    if(write(newdirp, (char *)&de, sizeof de) < 0) {
        problem((stderr, "%s: Can't write to \"%s\"\n", _prgname(), newdir));
        if(action==DO_LINK) close(disk);
        if(!same) close(olddirp);
        close(newdirp);
        return -1;
    }
    if(action != DO_LINK && !same) {		/* rename: delete old name */
        if((disk = open(nn, S_IREAD | S_IWRITE | S_IFDIR)) >= 0) {
            /* change .. when moving directory */
            if(lseek(disk, (long)sizeof de.dir_name, 0) < 0) {
                problem((stderr, "%s: can't position in \"%s\"\n",
                _prgname(), nn));
                close(disk);
                close(olddirp);
                close(newdirp);
                return -1;
            }
            if(write(disk, (char *)&newdir_addr, sizeof newdir_addr) < 0) {
                problem((stderr, "%s: Can't write to \"%s\"\n",
                _prgname(), nn));
                close(disk);
                close(olddirp);
                close(newdirp);
                return -1;
            }
            close(disk);
        }
        fdl = '\0';
        if(lseek(olddirp, old_slot - sizeof de, 0) < 0) {
            problem((stderr, "%s: can't position in \"%s\"\n", _prgname(),
            olddir));
            close(olddirp);
            close(newdirp);
            return -1;
        }
        if(write(olddirp, (char *)&fdl, sizeof fdl) < 0) {
            problem((stderr, "%s: can't write to \"%s\"\n", _prgname(),
            olddir));
            close(olddirp);
            close(newdirp);
            return -1;
        }
    }
    if(action == DO_LINK) close(disk);
    if(!same) close(olddirp);
    close(newdirp);
    return 0;
}

/* Compares two os9 file names, case independant.
 * The first argument may be terminated by setting the msb of
 * the last character or NUL terminated, the second must be NUL
 * termintated.	 Returns zero on same, non-zero on different.
 */
static int dncmp(cp1, cp2)
register char *cp1, *cp2;
{
    register int c1, c2;

    while((c1 = *cp1++) != 0) {
        if(c1 & 0x80) {
            c1 &= 0x7f;
            return ((c1 != (c2 = *cp2++)) &&
                (isupper(c1) ? (tolower(c1) != c2) :
            (!isupper(c2) || (tolower(c2) != c1)))) || *cp2;
        }
        if((c1 != (c2 = *cp2++)) && (isupper(c1) ? (tolower(c1) != c2) :
        (!isupper(c2) || (tolower(c2) != c1)))) return 1;
    }
    return *cp2;
}


