Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

673 lines
24 KiB

  1. /*++
  2. Copyright (c) 1988-1999 Microsoft Corporation
  3. Module Name:
  4. ctools1.c
  5. Abstract:
  6. Low level utilities
  7. --*/
  8. #include "cmd.h"
  9. extern unsigned tywild; /* type is wild flag */
  10. extern TCHAR CurDrvDir[], *SaveDir, PathChar, Delimiters[] ;
  11. extern TCHAR VolSrch[] ;
  12. extern TCHAR BSlash ;
  13. extern unsigned DosErr ;
  14. extern BOOL CtrlCSeen;
  15. static TCHAR szNull[] = TEXT("");
  16. /*** TokStr - tokenize argument strings
  17. *
  18. * Purpose:
  19. * Tokenize a string.
  20. * Allocate space for a new string and copy each token in src into the
  21. * new string and null terminate it. Tokens are whitespace delimited
  22. * unless changed by specialdelims and/or tsflags. The entire tokenized
  23. * string ends with 2 null bytes.
  24. *
  25. * TCHAR *TokStr(TCHAR *src, TCHAR *specialdelims, unsigned tsflags)
  26. *
  27. * Args:
  28. * src - the string to be tokenized
  29. * specialdelims - a string of other characters which are to be comsidered
  30. * as token delimiters
  31. * tsflags - bit 0 nonzero if whitespace are NOT delimiters
  32. * bit 1 nonzero if special delimiters are tokens themselves,
  33. * eg "foo=bar" => "foo0=0bar00"
  34. *
  35. * Returns:
  36. * A pointer to the new string.
  37. * A pointer to a null string if src is NULL
  38. * NULL if unable to allocate memory.
  39. *
  40. * Notes:
  41. * The format of the tokenized string dictates the way code is written
  42. * to process the tokens in that string. For instance, the code
  43. * "s += mystrlen(s)+1" is the way to update s to point to the next token
  44. * in the string.
  45. *
  46. * Command considers "=", ",", and ";" to be token delimiters just like
  47. * whitespace. The only time they are not treated like whitespace is
  48. * when they are included in specialdelims.
  49. *
  50. * *** W A R N I N G ! ***
  51. * THIS ROUTINE WILL CAUSE AN ABORT IF MEMORY CANNOT BE ALLOCATED
  52. * THIS ROUTINE MUST NOT BE CALLED DURING A SIGNAL
  53. * CRITICAL SECTION OR DURING RECOVERY FROM AN ABORT
  54. *
  55. */
  56. TCHAR *TokStr(src, specialdelims, tsflags)
  57. TCHAR *src ;
  58. TCHAR *specialdelims ;
  59. unsigned tsflags ;
  60. {
  61. TCHAR *ts ; /* Tokenized string pointer */
  62. TCHAR *tscpy, /* Copy of ts */
  63. delist[5], /* List of non-whitespace delimiter/separators */
  64. c; /* Work variable */
  65. int first, /* Flag, true if first time through the loop */
  66. lctdelim, /* Flag, true if last byte was token delimiter */
  67. i, j ; /* Index/loop counter */
  68. DEBUG((CTGRP, TSLVL, "TOKSTR: Entered &src = %04x, src = %ws",src,src));
  69. DEBUG((CTGRP, TSLVL, "TOKSTR: Making copy str of len %d",(mystrlen(src)*2+2))) ;
  70. if (src == NULL) {
  71. return(szNull); // This routine returns a doubly null terminated string
  72. } else {
  73. ts = tscpy = gmkstr((mystrlen(src)*2+2)*sizeof(TCHAR)) ; /*WARNING*/
  74. DEBUG((CTGRP, TSLVL, "TOKSTR: &tscpy = %04x",tscpy)) ;
  75. for (i = j = 0 ; c = *(&Delimiters[i]) ; i++)
  76. if (!mystrchr(specialdelims, c))
  77. delist[j++] = c ;
  78. delist[j] = NULLC ;
  79. DEBUG((CTGRP, TSLVL, "TOKSTR: Delimiter string built as `%ws'",delist)) ;
  80. for (first = TRUE, lctdelim = TRUE ; *src ; src++) {
  81. if (
  82. (*src != QUOTE) &&
  83. (_istspace(*src) || mystrchr(delist, *src)) &&
  84. (!(tsflags & TS_WSPACE) || first) &&
  85. (!(tsflags & TS_SDTOKENS) || !mystrchr(specialdelims, *src)) &&
  86. (!(tsflags & TS_NWSPACE) || !mystrchr(specialdelims, *src)) ) {
  87. while ( *src &&
  88. (*src != QUOTE) &&
  89. (_istspace(*src) || mystrchr(delist, *src)) &&
  90. (!(tsflags & TS_SDTOKENS) || !mystrchr(specialdelims, *src)) &&
  91. (!(tsflags & TS_NWSPACE) || !mystrchr(specialdelims, *src)) )
  92. src++ ;
  93. if (!(*src))
  94. break ;
  95. if (!first && !lctdelim)
  96. ts++ ;
  97. lctdelim = TRUE ;
  98. } ;
  99. first = FALSE ;
  100. if (specialdelims && mystrchr(specialdelims, *src)) {
  101. if (tsflags & TS_SDTOKENS) {
  102. if (lctdelim)
  103. *ts = *src ;
  104. else
  105. *++ts = *src ;
  106. lctdelim = TRUE ;
  107. ts++ ;
  108. } else {
  109. if ( tsflags & TS_NWSPACE )
  110. *ts = *src ;
  111. lctdelim = FALSE ;
  112. }
  113. ts++ ;
  114. continue ;
  115. } ;
  116. *ts++ = *src ;
  117. if ( *src == QUOTE ) {
  118. do {
  119. *ts++ = *(++src);
  120. } while ( src[0] && src[0] != QUOTE && src[1] );
  121. if ( !src[0] ) {
  122. src--;
  123. }
  124. }
  125. lctdelim = FALSE ;
  126. } ;
  127. DEBUG((CTGRP, TSLVL, "TOKSTR: String complete, resizing to %d",ts-tscpy+2)) ;
  128. return(resize(tscpy, ((UINT)(ts-tscpy)+2)*sizeof(TCHAR))) ;
  129. DEBUG((CTGRP, TSLVL, "TOKSTR: Resizing done, returning")) ;
  130. }
  131. }
  132. /******************************************************************************/
  133. /* */
  134. /* LoopThroughArgs - call a function on all args in a list */
  135. /* */
  136. /* Purpose: */
  137. /* This is function is called by many of the commands that take */
  138. /* multiple arguments. This function will parse the argument string, */
  139. /* complain if no args were given, and call func on each of the ards */
  140. /* in argstr. Optionally, it will also expand any wildcards in the */
  141. /* arguments. Execution stops if func ever returns FAILURE. */
  142. /* */
  143. /* int LoopThroughArgs(TCHAR *argstr, int (*func)(), int ltaflags) */
  144. /* */
  145. /* Args: */
  146. /* argstr - argument string */
  147. /* func - the function to pass each element of argstr */
  148. /* ltaflags - bit 0 on if wildcards are to be expanded */
  149. /* bit 1 on if it's ok for argstr to be empty (nothing but whitespace) */
  150. /* bit 2 on if file names should be passed through un changed */
  151. /* when the wildcard expansion fails to find any matches.*/
  152. /* */
  153. /* Returns: */
  154. /* The value returned by func the last time it is run. */
  155. /* */
  156. /******************************************************************************/
  157. int LoopThroughArgs(argstr, func, ltaflags)
  158. TCHAR *argstr ;
  159. PLOOP_THROUGH_ARGS_ROUTINE func ;
  160. int ltaflags ;
  161. {
  162. TCHAR *tas ; /* Tokenized argument string */
  163. TCHAR fspec[MAX_PATH] ; /* Holds filespec when expanding */
  164. WIN32_FIND_DATA buf ; /* Use for ffirst/fnext */
  165. HANDLE hnFirst ;
  166. CPYINFO fsinfo ;
  167. int catspot ; /* Fspec index where fname should be added */
  168. unsigned final_code = SUCCESS;
  169. unsigned error_code = SUCCESS;
  170. int multargs = FALSE;
  171. unsigned attr;/* attribute for ffirst for because of TYPE wild */
  172. unsigned taslen;
  173. tywild = FALSE; /* global type wild card flag ret */
  174. GetDir(CurDrvDir, GD_DEFAULT);
  175. if (*(tas = TokStr(argstr, NULL, TS_NOFLAGS)) == NULLC)
  176. {
  177. if (ltaflags & LTA_NULLOK)
  178. {
  179. /* return((*func)(tas)) ; */
  180. return((*func)( StripQuotes(tas) )) ;
  181. }
  182. PutStdErr(MSG_BAD_SYNTAX, NOARGS);
  183. return(FAILURE) ;
  184. }
  185. if (*(tas + mystrlen(tas) + 1) ) /* Check for multiple args */
  186. {
  187. multargs = TRUE;
  188. }
  189. for ( ; *tas ; tas += taslen+1 )
  190. {
  191. if (CtrlCSeen) {
  192. return(FAILURE);
  193. }
  194. taslen = mystrlen( tas );
  195. mystrcpy( tas, StripQuotes( tas ) );
  196. if (ltaflags & LTA_EXPAND)
  197. {
  198. if (cmdfound == TYTYP) /* if TYPE cmd then only files */
  199. {
  200. attr = FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_ARCHIVE;
  201. }
  202. else /* else */
  203. {
  204. attr = A_ALL; /* find all */
  205. }
  206. //
  207. // this is used to detect an error other then can not
  208. // find file. It is set in ffirst
  209. //
  210. DosErr = 0;
  211. if (!ffirst(tas, attr, &buf, &hnFirst))
  212. {
  213. //
  214. // Check that failure was not do to some system error such
  215. // as an abort on access to floppy
  216. //
  217. if (DosErr) {
  218. if ((DosErr != ERROR_FILE_NOT_FOUND) &&
  219. (DosErr != ERROR_NO_MORE_FILES)) {
  220. PutStdErr(DosErr, NOARGS);
  221. return( FAILURE );
  222. }
  223. }
  224. if (ltaflags & LTA_NOMATCH)
  225. {
  226. if ( error_code = ((*func)(tas)) )
  227. {
  228. final_code = FAILURE;
  229. if (multargs) /* if cmd failed then (TYPE)*/
  230. { /* display arg failed msg too */
  231. PutStdErr( MSG_ERR_PROC_ARG, ONEARG, tas );
  232. }
  233. }
  234. if ( error_code && !(ltaflags & LTA_CONT))
  235. {
  236. return(FAILURE) ;
  237. }
  238. else
  239. {
  240. continue;
  241. }
  242. }
  243. PutStdErr(((DosErr == ERROR_PATH_NOT_FOUND) ?
  244. MSG_REN_INVAL_PATH_FILENAME :
  245. ERROR_FILE_NOT_FOUND),
  246. NOARGS);
  247. return(FAILURE) ;
  248. }
  249. if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  250. {
  251. PutStdErr(MSG_REN_INVAL_PATH_FILENAME, NOARGS);
  252. return(FAILURE) ;
  253. }
  254. fsinfo.fspec = tas ;
  255. ScanFSpec(&fsinfo) ;
  256. catspot = (int)(fsinfo.fnptr-tas) ;
  257. mystrcpy(fspec, tas) ;
  258. do
  259. {
  260. fspec[catspot] = NULLC ;
  261. tywild |= multargs; /* if multiple args or wild then wild for TYPE */
  262. if ( error_code = ((*func)(mystrcat(fspec, buf.cFileName))) )
  263. {
  264. final_code = FAILURE;
  265. }
  266. if ( error_code && !(ltaflags & LTA_CONT))
  267. {
  268. return(FAILURE) ;
  269. }
  270. } while(fnext(&buf, attr, hnFirst));
  271. findclose(hnFirst) ;
  272. }
  273. else
  274. {
  275. tywild |= multargs; /* if multiple args or wild then wild for TYPE */
  276. /* if ( error_code = ((*func)(mystrcpy(fspec,tas))) ) */
  277. if ( error_code = ((*func)(tas)) )
  278. {
  279. final_code = FAILURE;
  280. }
  281. if ( error_code && !(ltaflags & LTA_CONT))
  282. {
  283. return(FAILURE) ;
  284. }
  285. }
  286. if (error_code && multargs) /* error this time through */
  287. {
  288. PutStdErr(MSG_ERR_PROC_ARG, ONEARG, tas );
  289. }
  290. }
  291. return( final_code ) ;
  292. }
  293. BOOLEAN
  294. IsDriveNameOnly (
  295. IN PTCHAR psz
  296. )
  297. {
  298. //
  299. // If it does not have any path character, is 2 chars long and
  300. // has a ':' it must be a drive
  301. //
  302. if (!mystrrchr(psz,PathChar)) {
  303. if ((mystrlen(psz) == 2) && psz[1] == COLON) {
  304. return( TRUE );
  305. }
  306. }
  307. return( FALSE );
  308. }
  309. /*** ScanFSpec - parse a path string
  310. *
  311. * Purpose:
  312. * To scan the filespec in cis to find the information needed to set the
  313. * pathend, fnptr, extptr, and flags field of the structure. Pathend is
  314. * a ptr to the end of the path and can be NULL. Fnptr is a ptr to the
  315. * filename and may point to a null character. Extptr is a ptr to the
  316. * extension (including ".") and may point to a null character.
  317. *
  318. * ScanFSpec(PCPYINFO cis)
  319. *
  320. * Arg:
  321. * cis - the copy information structure to fill
  322. *
  323. * Notes:
  324. * This function needs to be rewritten and cleanup more than any other
  325. * function in the entire program!!!
  326. *
  327. * *** W A R N I N G ! ***
  328. * THIS ROUTINE WILL CAUSE AN ABORT IF MEMORY CANNOT BE ALLOCATED
  329. * THIS ROUTINE MUST NOT BE CALLED DURING A SIGNAL
  330. * CRITICAL SECTION OR DURING RECOVERY FROM AN ABORT
  331. *
  332. */
  333. BOOL ScanFSpec(cis)
  334. PCPYINFO cis ;
  335. {
  336. unsigned att ;
  337. UINT OldErrorMode;
  338. TCHAR *sds = &VolSrch[2] ; /* "\*.*" Added to dir's */
  339. TCHAR *fspec ; /* Work Vars - Holds filespec */
  340. TCHAR *wptr ; /* - General string pointer */
  341. TCHAR c ; /* - Temp byte holder */
  342. TCHAR c2 = NULLC ; /* Another if two are needed */
  343. int cbPath, /* - Length of incoming fspec */
  344. dirflag = FALSE ; /* - FSpec is directory flag */
  345. CRTHANDLE hn;
  346. PWIN32_FIND_DATA pfdSave;
  347. DosErr = NO_ERROR;
  348. DEBUG((CTGRP, SFLVL, "SCANFSPEC: cis = %04x fspec = %04x `%ws'",
  349. cis, cis->fspec, cis->fspec)) ;
  350. cbPath = mystrlen(cis->fspec) ; /* Get length of filespec */
  351. if (*(wptr = lastc(cis->fspec)) == COLON && cbPath > 2) {
  352. *wptr-- = NULLC ; /* Zap colon if device name */
  353. OldErrorMode = SetErrorMode( 0 );
  354. hn = Copen(cis->fspec, O_RDONLY|O_BINARY );
  355. if ((hn == BADHANDLE) || (!FileIsDevice(hn) && !FileIsPipe(hn))) {
  356. *++wptr = COLON;
  357. if (cmdfound == CPYTYP) {
  358. if (cpydest == FALSE) {
  359. PutStdErr( MSG_CMD_NOT_RECOGNIZED, ONEARG, cis->fspec);
  360. }
  361. cdevfail = TRUE;
  362. } else {
  363. PutStdErr( MSG_CMD_NOT_RECOGNIZED, ONEARG, cis->fspec);
  364. }
  365. if (hn != BADHANDLE) {
  366. Cclose( hn );
  367. }
  368. } else {
  369. if ( FileIsDevice(hn) || FileIsPipe(hn) ) {
  370. Cclose( hn );
  371. }
  372. }
  373. SetErrorMode( OldErrorMode );
  374. }
  375. cis->buf = (PWIN32_FIND_DATA)gmkstr(sizeof(WIN32_FIND_DATA)) ; /*WARNING*/
  376. /* First it must be determined if this is a file or directory and if directory
  377. * a "\*.*" appended. Filespec's that are "." or "\" or those ending in "\",
  378. * ":.", ".." or "\." are assumed to be directories. Note that ROOT will fit
  379. * one of these patterns if explicitly named. If no such pattern is found,
  380. * a Get Attributes system call is performed as a final test. Note that
  381. * wildcards are not tested for, since the DOS call will fail defaulting them
  382. * to filenames. Success of any test assumes directory with the "\*.*" being
  383. * appended, while failure of all tests assumes filename.
  384. */
  385. /* If the filespec ends in a '\' set dirflag. Otherwise find where the
  386. * actual filename begins (by looking for last PathChar if there is one).
  387. * If there is no pathchar, then check if a drive and colon has been
  388. * specified. Update the pointer to point to the actual file name spec.
  389. * If it is a "." or ".." then set dirflag.
  390. */
  391. c = *wptr;
  392. if ( c == PathChar ) {
  393. dirflag = TRUE ;
  394. } else {
  395. wptr = mystrrchr(cis->fspec, PathChar);
  396. if (wptr == NULL) {
  397. wptr = cis->fspec ;
  398. if ((mystrlen(wptr) >= 2) && (wptr[1] == COLON)) {
  399. wptr = &wptr[2];
  400. }
  401. } else {
  402. wptr++ ;
  403. }
  404. if ((_tcsicmp(wptr, TEXT(".")) == 0) || (_tcsicmp(wptr, TEXT("..")) == 0)) {
  405. dirflag = TRUE ;
  406. }
  407. }
  408. if (!dirflag) {
  409. if (cmdfound == CPYTYP) { /* bypass if COPY cmd */
  410. if (cpydflag == TRUE) {
  411. att = GetFileAttributes(cis->fspec);
  412. DosErr = (att != -1 ? NO_ERROR : GetLastError());
  413. if (att != -1 ) {
  414. if (att & FILE_ATTRIBUTE_DIRECTORY) {
  415. dirflag = TRUE;
  416. }
  417. }
  418. } else {
  419. if (cpyfirst == TRUE) { /* and !first time */
  420. cpyfirst = FALSE; /* and !first time */
  421. att = GetFileAttributes(cis->fspec);
  422. DosErr = (att != -1 ? NO_ERROR : GetLastError());
  423. if (att != -1 ) {
  424. if (att & FILE_ATTRIBUTE_DIRECTORY) {
  425. dirflag = TRUE;
  426. }
  427. }
  428. }
  429. }
  430. } else {
  431. att = GetFileAttributes(cis->fspec);
  432. DosErr = (att != -1 ? NO_ERROR : GetLastError());
  433. if (att != -1 ) {
  434. if (att & FILE_ATTRIBUTE_DIRECTORY) {
  435. dirflag = TRUE;
  436. }
  437. }
  438. }
  439. }
  440. /* Note that in the following conditional, the directory attribute is set
  441. * in cis->buf->attributes. Some functions calling ScanFSpec() require this
  442. * knowledge.
  443. */
  444. if (dirflag) {
  445. if (c == PathChar) /* If ending in "\"... */
  446. {
  447. sds = &VolSrch[3] ; /* ...add only "*.*" */
  448. }
  449. //
  450. // If was a drive then don't put wild card stuff on end or
  451. // dir c: will be the same as dir c:\*
  452. // Otherwise append wild card spec.
  453. //
  454. if (!IsDriveNameOnly(cis->fspec)) {
  455. cis->fspec = mystrcpy(gmkstr((cbPath+5)*sizeof(TCHAR)), cis->fspec) ;
  456. mystrcat(cis->fspec, sds) ;
  457. }
  458. cis->buf->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY ; /* Fixup attribute */
  459. DEBUG((CTGRP, SFLVL, "SCANFSPEC: changed fspec to fspec = `%ws'",cis->fspec)) ;
  460. }
  461. /* Get a pointer to the end of the path in fspec. Everytime a PathChar or
  462. * a correctly placed COLON is found, the pointer is updated. "." and ".."
  463. * are not looked for because they should be caught above.
  464. */
  465. for (cbPath=1,wptr=NULL,fspec=cis->fspec; c=*fspec; fspec++,cbPath++) {
  466. if (c == PathChar || (c == COLON && cbPath == 2)) {
  467. wptr = fspec ;
  468. }
  469. }
  470. cis->pathend = wptr ;
  471. if (wptr) { /* Load ptr to fspec's filename */
  472. cis->fnptr = (*wptr) ? wptr+1 : wptr ;
  473. } else {
  474. wptr = cis->fnptr = cis->fspec ;
  475. }
  476. if (mystrchr(wptr, STAR) || mystrchr(wptr, QMARK)) { /* has wildcards*/
  477. cis->flags |= CI_NAMEWILD;
  478. tywild = TRUE; /* global type wild */
  479. }
  480. cis->extptr = mystrchr(wptr, DOT); /* look for extension */
  481. DEBUG((CTGRP, SFLVL,
  482. "SCANFSPEC: pathend = %04x fnptr = %04x extptr = %04x flags = %04x",
  483. cis->pathend, cis->fnptr, cis->extptr, cis->flags)) ;
  484. return TRUE;
  485. }
  486. /*** SetFsSetSaveDir - save current directory and change to another one
  487. *
  488. * Purpose:
  489. * Parse fspec.
  490. * Save the current directory and change to the new directory
  491. * specified in fspec.
  492. *
  493. * PCPYINFO SetFsSetSaveDir(TCHAR *fspec)
  494. *
  495. * Args:
  496. * fspec - the filespec to use
  497. *
  498. * Returns:
  499. * A ptr to the cpyinfo struct fsinfo.
  500. * FAILURE otherwise.
  501. * SaveDir will contain what default dir was when SetFsSetSaveDir was
  502. * called.
  503. * CurDrvDir will contain the directory to execute the command in.
  504. *
  505. * *** W A R N I N G ! ***
  506. * THIS ROUTINE WILL CAUSE AN ABORT IF MEMORY CANNOT BE ALLOCATED
  507. * THIS ROUTINE MUST NOT BE CALLED DURING A SIGNAL
  508. * CRITICAL SECTION OR DURING RECOVERY FROM AN ABORT
  509. *
  510. */
  511. PCPYINFO SetFsSetSaveDir(fspec)
  512. TCHAR *fspec ;
  513. {
  514. TCHAR *tmpptr;
  515. TCHAR *buftemp;
  516. TCHAR buft[MAX_PATH];
  517. TCHAR *pathend ; /* Ptr to the end of the path in fspec */
  518. TCHAR c ; /* Work variable */
  519. PCPYINFO fsinfo ;/* Filespec information struct */
  520. unsigned attr; /* work variable */
  521. PWIN32_FIND_DATA pfdSave;
  522. fsinfo = (PCPYINFO)gmkstr(sizeof(CPYINFO)) ; /*WARNING*/
  523. fsinfo->fspec = fspec ;
  524. ScanFSpec(fsinfo) ;
  525. pfdSave = fsinfo->buf; /* save original find buffer */
  526. fspec = fsinfo->fspec ;
  527. pathend = fsinfo->pathend ;
  528. DEBUG((CTGRP, SSLVL, "SFSSD: pathend = `%ws' fnptr = `%ws'",
  529. fsinfo->pathend, fsinfo->fnptr)) ;
  530. SaveDir = gmkstr(MAX_PATH*sizeof(TCHAR)) ; /*WARNING*/
  531. GetDir(SaveDir, GD_DEFAULT); /* SaveDir will be current default */
  532. DEBUG((CTGRP, SSLVL, "SFSSD: SaveDir = `%ws'", SaveDir)) ;
  533. /* Added new test to second conditional below to test for the byte
  534. * preceeding pathend to also be a PathChar. In this way, "\\"
  535. * in the root position will case a ChangeDir() call on "\\" which
  536. * will fail and cause an invalid directory error as do similar
  537. * sequences in other positions in the filespec.
  538. */
  539. if (FullPath(buft,fspec,MAX_PATH))
  540. {
  541. return((PCPYINFO) FAILURE) ;
  542. }
  543. buftemp = mystrrchr(buft,PathChar) + 1;
  544. *buftemp = NULLC;
  545. mystrcpy(CurDrvDir,buft);
  546. if (pathend && *pathend != COLON) {
  547. if (*pathend == PathChar &&
  548. (pathend == fspec ||
  549. *(tmpptr = prevc(fspec, pathend)) == COLON ||
  550. *tmpptr == PathChar)) {
  551. pathend++ ;
  552. }
  553. c = *pathend;
  554. *pathend = NULLC;
  555. DEBUG((CTGRP, SSLVL, "SFSSD: path = `%ws'", fspec)) ;
  556. attr = GetFileAttributes(fspec);
  557. DosErr = (attr != -1 ? NO_ERROR : GetLastError());
  558. *pathend = c;
  559. if (DosErr) {
  560. return((PCPYINFO) FAILURE) ;
  561. }
  562. }
  563. ScanFSpec(fsinfo) ; /* re-scan in case quotes disappeared */
  564. fsinfo->buf = pfdSave; /* reset original find buffer */
  565. /* the original is not freed, because */
  566. /* it will be freed by command cleanup */
  567. return(fsinfo) ;
  568. }