Remove redundant check.
[wine] / dlls / ntdll / path.c
1 /*
2  * Ntdll path functions
3  *
4  * Copyright 2002, 2003, 2004 Alexandre Julliard
5  * Copyright 2003 Eric Pouech
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21
22 #include "config.h"
23 #include "wine/port.h"
24
25 #include <stdarg.h>
26 #include <sys/types.h>
27 #include <errno.h>
28 #ifdef HAVE_SYS_STAT_H
29 # include <sys/stat.h>
30 #endif
31 #ifdef HAVE_UNISTD_H
32 # include <unistd.h>
33 #endif
34
35 #include "windef.h"
36 #include "winioctl.h"
37 #include "wine/unicode.h"
38 #include "wine/debug.h"
39 #include "wine/library.h"
40 #include "thread.h"
41 #include "ntdll_misc.h"
42
43 WINE_DEFAULT_DEBUG_CHANNEL(file);
44
45 static const WCHAR DeviceRootW[] = {'\\','\\','.','\\',0};
46 static const WCHAR NTDosPrefixW[] = {'\\','?','?','\\',0};
47 static const WCHAR UncPfxW[] = {'U','N','C','\\',0};
48
49 #define IS_SEPARATOR(ch)  ((ch) == '\\' || (ch) == '/')
50
51 #define MAX_DOS_DRIVES 26
52
53 struct drive_info
54 {
55     dev_t dev;
56     ino_t ino;
57 };
58
59 /***********************************************************************
60  *           get_drives_info
61  *
62  * Retrieve device/inode number for all the drives. Helper for find_drive_root.
63  */
64 static inline int get_drives_info( struct drive_info info[MAX_DOS_DRIVES] )
65 {
66     const char *config_dir = wine_get_config_dir();
67     char *buffer, *p;
68     struct stat st;
69     int i, ret;
70
71     buffer = RtlAllocateHeap( GetProcessHeap(), 0, strlen(config_dir) + sizeof("/dosdevices/a:") );
72     if (!buffer) return 0;
73     strcpy( buffer, config_dir );
74     strcat( buffer, "/dosdevices/a:" );
75     p = buffer + strlen(buffer) - 2;
76
77     for (i = ret = 0; i < MAX_DOS_DRIVES; i++)
78     {
79         *p = 'a' + i;
80         if (!stat( buffer, &st ))
81         {
82             info[i].dev = st.st_dev;
83             info[i].ino = st.st_ino;
84             ret++;
85         }
86         else
87         {
88             info[i].dev = 0;
89             info[i].ino = 0;
90         }
91     }
92     RtlFreeHeap( GetProcessHeap(), 0, buffer );
93     return ret;
94 }
95
96
97 /***********************************************************************
98  *           remove_last_componentA
99  *
100  * Remove the last component of the path. Helper for find_drive_rootA.
101  */
102 static inline unsigned int remove_last_componentA( const char *path, unsigned int len )
103 {
104     int level = 0;
105
106     while (level < 1)
107     {
108         /* find start of the last path component */
109         unsigned int prev = len;
110         if (prev <= 1) break;  /* reached root */
111         while (prev > 1 && path[prev - 1] != '/') prev--;
112         /* does removing it take us up a level? */
113         if (len - prev != 1 || path[prev] != '.')  /* not '.' */
114         {
115             if (len - prev == 2 && path[prev] == '.' && path[prev+1] == '.')  /* is it '..'? */
116                 level--;
117             else
118                 level++;
119         }
120         /* strip off trailing slashes */
121         while (prev > 1 && path[prev - 1] == '/') prev--;
122         len = prev;
123     }
124     return len;
125 }
126
127
128 /***********************************************************************
129  *           find_drive_rootA
130  *
131  * Find a drive for which the root matches the beginning of the given path.
132  * This can be used to translate a Unix path into a drive + DOS path.
133  * Return value is the drive, or -1 on error. On success, ppath is modified
134  * to point to the beginning of the DOS path.
135  */
136 static NTSTATUS find_drive_rootA( LPCSTR *ppath, unsigned int len, int *drive_ret )
137 {
138     /* Starting with the full path, check if the device and inode match any of
139      * the wine 'drives'. If not then remove the last path component and try
140      * again. If the last component was a '..' then skip a normal component
141      * since it's a directory that's ascended back out of.
142      */
143     int drive;
144     char *buffer;
145     const char *path = *ppath;
146     struct stat st;
147     struct drive_info info[MAX_DOS_DRIVES];
148
149     /* get device and inode of all drives */
150     if (!get_drives_info( info )) return STATUS_OBJECT_PATH_NOT_FOUND;
151
152     /* strip off trailing slashes */
153     while (len > 1 && path[len - 1] == '/') len--;
154
155     /* make a copy of the path */
156     if (!(buffer = RtlAllocateHeap( GetProcessHeap(), 0, len + 1 ))) return STATUS_NO_MEMORY;
157     memcpy( buffer, path, len );
158     buffer[len] = 0;
159
160     for (;;)
161     {
162         if (!stat( buffer, &st ) && S_ISDIR( st.st_mode ))
163         {
164             /* Find the drive */
165             for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
166             {
167                 if ((info[drive].dev == st.st_dev) && (info[drive].ino == st.st_ino))
168                 {
169                     if (len == 1) len = 0;  /* preserve root slash in returned path */
170                     TRACE( "%s -> drive %c:, root=%s, name=%s\n",
171                            debugstr_a(path), 'A' + drive, debugstr_a(buffer), debugstr_a(path + len));
172                     *ppath += len;
173                     *drive_ret = drive;
174                     RtlFreeHeap( GetProcessHeap(), 0, buffer );
175                     return STATUS_SUCCESS;
176                 }
177             }
178         }
179         if (len <= 1) break;  /* reached root */
180         len = remove_last_componentA( buffer, len );
181         buffer[len] = 0;
182     }
183     RtlFreeHeap( GetProcessHeap(), 0, buffer );
184     return STATUS_OBJECT_PATH_NOT_FOUND;
185 }
186
187
188 /***********************************************************************
189  *           remove_last_componentW
190  *
191  * Remove the last component of the path. Helper for find_drive_rootW.
192  */
193 static inline int remove_last_componentW( const WCHAR *path, int len )
194 {
195     int level = 0;
196
197     while (level < 1)
198     {
199         /* find start of the last path component */
200         int prev = len;
201         if (prev <= 1) break;  /* reached root */
202         while (prev > 1 && !IS_SEPARATOR(path[prev - 1])) prev--;
203         /* does removing it take us up a level? */
204         if (len - prev != 1 || path[prev] != '.')  /* not '.' */
205         {
206             if (len - prev == 2 && path[prev] == '.' && path[prev+1] == '.')  /* is it '..'? */
207                 level--;
208             else
209                 level++;
210         }
211         /* strip off trailing slashes */
212         while (prev > 1 && IS_SEPARATOR(path[prev - 1])) prev--;
213         len = prev;
214     }
215     return len;
216 }
217
218
219 /***********************************************************************
220  *           find_drive_rootW
221  *
222  * Find a drive for which the root matches the beginning of the given path.
223  * This can be used to translate a Unix path into a drive + DOS path.
224  * Return value is the drive, or -1 on error. On success, ppath is modified
225  * to point to the beginning of the DOS path.
226  */
227 static int find_drive_rootW( LPCWSTR *ppath )
228 {
229     /* Starting with the full path, check if the device and inode match any of
230      * the wine 'drives'. If not then remove the last path component and try
231      * again. If the last component was a '..' then skip a normal component
232      * since it's a directory that's ascended back out of.
233      */
234     int drive, lenA, lenW;
235     char *buffer, *p;
236     const WCHAR *path = *ppath;
237     struct stat st;
238     struct drive_info info[MAX_DOS_DRIVES];
239
240     /* get device and inode of all drives */
241     if (!get_drives_info( info )) return -1;
242
243     /* strip off trailing slashes */
244     lenW = strlenW(path);
245     while (lenW > 1 && IS_SEPARATOR(path[lenW - 1])) lenW--;
246
247     /* convert path to Unix encoding */
248     lenA = ntdll_wcstoumbs( 0, path, lenW, NULL, 0, NULL, NULL );
249     if (!(buffer = RtlAllocateHeap( GetProcessHeap(), 0, lenA + 1 ))) return -1;
250     lenA = ntdll_wcstoumbs( 0, path, lenW, buffer, lenA, NULL, NULL );
251     buffer[lenA] = 0;
252     for (p = buffer; *p; p++) if (*p == '\\') *p = '/';
253
254     for (;;)
255     {
256         if (!stat( buffer, &st ) && S_ISDIR( st.st_mode ))
257         {
258             /* Find the drive */
259             for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
260             {
261                 if ((info[drive].dev == st.st_dev) && (info[drive].ino == st.st_ino))
262                 {
263                     if (lenW == 1) lenW = 0;  /* preserve root slash in returned path */
264                     TRACE( "%s -> drive %c:, root=%s, name=%s\n",
265                            debugstr_w(path), 'A' + drive, debugstr_a(buffer), debugstr_w(path + lenW));
266                     *ppath += lenW;
267                     RtlFreeHeap( GetProcessHeap(), 0, buffer );
268                     return drive;
269                 }
270             }
271         }
272         if (lenW <= 1) break;  /* reached root */
273         lenW = remove_last_componentW( path, lenW );
274
275         /* we only need the new length, buffer already contains the converted string */
276         lenA = ntdll_wcstoumbs( 0, path, lenW, NULL, 0, NULL, NULL );
277         buffer[lenA] = 0;
278     }
279     RtlFreeHeap( GetProcessHeap(), 0, buffer );
280     return -1;
281 }
282
283
284 /***********************************************************************
285  *             RtlDetermineDosPathNameType_U   (NTDLL.@)
286  */
287 DOS_PATHNAME_TYPE WINAPI RtlDetermineDosPathNameType_U( PCWSTR path )
288 {
289     if (IS_SEPARATOR(path[0]))
290     {
291         if (!IS_SEPARATOR(path[1])) return ABSOLUTE_PATH;       /* "/foo" */
292         if (path[2] != '.') return UNC_PATH;                    /* "//foo" */
293         if (IS_SEPARATOR(path[3])) return DEVICE_PATH;          /* "//./foo" */
294         if (path[3]) return UNC_PATH;                           /* "//.foo" */
295         return UNC_DOT_PATH;                                    /* "//." */
296     }
297     else
298     {
299         if (!path[0] || path[1] != ':') return RELATIVE_PATH;   /* "foo" */
300         if (IS_SEPARATOR(path[2])) return ABSOLUTE_DRIVE_PATH;  /* "c:/foo" */
301         return RELATIVE_DRIVE_PATH;                             /* "c:foo" */
302     }
303 }
304
305 /***********************************************************************
306  *             RtlIsDosDeviceName_U   (NTDLL.@)
307  *
308  * Check if the given DOS path contains a DOS device name.
309  *
310  * Returns the length of the device name in the low word and its
311  * position in the high word (both in bytes, not WCHARs), or 0 if no
312  * device name is found.
313  */
314 ULONG WINAPI RtlIsDosDeviceName_U( PCWSTR dos_name )
315 {
316     static const WCHAR consoleW[] = {'\\','\\','.','\\','C','O','N',0};
317     static const WCHAR auxW[3] = {'A','U','X'};
318     static const WCHAR comW[3] = {'C','O','M'};
319     static const WCHAR conW[3] = {'C','O','N'};
320     static const WCHAR lptW[3] = {'L','P','T'};
321     static const WCHAR nulW[3] = {'N','U','L'};
322     static const WCHAR prnW[3] = {'P','R','N'};
323
324     const WCHAR *start, *end, *p;
325
326     switch(RtlDetermineDosPathNameType_U( dos_name ))
327     {
328     case INVALID_PATH:
329     case UNC_PATH:
330         return 0;
331     case DEVICE_PATH:
332         if (!strcmpiW( dos_name, consoleW ))
333             return MAKELONG( sizeof(conW), 4 * sizeof(WCHAR) );  /* 4 is length of \\.\ prefix */
334         return 0;
335     default:
336         break;
337     }
338
339     end = dos_name + strlenW(dos_name) - 1;
340     if (end >= dos_name && *end == ':') end--;  /* remove trailing ':' */
341
342     /* find start of file name */
343     for (start = end; start >= dos_name; start--)
344     {
345         if (IS_SEPARATOR(start[0])) break;
346         /* check for ':' but ignore if before extension (for things like NUL:.txt) */
347         if (start[0] == ':' && start[1] != '.') break;
348     }
349     start++;
350
351     /* remove extension */
352     if ((p = strchrW( start, '.' )))
353     {
354         end = p - 1;
355         if (end >= dos_name && *end == ':') end--;  /* remove trailing ':' before extension */
356     }
357     else
358     {
359         /* no extension, remove trailing spaces */
360         while (end >= dos_name && *end == ' ') end--;
361     }
362
363     /* now we have a potential device name between start and end, check it */
364     switch(end - start + 1)
365     {
366     case 3:
367         if (strncmpiW( start, auxW, 3 ) &&
368             strncmpiW( start, conW, 3 ) &&
369             strncmpiW( start, nulW, 3 ) &&
370             strncmpiW( start, prnW, 3 )) break;
371         return MAKELONG( 3 * sizeof(WCHAR), (start - dos_name) * sizeof(WCHAR) );
372     case 4:
373         if (strncmpiW( start, comW, 3 ) && strncmpiW( start, lptW, 3 )) break;
374         if (*end <= '0' || *end > '9') break;
375         return MAKELONG( 4 * sizeof(WCHAR), (start - dos_name) * sizeof(WCHAR) );
376     default:  /* can't match anything */
377         break;
378     }
379     return 0;
380 }
381
382
383 /**************************************************************************
384  *                 RtlDosPathNameToNtPathName_U         [NTDLL.@]
385  *
386  * dos_path: a DOS path name (fully qualified or not)
387  * ntpath:   pointer to a UNICODE_STRING to hold the converted
388  *           path name
389  * file_part:will point (in ntpath) to the file part in the path
390  * cd:       directory reference (optional)
391  *
392  * FIXME:
393  *      + fill the cd structure
394  */
395 BOOLEAN  WINAPI RtlDosPathNameToNtPathName_U(PCWSTR dos_path,
396                                              PUNICODE_STRING ntpath,
397                                              PWSTR* file_part,
398                                              CURDIR* cd)
399 {
400     static const WCHAR LongFileNamePfxW[4] = {'\\','\\','?','\\'};
401     ULONG sz, offset;
402     WCHAR local[MAX_PATH];
403     LPWSTR ptr;
404
405     TRACE("(%s,%p,%p,%p)\n",
406           debugstr_w(dos_path), ntpath, file_part, cd);
407
408     if (cd)
409     {
410         FIXME("Unsupported parameter\n");
411         memset(cd, 0, sizeof(*cd));
412     }
413
414     if (!dos_path || !*dos_path) return FALSE;
415
416     if (!strncmpW(dos_path, LongFileNamePfxW, 4))
417     {
418         ntpath->Length = strlenW(dos_path) * sizeof(WCHAR);
419         ntpath->MaximumLength = ntpath->Length + sizeof(WCHAR);
420         ntpath->Buffer = RtlAllocateHeap(GetProcessHeap(), 0, ntpath->MaximumLength);
421         if (!ntpath->Buffer) return FALSE;
422         memcpy( ntpath->Buffer, dos_path, ntpath->MaximumLength );
423         ntpath->Buffer[1] = '?';  /* change \\?\ to \??\ */
424         return TRUE;
425     }
426
427     ptr = local;
428     sz = RtlGetFullPathName_U(dos_path, sizeof(local), ptr, file_part);
429     if (sz == 0) return FALSE;
430     if (sz > sizeof(local))
431     {
432         if (!(ptr = RtlAllocateHeap(GetProcessHeap(), 0, sz))) return FALSE;
433         sz = RtlGetFullPathName_U(dos_path, sz, ptr, file_part);
434     }
435
436     ntpath->MaximumLength = sz + (4 /* unc\ */ + 4 /* \??\ */) * sizeof(WCHAR);
437     ntpath->Buffer = RtlAllocateHeap(GetProcessHeap(), 0, ntpath->MaximumLength);
438     if (!ntpath->Buffer)
439     {
440         if (ptr != local) RtlFreeHeap(GetProcessHeap(), 0, ptr);
441         return FALSE;
442     }
443
444     strcpyW(ntpath->Buffer, NTDosPrefixW);
445     switch (RtlDetermineDosPathNameType_U(ptr))
446     {
447     case UNC_PATH: /* \\foo */
448         offset = 2;
449         strcatW(ntpath->Buffer, UncPfxW);
450         break;
451     case DEVICE_PATH: /* \\.\foo */
452         offset = 4;
453         break;
454     default:
455         offset = 0;
456         break;
457     }
458
459     strcatW(ntpath->Buffer, ptr + offset);
460     ntpath->Length = strlenW(ntpath->Buffer) * sizeof(WCHAR);
461
462     if (file_part && *file_part)
463         *file_part = ntpath->Buffer + ntpath->Length / sizeof(WCHAR) - strlenW(*file_part);
464
465     /* FIXME: cd filling */
466
467     if (ptr != local) RtlFreeHeap(GetProcessHeap(), 0, ptr);
468     return TRUE;
469 }
470
471 /******************************************************************
472  *              RtlDosSearchPath_U
473  *
474  * Searchs a file of name 'name' into a ';' separated list of paths
475  * (stored in paths)
476  * Doesn't seem to search elsewhere than the paths list
477  * Stores the result in buffer (file_part will point to the position
478  * of the file name in the buffer)
479  * FIXME:
480  * - how long shall the paths be ??? (MAX_PATH or larger with \\?\ constructs ???)
481  */
482 ULONG WINAPI RtlDosSearchPath_U(LPCWSTR paths, LPCWSTR search, LPCWSTR ext, 
483                                 ULONG buffer_size, LPWSTR buffer, 
484                                 LPWSTR* file_part)
485 {
486     DOS_PATHNAME_TYPE type = RtlDetermineDosPathNameType_U(search);
487     ULONG len = 0;
488
489     if (type == RELATIVE_PATH)
490     {
491         ULONG allocated = 0, needed, filelen;
492         WCHAR *name = NULL;
493
494         filelen = 1 /* for \ */ + strlenW(search) + 1 /* \0 */;
495
496         /* Windows only checks for '.' without worrying about path components */
497         if (strchrW( search, '.' )) ext = NULL;
498         if (ext != NULL) filelen += strlenW(ext);
499
500         while (*paths)
501         {
502             LPCWSTR ptr;
503
504             for (needed = 0, ptr = paths; *ptr != 0 && *ptr++ != ';'; needed++);
505             if (needed + filelen > allocated)
506             {
507                 if (!name) name = RtlAllocateHeap(GetProcessHeap(), 0,
508                                                   (needed + filelen) * sizeof(WCHAR));
509                 else
510                 {
511                     WCHAR *newname = RtlReAllocateHeap(GetProcessHeap(), 0, name,
512                                                        (needed + filelen) * sizeof(WCHAR));
513                     if (!newname) RtlFreeHeap(GetProcessHeap(), 0, name);
514                     name = newname;
515                 }
516                 if (!name) return 0;
517                 allocated = needed + filelen;
518             }
519             memmove(name, paths, needed * sizeof(WCHAR));
520             /* append '\\' if none is present */
521             if (needed > 0 && name[needed - 1] != '\\') name[needed++] = '\\';
522             strcpyW(&name[needed], search);
523             if (ext) strcatW(&name[needed], ext);
524             if (RtlDoesFileExists_U(name))
525             {
526                 len = RtlGetFullPathName_U(name, buffer_size, buffer, file_part);
527                 break;
528             }
529             paths = ptr;
530         }
531         RtlFreeHeap(GetProcessHeap(), 0, name);
532     }
533     else if (RtlDoesFileExists_U(search))
534     {
535         len = RtlGetFullPathName_U(search, buffer_size, buffer, file_part);
536     }
537
538     return len;
539 }
540
541
542 /******************************************************************
543  *              collapse_path
544  *
545  * Helper for RtlGetFullPathName_U.
546  * Get rid of . and .. components in the path.
547  */
548 static inline void collapse_path( WCHAR *path, UINT mark )
549 {
550     WCHAR *p, *next;
551
552     /* convert every / into a \ */
553     for (p = path; *p; p++) if (*p == '/') *p = '\\';
554
555     /* collapse duplicate backslashes */
556     next = path + max( 1, mark );
557     for (p = next; *p; p++) if (*p != '\\' || next[-1] != '\\') *next++ = *p;
558     *next = 0;
559
560     p = path + mark;
561     while (*p)
562     {
563         if (*p == '.')
564         {
565             switch(p[1])
566             {
567             case '\\': /* .\ component */
568                 next = p + 2;
569                 memmove( p, next, (strlenW(next) + 1) * sizeof(WCHAR) );
570                 continue;
571             case 0:  /* final . */
572                 if (p > path + mark) p--;
573                 *p = 0;
574                 continue;
575             case '.':
576                 if (p[2] == '\\')  /* ..\ component */
577                 {
578                     next = p + 3;
579                     if (p > path + mark)
580                     {
581                         p--;
582                         while (p > path + mark && p[-1] != '\\') p--;
583                     }
584                     memmove( p, next, (strlenW(next) + 1) * sizeof(WCHAR) );
585                     continue;
586                 }
587                 else if (!p[2])  /* final .. */
588                 {
589                     if (p > path + mark)
590                     {
591                         p--;
592                         while (p > path + mark && p[-1] != '\\') p--;
593                         if (p > path + mark) p--;
594                     }
595                     *p = 0;
596                     continue;
597                 }
598                 break;
599             }
600         }
601         /* skip to the next component */
602         while (*p && *p != '\\') p++;
603         if (*p == '\\')
604         {
605             /* remove last dot in previous dir name */
606             if (p > path + mark && p[-1] == '.') memmove( p-1, p, (strlenW(p) + 1) * sizeof(WCHAR) );
607             else p++;
608         }
609     }
610
611     /* remove trailing spaces and dots (yes, Windows really does that, don't ask) */
612     while (p > path + mark && (p[-1] == ' ' || p[-1] == '.')) p--;
613     *p = 0;
614 }
615
616
617 /******************************************************************
618  *              skip_unc_prefix
619  *
620  * Skip the \\share\dir\ part of a file name. Helper for RtlGetFullPathName_U.
621  */
622 static const WCHAR *skip_unc_prefix( const WCHAR *ptr )
623 {
624     ptr += 2;
625     while (*ptr && !IS_SEPARATOR(*ptr)) ptr++;  /* share name */
626     while (IS_SEPARATOR(*ptr)) ptr++;
627     while (*ptr && !IS_SEPARATOR(*ptr)) ptr++;  /* dir name */
628     while (IS_SEPARATOR(*ptr)) ptr++;
629     return ptr;
630 }
631
632
633 /******************************************************************
634  *              get_full_path_helper
635  *
636  * Helper for RtlGetFullPathName_U
637  * Note: name and buffer are allowed to point to the same memory spot
638  */
639 static ULONG get_full_path_helper(LPCWSTR name, LPWSTR buffer, ULONG size)
640 {
641     ULONG                       reqsize = 0, mark = 0, dep = 0, deplen;
642     DOS_PATHNAME_TYPE           type;
643     LPWSTR                      ins_str = NULL;
644     LPCWSTR                     ptr;
645     const UNICODE_STRING*       cd;
646     WCHAR                       tmp[4];
647
648     /* return error if name only consists of spaces */
649     for (ptr = name; *ptr; ptr++) if (*ptr != ' ') break;
650     if (!*ptr) return 0;
651
652     RtlAcquirePebLock();
653
654     if (NtCurrentTeb()->Tib.SubSystemTib)  /* FIXME: hack */
655         cd = &((WIN16_SUBSYSTEM_TIB *)NtCurrentTeb()->Tib.SubSystemTib)->curdir.DosPath;
656     else
657         cd = &NtCurrentTeb()->Peb->ProcessParameters->CurrentDirectory.DosPath;
658
659     switch (type = RtlDetermineDosPathNameType_U(name))
660     {
661     case UNC_PATH:              /* \\foo   */
662         ptr = skip_unc_prefix( name );
663         mark = (ptr - name);
664         break;
665
666     case DEVICE_PATH:           /* \\.\foo */
667         mark = 4;
668         break;
669
670     case ABSOLUTE_DRIVE_PATH:   /* c:\foo  */
671         reqsize = sizeof(WCHAR);
672         tmp[0] = toupperW(name[0]);
673         ins_str = tmp;
674         dep = 1;
675         mark = 3;
676         break;
677
678     case RELATIVE_DRIVE_PATH:   /* c:foo   */
679         dep = 2;
680         if (toupperW(name[0]) != toupperW(cd->Buffer[0]) || cd->Buffer[1] != ':')
681         {
682             UNICODE_STRING      var, val;
683
684             tmp[0] = '=';
685             tmp[1] = name[0];
686             tmp[2] = ':';
687             tmp[3] = '\0';
688             var.Length = 3 * sizeof(WCHAR);
689             var.MaximumLength = 4 * sizeof(WCHAR);
690             var.Buffer = tmp;
691             val.Length = 0;
692             val.MaximumLength = size;
693             val.Buffer = RtlAllocateHeap(GetProcessHeap(), 0, size);
694
695             switch (RtlQueryEnvironmentVariable_U(NULL, &var, &val))
696             {
697             case STATUS_SUCCESS:
698                 /* FIXME: Win2k seems to check that the environment variable actually points 
699                  * to an existing directory. If not, root of the drive is used
700                  * (this seems also to be the only spot in RtlGetFullPathName that the 
701                  * existence of a part of a path is checked)
702                  */
703                 /* fall thru */
704             case STATUS_BUFFER_TOO_SMALL:
705                 reqsize = val.Length + sizeof(WCHAR); /* append trailing '\\' */
706                 val.Buffer[val.Length / sizeof(WCHAR)] = '\\';
707                 ins_str = val.Buffer;
708                 break;
709             case STATUS_VARIABLE_NOT_FOUND:
710                 reqsize = 3 * sizeof(WCHAR);
711                 tmp[0] = name[0];
712                 tmp[1] = ':';
713                 tmp[2] = '\\';
714                 ins_str = tmp;
715                 break;
716             default:
717                 ERR("Unsupported status code\n");
718                 break;
719             }
720             mark = 3;
721             break;
722         }
723         /* fall through */
724
725     case RELATIVE_PATH:         /* foo     */
726         reqsize = cd->Length;
727         ins_str = cd->Buffer;
728         if (cd->Buffer[1] != ':')
729         {
730             ptr = skip_unc_prefix( cd->Buffer );
731             mark = ptr - cd->Buffer;
732         }
733         else mark = 3;
734         break;
735
736     case ABSOLUTE_PATH:         /* \xxx    */
737         if (name[0] == '/')  /* may be a Unix path */
738         {
739             const WCHAR *ptr = name;
740             int drive = find_drive_rootW( &ptr );
741             if (drive != -1)
742             {
743                 reqsize = 3 * sizeof(WCHAR);
744                 tmp[0] = 'A' + drive;
745                 tmp[1] = ':';
746                 tmp[2] = '\\';
747                 ins_str = tmp;
748                 mark = 3;
749                 dep = ptr - name;
750                 break;
751             }
752         }
753         if (cd->Buffer[1] == ':')
754         {
755             reqsize = 2 * sizeof(WCHAR);
756             tmp[0] = cd->Buffer[0];
757             tmp[1] = ':';
758             ins_str = tmp;
759             mark = 3;
760         }
761         else
762         {
763             ptr = skip_unc_prefix( cd->Buffer );
764             reqsize = (ptr - cd->Buffer) * sizeof(WCHAR);
765             mark = reqsize / sizeof(WCHAR);
766             ins_str = cd->Buffer;
767         }
768         break;
769
770     case UNC_DOT_PATH:         /* \\.     */
771         reqsize = 4 * sizeof(WCHAR);
772         dep = 3;
773         tmp[0] = '\\';
774         tmp[1] = '\\';
775         tmp[2] = '.';
776         tmp[3] = '\\';
777         ins_str = tmp;
778         mark = 4;
779         break;
780
781     case INVALID_PATH:
782         goto done;
783     }
784
785     /* enough space ? */
786     deplen = strlenW(name + dep) * sizeof(WCHAR);
787     if (reqsize + deplen + sizeof(WCHAR) > size)
788     {
789         /* not enough space, return need size (including terminating '\0') */
790         reqsize += deplen + sizeof(WCHAR);
791         goto done;
792     }
793
794     memmove(buffer + reqsize / sizeof(WCHAR), name + dep, deplen + sizeof(WCHAR));
795     if (reqsize) memcpy(buffer, ins_str, reqsize);
796     reqsize += deplen;
797
798     if (ins_str && ins_str != tmp && ins_str != cd->Buffer)
799         RtlFreeHeap(GetProcessHeap(), 0, ins_str);
800
801     collapse_path( buffer, mark );
802     reqsize = strlenW(buffer) * sizeof(WCHAR);
803
804 done:
805     RtlReleasePebLock();
806     return reqsize;
807 }
808
809 /******************************************************************
810  *              RtlGetFullPathName_U  (NTDLL.@)
811  *
812  * Returns the number of bytes written to buffer (not including the
813  * terminating NULL) if the function succeeds, or the required number of bytes
814  * (including the terminating NULL) if the buffer is too small.
815  *
816  * file_part will point to the filename part inside buffer (except if we use
817  * DOS device name, in which case file_in_buf is NULL)
818  *
819  */
820 DWORD WINAPI RtlGetFullPathName_U(const WCHAR* name, ULONG size, WCHAR* buffer,
821                                   WCHAR** file_part)
822 {
823     WCHAR*      ptr;
824     DWORD       dosdev;
825     DWORD       reqsize;
826
827     TRACE("(%s %lu %p %p)\n", debugstr_w(name), size, buffer, file_part);
828
829     if (!name || !*name) return 0;
830
831     if (file_part) *file_part = NULL;
832
833     /* check for DOS device name */
834     dosdev = RtlIsDosDeviceName_U(name);
835     if (dosdev)
836     {
837         DWORD   offset = HIWORD(dosdev) / sizeof(WCHAR); /* get it in WCHARs, not bytes */
838         DWORD   sz = LOWORD(dosdev); /* in bytes */
839
840         if (8 + sz + 2 > size) return sz + 10;
841         strcpyW(buffer, DeviceRootW);
842         memmove(buffer + 4, name + offset, sz);
843         buffer[4 + sz / sizeof(WCHAR)] = '\0';
844         /* file_part isn't set in this case */
845         return sz + 8;
846     }
847
848     reqsize = get_full_path_helper(name, buffer, size);
849     if (!reqsize) return 0;
850     if (reqsize > size)
851     {
852         LPWSTR tmp = RtlAllocateHeap(GetProcessHeap(), 0, reqsize);
853         reqsize = get_full_path_helper(name, tmp, reqsize);
854         if (reqsize > size)  /* it may have worked the second time */
855         {
856             RtlFreeHeap(GetProcessHeap(), 0, tmp);
857             return reqsize + sizeof(WCHAR);
858         }
859         memcpy( buffer, tmp, reqsize + sizeof(WCHAR) );
860         RtlFreeHeap(GetProcessHeap(), 0, tmp);
861     }
862
863     /* find file part */
864     if (file_part && (ptr = strrchrW(buffer, '\\')) != NULL && ptr >= buffer + 2 && *++ptr)
865         *file_part = ptr;
866     return reqsize;
867 }
868
869 /*************************************************************************
870  * RtlGetLongestNtPathLength    [NTDLL.@]
871  *
872  * Get the longest allowed path length
873  *
874  * PARAMS
875  *  None.
876  *
877  * RETURNS
878  *  The longest allowed path length (277 characters under Win2k).
879  */
880 DWORD WINAPI RtlGetLongestNtPathLength(void)
881 {
882     return MAX_NT_PATH_LENGTH;
883 }
884
885 /******************************************************************
886  *             RtlIsNameLegalDOS8Dot3   (NTDLL.@)
887  *
888  * Returns TRUE iff unicode is a valid DOS (8+3) name.
889  * If the name is valid, oem gets filled with the corresponding OEM string
890  * spaces is set to TRUE if unicode contains spaces
891  */
892 BOOLEAN WINAPI RtlIsNameLegalDOS8Dot3( const UNICODE_STRING *unicode,
893                                        OEM_STRING *oem, BOOLEAN *spaces )
894 {
895     static const char* illegal = "*?<>|\"+=,;[]:/\\\345";
896     int dot = -1;
897     int i;
898     char buffer[12];
899     OEM_STRING oem_str;
900     BOOLEAN got_space = FALSE;
901
902     if (!oem)
903     {
904         oem_str.Length = sizeof(buffer);
905         oem_str.MaximumLength = sizeof(buffer);
906         oem_str.Buffer = buffer;
907         oem = &oem_str;
908     }
909     if (RtlUpcaseUnicodeStringToCountedOemString( oem, unicode, FALSE ) != STATUS_SUCCESS)
910         return FALSE;
911
912     if (oem->Length > 12) return FALSE;
913
914     /* a starting . is invalid, except for . and .. */
915     if (oem->Buffer[0] == '.')
916     {
917         if (oem->Length != 1 && (oem->Length != 2 || oem->Buffer[1] != '.')) return FALSE;
918         if (spaces) *spaces = FALSE;
919         return TRUE;
920     }
921
922     for (i = 0; i < oem->Length; i++)
923     {
924         switch (oem->Buffer[i])
925         {
926         case ' ':
927             /* leading/trailing spaces not allowed */
928             if (!i || i == oem->Length-1 || oem->Buffer[i+1] == '.') return FALSE;
929             got_space = TRUE;
930             break;
931         case '.':
932             if (dot != -1) return FALSE;
933             dot = i;
934             break;
935         default:
936             if (strchr(illegal, oem->Buffer[i])) return FALSE;
937             break;
938         }
939     }
940     /* check file part is shorter than 8, extension shorter than 3
941      * dot cannot be last in string
942      */
943     if (dot == -1)
944     {
945         if (oem->Length > 8) return FALSE;
946     }
947     else
948     {
949         if (dot > 8 || (oem->Length - dot > 4) || dot == oem->Length - 1) return FALSE;
950     }
951     if (spaces) *spaces = got_space;
952     return TRUE;
953 }
954
955 /******************************************************************
956  *              RtlGetCurrentDirectory_U (NTDLL.@)
957  *
958  */
959 NTSTATUS WINAPI RtlGetCurrentDirectory_U(ULONG buflen, LPWSTR buf)
960 {
961     UNICODE_STRING*     us;
962     ULONG               len;
963
964     TRACE("(%lu %p)\n", buflen, buf);
965
966     RtlAcquirePebLock();
967
968     if (NtCurrentTeb()->Tib.SubSystemTib)  /* FIXME: hack */
969         us = &((WIN16_SUBSYSTEM_TIB *)NtCurrentTeb()->Tib.SubSystemTib)->curdir.DosPath;
970     else
971         us = &NtCurrentTeb()->Peb->ProcessParameters->CurrentDirectory.DosPath;
972
973     len = us->Length / sizeof(WCHAR);
974     if (us->Buffer[len - 1] == '\\' && us->Buffer[len - 2] != ':')
975         len--;
976
977     if (buflen / sizeof(WCHAR) > len)
978     {
979         memcpy(buf, us->Buffer, len * sizeof(WCHAR));
980         buf[len] = '\0';
981     }
982     else
983     {
984         len++;
985     }
986
987     RtlReleasePebLock();
988
989     return len * sizeof(WCHAR);
990 }
991
992 /******************************************************************
993  *              RtlSetCurrentDirectory_U (NTDLL.@)
994  *
995  */
996 NTSTATUS WINAPI RtlSetCurrentDirectory_U(const UNICODE_STRING* dir)
997 {
998     FILE_FS_DEVICE_INFORMATION device_info;
999     OBJECT_ATTRIBUTES attr;
1000     UNICODE_STRING newdir;
1001     IO_STATUS_BLOCK io;
1002     CURDIR *curdir;
1003     HANDLE handle;
1004     NTSTATUS nts;
1005     ULONG size;
1006     PWSTR ptr;
1007
1008     newdir.Buffer = NULL;
1009
1010     RtlAcquirePebLock();
1011
1012     if (NtCurrentTeb()->Tib.SubSystemTib)  /* FIXME: hack */
1013         curdir = &((WIN16_SUBSYSTEM_TIB *)NtCurrentTeb()->Tib.SubSystemTib)->curdir;
1014     else
1015         curdir = &NtCurrentTeb()->Peb->ProcessParameters->CurrentDirectory;
1016
1017     if (!RtlDosPathNameToNtPathName_U( dir->Buffer, &newdir, NULL, NULL ))
1018     {
1019         nts = STATUS_OBJECT_NAME_INVALID;
1020         goto out;
1021     }
1022
1023     attr.Length = sizeof(attr);
1024     attr.RootDirectory = 0;
1025     attr.Attributes = OBJ_CASE_INSENSITIVE;
1026     attr.ObjectName = &newdir;
1027     attr.SecurityDescriptor = NULL;
1028     attr.SecurityQualityOfService = NULL;
1029
1030     nts = NtOpenFile( &handle, 0, &attr, &io, 0, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT );
1031     if (nts != STATUS_SUCCESS) goto out;
1032
1033     /* don't keep the directory handle open on removable media */
1034     if (!NtQueryVolumeInformationFile( handle, &io, &device_info,
1035                                        sizeof(device_info), FileFsDeviceInformation ) &&
1036         (device_info.Characteristics & FILE_REMOVABLE_MEDIA))
1037     {
1038         NtClose( handle );
1039         handle = 0;
1040     }
1041
1042     if (curdir->Handle) NtClose( curdir->Handle );
1043     curdir->Handle = handle;
1044
1045     /* append trailing \ if missing */
1046     size = newdir.Length / sizeof(WCHAR);
1047     ptr = newdir.Buffer;
1048     ptr += 4;  /* skip \??\ prefix */
1049     size -= 4;
1050     if (size && ptr[size - 1] != '\\') ptr[size++] = '\\';
1051
1052     memcpy( curdir->DosPath.Buffer, ptr, size * sizeof(WCHAR));
1053     curdir->DosPath.Buffer[size] = 0;
1054     curdir->DosPath.Length = size * sizeof(WCHAR);
1055
1056     TRACE( "curdir now %s %p\n", debugstr_w(curdir->DosPath.Buffer), curdir->Handle );
1057
1058  out:
1059     RtlFreeUnicodeString( &newdir );
1060     RtlReleasePebLock();
1061     return nts;
1062 }
1063
1064
1065 /******************************************************************
1066  *           wine_unix_to_nt_file_name  (NTDLL.@) Not a Windows API
1067  */
1068 NTSTATUS wine_unix_to_nt_file_name( const ANSI_STRING *name, UNICODE_STRING *nt )
1069 {
1070     static const WCHAR prefixW[] = {'\\','?','?','\\','a',':','\\'};
1071     unsigned int lenW, lenA = name->Length;
1072     const char *path = name->Buffer;
1073     char *cwd;
1074     WCHAR *p;
1075     NTSTATUS status;
1076     int drive;
1077
1078     if (!lenA || path[0] != '/')
1079     {
1080         char *newcwd, *end;
1081         size_t size;
1082
1083         if ((status = DIR_get_unix_cwd( &cwd )) != STATUS_SUCCESS) return status;
1084
1085         size = strlen(cwd) + lenA + 1;
1086         if (!(newcwd = RtlReAllocateHeap( GetProcessHeap(), 0, cwd, size )))
1087         {
1088             status = STATUS_NO_MEMORY;
1089             goto done;
1090         }
1091         cwd = newcwd;
1092         end = cwd + strlen(cwd);
1093         if (end > cwd && end[-1] != '/') *end++ = '/';
1094         memcpy( end, path, lenA );
1095         lenA += end - cwd;
1096         path = cwd;
1097
1098         status = find_drive_rootA( &path, lenA, &drive );
1099         lenA -= (path - cwd);
1100     }
1101     else
1102     {
1103         cwd = NULL;
1104         status = find_drive_rootA( &path, lenA, &drive );
1105         lenA -= (path - name->Buffer);
1106     }
1107
1108     if (status != STATUS_SUCCESS) goto done;
1109     while (lenA && path[0] == '/') { lenA--; path++; }
1110
1111     lenW = ntdll_umbstowcs( 0, path, lenA, NULL, 0 );
1112     if (!(nt->Buffer = RtlAllocateHeap( GetProcessHeap(), 0,
1113                                         (lenW + 1) * sizeof(WCHAR) + sizeof(prefixW) )))
1114     {
1115         status = STATUS_NO_MEMORY;
1116         goto done;
1117     }
1118
1119     memcpy( nt->Buffer, prefixW, sizeof(prefixW) );
1120     nt->Buffer[4] += drive;
1121     ntdll_umbstowcs( 0, path, lenA, nt->Buffer + sizeof(prefixW)/sizeof(WCHAR), lenW );
1122     lenW += sizeof(prefixW)/sizeof(WCHAR);
1123     nt->Buffer[lenW] = 0;
1124     nt->Length = lenW * sizeof(WCHAR);
1125     nt->MaximumLength = nt->Length + sizeof(WCHAR);
1126     for (p = nt->Buffer + sizeof(prefixW)/sizeof(WCHAR); *p; p++) if (*p == '/') *p = '\\';
1127
1128 done:
1129     RtlFreeHeap( GetProcessHeap(), 0, cwd );
1130     return status;
1131 }