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