Moved atom functions to dlls/kernel.
[wine] / dlls / ntdll / path.c
1 /*
2  * Ntdll path functions
3  *
4  * Copyright 2002, 2003 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
24 #include "winternl.h"
25 #include "wine/unicode.h"
26 #include "wine/debug.h"
27 #include "ntdll_misc.h"
28
29 WINE_DEFAULT_DEBUG_CHANNEL(file);
30
31 static const WCHAR DeviceRootW[] = {'\\','\\','.','\\',0};
32 static const WCHAR NTDosPrefixW[] = {'\\','?','?','\\',0};
33 static const WCHAR UncPfxW[] = {'U','N','C','\\',0};
34
35 #define IS_SEPARATOR(ch)  ((ch) == '\\' || (ch) == '/')
36
37 /***********************************************************************
38  *             RtlDetermineDosPathNameType_U   (NTDLL.@)
39  */
40 DOS_PATHNAME_TYPE WINAPI RtlDetermineDosPathNameType_U( PCWSTR path )
41 {
42     if (IS_SEPARATOR(path[0]))
43     {
44         if (!IS_SEPARATOR(path[1])) return ABSOLUTE_PATH;       /* "/foo" */
45         if (path[2] != '.') return UNC_PATH;                    /* "//foo" */
46         if (IS_SEPARATOR(path[3])) return DEVICE_PATH;          /* "//./foo" */
47         if (path[3]) return UNC_PATH;                           /* "//.foo" */
48         return UNC_DOT_PATH;                                    /* "//." */
49     }
50     else
51     {
52         if (!path[0] || path[1] != ':') return RELATIVE_PATH;   /* "foo" */
53         if (IS_SEPARATOR(path[2])) return ABSOLUTE_DRIVE_PATH;  /* "c:/foo" */
54         return RELATIVE_DRIVE_PATH;                             /* "c:foo" */
55     }
56 }
57
58 /***********************************************************************
59  *             RtlIsDosDeviceName_U   (NTDLL.@)
60  *
61  * Check if the given DOS path contains a DOS device name.
62  *
63  * Returns the length of the device name in the low word and its
64  * position in the high word (both in bytes, not WCHARs), or 0 if no
65  * device name is found.
66  */
67 ULONG WINAPI RtlIsDosDeviceName_U( PCWSTR dos_name )
68 {
69     static const WCHAR consoleW[] = {'\\','\\','.','\\','C','O','N',0};
70     static const WCHAR auxW[3] = {'A','U','X'};
71     static const WCHAR comW[3] = {'C','O','M'};
72     static const WCHAR conW[3] = {'C','O','N'};
73     static const WCHAR lptW[3] = {'L','P','T'};
74     static const WCHAR nulW[3] = {'N','U','L'};
75     static const WCHAR prnW[3] = {'P','R','N'};
76
77     const WCHAR *start, *end, *p;
78
79     switch(RtlDetermineDosPathNameType_U( dos_name ))
80     {
81     case INVALID_PATH:
82     case UNC_PATH:
83         return 0;
84     case DEVICE_PATH:
85         if (!strcmpiW( dos_name, consoleW ))
86             return MAKELONG( sizeof(conW), 4 * sizeof(WCHAR) );  /* 4 is length of \\.\ prefix */
87         return 0;
88     default:
89         break;
90     }
91
92     end = dos_name + strlenW(dos_name) - 1;
93     if (end >= dos_name && *end == ':') end--;  /* remove trailing ':' */
94
95     /* find start of file name */
96     for (start = end; start >= dos_name; start--)
97     {
98         if (IS_SEPARATOR(start[0])) break;
99         /* check for ':' but ignore if before extension (for things like NUL:.txt) */
100         if (start[0] == ':' && start[1] != '.') break;
101     }
102     start++;
103
104     /* remove extension */
105     if ((p = strchrW( start, '.' )))
106     {
107         end = p - 1;
108         if (end >= dos_name && *end == ':') end--;  /* remove trailing ':' before extension */
109     }
110     else
111     {
112         /* no extension, remove trailing spaces */
113         while (end >= dos_name && *end == ' ') end--;
114     }
115
116     /* now we have a potential device name between start and end, check it */
117     switch(end - start + 1)
118     {
119     case 3:
120         if (strncmpiW( start, auxW, 3 ) &&
121             strncmpiW( start, conW, 3 ) &&
122             strncmpiW( start, nulW, 3 ) &&
123             strncmpiW( start, prnW, 3 )) break;
124         return MAKELONG( 3 * sizeof(WCHAR), (start - dos_name) * sizeof(WCHAR) );
125     case 4:
126         if (strncmpiW( start, comW, 3 ) && strncmpiW( start, lptW, 3 )) break;
127         if (*end <= '0' || *end > '9') break;
128         return MAKELONG( 4 * sizeof(WCHAR), (start - dos_name) * sizeof(WCHAR) );
129     default:  /* can't match anything */
130         break;
131     }
132     return 0;
133 }
134
135
136 /**************************************************************************
137  *                 RtlDosPathNameToNtPathName_U         [NTDLL.@]
138  *
139  * dos_path: a DOS path name (fully qualified or not)
140  * ntpath:   pointer to a UNICODE_STRING to hold the converted
141  *           path name
142  * file_part:will point (in ntpath) to the file part in the path
143  * cd:       directory reference (optional)
144  *
145  * FIXME:
146  *      + fill the cd structure
147  */
148 BOOLEAN  WINAPI RtlDosPathNameToNtPathName_U(PWSTR dos_path,
149                                              PUNICODE_STRING ntpath,
150                                              PWSTR* file_part,
151                                              CURDIR* cd)
152 {
153     static const WCHAR LongFileNamePfxW[4] = {'\\','\\','?','\\'};
154     ULONG sz, ptr_sz, offset;
155     WCHAR local[MAX_PATH];
156     LPWSTR ptr;
157
158     TRACE("(%s,%p,%p,%p)\n",
159           debugstr_w(dos_path), ntpath, file_part, cd);
160
161     if (cd)
162     {
163         FIXME("Unsupported parameter\n");
164         memset(cd, 0, sizeof(*cd));
165     }
166
167     if (!dos_path || !*dos_path) return FALSE;
168
169     if (!memcmp(dos_path, LongFileNamePfxW, sizeof(LongFileNamePfxW)))
170     {
171         dos_path += sizeof(LongFileNamePfxW) / sizeof(WCHAR);
172         ptr = NULL;
173         ptr_sz = 0;
174     }
175     else
176     {
177         ptr = local;
178         ptr_sz = sizeof(local);
179     }
180     sz = RtlGetFullPathName_U(dos_path, ptr_sz, ptr, file_part);
181     if (sz == 0) return FALSE;
182     if (sz > ptr_sz)
183     {
184         ptr = RtlAllocateHeap(ntdll_get_process_heap(), 0, sz);
185         sz = RtlGetFullPathName_U(dos_path, sz, ptr, file_part);
186     }
187
188     ntpath->MaximumLength = sz + (4 /* unc\ */ + 4 /* \??\ */) * sizeof(WCHAR);
189     ntpath->Buffer = RtlAllocateHeap(ntdll_get_process_heap(), 0, ntpath->MaximumLength);
190     if (!ntpath->Buffer)
191     {
192         if (ptr != local) RtlFreeHeap(ntdll_get_process_heap(), 0, ptr);
193         return FALSE;
194     }
195
196     strcpyW(ntpath->Buffer, NTDosPrefixW);
197     offset = 0;
198     switch (RtlDetermineDosPathNameType_U(ptr))
199     {
200     case UNC_PATH: /* \\foo */
201         if (ptr[2] != '?')
202         {
203             offset = 2;
204             strcatW(ntpath->Buffer, UncPfxW);
205         }
206         break;
207     case DEVICE_PATH: /* \\.\foo */
208         offset = 4;
209         break;
210     default: break; /* needed to keep gcc quiet */
211     }
212
213     strcatW(ntpath->Buffer, ptr + offset);
214     ntpath->Length = strlenW(ntpath->Buffer) * sizeof(WCHAR);
215
216     if (file_part && *file_part)
217         *file_part = ntpath->Buffer + ntpath->Length / sizeof(WCHAR) - lstrlenW(*file_part);
218
219     /* FIXME: cd filling */
220
221     if (ptr != local) RtlFreeHeap(ntdll_get_process_heap(), 0, ptr);
222     return TRUE;
223 }
224
225 /******************************************************************
226  *              get_full_path_helper
227  *
228  * Helper for RtlGetFullPathName_U
229  */
230 static ULONG get_full_path_helper(LPCWSTR name, LPWSTR buffer, ULONG size)
231 {
232     ULONG               reqsize, mark = 0;
233     DOS_PATHNAME_TYPE   type;
234     LPWSTR              ptr;
235     UNICODE_STRING*     cd;
236
237     reqsize = sizeof(WCHAR); /* '\0' at the end */
238
239     RtlAcquirePebLock();
240     cd = &ntdll_get_process_pmts()->CurrentDirectoryName;
241
242     switch (type = RtlDetermineDosPathNameType_U(name))
243     {
244     case UNC_PATH:              /* \\foo   */
245     case DEVICE_PATH:           /* \\.\foo */
246         if (reqsize <= size) buffer[0] = '\0';
247         break;
248
249     case ABSOLUTE_DRIVE_PATH:   /* c:\foo  */
250         reqsize += sizeof(WCHAR);
251         if (reqsize <= size)
252         {
253             buffer[0] = toupperW(name[0]);
254             buffer[1] = '\0';
255         }
256         name++;
257         break;
258
259     case RELATIVE_DRIVE_PATH:   /* c:foo   */
260         if (toupperW(name[0]) != toupperW(cd->Buffer[0]) || cd->Buffer[1] != ':')
261         {
262             WCHAR               drive[4];
263             UNICODE_STRING      var, val;
264
265             drive[0] = '=';
266             drive[1] = name[0];
267             drive[2] = ':';
268             drive[3] = '\0';
269             var.Length = 6;
270             var.MaximumLength = 8;
271             var.Buffer = drive;
272             val.Length = 0;
273             val.MaximumLength = size;
274             val.Buffer = buffer;
275
276             name += 2;
277
278             switch (RtlQueryEnvironmentVariable_U(NULL, &var, &val))
279             {
280             case STATUS_SUCCESS:
281                 /* FIXME: Win2k seems to check that the environment variable actually points 
282                  * to an existing directory. If not, root of the drive is used
283                  * (this seems also to be the only spot in RtlGetFullPathName that the 
284                  * existence of a part of a path is checked)
285                  */
286                 /* fall thru */
287             case STATUS_BUFFER_TOO_SMALL:
288                 reqsize += val.Length;
289                 /* append trailing \\ */
290                 reqsize += sizeof(WCHAR);
291                 if (reqsize <= size)
292                 {
293                     buffer[reqsize / sizeof(WCHAR) - 2] = '\\';
294                     buffer[reqsize / sizeof(WCHAR) - 1] = '\0';
295                 }
296                 break;
297             case STATUS_VARIABLE_NOT_FOUND:
298                 reqsize += 3 * sizeof(WCHAR);
299                 if (reqsize <= size)
300                 {
301                     buffer[0] = drive[1];
302                     buffer[1] = ':';
303                     buffer[2] = '\\';
304                     buffer[3] = '\0';
305                 }
306                 break;
307             default:
308                 ERR("Unsupported status code\n");
309                 break;
310             }
311             break;
312         }
313         name += 2;
314         /* fall through */
315
316     case RELATIVE_PATH:         /* foo     */
317         reqsize += cd->Length;
318         mark = cd->Length / sizeof(WCHAR);
319         if (reqsize <= size)
320             strcpyW(buffer, cd->Buffer);
321         break;
322
323     case ABSOLUTE_PATH:         /* \xxx    */
324         if (cd->Buffer[1] == ':')
325         {
326             reqsize += 2 * sizeof(WCHAR);
327             if (reqsize <= size)
328             {
329                 buffer[0] = cd->Buffer[0];
330                 buffer[1] = ':';
331                 buffer[2] = '\0';
332             }
333         }
334         else
335         {
336             unsigned    len;
337
338             ptr = strchrW(cd->Buffer + 2, '\\');
339             if (ptr) ptr = strchrW(ptr + 1, '\\');
340             if (!ptr) ptr = cd->Buffer + strlenW(cd->Buffer);
341             len = (ptr - cd->Buffer) * sizeof(WCHAR);
342             reqsize += len;
343             mark = len / sizeof(WCHAR);
344             if (reqsize <= size)
345             {
346                 memcpy(buffer, cd->Buffer, len);
347                 buffer[len / sizeof(WCHAR)] = '\0';
348             }
349             else
350                 buffer[0] = '\0';
351         }
352         break;
353
354     case UNC_DOT_PATH:         /* \\.     */
355         reqsize += 4 * sizeof(WCHAR);
356         name += 3;
357         if (reqsize <= size)
358         {
359             buffer[0] = '\\';
360             buffer[1] = '\\';
361             buffer[2] = '.';
362             buffer[3] = '\\';
363             buffer[4] = '\0';
364         }
365         break;
366
367     case INVALID_PATH:
368         reqsize = 0;
369         goto done;
370     }
371
372     reqsize += strlenW(name) * sizeof(WCHAR);
373     if (reqsize > size) goto done;
374
375     strcatW(buffer, name);
376
377     /* convert every / into a \ */
378     for (ptr = buffer; ptr < buffer + size / sizeof(WCHAR); ptr++) 
379         if (*ptr == '/') *ptr = '\\';
380
381     reqsize -= sizeof(WCHAR); /* don't count trailing \0 */
382
383     /* mark is non NULL for UNC names, so start path collapsing after server & share name
384      * otherwise, it's a fully qualified DOS name, so start after the drive designation
385      */
386     for (ptr = buffer + (mark ? mark : 2); ptr < buffer + reqsize / sizeof(WCHAR); )
387     {
388         WCHAR* p = strchrW(ptr, '\\');
389         if (!p) break;
390
391         p++;
392         if (p[0] == '.')
393         {
394             switch (p[1])
395             {
396             case '.':
397                 switch (p[2])
398                 {
399                 case '\\':
400                     {
401                         WCHAR*      prev = p - 2;
402                         while (prev >= buffer + mark && *prev != '\\') prev--;
403                         /* either collapse \foo\.. into \ or \.. into \ */
404                         if (prev < buffer + mark) prev = p - 1;
405                         reqsize -= (p + 2 - prev) * sizeof(WCHAR);
406                         memmove(prev, p + 2, buffer + reqsize - prev + sizeof(WCHAR));
407                     }
408                     break;
409                 case '\0':
410                     reqsize -= 2 * sizeof(WCHAR);
411                     *p = 0;
412                     break;
413                 }
414                 break;
415             case '\\':
416                 reqsize -= 2 * sizeof(WCHAR);
417                 memmove(ptr + 2, ptr, buffer + reqsize - ptr + sizeof(WCHAR));
418                 break;
419             }
420         }
421         ptr = p;
422     }
423
424 done:
425     RtlReleasePebLock();
426     return reqsize;
427 }
428
429 /******************************************************************
430  *              RtlGetFullPathName_U  (NTDLL.@)
431  *
432  * Returns the number of bytes written to buffer (not including the
433  * terminating NULL) if the function succeeds, or the required number of bytes
434  * (including the terminating NULL) if the buffer is to small.
435  *
436  * file_part will point to the filename part inside buffer (except if we use
437  * DOS device name, in which case file_in_buf is NULL)
438  *
439  */
440 DWORD WINAPI RtlGetFullPathName_U(const WCHAR* name, ULONG size, WCHAR* buffer,
441                                   WCHAR** file_part)
442 {
443     DWORD       dosdev;
444     DWORD       reqsize;
445
446     TRACE("(%s %lu %p %p)\n", debugstr_w(name), size, buffer, file_part);
447
448     if (!name || !*name) return 0;
449
450     if (file_part) *file_part = NULL;
451
452     /* check for DOS device name */
453     dosdev = RtlIsDosDeviceName_U(name);
454     if (dosdev)
455     {
456         DWORD   offset = HIWORD(dosdev) / sizeof(WCHAR); /* get it in WCHARs, not bytes */
457         DWORD   sz = LOWORD(dosdev); /* in bytes */
458
459         if (8 + sz + 2 > size) return sz + 10;
460         strcpyW(buffer, DeviceRootW);
461         memmove(buffer + 4, name + offset, sz);
462         buffer[4 + sz / sizeof(WCHAR)] = '\0';
463         /* file_part isn't set in this case */
464         return sz + 8;
465     }
466
467     reqsize = get_full_path_helper(name, buffer, size);
468     if (reqsize > size)
469     {
470         LPWSTR  tmp = RtlAllocateHeap(ntdll_get_process_heap(), 0, reqsize);
471         reqsize = get_full_path_helper(name, tmp, reqsize) + sizeof(WCHAR);
472         RtlFreeHeap(ntdll_get_process_heap(), 0, tmp);
473     }
474     else
475     {
476         WCHAR*      ptr;
477         /* find file part */
478         if (file_part && (ptr = strrchrW(buffer, '\\')) != NULL && ptr >= buffer + 2 && *++ptr)
479             *file_part = ptr;
480     }
481     return reqsize;
482 }
483
484 /*************************************************************************
485  * RtlGetLongestNtPathLength    [NTDLL.@]
486  *
487  * Get the longest allowed path length
488  *
489  * PARAMS
490  *  None.
491  *
492  * RETURNS
493  *  The longest allowed path length (277 characters under Win2k).
494  */
495 DWORD WINAPI RtlGetLongestNtPathLength(void)
496 {
497     return 277;
498 }
499
500 /******************************************************************
501  *             RtlIsNameLegalDOS8Dot3   (NTDLL.@)
502  *
503  * Returns TRUE iff unicode is a valid DOS (8+3) name.
504  * If the name is valid, oem gets filled with the corresponding OEM string
505  * spaces is set to TRUE if unicode contains spaces
506  */
507 BOOLEAN WINAPI RtlIsNameLegalDOS8Dot3( const UNICODE_STRING *unicode,
508                                        OEM_STRING *oem, BOOLEAN *spaces )
509 {
510     int dot = -1;
511     unsigned int i;
512     char buffer[12];
513     OEM_STRING oem_str;
514     BOOLEAN got_space = FALSE;
515
516     if (!oem)
517     {
518         oem_str.Length = sizeof(buffer);
519         oem_str.MaximumLength = sizeof(buffer);
520         oem_str.Buffer = buffer;
521         oem = &oem_str;
522     }
523     if (RtlUpcaseUnicodeStringToCountedOemString( oem, unicode, FALSE ) != STATUS_SUCCESS)
524         return FALSE;
525
526     if (oem->Length > 12) return FALSE;
527
528     /* a starting . is invalid, except for . and .. */
529     if (oem->Buffer[0] == '.')
530     {
531         if (oem->Length != 1 && (oem->Length != 2 || oem->Buffer[1] != '.')) return FALSE;
532         if (spaces) *spaces = FALSE;
533         return TRUE;
534     }
535
536     for (i = 0; i < oem->Length; i++)
537     {
538         switch (oem->Buffer[i])
539         {
540         case ' ':
541             /* leading/trailing spaces not allowed */
542             if (!i || i == oem->Length-1 || oem->Buffer[i+1] == '.') return FALSE;
543             got_space = TRUE;
544             break;
545         case '.':
546             if (dot != -1) return FALSE;
547             dot = i;
548             break;
549         default:
550             /* FIXME: check for invalid chars */
551             break;
552         }
553     }
554     /* check file part is shorter than 8, extension shorter than 3
555      * dot cannot be last in string
556      */
557     if (dot == -1)
558     {
559         if (oem->Length > 8) return FALSE;
560     }
561     else
562     {
563         if (dot > 8 || (oem->Length - dot > 4) || dot == oem->Length - 1) return FALSE;
564     }
565     if (spaces) *spaces = got_space;
566     return TRUE;
567 }
568
569 /******************************************************************
570  *              RtlGetCurrentDirectory_U (NTDLL.@)
571  *
572  */
573 NTSTATUS WINAPI RtlGetCurrentDirectory_U(ULONG buflen, LPWSTR buf)
574 {
575     UNICODE_STRING*     us;
576     ULONG               len;
577
578     TRACE("(%lu %p)\n", buflen, buf);
579
580     RtlAcquirePebLock();
581
582     us = &ntdll_get_process_pmts()->CurrentDirectoryName;
583     len = us->Length / sizeof(WCHAR);
584     if (us->Buffer[len - 1] == '\\' && us->Buffer[len - 2] != ':')
585         len--;
586
587     if (buflen / sizeof(WCHAR) > len)
588     {
589         memcpy(buf, us->Buffer, len * sizeof(WCHAR));
590         buf[len] = '\0';
591     }
592     else
593     {
594         len++;
595     }
596
597     RtlReleasePebLock();
598
599     return len * sizeof(WCHAR);
600 }
601
602 /******************************************************************
603  *              RtlSetCurrentDirectory_U (NTDLL.@)
604  *
605  */
606 NTSTATUS WINAPI RtlSetCurrentDirectory_U(const UNICODE_STRING* dir)
607 {
608     UNICODE_STRING*     curdir;
609     NTSTATUS            nts = STATUS_SUCCESS;
610     ULONG               size;
611     PWSTR               buf = NULL;
612
613     TRACE("(%s)\n", debugstr_w(dir->Buffer));
614
615     RtlAcquirePebLock();
616
617     curdir = &ntdll_get_process_pmts()->CurrentDirectoryName;
618     size = curdir->MaximumLength;
619
620     buf = RtlAllocateHeap(ntdll_get_process_heap(), 0, size);
621     if (buf == NULL)
622     {
623         nts = STATUS_NO_MEMORY;
624         goto out;
625     }
626
627     size = RtlGetFullPathName_U(dir->Buffer, size, buf, 0);
628     if (!size)
629     {
630         nts = STATUS_OBJECT_NAME_INVALID;
631         goto out;
632     }
633
634     switch (RtlDetermineDosPathNameType_U(buf))
635     {
636     case ABSOLUTE_DRIVE_PATH:
637     case UNC_PATH:
638         break;
639     default:
640         FIXME("Don't support those cases yes\n");
641         nts = STATUS_NOT_IMPLEMENTED;
642         goto out;
643     }
644
645     /* FIXME: should check that the directory actually exists,
646      * and fill CurrentDirectoryHandle accordingly 
647      */
648
649     /* append trailing \ if missing */
650     if (buf[size / sizeof(WCHAR) - 1] != '\\')
651     {
652         buf[size / sizeof(WCHAR)] = '\\';
653         buf[size / sizeof(WCHAR) + 1] = '\0';
654         size += sizeof(WCHAR);
655     }
656
657     memmove(curdir->Buffer, buf, size + sizeof(WCHAR));
658     curdir->Length = size;
659
660 #if 0
661     if (curdir->Buffer[1] == ':')
662     {
663         UNICODE_STRING  env;
664         WCHAR           var[4];
665
666         var[0] = '=';
667         var[1] = curdir->Buffer[0];
668         var[2] = ':';
669         var[3] = 0;
670         env.Length = 3 * sizeof(WCHAR);
671         env.MaximumLength = 4 * sizeof(WCHAR);
672         env.Buffer = var;
673
674         RtlSetEnvironmentVariable(NULL, &env, curdir);
675     }
676 #endif
677
678  out:
679     if (buf) RtlFreeHeap(ntdll_get_process_heap(), 0, buf);
680
681     RtlReleasePebLock();
682
683     return nts;
684 }