Updated.
[wine] / dlls / msvcrt / dir.c
1 /*
2  * msvcrt.dll drive/directory functions
3  *
4  * Copyright 1996,1998 Marcus Meissner
5  * Copyright 1996 Jukka Iivonen
6  * Copyright 1997,2000 Uwe Bonnes
7  * Copyright 2000 Jon Griffiths
8  */
9
10 #include <time.h>
11 #include "ntddk.h"
12 #include "wine/unicode.h"
13 #include "msvcrt.h"
14 #include "ms_errno.h"
15
16 DEFAULT_DEBUG_CHANNEL(msvcrt);
17
18 typedef struct MSVCRT_finddata_t
19 {
20   unsigned      attrib;
21   time_t        time_create; /* -1 when N/A */
22   time_t        time_access; /* -1 when N/A */
23   time_t        time_write;
24   unsigned long size;        /* FIXME: 64 bit ??*/
25   char          name[MAX_PATH];
26 } MSVCRT_finddata_t;
27
28 typedef struct MSVCRT_wfinddata_t
29 {
30   unsigned      attrib;
31   time_t        time_create; /* -1 when N/A */
32   time_t        time_access; /* -1 when N/A */
33   time_t        time_write;
34   unsigned long size;        /* FIXME: 64 bit ??*/
35   WCHAR          name[MAX_PATH];
36 } MSVCRT_wfinddata_t;
37
38 typedef struct __MSVCRT_diskfree_t {
39   unsigned num_clusters;
40   unsigned available;
41   unsigned cluster_sectors;
42   unsigned sector_bytes;
43 } MSVCRT_diskfree_t;
44
45 /* INTERNAL: Translate finddata_t to PWIN32_FIND_DATAA */
46 static void MSVCRT__fttofd(LPWIN32_FIND_DATAA fd, MSVCRT_finddata_t* ft)
47 {
48   DWORD dw;
49
50   if (fd->dwFileAttributes == FILE_ATTRIBUTE_NORMAL)
51     ft->attrib = 0;
52   else
53     ft->attrib = fd->dwFileAttributes;
54
55   RtlTimeToSecondsSince1970( &fd->ftCreationTime, &dw );
56   ft->time_create = dw;
57   RtlTimeToSecondsSince1970( &fd->ftLastAccessTime, &dw );
58   ft->time_access = dw;
59   RtlTimeToSecondsSince1970( &fd->ftLastWriteTime, &dw );
60   ft->time_write = dw;
61   ft->size = fd->nFileSizeLow;
62   strcpy(ft->name, fd->cFileName);
63 }
64
65 /* INTERNAL: Translate wfinddata_t to PWIN32_FIND_DATAA */
66 static void MSVCRT__wfttofd(LPWIN32_FIND_DATAW fd, MSVCRT_wfinddata_t* ft)
67 {
68   DWORD dw;
69
70   if (fd->dwFileAttributes == FILE_ATTRIBUTE_NORMAL)
71     ft->attrib = 0;
72   else
73     ft->attrib = fd->dwFileAttributes;
74
75   RtlTimeToSecondsSince1970( &fd->ftCreationTime, &dw );
76   ft->time_create = dw;
77   RtlTimeToSecondsSince1970( &fd->ftLastAccessTime, &dw );
78   ft->time_access = dw;
79   RtlTimeToSecondsSince1970( &fd->ftLastWriteTime, &dw );
80   ft->time_write = dw;
81   ft->size = fd->nFileSizeLow;
82   strcpyW(ft->name, fd->cFileName);
83 }
84
85 char * MSVCRT__strndup(const char *, unsigned int);
86 LPWSTR __cdecl MSVCRT__wcsdup( LPCWSTR );
87 LPWSTR __cdecl MSVCRT__wstrndup( LPCWSTR , unsigned int );
88 char *__cdecl  MSVCRT_getenv(const char *);
89 WCHAR *__cdecl wcscpy(WCHAR *,const WCHAR *);
90 WCHAR *__cdecl wcsncpy(WCHAR *,const WCHAR *,unsigned int);
91 WCHAR *__cdecl wcscat(WCHAR *,const WCHAR *);
92 WCHAR *__cdecl wcschr(WCHAR *,WCHAR);
93 WCHAR *__cdecl wcsrchr(WCHAR *,WCHAR);
94 void __cdecl _splitpath(const char *,char *, char *,char *,char *);
95
96 /*********************************************************************
97  *              _chdir (MSVCRT.@)
98  */
99 int __cdecl MSVCRT__chdir(const char * newdir)
100 {
101   if (!SetCurrentDirectoryA(newdir))
102   {
103     MSVCRT__set_errno(newdir?GetLastError():0);
104     return -1;
105   }
106   return 0;
107 }
108
109 /*********************************************************************
110  *              _wchdir (MSVCRT.@)
111  */
112 int __cdecl MSVCRT__wchdir(const WCHAR * newdir)
113 {
114   if (!SetCurrentDirectoryW(newdir))
115   {
116     MSVCRT__set_errno(newdir?GetLastError():0);
117     return -1;
118   }
119   return 0;
120 }
121
122 /*********************************************************************
123  *              _chdrive (MSVCRT.@)
124  */
125 int __cdecl MSVCRT__chdrive(int newdrive)
126 {
127   char buffer[3] = "A:";
128   buffer[0] += newdrive - 1;
129   if (!SetCurrentDirectoryA( buffer ))
130   {
131     MSVCRT__set_errno(GetLastError());
132     if (newdrive <= 0)
133       SET_THREAD_VAR(errno,MSVCRT_EACCES);
134     return -1;
135   }
136   return 0;
137 }
138
139 /*********************************************************************
140  *              _findclose (MSVCRT.@)
141  */
142 int __cdecl MSVCRT__findclose(DWORD hand)
143 {
144   TRACE(":handle %ld\n",hand);
145   if (!FindClose((HANDLE)hand))
146   {
147     MSVCRT__set_errno(GetLastError());
148     return -1;
149   }
150   return 0;
151 }
152
153 /*********************************************************************
154  *              _findfirst (MSVCRT.@)
155  */
156 DWORD __cdecl MSVCRT__findfirst(const char * fspec, MSVCRT_finddata_t* ft)
157 {
158   WIN32_FIND_DATAA find_data;
159   HANDLE hfind;
160
161   hfind  = FindFirstFileA(fspec, &find_data);
162   if (hfind == INVALID_HANDLE_VALUE)
163   {
164     MSVCRT__set_errno(GetLastError());
165     return -1;
166   }
167   MSVCRT__fttofd(&find_data,ft);
168   TRACE(":got handle %d\n",hfind);
169   return hfind;
170 }
171
172 /*********************************************************************
173  *              _wfindfirst (MSVCRT.@)
174  */
175 DWORD __cdecl MSVCRT__wfindfirst(const WCHAR * fspec, MSVCRT_wfinddata_t* ft)
176 {
177   WIN32_FIND_DATAW find_data;
178   HANDLE hfind;
179
180   hfind  = FindFirstFileW(fspec, &find_data);
181   if (hfind == INVALID_HANDLE_VALUE)
182   {
183     MSVCRT__set_errno(GetLastError());
184     return -1;
185   }
186   MSVCRT__wfttofd(&find_data,ft);
187   TRACE(":got handle %d\n",hfind);
188   return hfind;
189 }
190
191 /*********************************************************************
192  *              _findnext (MSVCRT.@)
193  */
194 int __cdecl MSVCRT__findnext(DWORD hand, MSVCRT_finddata_t * ft)
195 {
196   WIN32_FIND_DATAA find_data;
197
198   if (!FindNextFileA(hand, &find_data))
199   {
200     SET_THREAD_VAR(errno,MSVCRT_ENOENT);
201     return -1;
202   }
203
204   MSVCRT__fttofd(&find_data,ft);
205   return 0;
206 }
207
208 /*********************************************************************
209  *              _wfindnext (MSVCRT.@)
210  */
211 int __cdecl MSVCRT__wfindnext(DWORD hand, MSVCRT_wfinddata_t * ft)
212 {
213   WIN32_FIND_DATAW find_data;
214
215   if (!FindNextFileW(hand, &find_data))
216   {
217     SET_THREAD_VAR(errno,MSVCRT_ENOENT);
218     return -1;
219   }
220
221   MSVCRT__wfttofd(&find_data,ft);
222   return 0;
223 }
224
225 /*********************************************************************
226  *              _getcwd (MSVCRT.@)
227  */
228 char* __cdecl MSVCRT__getcwd(char * buf, int size)
229 {
230   char dir[_MAX_PATH];
231   int dir_len = GetCurrentDirectoryA(MAX_PATH,dir);
232
233   if (dir_len < 1)
234     return NULL; /* FIXME: Real return value untested */
235
236   if (!buf)
237   {
238     if (size < 0)
239       return MSVCRT__strdup(dir);
240     return MSVCRT__strndup(dir,size);
241   }
242   if (dir_len >= size)
243   {
244     SET_THREAD_VAR(errno,MSVCRT_ERANGE);
245     return NULL; /* buf too small */
246   }
247   strcpy(buf,dir);
248   return buf;
249 }
250
251 /*********************************************************************
252  *              _wgetcwd (MSVCRT.@)
253  */
254 WCHAR* __cdecl MSVCRT__wgetcwd(WCHAR * buf, int size)
255 {
256   WCHAR dir[_MAX_PATH];
257   int dir_len = GetCurrentDirectoryW(MAX_PATH,dir);
258
259   if (dir_len < 1)
260     return NULL; /* FIXME: Real return value untested */
261
262   if (!buf)
263   {
264     if (size < 0)
265       return MSVCRT__wcsdup(dir);
266     return MSVCRT__wstrndup(dir,size);
267   }
268   if (dir_len >= size)
269   {
270     SET_THREAD_VAR(errno,MSVCRT_ERANGE);
271     return NULL; /* buf too small */
272   }
273   strcpyW(buf,dir);
274   return buf;
275 }
276
277 /*********************************************************************
278  *              _getdrive (MSVCRT.@)
279  */
280 int __cdecl MSVCRT__getdrive(void)
281 {
282     char buffer[MAX_PATH];
283     if (!GetCurrentDirectoryA( sizeof(buffer), buffer )) return 0;
284     if (buffer[1] != ':') return 0;
285     return toupper(buffer[0]) - 'A' + 1;
286 }
287
288 /*********************************************************************
289  *              _getdcwd (MSVCRT.@)
290  */
291 char* __cdecl MSVCRT__getdcwd(int drive, char * buf, int size)
292 {
293   static char* dummy;
294
295   TRACE(":drive %d(%c), size %d\n",drive, drive + 'A' - 1, size);
296
297   if (!drive || drive == MSVCRT__getdrive())
298     return MSVCRT__getcwd(buf,size); /* current */
299   else
300   {
301     char dir[_MAX_PATH];
302     char drivespec[4] = {'A', ':', '\\', 0};
303     int dir_len;
304
305     drivespec[0] += drive - 1;
306     if (GetDriveTypeA(drivespec) < DRIVE_REMOVABLE)
307     {
308       SET_THREAD_VAR(errno,MSVCRT_EACCES);
309       return NULL;
310     }
311
312     dir_len = GetFullPathNameA(drivespec,_MAX_PATH,dir,&dummy);
313     if (dir_len >= size || dir_len < 1)
314     {
315       SET_THREAD_VAR(errno,MSVCRT_ERANGE);
316       return NULL; /* buf too small */
317     }
318
319     TRACE(":returning '%s'\n", dir);
320     if (!buf)
321       return MSVCRT__strdup(dir); /* allocate */
322
323     strcpy(buf,dir);
324   }
325   return buf;
326 }
327
328 /*********************************************************************
329  *              _wgetdcwd (MSVCRT.@)
330  */
331 WCHAR* __cdecl MSVCRT__wgetdcwd(int drive, WCHAR * buf, int size)
332 {
333   static WCHAR* dummy;
334
335   TRACE(":drive %d(%c), size %d\n",drive, drive + 'A' - 1, size);
336
337   if (!drive || drive == MSVCRT__getdrive())
338     return MSVCRT__wgetcwd(buf,size); /* current */
339   else
340   {
341     WCHAR dir[_MAX_PATH];
342     WCHAR drivespec[4] = {'A', ':', '\\', 0};
343     int dir_len;
344
345     drivespec[0] += drive - 1;
346     if (GetDriveTypeW(drivespec) < DRIVE_REMOVABLE)
347     {
348       SET_THREAD_VAR(errno,MSVCRT_EACCES);
349       return NULL;
350     }
351
352     dir_len = GetFullPathNameW(drivespec,_MAX_PATH,dir,&dummy);
353     if (dir_len >= size || dir_len < 1)
354     {
355       SET_THREAD_VAR(errno,MSVCRT_ERANGE);
356       return NULL; /* buf too small */
357     }
358
359     TRACE(":returning '%s'\n", debugstr_w(dir));
360     if (!buf)
361       return MSVCRT__wcsdup(dir); /* allocate */
362     strcpyW(buf,dir);
363   }
364   return buf;
365 }
366
367 /*********************************************************************
368  *              _getdiskfree (MSVCRT.@)
369  */
370 unsigned int __cdecl MSVCRT__getdiskfree(unsigned int disk, MSVCRT_diskfree_t* d)
371 {
372   char drivespec[4] = {'@', ':', '\\', 0};
373   DWORD ret[4];
374   unsigned int err;
375
376   if (disk > 26)
377     return ERROR_INVALID_PARAMETER; /* MSVCRT doesn't set errno here */
378
379   drivespec[0] += disk; /* make a drive letter */
380
381   if (GetDiskFreeSpaceA(disk==0?NULL:drivespec,ret,ret+1,ret+2,ret+3))
382   {
383     d->cluster_sectors = (unsigned)ret[0];
384     d->sector_bytes = (unsigned)ret[1];
385     d->available = (unsigned)ret[2];
386     d->num_clusters = (unsigned)ret[3];
387     return 0;
388   }
389   err = GetLastError();
390   MSVCRT__set_errno(err);
391   return err;
392 }
393
394 /*********************************************************************
395  *              _mkdir (MSVCRT.@)
396  */
397 int __cdecl MSVCRT__mkdir(const char * newdir)
398 {
399   if (CreateDirectoryA(newdir,NULL))
400     return 0;
401   MSVCRT__set_errno(GetLastError());
402   return -1;
403 }
404
405 /*********************************************************************
406  *              _wmkdir (MSVCRT.@)
407  */
408 int __cdecl MSVCRT__wmkdir(const WCHAR* newdir)
409 {
410   if (CreateDirectoryW(newdir,NULL))
411     return 0;
412   MSVCRT__set_errno(GetLastError());
413   return -1;
414 }
415
416 /*********************************************************************
417  *              _rmdir (MSVCRT.@)
418  */
419 int __cdecl MSVCRT__rmdir(const char * dir)
420 {
421   if (RemoveDirectoryA(dir))
422     return 0;
423   MSVCRT__set_errno(GetLastError());
424   return -1;
425 }
426
427 /*********************************************************************
428  *              _wrmdir (MSVCRT.@)
429  */
430 int __cdecl MSVCRT__wrmdir(const WCHAR * dir)
431 {
432   if (RemoveDirectoryW(dir))
433     return 0;
434   MSVCRT__set_errno(GetLastError());
435   return -1;
436 }
437
438 /*********************************************************************
439  *              _wsplitpath (MSVCRT.@)
440  */
441 void __cdecl MSVCRT__wsplitpath(const WCHAR *inpath, WCHAR *drv, WCHAR *dir,
442                                 WCHAR *fname, WCHAR *ext )
443 {
444   /* Modified PD code from 'snippets' collection. */
445   WCHAR ch, *ptr, *p;
446   WCHAR pathbuff[MAX_PATH],*path=pathbuff;
447
448   TRACE(":splitting path '%s'\n",debugstr_w(path));
449   wcscpy(pathbuff, inpath);
450
451   /* convert slashes to backslashes for searching */
452   for (ptr = (WCHAR*)path; *ptr; ++ptr)
453     if (*ptr == (WCHAR)L'/')
454       *ptr = (WCHAR)L'\\';
455
456   /* look for drive spec */
457   if ((ptr = wcschr(path, (WCHAR)L':')) != (WCHAR)L'\0')
458   {
459     ++ptr;
460     if (drv)
461     {
462       wcsncpy(drv, path, ptr - path);
463       drv[ptr - path] = (WCHAR)L'\0';
464     }
465     path = ptr;
466   }
467   else if (drv)
468     *drv = (WCHAR)L'\0';
469
470   /* find rightmost backslash or leftmost colon */
471   if ((ptr = wcsrchr(path, (WCHAR)L'\\')) == NULL)
472     ptr = (wcschr(path, (WCHAR)L':'));
473
474   if (!ptr)
475   {
476     ptr = (WCHAR *)path; /* no path */
477     if (dir)
478       *dir = (WCHAR)L'\0';
479   }
480   else
481   {
482     ++ptr; /* skip the delimiter */
483     if (dir)
484     {
485       ch = *ptr;
486       *ptr = (WCHAR)L'\0';
487       wcscpy(dir, path);
488       *ptr = ch;
489     }
490   }
491
492   if ((p = wcsrchr(ptr, (WCHAR)L'.')) == NULL)
493   {
494     if (fname)
495       wcscpy(fname, ptr);
496     if (ext)
497       *ext = (WCHAR)L'\0';
498   }
499   else
500   {
501     *p = (WCHAR)L'\0';
502     if (fname)
503       wcscpy(fname, ptr);
504     *p = (WCHAR)L'.';
505     if (ext)
506       wcscpy(ext, p);
507   }
508
509   /* Fix pathological case - Win returns ':' as part of the
510    * directory when no drive letter is given.
511    */
512   if (drv && drv[0] == (WCHAR)L':')
513   {
514     *drv = (WCHAR)L'\0';
515     if (dir)
516     {
517       pathbuff[0] = (WCHAR)L':';
518       pathbuff[1] = (WCHAR)L'\0';
519       wcscat(pathbuff,dir);
520       wcscpy(dir, pathbuff);
521     }
522   }
523 }
524
525 /* INTERNAL: Helper for _fullpath. Modified PD code from 'snippets'. */
526 static void fln_fix(char *path)
527 {
528   int dir_flag = 0, root_flag = 0;
529   char *r, *p, *q, *s;
530
531   /* Skip drive */
532   if (NULL == (r = strrchr(path, ':')))
533     r = path;
534   else
535     ++r;
536
537   /* Ignore leading slashes */
538   while ('\\' == *r)
539     if ('\\' == r[1])
540       strcpy(r, &r[1]);
541     else
542     {
543       root_flag = 1;
544       ++r;
545     }
546
547   p = r; /* Change "\\" to "\" */
548   while (NULL != (p = strchr(p, '\\')))
549     if ('\\' ==  p[1])
550       strcpy(p, &p[1]);
551     else
552       ++p;
553
554   while ('.' == *r) /* Scrunch leading ".\" */
555   {
556     if ('.' == r[1])
557     {
558       /* Ignore leading ".." */
559       for (p = (r += 2); *p && (*p != '\\'); ++p)
560         ;
561     }
562     else
563     {
564       for (p = r + 1 ;*p && (*p != '\\'); ++p)
565         ;
566     }
567     strcpy(r, p + ((*p) ? 1 : 0));
568   }
569
570   while ('\\' == path[strlen(path)-1])   /* Strip last '\\' */
571   {
572     dir_flag = 1;
573     path[strlen(path)-1] = '\0';
574   }
575
576   s = r;
577
578   /* Look for "\." in path */
579
580   while (NULL != (p = strstr(s, "\\.")))
581   {
582     if ('.' == p[2])
583     {
584       /* Execute this section if ".." found */
585       q = p - 1;
586       while (q > r)           /* Backup one level           */
587       {
588         if (*q == '\\')
589           break;
590         --q;
591       }
592       if (q > r)
593       {
594         strcpy(q, p + 3);
595         s = q;
596       }
597       else if ('.' != *q)
598       {
599         strcpy(q + ((*q == '\\') ? 1 : 0),
600                p + 3 + ((*(p + 3)) ? 1 : 0));
601         s = q;
602       }
603       else  s = ++p;
604     }
605     else
606     {
607       /* Execute this section if "." found */
608       q = p + 2;
609       for ( ;*q && (*q != '\\'); ++q)
610         ;
611       strcpy (p, q);
612     }
613   }
614
615   if (root_flag)  /* Embedded ".." could have bubbled up to root  */
616   {
617     for (p = r; *p && ('.' == *p || '\\' == *p); ++p)
618       ;
619     if (r != p)
620       strcpy(r, p);
621   }
622
623   if (dir_flag)
624     strcat(path, "\\");
625 }
626
627 /*********************************************************************
628  *              _fullpath (MSVCRT.@)
629  */
630 char *__cdecl MSVCRT__fullpath(char * absPath, const char* relPath, unsigned int size)
631 {
632   char drive[5],dir[MAX_PATH],file[MAX_PATH],ext[MAX_PATH];
633   char res[MAX_PATH];
634   size_t len;
635
636   res[0] = '\0';
637
638   if (!relPath || !*relPath)
639     return MSVCRT__getcwd(absPath, size);
640
641   if (size < 4)
642   {
643     SET_THREAD_VAR(errno,MSVCRT_ERANGE);
644     return NULL;
645   }
646
647   TRACE(":resolving relative path '%s'\n",relPath);
648
649   _splitpath(relPath, drive, dir, file, ext);
650
651   /* Get Directory and drive into 'res' */
652   if (!dir[0] || (dir[0] != '/' && dir[0] != '\\'))
653   {
654     /* Relative or no directory given */
655     MSVCRT__getdcwd(drive[0] ? toupper(drive[0]) - 'A' + 1 :  0, res, MAX_PATH);
656     strcat(res,"\\");
657     if (dir[0])
658       strcat(res,dir);
659     if (drive[0])
660       res[0] = drive[0]; /* If given a drive, preserve the letter case */
661   }
662   else
663   {
664     strcpy(res,drive);
665     strcat(res,dir);
666   }
667
668   strcat(res,"\\");
669   strcat(res, file);
670   strcat(res, ext);
671   fln_fix(res);
672
673   len = strlen(res);
674   if (len >= MAX_PATH || len >= (size_t)size)
675     return NULL; /* FIXME: errno? */
676
677   if (!absPath)
678     return MSVCRT__strdup(res);
679   strcpy(absPath,res);
680   return absPath;
681 }
682
683 /*********************************************************************
684  *              _makepath (MSVCRT.@)
685  */
686 VOID __cdecl MSVCRT__makepath(char * path, const char * drive,
687                               const char *directory, const char * filename,
688                               const char * extension )
689 {
690     char ch;
691     TRACE("MSVCRT__makepath got %s %s %s %s\n", drive, directory,
692           filename, extension);
693
694     if ( !path )
695         return;
696
697     path[0] = 0;
698     if (drive && drive[0])
699     {
700         path[0] = drive[0];
701         path[1] = ':';
702         path[2] = 0;
703     }
704     if (directory && directory[0])
705     {
706         strcat(path, directory);
707         ch = path[strlen(path)-1];
708         if (ch != '/' && ch != '\\')
709             strcat(path,"\\");
710     }
711     if (filename && filename[0])
712     {
713         strcat(path, filename);
714         if (extension && extension[0])
715         {
716             if ( extension[0] != '.' )
717                 strcat(path,".");
718             strcat(path,extension);
719         }
720     }
721
722     TRACE("MSVCRT__makepath returns %s\n",path);
723 }
724
725
726 /*********************************************************************
727  *              _searchenv (MSVCRT.@)
728  */
729 void __cdecl MSVCRT__searchenv(const char* file, const char* env, char *buf)
730 {
731   char*envVal, *penv;
732   char curPath[MAX_PATH];
733
734   *buf = '\0';
735
736   /* Try CWD first */
737   if (GetFileAttributesA( file ) != 0xFFFFFFFF)
738   {
739     GetFullPathNameA( file, MAX_PATH, buf, NULL );
740     /* Sigh. This error is *always* set, regardless of success */
741     MSVCRT__set_errno(ERROR_FILE_NOT_FOUND);
742     return;
743   }
744
745   /* Search given environment variable */
746   envVal = MSVCRT_getenv(env);
747   if (!envVal)
748   {
749     MSVCRT__set_errno(ERROR_FILE_NOT_FOUND);
750     return;
751   }
752
753   penv = envVal;
754   TRACE(":searching for %s in paths %s\n", file, envVal);
755
756   do
757   {
758     char *end = penv;
759
760     while(*end && *end != ';') end++; /* Find end of next path */
761     if (penv == end || !*penv)
762     {
763       MSVCRT__set_errno(ERROR_FILE_NOT_FOUND);
764       return;
765     }
766     strncpy(curPath, penv, end - penv);
767     if (curPath[end - penv] != '/' || curPath[end - penv] != '\\')
768     {
769       curPath[end - penv] = '\\';
770       curPath[end - penv + 1] = '\0';
771     }
772     else
773       curPath[end - penv] = '\0';
774
775     strcat(curPath, file);
776     TRACE("Checking for file %s\n", curPath);
777     if (GetFileAttributesA( curPath ) != 0xFFFFFFFF)
778     {
779       strcpy(buf, curPath);
780       MSVCRT__set_errno(ERROR_FILE_NOT_FOUND);
781       return; /* Found */
782     }
783     penv = *end ? end + 1 : end;
784   } while(1);
785 }
786