/*
 * 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 <http://www.gnu.org/licenses/>.
 */

/*
 * 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(&regs, &regs, &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(&regs, &regs);
}
#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, &regsin, &regsout);
  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, &regsin, &regsout);
  }
#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, &regs, &regs);
  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_path_is_dir
 *
 * Purpose:
 *   Tests whether a path should be treated as a directory.
 *
 * Parameters:
 *   path - DOS/NetWare path to test.
 *
 * Returns:
 *   Non-zero when the path is empty/current-directory syntax or stat() reports
 *   a directory, otherwise zero.
 */
int tool_path_is_dir(char *path)
{
  struct stat st;

  if (tool_is_current_path(path))
    return(1);

  if (stat(path, &st) == 0) {
    if (st.st_mode & S_IFDIR)
      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_path
 *
 * Purpose:
 *   Copies the parent directory part of a DOS/NetWare path.  The result is
 *   uppercased and keeps a trailing volume colon for paths like SYS:FILE.TXT.
 */
void tool_parent_path(char *dst, char *src, int max)
{
  char tmp[260];
  char *p;

  tool_upcopy(tmp, src, sizeof(tmp));
  p = strrchr(tmp, '\\');
  if (!p) p = strrchr(tmp, ':');

  if (!p) {
    dst[0] = '\0';
    return;
  }

  if (*p == ':') {
    p++;
    *p = '\0';
  } else {
    *p = '\0';
  }

  strmaxcpy(dst, tmp, max - 1);
}

/*
 * 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);
}
