text: /*++
  
  Copyright (c) 1988-1999  Microsoft Corporation
  
  Module Name:
  
      ctools1.c
  
  Abstract:
  
      Low level utilities
  
  --*/
  
  #include "cmd.h"
  
  extern unsigned tywild; /* type is wild flag */
  extern TCHAR CurDrvDir[], *SaveDir, PathChar, Delimiters[] ;
  
  extern TCHAR VolSrch[] ;
  
  extern TCHAR BSlash ;
  
  extern unsigned DosErr ;
  
  extern BOOL CtrlCSeen;
  
  static TCHAR szNull[] = TEXT("");
  
  
  /***    TokStr - tokenize argument strings
   *
   *  Purpose:
   *      Tokenize a string.
   *      Allocate space for a new string and copy each token in src into the
   *      new string and null terminate it.  Tokens are whitespace delimited
   *      unless changed by specialdelims and/or tsflags.  The entire tokenized
   *      string ends with 2 null bytes.
   *
   *  TCHAR *TokStr(TCHAR *src, TCHAR *specialdelims, unsigned tsflags)
   *
   *  Args:
   *      src - the string to be tokenized
   *      specialdelims - a string of other characters which are to be comsidered
   *          as token delimiters
   *      tsflags - bit 0 nonzero if whitespace are NOT delimiters
   *            bit 1 nonzero if special delimiters are tokens themselves,
   *            eg "foo=bar" => "foo0=0bar00"
   *
   *  Returns:
   *      A pointer to the new string.
   *      A pointer to a null string if src is NULL
   *      NULL if unable to allocate memory.
   *
   *  Notes:
   *      The format of the tokenized string dictates the way code is written
   *      to process the tokens in that string.  For instance, the code
   *      "s += mystrlen(s)+1" is the way to update s to point to the next token
   *      in the string.
   *
   *      Command considers "=", ",", and ";" to be token delimiters just like
   *      whitespace.  The only time they are not treated like whitespace is
   *      when they are included in specialdelims.
   *
   *                      *** W A R N I N G ! ***
   *      THIS ROUTINE WILL CAUSE AN ABORT IF MEMORY CANNOT BE ALLOCATED
   *              THIS ROUTINE MUST NOT BE CALLED DURING A SIGNAL
   *              CRITICAL SECTION OR DURING RECOVERY FROM AN ABORT
   *
   */
  
  TCHAR *TokStr(src, specialdelims, tsflags)
  TCHAR *src ;
  TCHAR *specialdelims ;
  unsigned tsflags ;
  {
      TCHAR *ts ;     /* Tokenized string pointer                */
      TCHAR *tscpy,                /* Copy of ts                           */
          delist[5],          /* List of non-whitespace delimiter/separators  */
          c;                  /* Work variable                                */
  
      int first,          /* Flag, true if first time through the loop    */
          lctdelim,           /* Flag, true if last byte was token delimiter  */
          i, j ;              /* Index/loop counter                           */
  
          DEBUG((CTGRP, TSLVL, "TOKSTR: Entered &src = %04x, src = %ws",src,src));
          DEBUG((CTGRP, TSLVL, "TOKSTR: Making copy str of len %d",(mystrlen(src)*2+2))) ;
  
      if (src == NULL) {
          return(szNull);   // This routine returns a doubly null terminated string
          } else {
          ts = tscpy = gmkstr((mystrlen(src)*2+2)*sizeof(TCHAR)) ;  /*WARNING*/
  
          DEBUG((CTGRP, TSLVL, "TOKSTR: &tscpy = %04x",tscpy)) ;
  
          for (i = j = 0 ; c = *(&Delimiters[i]) ; i++)
                  if (!mystrchr(specialdelims, c))
                          delist[j++] = c ;
          delist[j] = NULLC ;
  
          DEBUG((CTGRP, TSLVL, "TOKSTR: Delimiter string built as `%ws'",delist)) ;
  
          for (first = TRUE, lctdelim = TRUE ; *src ; src++) {
  
              if (
                 (*src != QUOTE) &&
                 (_istspace(*src) || mystrchr(delist, *src)) &&
                 (!(tsflags & TS_WSPACE) || first) &&
                 (!(tsflags & TS_SDTOKENS) || !mystrchr(specialdelims, *src)) &&
                 (!(tsflags & TS_NWSPACE) || !mystrchr(specialdelims, *src)) ) {
  
                  while ( *src &&
                      (*src != QUOTE) &&
                      (_istspace(*src) || mystrchr(delist, *src)) &&
                      (!(tsflags & TS_SDTOKENS) || !mystrchr(specialdelims, *src)) &&
                      (!(tsflags & TS_NWSPACE) || !mystrchr(specialdelims, *src)) )
                     src++ ;
  
                  if (!(*src))
                      break ;
  
                  if (!first && !lctdelim)
                      ts++ ;
  
                  lctdelim = TRUE ;
              } ;
  
              first = FALSE ;
  
              if (specialdelims && mystrchr(specialdelims, *src)) {
                      if (tsflags & TS_SDTOKENS) {
                              if (lctdelim)
                                      *ts = *src ;
                              else
                                      *++ts = *src ;
                              lctdelim = TRUE ;
                              ts++ ;
                      } else {
                              if ( tsflags & TS_NWSPACE )
                                      *ts = *src ;
                              lctdelim = FALSE ;
                      }
  
                      ts++ ;
                      continue ;
              } ;
  
             *ts++ = *src ;
              if ( *src == QUOTE ) {
                 do {
                      *ts++ = *(++src);
                 } while ( src[0] && src[0] != QUOTE && src[1] );
                 if ( !src[0] ) {
                    src--;
                 }
              }
  
              lctdelim = FALSE ;
          } ;
  
          DEBUG((CTGRP, TSLVL, "TOKSTR: String complete, resizing to %d",ts-tscpy+2)) ;
  
          return(resize(tscpy, ((UINT)(ts-tscpy)+2)*sizeof(TCHAR))) ;
  
          DEBUG((CTGRP, TSLVL, "TOKSTR: Resizing done, returning")) ;
      }
  }
  
  
  
  /******************************************************************************/
  /*                                                                            */
  /*      LoopThroughArgs - call a function on all args in a list               */
  /*                                                                            */
  /*  Purpose:                                                                  */
  /*      This is function is called by many of the commands that take          */
  /*      multiple arguments.  This function will parse the argument string,    */
  /*      complain if no args were given, and call func on each of the ards     */
  /*      in argstr.  Optionally, it will also expand any wildcards in the      */
  /*      arguments.  Execution stops if func ever returns FAILURE.             */
  /*                                                                            */
  /*  int LoopThroughArgs(TCHAR *argstr, int (*func)(), int ltaflags)           */
  /*                                                                            */
  /*  Args:                                                                     */
  /*      argstr - argument string                                              */
  /*      func - the function to pass each element of argstr                    */
  /*      ltaflags - bit 0 on if wildcards are to be expanded                   */
  /*                 bit 1 on if it's ok for argstr to be empty (nothing but whitespace)    */
  /*                 bit 2 on if file names should be passed through un changed */
  /*                      when the wildcard expansion fails to find any matches.*/
  /*                                                                            */
  /*  Returns:                                                                  */
  /*      The value returned by func the last time it is run.                   */
  /*                                                                            */
  /******************************************************************************/
  
  int LoopThroughArgs(argstr, func, ltaflags)
  TCHAR *argstr ;
  PLOOP_THROUGH_ARGS_ROUTINE func ;
  int ltaflags ;
             {
              TCHAR *tas ;                /* Tokenized argument string       */
              TCHAR fspec[MAX_PATH] ;     /* Holds filespec when expanding   */
              WIN32_FIND_DATA buf ;       /* Use for ffirst/fnext            */
              HANDLE hnFirst ;
              CPYINFO fsinfo ;
              int catspot ;      /* Fspec index where fname should be added  */
              unsigned final_code = SUCCESS;
              unsigned error_code = SUCCESS;
              int multargs = FALSE;
              unsigned attr;/* attribute for ffirst for because of TYPE wild */
              unsigned taslen;
  
  
              tywild = FALSE;              /* global type wild card flag ret */
  
              GetDir(CurDrvDir, GD_DEFAULT);
  
              if (*(tas = TokStr(argstr, NULL, TS_NOFLAGS)) == NULLC)
              {
                 if (ltaflags & LTA_NULLOK)
                 {
                 /* return((*func)(tas)) ; */
                    return((*func)( StripQuotes(tas) )) ;
                 }
  
                 PutStdErr(MSG_BAD_SYNTAX, NOARGS);
                 return(FAILURE) ;
              }
  
              if (*(tas + mystrlen(tas) + 1) )    /* Check for multiple args */
              {
                 multargs = TRUE;
              }
  
              for ( ; *tas ; tas += taslen+1 )
              {
                 if (CtrlCSeen) {
                     return(FAILURE);
                 }
                 taslen = mystrlen( tas );
                 mystrcpy( tas, StripQuotes( tas ) );
  
                 if (ltaflags & LTA_EXPAND)
                 {
                    if (cmdfound == TYTYP)   /* if TYPE cmd then only files   */
                    {
                       attr = FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_ARCHIVE;
                    }
                    else                     /* else                          */
                    {
                       attr = A_ALL;         /* find all                      */
                    }
                    //
                    // this is used to detect an error other then can not
                    // find file. It is set in ffirst
                    //
                    DosErr = 0;
                    if (!ffirst(tas, attr, &buf, &hnFirst))
                    {
                       //
                       // Check that failure was not do to some system error such
                       // as an abort on access to floppy
                       //
                       if (DosErr) {
  
                         if ((DosErr != ERROR_FILE_NOT_FOUND) &&
                             (DosErr != ERROR_NO_MORE_FILES)) {
  
                             PutStdErr(DosErr, NOARGS);
  
                             return( FAILURE );
  
                         }
                       }
  
                       if (ltaflags & LTA_NOMATCH)
                       {
                          if ( error_code = ((*func)(tas)) )
                          {
                             final_code = FAILURE;
                             if (multargs)     /* if cmd  failed then  (TYPE)*/
                             {                 /* display arg failed msg too */
                                PutStdErr( MSG_ERR_PROC_ARG, ONEARG, tas );
                             }
                          }
                          if ( error_code && !(ltaflags & LTA_CONT))
                          {
                             return(FAILURE) ;
                          }
                          else
                          {
                             continue;
                          }
                       }
                       PutStdErr(((DosErr == ERROR_PATH_NOT_FOUND) ?
                                   MSG_REN_INVAL_PATH_FILENAME :
                                   ERROR_FILE_NOT_FOUND),
                                   NOARGS);
                       return(FAILURE) ;
                    }
  
                    if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                      {
                       PutStdErr(MSG_REN_INVAL_PATH_FILENAME, NOARGS);
                       return(FAILURE) ;
                    }
  
                    fsinfo.fspec = tas ;
                    ScanFSpec(&fsinfo) ;
                    catspot = (int)(fsinfo.fnptr-tas) ;
                    mystrcpy(fspec, tas) ;
  
                    do
                    {
                       fspec[catspot] = NULLC ;
                       tywild |= multargs;      /* if multiple args or wild then wild for TYPE */
                       if ( error_code = ((*func)(mystrcat(fspec, buf.cFileName))) )
                       {
                          final_code = FAILURE;
                       }
                       if ( error_code && !(ltaflags & LTA_CONT))
                       {
                          return(FAILURE) ;
                       }
                    } while(fnext(&buf, attr, hnFirst));
  
                            findclose(hnFirst) ;
                 }
                 else
                 {
                    tywild |= multargs;         /* if multiple args or wild then wild for TYPE */
          /*        if ( error_code = ((*func)(mystrcpy(fspec,tas))) )   */
                    if ( error_code = ((*func)(tas)) )
                    {
                       final_code = FAILURE;
                    }
                    if ( error_code && !(ltaflags & LTA_CONT))
                    {
                       return(FAILURE) ;
                    }
                 }
  
                 if (error_code && multargs)   /* error this time through */
                 {
                    PutStdErr(MSG_ERR_PROC_ARG, ONEARG, tas );
                 }
              }
              return( final_code ) ;
           }
  
  
  
  BOOLEAN
  IsDriveNameOnly (
      IN  PTCHAR psz
      )
  {
  
      //
      // If it does not have any path character, is 2 chars long and
      // has a ':' it must be a drive
      //
      if (!mystrrchr(psz,PathChar)) {
          if ((mystrlen(psz) == 2) && psz[1] == COLON) {
              return( TRUE );
          }
      }
      return( FALSE );
  }
  
  
  /***    ScanFSpec - parse a path string
   *
   *  Purpose:
   *      To scan the filespec in cis to find the information needed to set the
   *      pathend, fnptr, extptr, and flags field of the structure.  Pathend is
   *      a ptr to the end of the path and can be NULL.  Fnptr is a ptr to the
   *      filename and may point to a null character.  Extptr is a ptr to the
   *      extension (including ".") and may point to a null character.
   *
   *  ScanFSpec(PCPYINFO cis)
   *
   *  Arg:
   *      cis - the copy information structure to fill
   *
   *  Notes:
   *      This function needs to be rewritten and cleanup more than any other
   *      function in the entire program!!!
   *
   *                      *** W A R N I N G ! ***
   *      THIS ROUTINE WILL CAUSE AN ABORT IF MEMORY CANNOT BE ALLOCATED
   *              THIS ROUTINE MUST NOT BE CALLED DURING A SIGNAL
   *              CRITICAL SECTION OR DURING RECOVERY FROM AN ABORT
   *
   */
  
  BOOL ScanFSpec(cis)
  PCPYINFO cis ;
  {
      unsigned att ;
      UINT OldErrorMode;
  
      TCHAR *sds = &VolSrch[2] ;      /* "\*.*" Added to dir's    */
  
      TCHAR *fspec ;          /* Work Vars - Holds filespec              */
      TCHAR *wptr ;           /*           - General string pointer      */
      TCHAR c ;               /*           - Temp byte holder            */
      TCHAR c2 = NULLC ;      /* Another if two are needed   */
      int cbPath,             /*           - Length of incoming fspec    */
      dirflag = FALSE ;       /*           - FSpec is directory flag     */
      CRTHANDLE hn;
      PWIN32_FIND_DATA pfdSave;
  
      DosErr = NO_ERROR;
      DEBUG((CTGRP, SFLVL, "SCANFSPEC: cis = %04x  fspec = %04x  `%ws'",
             cis, cis->fspec, cis->fspec)) ;
  
      cbPath = mystrlen(cis->fspec) ;  /* Get length of filespec          */
  
      if (*(wptr = lastc(cis->fspec)) == COLON && cbPath > 2) {
          *wptr-- = NULLC ;       /* Zap colon if device name      */
  
          OldErrorMode = SetErrorMode( 0 );
          hn = Copen(cis->fspec, O_RDONLY|O_BINARY );
          if ((hn == BADHANDLE) || (!FileIsDevice(hn) && !FileIsPipe(hn))) {
              *++wptr = COLON;
              if (cmdfound == CPYTYP) {
                  if (cpydest == FALSE) {
                      PutStdErr( MSG_CMD_NOT_RECOGNIZED, ONEARG, cis->fspec);
                  }
                  cdevfail = TRUE;
              } else {
                  PutStdErr( MSG_CMD_NOT_RECOGNIZED, ONEARG, cis->fspec);
              }
              if (hn != BADHANDLE) {
                  Cclose( hn );
              }
  
          } else {
              if ( FileIsDevice(hn) || FileIsPipe(hn) ) {
                  Cclose( hn );
              }
          }
          SetErrorMode( OldErrorMode );
      }
  
      cis->buf = (PWIN32_FIND_DATA)gmkstr(sizeof(WIN32_FIND_DATA)) ;        /*WARNING*/
  
  /* First it must be determined if this is a file or directory and if directory
   * a "\*.*" appended.  Filespec's that are "." or "\" or those ending in "\",
   * ":.", ".." or "\." are assumed to be directories.  Note that ROOT will fit
   * one of these patterns if explicitly named.  If no such pattern is found,
   * a Get Attributes system call is performed as a final test.  Note that
   * wildcards are not tested for, since the DOS call will fail defaulting them
   * to filenames.  Success of any test assumes directory with the "\*.*" being
   * appended, while failure of all tests assumes filename.
   */
  
  /* If the filespec ends in a '\' set dirflag. Otherwise find where the
   * actual filename begins (by looking for last PathChar if there is one).
   * If there is no pathchar, then check if a drive and colon has been
   * specified. Update the pointer to point to the actual file name spec.
   * If it is a "." or ".." then set dirflag.
   */
      c = *wptr;
      if ( c  == PathChar ) {
          dirflag = TRUE ;
      } else {
          wptr = mystrrchr(cis->fspec, PathChar);
          if (wptr == NULL) {
              wptr = cis->fspec ;
              if ((mystrlen(wptr) >= 2) && (wptr[1] == COLON)) {
                  wptr = &wptr[2];
              }
          } else {
              wptr++ ;
          }
          if ((_tcsicmp(wptr, TEXT(".")) == 0) || (_tcsicmp(wptr, TEXT("..")) == 0)) {
              dirflag = TRUE ;
          }
      }
  
      if (!dirflag) {
          if (cmdfound == CPYTYP) {               /* bypass if COPY cmd */
              if (cpydflag == TRUE) {
                  att = GetFileAttributes(cis->fspec);
                  DosErr = (att != -1 ? NO_ERROR : GetLastError());
                  if (att != -1 ) {
                      if (att & FILE_ATTRIBUTE_DIRECTORY) {
                          dirflag = TRUE;
                      }
                  }
              } else {
                  if (cpyfirst == TRUE) {              /* and !first time    */
                      cpyfirst = FALSE;               /* and !first time    */
                      att = GetFileAttributes(cis->fspec);
                      DosErr = (att != -1 ? NO_ERROR : GetLastError());
                      if (att != -1 ) {
                          if (att & FILE_ATTRIBUTE_DIRECTORY) {
                              dirflag = TRUE;
                          }
                      }
                  }
              }
          } else {
              att = GetFileAttributes(cis->fspec);
              DosErr = (att != -1 ? NO_ERROR : GetLastError());
              if (att != -1 ) {
                  if (att & FILE_ATTRIBUTE_DIRECTORY) {
                      dirflag = TRUE;
                  }
              }
          }
      }
  
  /* Note that in the following conditional, the directory attribute is set
   * in cis->buf->attributes.  Some functions calling ScanFSpec() require this
   * knowledge.
   */
      if (dirflag) {
          if (c == PathChar)              /* If ending in "\"...     */
          {
              sds = &VolSrch[3] ;          /* ...add only "*.*" */
          }
  
          //
          // If was a drive then don't put wild card stuff on end or
          // dir c: will be the same as dir c:\*
          // Otherwise append wild card spec.
          //
          if (!IsDriveNameOnly(cis->fspec)) {
              cis->fspec = mystrcpy(gmkstr((cbPath+5)*sizeof(TCHAR)), cis->fspec) ;
              mystrcat(cis->fspec, sds) ;
          }
  
          cis->buf->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY ;    /* Fixup attribute           */
          DEBUG((CTGRP, SFLVL, "SCANFSPEC: changed fspec to fspec = `%ws'",cis->fspec)) ;
      }
  
  
  /* Get a pointer to the end of the path in fspec.  Everytime a PathChar or
   * a correctly placed COLON is found, the pointer is updated.  "." and ".."
   * are not looked for because they should be caught above.
   */
  
      for (cbPath=1,wptr=NULL,fspec=cis->fspec; c=*fspec; fspec++,cbPath++) {
          if (c == PathChar || (c == COLON && cbPath == 2)) {
              wptr = fspec ;
          }
      }
  
      cis->pathend = wptr ;
  
      if (wptr) {                       /* Load ptr to fspec's filename    */
          cis->fnptr = (*wptr) ? wptr+1 : wptr ;
      } else {
          wptr = cis->fnptr = cis->fspec ;
      }
  
      if (mystrchr(wptr, STAR) || mystrchr(wptr, QMARK)) { /* has wildcards*/
          cis->flags |= CI_NAMEWILD;
          tywild = TRUE;               /* global type wild */
      }
      cis->extptr = mystrchr(wptr, DOT);        /* look for extension */
  
      DEBUG((CTGRP, SFLVL,
             "SCANFSPEC: pathend = %04x  fnptr = %04x  extptr = %04x  flags = %04x",
             cis->pathend, cis->fnptr, cis->extptr, cis->flags)) ;
  
      return TRUE;
  }
  
  
  
  
  /***    SetFsSetSaveDir - save current directory and change to another one
   *
   *  Purpose:
   *      Parse fspec.
   *      Save the current directory and change to the new directory
   *      specified in fspec.
   *
   *  PCPYINFO SetFsSetSaveDir(TCHAR *fspec)
   *
   *  Args:
   *      fspec - the filespec to use
   *
   *  Returns:
   *      A ptr to the cpyinfo struct fsinfo.
   *      FAILURE otherwise.
   *      SaveDir will contain what default dir was when SetFsSetSaveDir was
   *         called.
   *      CurDrvDir will contain the directory to execute the command in.
   *
   *                      *** W A R N I N G ! ***
   *      THIS ROUTINE WILL CAUSE AN ABORT IF MEMORY CANNOT BE ALLOCATED
   *              THIS ROUTINE MUST NOT BE CALLED DURING A SIGNAL
   *              CRITICAL SECTION OR DURING RECOVERY FROM AN ABORT
   *
   */
  
  PCPYINFO SetFsSetSaveDir(fspec)
  TCHAR *fspec ;
  {
          TCHAR *tmpptr;
          TCHAR *buftemp;
          TCHAR  buft[MAX_PATH];
  
          TCHAR *pathend ;        /* Ptr to the end of the path in fspec  */
          TCHAR c ;               /* Work variable                        */
          PCPYINFO fsinfo ;/* Filespec information struct  */
          unsigned attr;          /* work variable                */
          PWIN32_FIND_DATA pfdSave;
  
  
          fsinfo = (PCPYINFO)gmkstr(sizeof(CPYINFO)) ; /*WARNING*/
  
          fsinfo->fspec = fspec ;
  
          ScanFSpec(fsinfo) ;
  
          pfdSave = fsinfo->buf;          /* save original find buffer */
          fspec = fsinfo->fspec ;
          pathend = fsinfo->pathend ;
          DEBUG((CTGRP, SSLVL, "SFSSD: pathend = `%ws'  fnptr = `%ws'",
                                  fsinfo->pathend, fsinfo->fnptr)) ;
  
          SaveDir = gmkstr(MAX_PATH*sizeof(TCHAR)) ;         /*WARNING*/
          GetDir(SaveDir, GD_DEFAULT);      /* SaveDir will be current default */
          DEBUG((CTGRP, SSLVL, "SFSSD: SaveDir = `%ws'", SaveDir)) ;
  
  /*      Added new test to second conditional below to test for the byte
   *         preceeding pathend to also be a PathChar.  In this way, "\\"
   *         in the root position will case a ChangeDir() call on "\\" which
   *         will fail and cause an invalid directory error as do similar
   *         sequences in other positions in the filespec.
   */
          if (FullPath(buft,fspec,MAX_PATH))
            {
             return((PCPYINFO) FAILURE) ;
            }
  
          buftemp = mystrrchr(buft,PathChar) + 1;
  
          *buftemp = NULLC;
  
          mystrcpy(CurDrvDir,buft);
  
          if (pathend && *pathend != COLON) {
             if (*pathend == PathChar &&
                (pathend == fspec ||
                *(tmpptr = prevc(fspec, pathend)) == COLON ||
                *tmpptr == PathChar)) {
                   pathend++ ;
                 }
             c = *pathend;
             *pathend = NULLC;
             DEBUG((CTGRP, SSLVL, "SFSSD: path = `%ws'", fspec)) ;
             attr = GetFileAttributes(fspec);
             DosErr = (attr != -1 ? NO_ERROR : GetLastError());
             *pathend = c;
             if (DosErr) {
                return((PCPYINFO) FAILURE) ;
             }
          }
  
          ScanFSpec(fsinfo) ;     /* re-scan in case quotes disappeared */
          fsinfo->buf = pfdSave;  /* reset original find buffer */
                                  /* the original is not freed, because */
                                  /* it will be freed by command cleanup */
          return(fsinfo) ;
  }
  