diff --git a/share/share.c b/share/share.c new file mode 100644 index 0000000..fde6849 --- /dev/null +++ b/share/share.c @@ -0,0 +1,701 @@ +/* + FreeDOS SHARE + Copyright (c) 2000 Ronald B. Cemer under the GNU GPL + You know the drill. + If not, see www.gnu.org for details. Read it, learn it, BE IT. :-) +*/ + +#include +#include +#include +#include +#include +#include + +#ifndef __TURBOC__ +#error "This software must be compiled with TurboC or TurboC++." +#endif + +#define MUX_INT_NO 0x2f +#define MULTIPLEX_ID 0x10 + +#define FILE_TABLE_MIN 128 +#define FILE_TABLE_MAX 62000 + +#define LOCK_TABLE_MIN 1 +#define LOCK_TABLE_MAX 3800 + + /* Valid values for openmode: */ +#define OPEN_READ_ONLY 0 +#define OPEN_WRITE_ONLY 1 +#define OPEN_READ_WRITE 2 + + /* Valid values for sharemode: */ +#define SHARE_COMPAT 0 +#define SHARE_DENY_ALL 1 +#define SHARE_DENY_WRITE 2 +#define SHARE_DENY_READ 3 +#define SHARE_DENY_NONE 4 + + /* Register structure for an interrupt function. */ +typedef struct { + unsigned bp; + unsigned di; + unsigned si; + unsigned ds; + unsigned es; + unsigned dx; + unsigned cx; + unsigned bx; + unsigned ax; + unsigned ip; + unsigned cs; + unsigned flags; +} intregs_t; + + /* This table determines the action to take when attempting to open + a file. The first array index is the sharing mode of a previous + open on the same file. The second array index is the sharing mode + of the current open attempt on the same file. Action codes are + defined as follows: + 0 = open may proceed + 1 = open fails with error code 05h + 2 = open fails and an INT 24h is generated + 3 = open proceeds if the file is read-only; otherwise fails + with error code (used only in exception table below) + 4 = open proceeds if the file is read-only; otherwise fails + with INT 24H (used only in exception table below) + Exceptions to the rules are handled in the table + below, so this table only covers the general rules. + */ +static unsigned char open_actions[5][5] = { + { 0, 1, 1, 1, 1 }, + { 2, 1, 1, 1, 1 }, + { 2, 1, 1, 1, 1 }, + { 2, 1, 1, 1, 1 }, + { 2, 1, 1, 1, 0 }, +}; + +typedef struct { + unsigned char first_sharemode; + unsigned char first_openmode; + unsigned char current_sharemode; + unsigned char current_openmode; + unsigned char action; +} open_action_exception_t; + +static open_action_exception_t open_exceptions[] = { + { 0, 0, 2, 0, 3 }, + { 2, 0, 0, 0, 4 }, + { 2, 0, 2, 0, 0 }, + { 2, 0, 4, 0, 0 }, + { 3, 0, 2, 1, 0 }, + { 3, 0, 4, 1, 0 }, + { 3, 1, 4, 1, 0 }, + { 3, 2, 4, 1, 0 }, + { 4, 0, 0, 0, 4 }, + { 4, 0, 2, 0, 0 }, + { 4, 0, 2, 1, 0 }, + { 4, 0, 2, 2, 0 }, + { 4, 1, 3, 0, 0 }, + { 4, 1, 3, 1, 0 }, + { 4, 1, 3, 2, 0 }, +}; + + /* One of these exists for each instance of an open file. */ +typedef struct { + char filename[128]; /* fully-qualified filename; "\0" if unused */ + unsigned short psp; /* PSP of process which opened this file */ + unsigned char openmode; /* 0=read-only, 1=write-only, 2=read-write */ + unsigned char sharemode;/* SHARE_COMPAT, etc... */ + unsigned char first_openmode; /* openmode of first open */ + unsigned char first_sharemode; /* sharemode of first open */ +} file_t; + + /* One of these exists for each active lock region. */ +typedef struct { + unsigned char used; /* Non-zero if this entry is used. */ + unsigned long start; /* Beginning offset of locked region */ + unsigned long end; /* Ending offset of locked region */ + unsigned short fileno; /* file_table entry number */ + unsigned short psp; /* PSP of process which owns the lock */ +} lock_t; + +static char progname[9]; +static unsigned int file_table_size_bytes = 2048; +static unsigned int file_table_size = 0; /* # of file_t we can have */ +static file_t *file_table = NULL; +static unsigned int lock_table_size = 20; /* # of lock_t we can have */ +static lock_t *lock_table = NULL; + + /* DOS calls this to see if it's okay to open the file. + Returns a file_table entry number to use (>= 0) if okay + to open. Otherwise returns < 0 and may generate a critical + error. If < 0 is returned, it is the negated error return + code, so DOS simply negates this value and returns it in + AX. */ +static int open_check + (char far *filename,/* far pointer to fully qualified filename */ + unsigned short psp,/* psp segment address of owner process */ + int openmode, /* 0=read-only, 1=write-only, 2=read-write */ + int sharemode); /* SHARE_COMPAT, etc... */ + + /* DOS calls this to record the fact that it has successfully + closed a file, or the fact that the open for this file failed. */ +static void close_file + (int fileno); /* file_table entry number */ + + /* DOS calls this to determine whether it can access (read or + write) a specific section of a file. We call it internally + from lock_unlock (only when locking) to see if any portion + of the requested region is already locked. If psp is zero, + then it matches any psp in the lock table. Otherwise, only + locks which DO NOT belong to psp will be considered. + Returns zero if okay to access or lock (no portion of the + region is already locked). Otherwise returns non-zero and + generates a critical error (if allowcriter is non-zero). + If non-zero is returned, it is the negated return value for + the DOS call. */ +static int access_check + (unsigned short psp,/* psp segment address of owner process */ + int fileno, /* file_table entry number */ + unsigned long ofs, /* offset into file */ + unsigned long len, /* length (in bytes) of region to access */ + int allowcriter); /* allow a critical error to be generated */ + + /* DOS calls this to lock or unlock a specific section of a file. + Returns zero if successfully locked or unlocked. Otherwise + returns non-zero. + If the return value is non-zero, it is the negated error + return code for the DOS 0x5c call. */ +static int lock_unlock + (unsigned short psp,/* psp segment address of owner process */ + int fileno, /* file_table entry number */ + unsigned long ofs, /* offset into file */ + unsigned long len, /* length (in bytes) of region to lock or unlock */ + int unlock); /* non-zero to unlock; zero to lock */ + + /* Multiplex interrupt handler */ + +static void interrupt far (*old_handler2f)() = NULL; + +static void interrupt far handler2f(intregs_t iregs) { + +#define chain_old_handler2f { \ + _BX = iregs.bx; /* Restore BX */ \ + _CX = iregs.ax; /* Save original AX contents into CX */ \ + iregs.ax = FP_SEG((void far *)old_handler2f); /* Set chain segment */ \ + iregs.bx = FP_OFF((void far *)old_handler2f); /* Set chain offset */ \ + _AX = _CX; /* Restore AX */ \ + __emit__(0x5D); /* POP BP */ \ + __emit__(0x5F); /* POP DI */ \ + __emit__(0x5E); /* POP SI */ \ + __emit__(0x1F); /* POP DS */ \ + __emit__(0x07); /* POP ES */ \ + __emit__(0x5A); /* POP DX */ \ + __emit__(0x59); /* POP CX */ \ + __emit__(0xCB); /* RETF */ \ +} + + if (((iregs.ax >> 8) & 0xff) == MULTIPLEX_ID) { + if ((iregs.ax & 0xff) == 0) { + /* Installation check. Return 0xff in AL. */ + iregs.ax |= 0xff; + return; + } + /* These subfuctions are nonstandard, but are highly + unlikely to be used by another multiplex TSR, since + our multiplex Id (0x10) is basically reserved for + SHARE. So we should be able to get away with using + these for our own purposes. */ + /* open_check */ + if ((iregs.ax & 0xff) == 0xa0) { + iregs.ax = open_check + (MK_FP(iregs.ds, iregs.si), + iregs.bx, + iregs.cx, + iregs.dx); + return; + } + /* close_file */ + if ((iregs.ax & 0xff) == 0xa1) { + close_file(iregs.bx); + return; + } + /* access_check (0xa2) */ + /* access_check with critical error (0xa3) */ + if ((iregs.ax & 0xfe) == 0xa2) { + iregs.ax = access_check + (iregs.bx, + iregs.cx, + ( ((((unsigned long)iregs.si)<<16) & 0xffff0000L) | + (((unsigned long)iregs.di) & 0xffffL) ), + ( ((((unsigned long)iregs.es)<<16) & 0xffff0000L) | + (((unsigned long)iregs.dx) & 0xffffL) ), + (iregs.ax & 0x01)); + return; + } + /* lock_unlock lock (0xa4)*/ + /* lock_unlock unlock (0xa5) */ + if ((iregs.ax & 0xfe) == 0xa4) { + iregs.ax = lock_unlock + (iregs.bx, + iregs.cx, + ( ((((unsigned long)iregs.si)<<16) & 0xffff0000L) | + (((unsigned long)iregs.di) & 0xffffL) ), + ( ((((unsigned long)iregs.es)<<16) & 0xffff0000L) | + (((unsigned long)iregs.dx) & 0xffffL) ), + (iregs.ax & 0x01)); + return; + } + } + /* Chain to the next handler. */ + chain_old_handler2f; +} + +static void remove_all_locks(int fileno) { + int i; + lock_t *lptr; + + for (i = 0; i < lock_table_size; i++) { + lptr = &lock_table[i]; + if (lptr->fileno == fileno) lptr->used = 0; + } +} + +static void free_file_table_entry(int fileno) { + file_table[fileno].filename[0] = '\0'; +} + +static int file_is_read_only(char far *filename) { + union REGS regs; + struct SREGS sregs; + + regs.x.ax = 0x4300; + sregs.ds = FP_SEG(filename); + regs.x.dx = FP_OFF(filename); + intdosx(®s, ®s, &sregs); + if (regs.x.cflag) return 0; + return ((regs.h.cl & 0x19) == 0x01); +} + +static int fnmatches(char far *fn1, char far *fn2) { + while (*fn1) { + if (*fn1 != *fn2) return 0; + fn1++; + fn2++; + } + return (*fn1 == *fn2); +} + +static int do_open_check + (int fileno) { /* file_table entry number */ + file_t *p, *fptr = &file_table[fileno]; + int i, j, action = 0, foundexc; + unsigned char current_sharemode = fptr->sharemode; + unsigned char current_openmode = fptr->openmode; + open_action_exception_t *excptr; + + fptr->first_sharemode = fptr->sharemode; + fptr->first_openmode = fptr->openmode; + for (i = 0; i < file_table_size; i++) { + if (i == fileno) continue; + p = &file_table[i]; + if (p->filename[0] == '\0') continue; + if (!fnmatches(p->filename, fptr->filename)) continue; + fptr->first_sharemode = p->first_sharemode; + fptr->first_openmode = p->first_openmode; + /* Look for exceptions to the general rules first. */ + foundexc = 0; + for (j = 0; + j < (sizeof(open_exceptions)/sizeof(open_action_exception_t)); + j++) { + excptr = &open_exceptions[j]; + if ( (excptr->first_sharemode == fptr->first_sharemode) + && (excptr->current_sharemode == current_sharemode) + && (excptr->first_openmode == fptr->first_openmode) + && (excptr->current_openmode == current_openmode) ) { + foundexc = 1; + action = excptr->action; + break; + } + } + /* If no exception to rules, use normal rules. */ + if (!foundexc) + action = open_actions[fptr->first_sharemode][current_sharemode]; + /* Fail appropriately based on action. */ + switch (action) { + case 0: /* proceed with open */ + break; + case 3: /* succeed if file read-only, else fail with error 05h */ + if (file_is_read_only(fptr->filename)) break; + case 1: /* fail with error code 05h */ + free_file_table_entry(fileno); + return -5; + case 4: /* succeed if file read-only, else fail with int 24h */ + if (file_is_read_only(fptr->filename)) break; + case 2: /* fail with int 24h */ + { + union REGS regs; + + regs.h.ah = 0x0e; /* disk I/O; fail allowed; data area */ + regs.h.al = 0; + regs.x.di = 0x0d; /* sharing violation */ + if ( (fptr->filename[0]!='\0') && (fptr->filename[1]==':') ) + regs.h.al = fptr->filename[0]-'A'; + free_file_table_entry(fileno); + int86(0x24, ®s, ®s); + } + return -0x20; /* sharing violation */ + } + break; + } + return fileno; +} + + /* DOS calls this to see if it's okay to open the file. + Returns a file_table entry number to use (>= 0) if okay + to open. Otherwise returns < 0 and may generate a critical + error. If < 0 is returned, it is the negated error return + code, so DOS simply negates this value and returns it in + AX. */ +static int open_check + (char far *filename,/* far pointer to fully qualified filename */ + unsigned short psp,/* psp segment address of owner process */ + int openmode, /* 0=read-only, 1=write-only, 2=read-write */ + int sharemode) { /* SHARE_COMPAT, etc... */ + + int i, fileno = -1; + file_t *fptr; + + /* Whack off unused bits in the share mode + in case we were careless elsewhere. */ + sharemode &= 0x07; + + /* Assume compatibility mode if invalid share mode. */ +/* ??? IS THIS CORRECT ??? */ + if ( (sharemode < SHARE_COMPAT) || (sharemode > SHARE_DENY_NONE) ) + sharemode = SHARE_COMPAT; + + /* Whack off unused bits in the open mode + in case we were careless elsewhere. */ + openmode &= 0x03; + + /* Assume read-only mode if invalid open mode. */ +/* ??? IS THIS CORRECT ??? */ + if ( (openmode < OPEN_READ_ONLY) || (openmode > OPEN_READ_WRITE) ) + openmode = OPEN_READ_ONLY; + + for (i = 0; i < file_table_size; i++) { + if (file_table[i].filename[0] == '\0') { + fileno = i; + break; + } + } + if (fileno == -1) return -1; + fptr = &file_table[fileno]; + + /* Copy the filename into ftpr->filename. */ + for (i = 0; i < sizeof(fptr->filename); i++) { + if ((fptr->filename[i] = filename[i]) == '\0') break; + } + fptr->psp = psp; + fptr->openmode = (unsigned char)openmode; + fptr->sharemode = (unsigned char)sharemode; + /* Do the sharing check and return fileno if + okay, or < 0 (and free the entry) if error. */ + return do_open_check(fileno); +} + + /* DOS calls this to record the fact that it has successfully + closed a file, or the fact that the open for this file failed. */ +static void close_file + (int fileno) { /* file_table entry number */ + + remove_all_locks(fileno); + free_file_table_entry(fileno); +} + + /* DOS calls this to determine whether it can access (read or + write) a specific section of a file. We call it internally + from lock_unlock (only when locking) to see if any portion + of the requested region is already locked. If psp is zero, + then it matches any psp in the lock table. Otherwise, only + locks which DO NOT belong to psp will be considered. + Returns zero if okay to access or lock (no portion of the + region is already locked). Otherwise returns non-zero and + generates a critical error (if allowcriter is non-zero). + If non-zero is returned, it is the negated return value for + the DOS call. */ +static int access_check + (unsigned short psp,/* psp segment address of owner process */ + int fileno, /* file_table entry number */ + unsigned long ofs, /* offset into file */ + unsigned long len, /* length (in bytes) of region to access */ + int allowcriter) { /* allow a critical error to be generated */ + int i; + file_t *fptr = &file_table[fileno]; + char far *filename = fptr->filename; + lock_t *lptr; + unsigned long endofs = ofs + len; + + if (endofs < ofs) { + endofs = 0xffffffffL; + len = endofs-ofs; + } + + if (len < 1L) return 0; + + for (i = 0; i < lock_table_size; i++) { + lptr = &lock_table[i]; + if ( (lptr->used) + && ( (psp == 0) || (lptr->psp != psp) ) + && (fnmatches(filename, file_table[lptr->fileno].filename)) + && ( ( (ofs>=lptr->start) && (ofsend) ) + || ( (endofs>lptr->start) && (endofs<=lptr->end) ) ) ) { + if (allowcriter) { + union REGS regs; + + regs.h.ah = 0x0e; /* disk I/O; fail allowed; data area */ + regs.h.al = 0; + regs.x.di = 0x0e; /* lock violation */ + if ( (fptr->filename[0]!='\0') && (fptr->filename[1]==':') ) + regs.h.al = fptr->filename[0]-'A'; + free_file_table_entry(fileno); + int86(0x24, ®s, ®s); + } + return -0x21; /* lock violation */ + } + } + return 0; +} + + /* DOS calls this to lock or unlock a specific section of a file. + Returns zero if successfully locked or unlocked. Otherwise + returns non-zero. + If the return value is non-zero, it is the negated error + return code for the DOS 0x5c call. */ +static int lock_unlock + (unsigned short psp,/* psp segment address of owner process */ + int fileno, /* file_table entry number */ + unsigned long ofs, /* offset into file */ + unsigned long len, /* length (in bytes) of region to lock or unlock */ + int unlock) { /* non-zero to unlock; zero to lock */ + + int i; + lock_t *lptr; + unsigned long endofs = ofs + len; + + if (endofs < ofs) { + endofs = 0xffffffffL; + len = endofs-ofs; + } + + if (len < 1L) return 0; + + if (unlock) { + for (i = 0; i < lock_table_size; i++) { + lptr = &lock_table[i]; + if ( (lptr->used) + && (lptr->psp == psp) + && (lptr->fileno == fileno) + && (lptr->start == ofs) + && (lptr->end == endofs) ) { + lptr->used = 0; + return 0; + } + /* Not already locked by us; can't unlock. */ + return -(0x21); /* lock violation */ + } + return -(0x24); /* sharing buffer overflow */ + } else { + if (access_check(0, fileno, ofs, len, 0)) { + /* Already locked; can't lock. */ + return -(0x21); /* lock violation */ + } + for (i = 0; i < lock_table_size; i++) { + lptr = &lock_table[i]; + if (!lptr->used) { + lptr->used = 1; + lptr->start = ofs; + lptr->end = ofs+(unsigned long)len; + lptr->fileno = fileno; + lptr->psp = psp; + return 0; + } + } + return -(0x24); /* sharing buffer overflow */ + } +} + + /* Allocate tables and install hooks into the kernel. + If we run out of memory, return non-zero. */ +static int init(void) { + int i; + + file_table_size = file_table_size_bytes/sizeof(file_t); + if ((file_table=malloc(file_table_size_bytes)) == NULL) return 1; + memset(file_table, 0, file_table_size_bytes); + if ((lock_table=malloc(lock_table_size*sizeof(lock_t))) == NULL) return 1; + memset(lock_table, 0, lock_table_size*sizeof(lock_t)); + return 0; +} + +static void usage(void) { + fprintf + (stderr, + "Installs file-sharing and locking " + "capabilities on your hard disk.\n\n" + "%s [/F:space] [/L:locks]\n\n" + " /F:space Allocates file space (in bytes) " + "for file-sharing information.\n" + " /L:locks Sets the number of files that can " + "be locked at one time.\n", + progname); +} + +static void bad_params(void) { + fprintf(stderr,"%s: parameter out of range!\n",progname); +} + +static void out_of_memory(void) { + fprintf(stderr,"%s: out of memory!\n",progname); +} + +int main(int argc, char **argv) { + unsigned short far *usfptr; + unsigned char far *uscptr; + unsigned short top_of_tsr; + int installed = 0; + int i; + + /* Extract program name from argv[0] into progname. */ + if (argv[0] != NULL) { + char *p = argv[0], *p2, c; + int i; + if ( (p[0] != '\0') && (p[1] == ':') ) p += 2; + while ((p2 = strchr(p, '\\')) != NULL) p = p2+1; + p2 = progname; + for (i = 0; i < 8; i++) { + c = p[i]; + if ( (c == '.') || (c == '\0') ) break; + *(p2++) = c; + } + *p2 = '\0'; + } + + /* See if the TSR is already installed. */ + disable(); + if (getvect(MUX_INT_NO) != NULL) { + union REGS regs; + enable(); + regs.h.ah = MULTIPLEX_ID; + regs.h.al = 0; + int86(MUX_INT_NO,®s,®s); + installed = ((regs.x.ax & 0xff) == 0xff); + + } else { + enable(); + } + + /* Process command line arguments. Bail if errors. */ + for (i = 1; i < argc; i++) { + char *arg = argv[i]; + if (arg == NULL) continue; + if (arg[0] != '/') { + usage(); + return 3; + } + arg++; + switch(*arg) { + case '?': + usage(); + return 3; + case 'f': + case 'F': + arg++; + if (*arg != ':') { + usage(); + return 3; + } + arg++; + { + long temp = atol(arg); + if ( (temp < (long)FILE_TABLE_MIN) + || (temp > (long)FILE_TABLE_MAX) ) { + bad_params(); + return 3; + } + file_table_size_bytes = (unsigned int)temp; + } + break; + case 'l': + case 'L': + arg++; + if (*arg != ':') { + usage(); + return 3; + } + arg++; + lock_table_size = atoi(arg); + if ( (lock_table_size < LOCK_TABLE_MIN) + || (lock_table_size > LOCK_TABLE_MAX) ) { + bad_params(); + return 3; + } + break; + } + } + + /* Now try to install. */ + + if (installed) { + fprintf(stderr,"%s is already installed!\n",progname); + return 1; + } + + if (init() != 0) { + out_of_memory(); + return 2; + } + + /* Allocate a single byte. This tells us the size of the TSR. + Free the byte when we know the address. */ + uscptr = (unsigned char far *)malloc(1); + if (uscptr == NULL) { + out_of_memory(); + return 2; + } + top_of_tsr = (FP_SEG(uscptr)+((FP_OFF(uscptr)+15) >> 4)) - _psp; + free((void *)uscptr); + + + /* Hook the interrupt for the handler routine. */ + disable(); + old_handler2f = getvect(MUX_INT_NO); + setvect(MUX_INT_NO,handler2f); + enable(); + + /* Let them know we're installed. */ + fprintf(stdout,"%s installed.\n",progname); + + /* Any access to environment variables must */ + /* be done prior to this point. Here we */ + /* free the environment table to prevent */ + /* wasting that memory. In fact, if the */ + /* TSR were removed from memory and we did */ + /* not do this, we would not be able to */ + /* recover this memory. */ + + FP_SEG(usfptr) = _psp; + FP_OFF(usfptr) = 0x2c; + freemem(*usfptr); + + /* Free the remainder of memory for use by applications. */ + setblock(_psp,top_of_tsr); + + /* Terminate and stay resident. */ + keep(0,top_of_tsr); + return 0; +} +