/* * mars-nwe-dosutils - NetWare/DOS utility tools. * * Copyright (C) 2026 Mario Fetka * Copyright (C) 1993,1996 Martin Stover, Marburg, Germany * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* * Purpose: Shared utility functions used by the DOS command implementations. * Depends on: net.h and the command modules that call common parsing, path, memory and terminal helpers. */ #include "net.h" /* * tool_put_word_lh * * Purpose: * Stores a 16-bit value in NetWare low-high (little-endian) order. */ void tool_put_word_lh(uint8 *p, uint16 v) { p[0] = (uint8)(v & 0xff); p[1] = (uint8)((v >> 8) & 0xff); } /* * tool_put_dword_lh * * Purpose: * Stores a 32-bit value in NetWare low-high (little-endian) order. */ void tool_put_dword_lh(uint8 *p, uint32 v) { p[0] = (uint8)(v & 0xff); p[1] = (uint8)((v >> 8) & 0xff); p[2] = (uint8)((v >> 16) & 0xff); p[3] = (uint8)((v >> 24) & 0xff); } /* * tool_get_word_lh * * Purpose: * Reads a 16-bit value stored in NetWare low-high order. */ uint16 tool_get_word_lh(uint8 *p) { return((uint16)p[0] | ((uint16)p[1] << 8)); } /* * tool_get_dword_lh * * Purpose: * Reads a 32-bit value stored in NetWare low-high order. */ uint32 tool_get_dword_lh(uint8 *p) { return((uint32)p[0] | ((uint32)p[1] << 8) | ((uint32)p[2] << 16) | ((uint32)p[3] << 24)); } /* * tool_put_word_hl * * Purpose: * Stores a 16-bit value in NetWare high-low (big-endian) order. */ void tool_put_word_hl(uint8 *p, uint16 v) { p[0] = (uint8)((v >> 8) & 0xff); p[1] = (uint8)(v & 0xff); } /* * tool_put_dword_hl * * Purpose: * Stores a 32-bit value in NetWare high-low (big-endian) order. */ void tool_put_dword_hl(uint8 *p, uint32 v) { p[0] = (uint8)((v >> 24) & 0xff); p[1] = (uint8)((v >> 16) & 0xff); p[2] = (uint8)((v >> 8) & 0xff); p[3] = (uint8)(v & 0xff); } /* * tool_get_word_hl * * Purpose: * Reads a 16-bit value stored in NetWare high-low order. */ uint16 tool_get_word_hl(uint8 *p) { return(((uint16)p[0] << 8) | (uint16)p[1]); } /* * tool_get_dword_hl * * Purpose: * Reads a 32-bit value stored in NetWare high-low order. */ uint32 tool_get_dword_hl(uint8 *p) { return(((uint32)p[0] << 24) | ((uint32)p[1] << 16) | ((uint32)p[2] << 8) | (uint32)p[3]); } /* * tool_copy_ncp22_name * * Purpose: * Converts one DOS filename component into the uppercase NCP22 name format. * The helper rejects paths and keeps the historical 12-byte DOS namespace * component limit used by the old directory-entry NCPs. */ int tool_copy_ncp22_name(uint8 *dst, char *src, uint8 *len_out) { char tmp[260]; int len; if (!dst || !len_out) return(-1); if (!src) src = ""; tool_upcopy(tmp, src, sizeof(tmp)); if (strchr(tmp, '\\') || strchr(tmp, '/') || strchr(tmp, ':')) return(-1); len = strlen(tmp); if (len < 1 || len > 12) return(-1); memcpy(dst, tmp, len); *len_out = (uint8)len; return(0); } #ifdef __WATCOMC__ /* * getcurdir * * Purpose: * Borland-compatible wrapper for DOS INT 21h AH=47h. It returns the * current directory for the selected drive without the drive letter. * * Notes: * Open Watcom does not provide getcurdir() under the Borland name used by * the historical MARS-NWE DOS tools. */ int getcurdir(int drive, char *directory) { REGS regs; SREGS sregs; regs.h.ah = 0x47; /* DOS: get current directory */ regs.h.dl = (unsigned char)drive; /* 0=current, 1=A:, 2=B:, ... */ sregs.ds = FP_SEG(directory); regs.x.si = FP_OFF(directory); intdosx(®s, ®s, &sregs); return(regs.x.cflag ? -1 : 0); } /* * setdisk * * Purpose: * Borland-compatible wrapper for DOS INT 21h AH=0eh. It selects the DOS * default drive using the Borland drive numbering convention. */ void setdisk(int drive) { REGS regs; regs.h.ah = 0x0e; /* DOS: select default drive */ regs.h.dl = (unsigned char)drive; /* 0=A:, 1=B:, ... */ intdos(®s, ®s); } #endif /* * key_pressed * * Purpose: * Checks whether a key is waiting in the BIOS keyboard buffer without * consuming it. */ int key_pressed(void) { #ifdef __WATCOMC__ /* * Open Watcom's WORDREGS does not expose x.flags. The old Borland * code checked the BIOS INT 16h ZF bit after AH=01h. Open Watcom's * bios.h provides _bios_keybrd(), which wraps the same BIOS keyboard * service and returns 0 when no key is waiting. */ return(_bios_keybrd(_KEYBRD_READY) != 0); #else REGS regsin, regsout; regsin.h.ah = 0x01; /* read key-press */ int86(0x16, ®sin, ®sout); return((regsout.x.flags & 0x40) ? 0 : 1); /* zeroflag != 0 */ #endif } /* * clear_kb * * Purpose: * Drains all pending keys from the BIOS keyboard buffer. */ void clear_kb(void) { #ifdef __WATCOMC__ while (key_pressed()) { (void)_bios_keybrd(_KEYBRD_READ); } #else REGS regsin, regsout; while (key_pressed()) { /* zeroflag != 0 */ regsin.h.ah = 0x00; /* read key-press */ int86(0x16, ®sin, ®sout); } #endif } /* * ask_user * * Purpose: * Prints a formatted yes/no question and waits until the user answers with * Y/J or N. * * Returns: * 1 for yes, 0 for no. */ int ask_user(char *p, ...) { int key; int flag = 0; va_list argptr; va_start(argptr, p); vfprintf(stderr, p, argptr); va_end(argptr); fprintf(stderr, "\n Please answer: Y)es or N)o!"); while (1) { key = getch(); if (key == 'J' || key == 'j' || key== 'y' || key == 'Y') { fprintf(stderr, "Y\n\n"); flag = 1; break; } if (key == 'N' || key == 'n') { fprintf(stderr, "N\n\n"); flag = 0; break; } } clear_kb(); return(flag); } /* * xmalloc * * Purpose: * Allocates memory or terminates the utility with a diagnostic. */ char *xmalloc(uint size) { char *p = (size) ? (char *)malloc(size) : (char*)NULL; if (p == (char *)NULL && size){ fprintf(stderr, "not enough core, need %d Bytes\n", size); exit(1); } return(p); } /* * xcmalloc * * Purpose: * Allocates zero-filled memory or terminates the utility with a diagnostic. */ char *xcmalloc(uint size) { char *p = xmalloc(size); if (size) memset(p, 0, size); return(p); } /* * x_x_xfree * * Purpose: * Frees an allocated pointer and clears the caller's pointer value. */ void x_x_xfree(char **p) { if (*p != (char *)NULL){ free(*p); *p = (char*)NULL; } } /* * strmaxcpy * * Purpose: * Copies at most len bytes from source to dest and always terminates dest. * * Returns: * Number of copied source bytes, excluding the terminating zero byte. */ int strmaxcpy(char *dest, char *source, int len) { int slen = (source != (char *)NULL) ? min(len, strlen(source)) : 0; if (dest == (char *)NULL) return(0); if (slen) memcpy(dest, source, slen); dest[slen] = '\0'; return(slen); } /* * xadd_char * * Purpose: * Appends one character to a bounded C string and keeps it terminated. */ char *xadd_char(char *s, int c, int maxlen) { if (s && maxlen) { int namlen = strlen(s); if (maxlen > -1 && namlen >= maxlen) namlen=maxlen-1; s[namlen++] = c; s[namlen] = '\0'; } return(s); } /* * down_char * * Purpose: * Converts one DOS character to lowercase for the ASCII range and the * German OEM characters historically handled by MARS-NWE. */ static uint8 down_char(uint8 ch) { if (ch > 64 && ch < 91) return(ch + 32); switch(ch){ case 142: ch = 132; break; case 153: ch = 148; break; case 154: ch = 129; break; default :break; } return(ch); } /* * up_char * * Purpose: * Converts one DOS character to uppercase for the ASCII range and the * German OEM characters historically handled by MARS-NWE. */ static uint8 up_char(uint8 ch) { if (ch > 96 && ch < 123) return(ch - 32); switch(ch) { case 132: ch = 142; break; case 148: ch = 153; break; case 129: ch = 154; break; default : break; } return(ch); } /* * upstr * * Purpose: * Uppercases a DOS string in place. */ uint8 *upstr(uint8 *s) { if (!s) return((uint8*)NULL); for (;*s;s++) *s=up_char(*s); return(s); } /* * deb * * Purpose: * Removes trailing spaces and tabs from a string in place. */ void deb(uint8 *s) { if (!s || !*s) return; else { uint8 *p = s + strlen(s); while (p > s && (*--p==32 || *p==9));; if (*p==32 || *p==9) *p='\0'; else *(p+1) = '\0'; } } /* * leb * * Purpose: * Historical leading-space helper used by the old tools. It keeps the * original behavior and shifts the string to the first whitespace boundary. */ void leb(uint8 *s) { if (!s || !*s || (*s != 32 && *s != 9)) return; else { uint8 *p = s; for (;*p && *p!=32 && *p!=9;p++);; strcpy(s, p); } } /* * korrpath * * Purpose: * Normalizes a DOS/NetWare path for the older tools by converting backslash * separators to slash and lowercasing DOS characters. */ void korrpath(char *s) { if (!s) return; for (;*s;s++) { if (*s=='\\') *s='/'; else *s=down_char(*s); } } /* * get_path_fn * * Purpose: * Splits a historical slash/colon path into directory part and file name. */ void get_path_fn(char *s, char *p, char *fn) { int j= strlen(s); if (p != (char *)NULL) p[0] = 0; if (fn != (char*) NULL) fn[0] = 0; if (!j) return; if (s[0] == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0) ) ) { if (p != (char *)NULL) { strcpy(p, s); strcat(p, "/"); } if (fn != (char *)NULL) fn[0] = 0; return; } while (j--){ if ((s[j] == '/') || (s[j] == ':') ) { if (fn != (char *)NULL) strcpy(fn, s+j+1); if (p != (char *)NULL) { strncpy(p, s, j+1); p[j+1] = 0; } return; } } if (fn != (char *)NULL) strcpy(fn, s); /* no path */ } typedef struct { uint16 adr1; uint16 adr2; char reserve[6]; uint32 ladrs[3]; uint16 father_psp_seg; char handles[20]; uint16 environ_seg; } PROG_PSP; typedef struct { uint8 kennung; uint16 prozess_seg; uint16 blocks; } SPEICH_BLOCK; /* * getglobenvironment * * Purpose: * Locates the parent DOS environment block through the PSP and reports its * allocated and currently used size. */ static char *getglobenvironment(uint16 *maxsize, uint16 *aktsize) { static uint16 globmaxenvsize=0; static char *globenviron=NULL; if (globenviron == (char *) NULL) { PROG_PSP *mypsp = MK_FP(_psp, 0); PROG_PSP *fatherpsp = MK_FP(mypsp->father_psp_seg, 0); SPEICH_BLOCK *spb = MK_FP(fatherpsp->environ_seg-1, 0); globenviron = (char *)MK_FP(fatherpsp->environ_seg, 0); globmaxenvsize = spb->blocks * 16; } if (globmaxenvsize){ char *search = globenviron; char *maxsearch = search+globmaxenvsize; while (*search && search < maxsearch) { int slen=strlen(search); search+=(slen+1); } *aktsize = max(2, (uint16)(search+1 - globenviron)); } else *aktsize=0; *maxsize = globmaxenvsize; /* printf("globenv=%p maxsize=%d, aktsize=%d\n", globenviron, globmaxenvsize, *aktsize); */ return(globenviron); } /* * getglobenv * * Purpose: * Reads one variable from the parent DOS environment block. */ char *getglobenv(char *option) { uint16 maxenvsize; uint16 aktenvsize; char *search = getglobenvironment(&maxenvsize, &aktenvsize); int length = (option == NULL) ? 0 : strlen(option); if (aktenvsize && length){ char *maxsearch=search+aktenvsize; while (*search && search < maxsearch) { int slen=strlen(search); if (slen > length && (*(search + length) == '=') && (strncmp(search, option, length) == 0)) { /* printf("GET GLOB %s=%s\n", option, search+length+1); */ return(search + length + 1); } search+=(slen+1); } } return(NULL); } /* * putglobenv * * Purpose: * Updates or removes one variable in the parent DOS environment block. */ int putglobenv(char *option) { uint16 maxenvsize; uint16 aktenvsize; char *search = getglobenvironment(&maxenvsize, &aktenvsize); int optionlen = (option == NULL) ? 0 : strlen(option); /* printf("PUT GLOB option=%s\n", option); */ if (optionlen && maxenvsize){ int length; char *equal; for (equal = option; *equal && *equal != '='; equal++);; length = (int) (equal - option); if (length > 0 && *equal == '='){ char *maxsearch=search+aktenvsize; while (*search && search < maxsearch) { int slen = strlen(search); char *nextp = search+slen+1; if (slen > length && (*(search + length) == '=') && (strncmp(search, option, length) == 0)) { /* gefunden */ int diffsize = optionlen-slen; if (diffsize){ int movesize = (int)(maxsearch - nextp); if (diffsize > (int)(maxenvsize - aktenvsize)) return(-1); /* Kein Platz mehr */ if (!*(equal+1)) diffsize -= (length+2); xmemmove(nextp+diffsize, nextp, movesize); } if (*(equal+1)) strcpy(search, option); return(0); } search=nextp; } /* nicht gefunden , nun eintragen, falls m�glich */ if (*(equal+1) && optionlen < maxenvsize - aktenvsize) { strcpy(search, option); *(search+optionlen+1) = '\0'; /* letzter Eintrag '\0' nicht vergessen */ return(0); } else return(-1); } } return(-1); } /* * tool_page_line * * Purpose: * Implements Novell-style paged output prompting after 24 printed lines. */ int tool_page_line(int *line_count, int *continuous) { int ch; if (!line_count || !continuous) return(0); if (*continuous) return(0); (*line_count)++; if (*line_count < 24) return(0); fprintf(stdout, "Press any key to continue ('C' for continue)"); fflush(stdout); ch = getch(); fprintf(stdout, "\n"); if (ch == 'c' || ch == 'C') *continuous = 1; *line_count = 0; return(0); } /**************************************************************** * Shared helpers for the newer DOS utility frontends. * * These deliberately stay in tools.c instead of in grant/revoke/remove/ * rights/flagdir so the current multicall binary and possible future * grouped multicall binaries can reuse the same code without dragging in * command-specific modules. ****************************************************************/ /* * tool_strsame * * Purpose: * Compares two command-line strings case-insensitively for ASCII letters. */ int tool_strsame(char *a, char *b) { if (!a) a = ""; if (!b) b = ""; while (*a || *b) { int ca = *a++; int cb = *b++; if (ca >= 'a' && ca <= 'z') ca -= 32; if (cb >= 'a' && cb <= 'z') cb -= 32; if (ca != cb) return(0); } return(1); } /* * tool_is_help_arg * * Purpose: * Recognizes the common help arguments used by the DOS utilities. */ int tool_is_help_arg(char *s) { if (!s) return(0); return(tool_strsame(s, "/?") || tool_strsame(s, "-?") || tool_strsame(s, "?")); } /* * tool_is_option * * Purpose: * Checks whether an argument is a DOS-style option beginning with / or -. */ int tool_is_option(char *s) { if (!s) return(0); return(s[0] == '/' || s[0] == '-'); } /* * tool_is_files_option * * Purpose: * Recognizes the /FILES option and its short form. */ int tool_is_files_option(char *s) { if (!s) return(0); return(tool_strsame(s, "/FILES") || tool_strsame(s, "-FILES") || tool_strsame(s, "/F") || tool_strsame(s, "-F")); } /* * tool_is_subdirs_option * * Purpose: * Recognizes the /SUBDIRS, /SUBDIRECTORIES and /S recursive options. */ int tool_is_subdirs_option(char *s) { if (!s) return(0); return(tool_strsame(s, "/SUBDIRS") || tool_strsame(s, "-SUBDIRS") || tool_strsame(s, "/SUBDIRECTORIES") || tool_strsame(s, "-SUBDIRECTORIES") || tool_strsame(s, "/S") || tool_strsame(s, "-S")); } /* * tool_get_current_drive * * Purpose: * Returns the DOS default drive using INT 21h AH=19h. */ int tool_get_current_drive(void) { REGS regs; regs.h.ah = 0x19; int86(0x21, ®s, ®s); return((int)regs.h.al); } /* * tool_current_dhandle * * Purpose: * Resolves the current DOS drive to the active NetWare connection and * directory handle. */ int tool_current_dhandle(uint8 *connid, uint8 *dhandle) { uint8 flags = 0; int drive = tool_get_current_drive(); if (get_drive_info((uint8)drive, connid, dhandle, &flags)) return(-1); if (!*connid || (flags & 0x80)) return(-1); return(0); } /* * tool_current_dhandle_only * * Purpose: * Convenience wrapper for callers that only need the current directory * handle and do not use the connection id. */ int tool_current_dhandle_only(uint8 *dhandle) { uint8 connid = 0; return(tool_current_dhandle(&connid, dhandle)); } /* * tool_current_prefix * * Purpose: * Builds the current SERVER\VOLUME:PATH display prefix used by Novell-like * command output. */ int tool_current_prefix(char *out, int max) { uint8 connid = 0; uint8 dhandle = 0; uint8 flags = 0; int drive; char server[52]; char dpath[260]; if (!out || max < 8) return(-1); out[0] = '\0'; drive = tool_get_current_drive(); if (get_drive_info((uint8)drive, &connid, &dhandle, &flags)) return(-1); if (!connid || (flags & 0x80)) return(-1); server[0] = '\0'; if (get_fs_name(connid, server)) server[0] = '\0'; dpath[0] = '\0'; if (get_dir_path(dhandle, dpath) || !dpath[0]) return(-1); tool_upcopy(dpath, dpath, sizeof(dpath)); if (server[0]) sprintf(out, "%s\\%s", server, dpath); else sprintf(out, "%s", dpath); return(0); } /* * tool_is_current_path * * Purpose: * Checks whether a path argument refers to the current directory. */ int tool_is_current_path(char *path) { if (!path || !*path) return(1); if (tool_strsame(path, ".")) return(1); if (tool_strsame(path, ".\\")) return(1); if (tool_strsame(path, "./")) return(1); return(0); } /* * tool_upcopy * * Purpose: * Copies a path/string while uppercasing ASCII letters and converting / to \. */ void tool_upcopy(char *dst, char *src, int max) { int i = 0; if (!src) src = ""; while (*src && i < max - 1) { char c = *src++; if (c == '/') c = '\\'; if (c >= 'a' && c <= 'z') c -= 32; dst[i++] = c; } dst[i] = 0; } /* * tool_basename * * Purpose: * Extracts the final DOS/NetWare path component after uppercasing and path * separator normalization. */ void tool_basename(char *dst, char *src, int max) { char up[260]; char *p; tool_upcopy(up, src, sizeof(up)); p = strrchr(up, '\\'); if (!p) p = strrchr(up, ':'); if (p) strmaxcpy(dst, p + 1, max - 1); else strmaxcpy(dst, up, max - 1); } /* * tool_header_path * * Purpose: * Formats a path for Novell-style command headers relative to the current * mapped NetWare prefix. */ void tool_header_path(char *out, char *path, int max) { char prefix[260]; char up[260]; char *p; int len; if (tool_current_prefix(prefix, sizeof(prefix))) prefix[0] = '\0'; if (tool_is_current_path(path)) { strmaxcpy(out, prefix, max - 1); return; } tool_upcopy(up, path, sizeof(up)); /* Convert DOS drive-qualified paths like F:\DIR to a NetWare path * relative to the mapped volume, matching Novell's display. */ p = up; if (p[0] && p[1] == ':') { p += 2; if (*p == '\\') p++; } while (*p == '\\') p++; strmaxcpy(out, prefix, max - 1); len = strlen(out); if (*p && len > 0 && out[len - 1] != ':' && out[len - 1] != '\\') { if (len < max - 1) { out[len++] = '\\'; out[len] = '\0'; } } if ((int)(strlen(out) + strlen(p)) < max - 1) strcat(out, p); } /* * tool_is_dot_dir * * Purpose: * Checks for the special . and .. directory entries. */ int tool_is_dot_dir(char *name) { if (!name) return(0); if (name[0] == '.' && name[1] == '\0') return(1); if (name[0] == '.' && name[1] == '.' && name[2] == '\0') return(1); return(0); } /* * tool_join_path * * Purpose: * Joins a base path and child name with a DOS/NetWare separator when needed. */ void tool_join_path(char *out, char *base, char *name, int max) { int len; out[0] = '\0'; strmaxcpy(out, base, max - 1); len = strlen(out); if (len > 0 && out[len - 1] != '\\' && out[len - 1] != '/' && out[len - 1] != ':') { if (len < max - 1) { out[len++] = '\\'; out[len] = '\0'; } } if ((int)(strlen(out) + strlen(name)) < max - 1) strcat(out, name); } /* * tool_has_wildcards * * Purpose: * Checks whether a path contains DOS wildcard characters. */ int tool_has_wildcards(char *path) { if (!path) return(0); while (*path) { if (*path == '*' || *path == '?') return(1); path++; } return(0); } /* * tool_parent_pattern * * Purpose: * Splits a wildcard path into parent directory and search pattern. Missing * patterns default to *.* for Novell-style directory scans. */ void tool_parent_pattern(char *dir, char *pattern, char *path, int maxdir, int maxpat) { char tmp[260]; char *p; tool_upcopy(tmp, path, sizeof(tmp)); p = strrchr(tmp, '\\'); if (!p) p = strrchr(tmp, ':'); if (p) { if (*p == ':') { p++; strmaxcpy(pattern, p, maxpat - 1); *p = '\0'; strmaxcpy(dir, tmp, maxdir - 1); } else { strmaxcpy(pattern, p + 1, maxpat - 1); *p = '\0'; strmaxcpy(dir, tmp, maxdir - 1); } } else { strmaxcpy(dir, ".", maxdir - 1); strmaxcpy(pattern, tmp, maxpat - 1); } if (!pattern[0]) strmaxcpy(pattern, "*.*", maxpat - 1); }