Replace the get_file_info request by an fstat() on the client side.
[wine] / dlls / kernel / path.c
1 /*
2  * File handling functions
3  *
4  * Copyright 1993 Erik Bos
5  * Copyright 1996 Alexandre Julliard
6  * Copyright 2003 Eric Pouech
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23
24 #include "config.h"
25 #include "wine/port.h"
26
27 #include <stdarg.h>
28
29 #define NONAMELESSUNION
30 #define NONAMELESSSTRUCT
31 #include "winerror.h"
32 #include "ntstatus.h"
33 #include "windef.h"
34 #include "winbase.h"
35 #include "winreg.h"
36 #include "winternl.h"
37
38 #include "kernel_private.h"
39 #include "wine/unicode.h"
40 #include "wine/debug.h"
41
42 WINE_DEFAULT_DEBUG_CHANNEL(file);
43
44 #define MAX_PATHNAME_LEN        1024
45
46 /***********************************************************************
47  *           GetFullPathNameW   (KERNEL32.@)
48  * NOTES
49  *   if the path closed with '\', *lastpart is 0
50  */
51 DWORD WINAPI GetFullPathNameW( LPCWSTR name, DWORD len, LPWSTR buffer,
52                                LPWSTR *lastpart )
53 {
54     return RtlGetFullPathName_U(name, len * sizeof(WCHAR), buffer, lastpart) / sizeof(WCHAR);
55 }
56
57 /***********************************************************************
58  *           GetFullPathNameA   (KERNEL32.@)
59  * NOTES
60  *   if the path closed with '\', *lastpart is 0
61  */
62 DWORD WINAPI GetFullPathNameA( LPCSTR name, DWORD len, LPSTR buffer,
63                                LPSTR *lastpart )
64 {
65     UNICODE_STRING nameW;
66     WCHAR bufferW[MAX_PATH];
67     DWORD ret, retW;
68
69     if (!name)
70     {
71         SetLastError(ERROR_INVALID_PARAMETER);
72         return 0;
73     }
74
75     if (!RtlCreateUnicodeStringFromAsciiz(&nameW, name))
76     {
77         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
78         return 0;
79     }
80
81     retW = GetFullPathNameW( nameW.Buffer, MAX_PATH, bufferW, NULL);
82
83     if (!retW)
84         ret = 0;
85     else if (retW > MAX_PATH)
86     {
87         SetLastError(ERROR_FILENAME_EXCED_RANGE);
88         ret = 0;
89     }
90     else
91     {
92         ret = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
93         if (ret && ret <= len)
94         {
95             WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, len, NULL, NULL);
96             ret--; /* length without 0 */
97
98             if (lastpart)
99             {
100                 LPSTR p = buffer + strlen(buffer) - 1;
101
102                 if (*p != '\\')
103                 {
104                     while ((p > buffer + 2) && (*p != '\\')) p--;
105                     *lastpart = p + 1;
106                 }
107                 else *lastpart = NULL;
108             }
109         }
110     }
111
112     RtlFreeUnicodeString(&nameW);
113     return ret;
114 }
115
116
117 /***********************************************************************
118  *           GetLongPathNameW   (KERNEL32.@)
119  *
120  * NOTES
121  *  observed (Win2000):
122  *  shortpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
123  *  shortpath="":   LastError=ERROR_PATH_NOT_FOUND, ret=0
124  */
125 DWORD WINAPI GetLongPathNameW( LPCWSTR shortpath, LPWSTR longpath, DWORD longlen )
126 {
127     WCHAR               tmplongpath[MAX_PATHNAME_LEN];
128     LPCWSTR             p;
129     DWORD               sp = 0, lp = 0;
130     DWORD               tmplen;
131     BOOL                unixabsolute = (shortpath[0] == '/');
132     WIN32_FIND_DATAW    wfd;
133     HANDLE              goit;
134
135     if (!shortpath)
136     {
137         SetLastError(ERROR_INVALID_PARAMETER);
138         return 0;
139     }
140     if (!shortpath[0])
141     {
142         SetLastError(ERROR_PATH_NOT_FOUND);
143         return 0;
144     }
145
146     TRACE("%s,%p,%ld\n", debugstr_w(shortpath), longpath, longlen);
147
148     if (shortpath[0] == '\\' && shortpath[1] == '\\')
149     {
150         ERR("UNC pathname %s\n", debugstr_w(shortpath));
151         lstrcpynW( longpath, shortpath, longlen );
152         return strlenW(longpath);
153     }
154
155     /* check for drive letter */
156     if (!unixabsolute && shortpath[1] == ':' )
157     {
158         tmplongpath[0] = shortpath[0];
159         tmplongpath[1] = ':';
160         lp = sp = 2;
161     }
162
163     while (shortpath[sp])
164     {
165         /* check for path delimiters and reproduce them */
166         if (shortpath[sp] == '\\' || shortpath[sp] == '/')
167         {
168             if (!lp || tmplongpath[lp-1] != '\\')
169             {
170                 /* strip double "\\" */
171                 tmplongpath[lp++] = '\\';
172             }
173             tmplongpath[lp] = 0; /* terminate string */
174             sp++;
175             continue;
176         }
177
178         p = shortpath + sp;
179         if (sp == 0 && p[0] == '.' && (p[1] == '/' || p[1] == '\\'))
180         {
181             tmplongpath[lp++] = *p++;
182             tmplongpath[lp++] = *p++;
183         }
184         for (; *p && *p != '/' && *p != '\\'; p++);
185         tmplen = p - (shortpath + sp);
186         lstrcpynW(tmplongpath + lp, shortpath + sp, tmplen + 1);
187         /* Check if the file exists and use the existing file name */
188         goit = FindFirstFileW(tmplongpath, &wfd);
189         if (goit == INVALID_HANDLE_VALUE)
190         {
191             TRACE("not found %s!\n", debugstr_w(tmplongpath));
192             SetLastError ( ERROR_FILE_NOT_FOUND );
193             return 0;
194         }
195         FindClose(goit);
196         strcpyW(tmplongpath + lp, wfd.cFileName);
197         lp += strlenW(tmplongpath + lp);
198         sp += tmplen;
199     }
200     tmplen = strlenW(shortpath) - 1;
201     if ((shortpath[tmplen] == '/' || shortpath[tmplen] == '\\') &&
202         (tmplongpath[lp - 1] != '/' && tmplongpath[lp - 1] != '\\'))
203         tmplongpath[lp++] = shortpath[tmplen];
204     tmplongpath[lp] = 0;
205
206     tmplen = strlenW(tmplongpath) + 1;
207     if (tmplen <= longlen)
208     {
209         strcpyW(longpath, tmplongpath);
210         TRACE("returning %s\n", debugstr_w(longpath));
211         tmplen--; /* length without 0 */
212     }
213
214     return tmplen;
215 }
216
217 /***********************************************************************
218  *           GetLongPathNameA   (KERNEL32.@)
219  */
220 DWORD WINAPI GetLongPathNameA( LPCSTR shortpath, LPSTR longpath, DWORD longlen )
221 {
222     UNICODE_STRING shortpathW;
223     WCHAR longpathW[MAX_PATH];
224     DWORD ret, retW;
225
226     if (!shortpath)
227     {
228         SetLastError(ERROR_INVALID_PARAMETER);
229         return 0;
230     }
231
232     TRACE("%s\n", debugstr_a(shortpath));
233
234     if (!RtlCreateUnicodeStringFromAsciiz(&shortpathW, shortpath))
235     {
236         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
237         return 0;
238     }
239
240     retW = GetLongPathNameW(shortpathW.Buffer, longpathW, MAX_PATH);
241
242     if (!retW)
243         ret = 0;
244     else if (retW > MAX_PATH)
245     {
246         SetLastError(ERROR_FILENAME_EXCED_RANGE);
247         ret = 0;
248     }
249     else
250     {
251         ret = WideCharToMultiByte(CP_ACP, 0, longpathW, -1, NULL, 0, NULL, NULL);
252         if (ret <= longlen)
253         {
254             WideCharToMultiByte(CP_ACP, 0, longpathW, -1, longpath, longlen, NULL, NULL);
255             ret--; /* length without 0 */
256         }
257     }
258
259     RtlFreeUnicodeString(&shortpathW);
260     return ret;
261 }
262
263
264 /***********************************************************************
265  *           GetShortPathNameW   (KERNEL32.@)
266  *
267  * NOTES
268  *  observed:
269  *  longpath=NULL: LastError=ERROR_INVALID_PARAMETER, ret=0
270  *  longpath="" or invalid: LastError=ERROR_BAD_PATHNAME, ret=0
271  *
272  * more observations ( with NT 3.51 (WinDD) ):
273  * longpath <= 8.3 -> just copy longpath to shortpath
274  * longpath > 8.3  ->
275  *             a) file does not exist -> return 0, LastError = ERROR_FILE_NOT_FOUND
276  *             b) file does exist     -> set the short filename.
277  * - trailing slashes are reproduced in the short name, even if the
278  *   file is not a directory
279  * - the absolute/relative path of the short name is reproduced like found
280  *   in the long name
281  * - longpath and shortpath may have the same address
282  * Peter Ganten, 1999
283  */
284 DWORD WINAPI GetShortPathNameW( LPCWSTR longpath, LPWSTR shortpath, DWORD shortlen )
285 {
286     WCHAR               tmpshortpath[MAX_PATHNAME_LEN];
287     LPCWSTR             p;
288     DWORD               sp = 0, lp = 0;
289     DWORD               tmplen;
290     BOOL                unixabsolute = (longpath[0] == '/');
291     WIN32_FIND_DATAW    wfd;
292     HANDLE              goit;
293     UNICODE_STRING      ustr;
294     WCHAR               ustr_buf[8+1+3+1];
295
296     TRACE("%s\n", debugstr_w(longpath));
297
298     if (!longpath)
299     {
300         SetLastError(ERROR_INVALID_PARAMETER);
301         return 0;
302     }
303     if (!longpath[0])
304     {
305         SetLastError(ERROR_BAD_PATHNAME);
306         return 0;
307     }
308
309     /* check for drive letter */
310     if (!unixabsolute && longpath[1] == ':' )
311     {
312         tmpshortpath[0] = longpath[0];
313         tmpshortpath[1] = ':';
314         sp = lp = 2;
315     }
316
317     ustr.Buffer = ustr_buf;
318     ustr.Length = 0;
319     ustr.MaximumLength = sizeof(ustr_buf);
320
321     while (longpath[lp])
322     {
323         /* check for path delimiters and reproduce them */
324         if (longpath[lp] == '\\' || longpath[lp] == '/')
325         {
326             if (!sp || tmpshortpath[sp-1] != '\\')
327             {
328                 /* strip double "\\" */
329                 tmpshortpath[sp] = '\\';
330                 sp++;
331             }
332             tmpshortpath[sp] = 0; /* terminate string */
333             lp++;
334             continue;
335         }
336
337         for (p = longpath + lp; *p && *p != '/' && *p != '\\'; p++);
338         tmplen = p - (longpath + lp);
339         lstrcpynW(tmpshortpath + sp, longpath + lp, tmplen + 1);
340         /* Check, if the current element is a valid dos name */
341         if (tmplen <= 8+1+3+1)
342         {
343             BOOLEAN spaces;
344             memcpy(ustr_buf, longpath + lp, tmplen * sizeof(WCHAR));
345             ustr_buf[tmplen] = '\0';
346             ustr.Length = tmplen * sizeof(WCHAR);
347             if (RtlIsNameLegalDOS8Dot3(&ustr, NULL, &spaces) && !spaces)
348             {
349                 sp += tmplen;
350                 lp += tmplen;
351                 continue;
352             }
353         }
354
355         /* Check if the file exists and use the existing short file name */
356         goit = FindFirstFileW(tmpshortpath, &wfd);
357         if (goit == INVALID_HANDLE_VALUE) goto notfound;
358         FindClose(goit);
359         strcpyW(tmpshortpath + sp, wfd.cAlternateFileName);
360         sp += strlenW(tmpshortpath + sp);
361         lp += tmplen;
362     }
363     tmpshortpath[sp] = 0;
364
365     tmplen = strlenW(tmpshortpath) + 1;
366     if (tmplen <= shortlen)
367     {
368         strcpyW(shortpath, tmpshortpath);
369         TRACE("returning %s\n", debugstr_w(shortpath));
370         tmplen--; /* length without 0 */
371     }
372
373     return tmplen;
374
375  notfound:
376     TRACE("not found!\n" );
377     SetLastError ( ERROR_FILE_NOT_FOUND );
378     return 0;
379 }
380
381 /***********************************************************************
382  *           GetShortPathNameA   (KERNEL32.@)
383  */
384 DWORD WINAPI GetShortPathNameA( LPCSTR longpath, LPSTR shortpath, DWORD shortlen )
385 {
386     UNICODE_STRING longpathW;
387     WCHAR shortpathW[MAX_PATH];
388     DWORD ret, retW;
389
390     if (!longpath)
391     {
392         SetLastError(ERROR_INVALID_PARAMETER);
393         return 0;
394     }
395
396     TRACE("%s\n", debugstr_a(longpath));
397
398     if (!RtlCreateUnicodeStringFromAsciiz(&longpathW, longpath))
399     {
400         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
401         return 0;
402     }
403
404     retW = GetShortPathNameW(longpathW.Buffer, shortpathW, MAX_PATH);
405
406     if (!retW)
407         ret = 0;
408     else if (retW > MAX_PATH)
409     {
410         SetLastError(ERROR_FILENAME_EXCED_RANGE);
411         ret = 0;
412     }
413     else
414     {
415         ret = WideCharToMultiByte(CP_ACP, 0, shortpathW, -1, NULL, 0, NULL, NULL);
416         if (ret <= shortlen)
417         {
418             WideCharToMultiByte(CP_ACP, 0, shortpathW, -1, shortpath, shortlen, NULL, NULL);
419             ret--; /* length without 0 */
420         }
421     }
422
423     RtlFreeUnicodeString(&longpathW);
424     return ret;
425 }
426
427
428 /***********************************************************************
429  *           GetTempPathA   (KERNEL32.@)
430  */
431 UINT WINAPI GetTempPathA( UINT count, LPSTR path )
432 {
433     WCHAR pathW[MAX_PATH];
434     UINT ret;
435
436     ret = GetTempPathW(MAX_PATH, pathW);
437
438     if (!ret)
439         return 0;
440
441     if (ret > MAX_PATH)
442     {
443         SetLastError(ERROR_FILENAME_EXCED_RANGE);
444         return 0;
445     }
446
447     ret = WideCharToMultiByte(CP_ACP, 0, pathW, -1, NULL, 0, NULL, NULL);
448     if (ret <= count)
449     {
450         WideCharToMultiByte(CP_ACP, 0, pathW, -1, path, count, NULL, NULL);
451         ret--; /* length without 0 */
452     }
453     return ret;
454 }
455
456
457 /***********************************************************************
458  *           GetTempPathW   (KERNEL32.@)
459  */
460 UINT WINAPI GetTempPathW( UINT count, LPWSTR path )
461 {
462     static const WCHAR tmp[]  = { 'T', 'M', 'P', 0 };
463     static const WCHAR temp[] = { 'T', 'E', 'M', 'P', 0 };
464     WCHAR tmp_path[MAX_PATH];
465     UINT ret;
466
467     TRACE("%u,%p\n", count, path);
468
469     if (!(ret = GetEnvironmentVariableW( tmp, tmp_path, MAX_PATH )))
470         if (!(ret = GetEnvironmentVariableW( temp, tmp_path, MAX_PATH )))
471             if (!(ret = GetCurrentDirectoryW( MAX_PATH, tmp_path )))
472                 return 0;
473
474     if (ret > MAX_PATH)
475     {
476         SetLastError(ERROR_FILENAME_EXCED_RANGE);
477         return 0;
478     }
479
480     ret = GetFullPathNameW(tmp_path, MAX_PATH, tmp_path, NULL);
481     if (!ret) return 0;
482
483     if (ret > MAX_PATH - 2)
484     {
485         SetLastError(ERROR_FILENAME_EXCED_RANGE);
486         return 0;
487     }
488
489     if (tmp_path[ret-1] != '\\')
490     {
491         tmp_path[ret++] = '\\';
492         tmp_path[ret]   = '\0';
493     }
494
495     ret++; /* add space for terminating 0 */
496
497     if (count)
498     {
499         lstrcpynW(path, tmp_path, count);
500         if (count >= ret)
501             ret--; /* return length without 0 */
502         else if (count < 4)
503             path[0] = 0; /* avoid returning ambiguous "X:" */
504     }
505
506     TRACE("returning %u, %s\n", ret, debugstr_w(path));
507     return ret;
508 }
509
510
511 /***********************************************************************
512  *           contains_pathW
513  *
514  * Check if the file name contains a path; helper for SearchPathW.
515  * A relative path is not considered a path unless it starts with ./ or ../
516  */
517 inline static BOOL contains_pathW (LPCWSTR name)
518 {
519     if (RtlDetermineDosPathNameType_U( name ) != RELATIVE_PATH) return TRUE;
520     if (name[0] != '.') return FALSE;
521     if (name[1] == '/' || name[1] == '\\') return TRUE;
522     return (name[1] == '.' && (name[2] == '/' || name[2] == '\\'));
523 }
524
525
526 /***********************************************************************
527  * SearchPathW [KERNEL32.@]
528  *
529  * Searches for a specified file in the search path.
530  *
531  * PARAMS
532  *    path      [I] Path to search
533  *    name      [I] Filename to search for.
534  *    ext       [I] File extension to append to file name. The first
535  *                  character must be a period. This parameter is
536  *                  specified only if the filename given does not
537  *                  contain an extension.
538  *    buflen    [I] size of buffer, in characters
539  *    buffer    [O] buffer for found filename
540  *    lastpart  [O] address of pointer to last used character in
541  *                  buffer (the final '\')
542  *
543  * RETURNS
544  *    Success: length of string copied into buffer, not including
545  *             terminating null character. If the filename found is
546  *             longer than the length of the buffer, the length of the
547  *             filename is returned.
548  *    Failure: Zero
549  *
550  * NOTES
551  *    If the file is not found, calls SetLastError(ERROR_FILE_NOT_FOUND)
552  *    (tested on NT 4.0)
553  */
554 DWORD WINAPI SearchPathW( LPCWSTR path, LPCWSTR name, LPCWSTR ext, DWORD buflen,
555                           LPWSTR buffer, LPWSTR *lastpart )
556 {
557     DWORD ret = 0;
558
559     /* If the name contains an explicit path, ignore the path */
560
561     if (contains_pathW(name))
562     {
563         /* try first without extension */
564         if (RtlDoesFileExists_U( name ))
565             return GetFullPathNameW( name, buflen, buffer, lastpart );
566
567         if (ext)
568         {
569             LPCWSTR p = strrchrW( name, '.' );
570             if (p && !strchrW( p, '/' ) && !strchrW( p, '\\' ))
571                 ext = NULL;  /* Ignore the specified extension */
572         }
573
574         /* Allocate a buffer for the file name and extension */
575         if (ext)
576         {
577             LPWSTR tmp;
578             DWORD len = strlenW(name) + strlenW(ext);
579
580             if (!(tmp = HeapAlloc( GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) )))
581             {
582                 SetLastError( ERROR_OUTOFMEMORY );
583                 return 0;
584             }
585             strcpyW( tmp, name );
586             strcatW( tmp, ext );
587             if (RtlDoesFileExists_U( tmp ))
588                 ret = GetFullPathNameW( tmp, buflen, buffer, lastpart );
589             HeapFree( GetProcessHeap(), 0, tmp );
590         }
591     }
592     else if (path && path[0])  /* search in the specified path */
593     {
594         ret = RtlDosSearchPath_U( path, name, ext, buflen * sizeof(WCHAR),
595                                   buffer, lastpart ) / sizeof(WCHAR);
596     }
597     else  /* search in the default path */
598     {
599         WCHAR *dll_path = MODULE_get_dll_load_path( NULL );
600
601         if (dll_path)
602         {
603             ret = RtlDosSearchPath_U( dll_path, name, ext, buflen * sizeof(WCHAR),
604                                       buffer, lastpart ) / sizeof(WCHAR);
605             HeapFree( GetProcessHeap(), 0, dll_path );
606         }
607         else
608         {
609             SetLastError( ERROR_OUTOFMEMORY );
610             return 0;
611         }
612     }
613
614     if (!ret) SetLastError( ERROR_FILE_NOT_FOUND );
615     else TRACE( "found %s\n", debugstr_w(buffer) );
616     return ret;
617 }
618
619
620 /***********************************************************************
621  *           SearchPathA   (KERNEL32.@)
622  */
623 DWORD WINAPI SearchPathA( LPCSTR path, LPCSTR name, LPCSTR ext,
624                           DWORD buflen, LPSTR buffer, LPSTR *lastpart )
625 {
626     UNICODE_STRING pathW, nameW, extW;
627     WCHAR bufferW[MAX_PATH];
628     DWORD ret, retW;
629
630     if (path) RtlCreateUnicodeStringFromAsciiz(&pathW, path);
631     else pathW.Buffer = NULL;
632     if (name) RtlCreateUnicodeStringFromAsciiz(&nameW, name);
633     else nameW.Buffer = NULL;
634     if (ext) RtlCreateUnicodeStringFromAsciiz(&extW, ext);
635     else extW.Buffer = NULL;
636
637     retW = SearchPathW(pathW.Buffer, nameW.Buffer, extW.Buffer, MAX_PATH, bufferW, NULL);
638
639     if (!retW)
640         ret = 0;
641     else if (retW > MAX_PATH)
642     {
643         SetLastError(ERROR_FILENAME_EXCED_RANGE);
644         ret = 0;
645     }
646     else
647     {
648         ret = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
649         if (buflen >= ret)
650         {
651             WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buffer, buflen, NULL, NULL);
652             ret--; /* length without 0 */
653             if (lastpart) *lastpart = strrchr(buffer, '\\') + 1;
654         }
655     }
656
657     RtlFreeUnicodeString(&pathW);
658     RtlFreeUnicodeString(&nameW);
659     RtlFreeUnicodeString(&extW);
660     return ret;
661 }