Added regedit unit test, a couple minor changes to regedit.
[wine] / files / directory.c
1 /*
2  * DOS directories functions
3  *
4  * Copyright 1995 Alexandre Julliard
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include "config.h"
22
23 #include <ctype.h>
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <errno.h>
31 #ifdef HAVE_SYS_ERRNO_H
32 #include <sys/errno.h>
33 #endif
34
35 #include "winbase.h"
36 #include "wine/winbase16.h"
37 #include "windef.h"
38 #include "wingdi.h"
39 #include "wine/winuser16.h"
40 #include "winerror.h"
41 #include "winreg.h"
42 #include "drive.h"
43 #include "file.h"
44 #include "heap.h"
45 #include "msdos.h"
46 #include "wine/debug.h"
47
48 WINE_DEFAULT_DEBUG_CHANNEL(dosfs);
49 WINE_DECLARE_DEBUG_CHANNEL(file);
50
51 static DOS_FULL_NAME DIR_Windows;
52 static DOS_FULL_NAME DIR_System;
53
54
55 /***********************************************************************
56  *           DIR_GetPath
57  *
58  * Get a path name from the wine.ini file and make sure it is valid.
59  */
60 static int DIR_GetPath( const char *keyname, const char *defval,
61                         DOS_FULL_NAME *full_name, char * longname, BOOL warn )
62 {
63     char path[MAX_PATHNAME_LEN];
64     BY_HANDLE_FILE_INFORMATION info;
65     const char *mess = "does not exist";
66
67     PROFILE_GetWineIniString( "wine", keyname, defval, path, sizeof(path) );
68     if (!DOSFS_GetFullName( path, TRUE, full_name ) ||
69         (!FILE_Stat( full_name->long_name, &info ) && (mess=strerror(errno)))||
70         (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (mess="not a directory")) ||
71         (!(GetLongPathNameA(full_name->short_name, longname, MAX_PATHNAME_LEN))) )
72     {
73         if (warn)
74            MESSAGE("Invalid path '%s' for %s directory: %s\n", path, keyname, mess);
75         return 0;
76     }
77     return 1;
78 }
79
80
81 /***********************************************************************
82  *           DIR_Init
83  */
84 int DIR_Init(void)
85 {
86     char path[MAX_PATHNAME_LEN];
87     char longpath[MAX_PATHNAME_LEN];
88     DOS_FULL_NAME tmp_dir, profile_dir;
89     int drive;
90     const char *cwd;
91
92     if (!getcwd( path, MAX_PATHNAME_LEN ))
93     {
94         perror( "Could not get current directory" );
95         return 0;
96     }
97     cwd = path;
98     if ((drive = DRIVE_FindDriveRoot( &cwd )) == -1)
99     {
100         MESSAGE("Warning: could not find wine config [Drive x] entry "
101             "for current working directory %s; "
102             "starting in windows directory.\n", cwd );
103     }
104     else
105     {
106         DRIVE_SetCurrentDrive( drive );
107         DRIVE_Chdir( drive, cwd );
108     }
109
110     if (!(DIR_GetPath( "windows", "c:\\windows", &DIR_Windows, longpath, TRUE )) ||
111         !(DIR_GetPath( "system", "c:\\windows\\system", &DIR_System, longpath, TRUE )) ||
112         !(DIR_GetPath( "temp", "c:\\windows", &tmp_dir, longpath, TRUE )))
113     {
114         PROFILE_UsageWineIni();
115         return 0;
116     }
117     if (-1 == access( tmp_dir.long_name, W_OK ))
118     {
119         if (errno==EACCES)
120         {
121                 MESSAGE("Warning: the temporary directory '%s' (specified in wine configuration file) is not writeable.\n", tmp_dir.long_name);
122                 PROFILE_UsageWineIni();
123         }
124         else
125                 MESSAGE("Warning: access to temporary directory '%s' failed (%s).\n",
126                     tmp_dir.long_name, strerror(errno));
127     }
128
129     if (drive == -1)
130     {
131         drive = DIR_Windows.drive;
132         DRIVE_SetCurrentDrive( drive );
133         DRIVE_Chdir( drive, DIR_Windows.short_name + 2 );
134     }
135
136     PROFILE_GetWineIniString("wine", "path", "c:\\windows;c:\\windows\\system",
137                              path, sizeof(path) );
138     if (strchr(path, '/'))
139     {
140         MESSAGE("Fix your wine config to use DOS drive syntax in [wine] 'Path=' statement! (no '/' allowed)\n");
141         PROFILE_UsageWineIni();
142         ExitProcess(1);
143     }
144
145     /* Set the environment variables */
146
147     SetEnvironmentVariableA( "PATH", path );
148     SetEnvironmentVariableA( "TEMP", tmp_dir.short_name );
149     SetEnvironmentVariableA( "TMP", tmp_dir.short_name );
150     SetEnvironmentVariableA( "windir", DIR_Windows.short_name );
151     SetEnvironmentVariableA( "winsysdir", DIR_System.short_name );
152
153     /* set COMSPEC only if it doesn't exist already */
154     if (!GetEnvironmentVariableA( "COMSPEC", NULL, 0 ))
155         SetEnvironmentVariableA( "COMSPEC", "c:\\command.com" );
156
157     TRACE("WindowsDir = %s (%s)\n",
158           DIR_Windows.short_name, DIR_Windows.long_name );
159     TRACE("SystemDir  = %s (%s)\n",
160           DIR_System.short_name, DIR_System.long_name );
161     TRACE("TempDir    = %s (%s)\n",
162           tmp_dir.short_name, tmp_dir.long_name );
163     TRACE("Path       = %s\n", path );
164     TRACE("Cwd        = %c:\\%s\n",
165           'A' + drive, DRIVE_GetDosCwd( drive ) );
166
167     if (DIR_GetPath( "profile", "", &profile_dir, longpath, FALSE ))
168     {
169         TRACE("USERPROFILE= %s\n", longpath );
170         SetEnvironmentVariableA( "USERPROFILE", longpath );
171     }
172
173     TRACE("SYSTEMROOT = %s\n", DIR_Windows.short_name );
174     SetEnvironmentVariableA( "SYSTEMROOT", DIR_Windows.short_name );
175
176     return 1;
177 }
178
179
180 /***********************************************************************
181  *           GetTempPathA   (KERNEL32.@)
182  */
183 UINT WINAPI GetTempPathA( UINT count, LPSTR path )
184 {
185     UINT ret;
186     if (!(ret = GetEnvironmentVariableA( "TMP", path, count )))
187         if (!(ret = GetEnvironmentVariableA( "TEMP", path, count )))
188             if (!(ret = GetCurrentDirectoryA( count, path )))
189                 return 0;
190     if (count && (ret < count - 1) && (path[ret-1] != '\\'))
191     {
192         path[ret++] = '\\';
193         path[ret]   = '\0';
194     }
195     return ret;
196 }
197
198
199 /***********************************************************************
200  *           GetTempPathW   (KERNEL32.@)
201  */
202 UINT WINAPI GetTempPathW( UINT count, LPWSTR path )
203 {
204     static const WCHAR tmp[]  = { 'T', 'M', 'P', 0 };
205     static const WCHAR temp[] = { 'T', 'E', 'M', 'P', 0 };
206     UINT ret;
207     if (!(ret = GetEnvironmentVariableW( tmp, path, count )))
208         if (!(ret = GetEnvironmentVariableW( temp, path, count )))
209             if (!(ret = GetCurrentDirectoryW( count, path )))
210                 return 0;
211     if (count && (ret < count - 1) && (path[ret-1] != '\\'))
212     {
213         path[ret++] = '\\';
214         path[ret]   = '\0';
215     }
216     return ret;
217 }
218
219
220 /***********************************************************************
221  *           DIR_GetWindowsUnixDir
222  */
223 UINT DIR_GetWindowsUnixDir( LPSTR path, UINT count )
224 {
225     if (path) lstrcpynA( path, DIR_Windows.long_name, count );
226     return strlen( DIR_Windows.long_name );
227 }
228
229
230 /***********************************************************************
231  *           DIR_GetSystemUnixDir
232  */
233 UINT DIR_GetSystemUnixDir( LPSTR path, UINT count )
234 {
235     if (path) lstrcpynA( path, DIR_System.long_name, count );
236     return strlen( DIR_System.long_name );
237 }
238
239
240 /***********************************************************************
241  *           GetTempDrive   (KERNEL.92)
242  * A closer look at krnl386.exe shows what the SDK doesn't mention:
243  *
244  * returns:
245  *   AL: driveletter
246  *   AH: ':'            - yes, some kernel code even does stosw with
247  *                            the returned AX.
248  *   DX: 1 for success
249  */
250 UINT WINAPI GetTempDrive( BYTE ignored )
251 {
252     char *buffer;
253     BYTE ret;
254     UINT len = GetTempPathA( 0, NULL );
255
256     if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len + 1 )) )
257         ret = DRIVE_GetCurrentDrive() + 'A';
258     else
259     {
260         /* FIXME: apparently Windows does something with the ignored byte */
261         if (!GetTempPathA( len, buffer )) buffer[0] = 'C';
262         ret = toupper(buffer[0]);
263         HeapFree( GetProcessHeap(), 0, buffer );
264     }
265     return MAKELONG( ret | (':' << 8), 1 );
266 }
267
268
269 /***********************************************************************
270  *           GetWindowsDirectory   (KERNEL.134)
271  */
272 UINT16 WINAPI GetWindowsDirectory16( LPSTR path, UINT16 count )
273 {
274     return (UINT16)GetWindowsDirectoryA( path, count );
275 }
276
277
278 /***********************************************************************
279  *           GetWindowsDirectoryA   (KERNEL32.@)
280  *
281  * See comment for GetWindowsDirectoryW.
282  */
283 UINT WINAPI GetWindowsDirectoryA( LPSTR path, UINT count )
284 {
285     UINT len = strlen( DIR_Windows.short_name ) + 1;
286     if (path && count >= len)
287     {
288         strcpy( path, DIR_Windows.short_name );
289         len--;
290     }
291     return len;
292 }
293
294
295 /***********************************************************************
296  *           GetWindowsDirectoryW   (KERNEL32.@)
297  *
298  * Return value:
299  * If buffer is large enough to hold full path and terminating '\0' character
300  * function copies path to buffer and returns length of the path without '\0'.
301  * Otherwise function returns required size including '\0' character and
302  * does not touch the buffer.
303  */
304 UINT WINAPI GetWindowsDirectoryW( LPWSTR path, UINT count )
305 {
306     UINT len = MultiByteToWideChar( CP_ACP, 0, DIR_Windows.short_name, -1, NULL, 0 );
307     if (path && count >= len)
308     {
309         MultiByteToWideChar( CP_ACP, 0, DIR_Windows.short_name, -1, path, count );
310         len--;
311     }
312     return len;
313 }
314
315
316 /***********************************************************************
317  *           GetSystemWindowsDirectoryA   (KERNEL32.@) W2K, TS4.0SP4
318  */
319 UINT WINAPI GetSystemWindowsDirectoryA( LPSTR path, UINT count )
320 {
321     return GetWindowsDirectoryA( path, count );
322 }
323
324
325 /***********************************************************************
326  *           GetSystemWindowsDirectoryW   (KERNEL32.@) W2K, TS4.0SP4
327  */
328 UINT WINAPI GetSystemWindowsDirectoryW( LPWSTR path, UINT count )
329 {
330     return GetWindowsDirectoryW( path, count );
331 }
332
333
334 /***********************************************************************
335  *           GetSystemDirectory   (KERNEL.135)
336  */
337 UINT16 WINAPI GetSystemDirectory16( LPSTR path, UINT16 count )
338 {
339     return (UINT16)GetSystemDirectoryA( path, count );
340 }
341
342
343 /***********************************************************************
344  *           GetSystemDirectoryA   (KERNEL32.@)
345  *
346  * See comment for GetWindowsDirectoryW.
347  */
348 UINT WINAPI GetSystemDirectoryA( LPSTR path, UINT count )
349 {
350     UINT len = strlen( DIR_System.short_name ) + 1;
351     if (path && count >= len)
352     {
353         strcpy( path, DIR_System.short_name );
354         len--;
355     }
356     return len;
357 }
358
359
360 /***********************************************************************
361  *           GetSystemDirectoryW   (KERNEL32.@)
362  *
363  * See comment for GetWindowsDirectoryW.
364  */
365 UINT WINAPI GetSystemDirectoryW( LPWSTR path, UINT count )
366 {
367     UINT len = MultiByteToWideChar( CP_ACP, 0, DIR_System.short_name, -1, NULL, 0 );
368     if (path && count >= len)
369     {
370         MultiByteToWideChar( CP_ACP, 0, DIR_System.short_name, -1, path, count );
371         len--;
372     }
373     return len;
374 }
375
376
377 /***********************************************************************
378  *           CreateDirectory   (KERNEL.144)
379  */
380 BOOL16 WINAPI CreateDirectory16( LPCSTR path, LPVOID dummy )
381 {
382     TRACE_(file)("(%s,%p)\n", path, dummy );
383     return (BOOL16)CreateDirectoryA( path, NULL );
384 }
385
386
387 /***********************************************************************
388  *           CreateDirectoryA   (KERNEL32.@)
389  * RETURNS:
390  *      TRUE : success
391  *      FALSE : failure
392  *              ERROR_DISK_FULL:        on full disk
393  *              ERROR_ALREADY_EXISTS:   if directory name exists (even as file)
394  *              ERROR_ACCESS_DENIED:    on permission problems
395  *              ERROR_FILENAME_EXCED_RANGE: too long filename(s)
396  */
397 BOOL WINAPI CreateDirectoryA( LPCSTR path,
398                                   LPSECURITY_ATTRIBUTES lpsecattribs )
399 {
400     DOS_FULL_NAME full_name;
401
402     TRACE_(file)("(%s,%p)\n", path, lpsecattribs );
403     if (DOSFS_GetDevice( path ))
404     {
405         TRACE_(file)("cannot use device '%s'!\n",path);
406         SetLastError( ERROR_ACCESS_DENIED );
407         return FALSE;
408     }
409     if (!DOSFS_GetFullName( path, FALSE, &full_name )) return 0;
410     if (mkdir( full_name.long_name, 0777 ) == -1) {
411         WARN_(file)("Error '%s' trying to create directory '%s'\n", strerror(errno), full_name.long_name);
412         /* the FILE_SetDosError() generated error codes don't match the
413          * CreateDirectory ones for some errnos */
414         switch (errno) {
415         case EEXIST: SetLastError(ERROR_ALREADY_EXISTS); break;
416         case ENOSPC: SetLastError(ERROR_DISK_FULL); break;
417         default: FILE_SetDosError();break;
418         }
419         return FALSE;
420     }
421     return TRUE;
422 }
423
424
425 /***********************************************************************
426  *           CreateDirectoryW   (KERNEL32.@)
427  */
428 BOOL WINAPI CreateDirectoryW( LPCWSTR path,
429                                   LPSECURITY_ATTRIBUTES lpsecattribs )
430 {
431     LPSTR xpath = HEAP_strdupWtoA( GetProcessHeap(), 0, path );
432     BOOL ret = CreateDirectoryA( xpath, lpsecattribs );
433     HeapFree( GetProcessHeap(), 0, xpath );
434     return ret;
435 }
436
437
438 /***********************************************************************
439  *           CreateDirectoryExA   (KERNEL32.@)
440  */
441 BOOL WINAPI CreateDirectoryExA( LPCSTR template, LPCSTR path,
442                                     LPSECURITY_ATTRIBUTES lpsecattribs)
443 {
444     return CreateDirectoryA(path,lpsecattribs);
445 }
446
447
448 /***********************************************************************
449  *           CreateDirectoryExW   (KERNEL32.@)
450  */
451 BOOL WINAPI CreateDirectoryExW( LPCWSTR template, LPCWSTR path,
452                                     LPSECURITY_ATTRIBUTES lpsecattribs)
453 {
454     return CreateDirectoryW(path,lpsecattribs);
455 }
456
457
458 /***********************************************************************
459  *           RemoveDirectory   (KERNEL.145)
460  */
461 BOOL16 WINAPI RemoveDirectory16( LPCSTR path )
462 {
463     return (BOOL16)RemoveDirectoryA( path );
464 }
465
466
467 /***********************************************************************
468  *           RemoveDirectoryA   (KERNEL32.@)
469  */
470 BOOL WINAPI RemoveDirectoryA( LPCSTR path )
471 {
472     DOS_FULL_NAME full_name;
473
474     TRACE_(file)("'%s'\n", path );
475
476     if (DOSFS_GetDevice( path ))
477     {
478         TRACE_(file)("cannot remove device '%s'!\n", path);
479         SetLastError( ERROR_FILE_NOT_FOUND );
480         return FALSE;
481     }
482     if (!DOSFS_GetFullName( path, TRUE, &full_name )) return FALSE;
483     if (rmdir( full_name.long_name ) == -1)
484     {
485         FILE_SetDosError();
486         return FALSE;
487     }
488     return TRUE;
489 }
490
491
492 /***********************************************************************
493  *           RemoveDirectoryW   (KERNEL32.@)
494  */
495 BOOL WINAPI RemoveDirectoryW( LPCWSTR path )
496 {
497     LPSTR xpath = HEAP_strdupWtoA( GetProcessHeap(), 0, path );
498     BOOL ret = RemoveDirectoryA( xpath );
499     HeapFree( GetProcessHeap(), 0, xpath );
500     return ret;
501 }
502
503
504 /***********************************************************************
505  *           DIR_TryPath
506  *
507  * Helper function for DIR_SearchPath.
508  */
509 static BOOL DIR_TryPath( const DOS_FULL_NAME *dir, LPCSTR name,
510                            DOS_FULL_NAME *full_name )
511 {
512     LPSTR p_l = full_name->long_name + strlen(dir->long_name) + 1;
513     LPSTR p_s = full_name->short_name + strlen(dir->short_name) + 1;
514
515     if ((p_s >= full_name->short_name + sizeof(full_name->short_name) - 14) ||
516         (p_l >= full_name->long_name + sizeof(full_name->long_name) - 1))
517     {
518         SetLastError( ERROR_PATH_NOT_FOUND );
519         return FALSE;
520     }
521     if (!DOSFS_FindUnixName( dir->long_name, name, p_l,
522                    sizeof(full_name->long_name) - (p_l - full_name->long_name),
523                    p_s, !(DRIVE_GetFlags(dir->drive) & DRIVE_CASE_SENSITIVE) ))
524         return FALSE;
525     strcpy( full_name->long_name, dir->long_name );
526     p_l[-1] = '/';
527     strcpy( full_name->short_name, dir->short_name );
528     p_s[-1] = '\\';
529     return TRUE;
530 }
531
532 static BOOL DIR_SearchSemicolonedPaths(LPCSTR name, DOS_FULL_NAME *full_name, LPSTR pathlist)
533 {
534     LPSTR next, buffer = NULL;
535     INT len = strlen(name), newlen, currlen = 0;
536     BOOL ret = FALSE;
537
538     next = pathlist;
539     while (!ret && next)
540     {
541         LPSTR cur = next;
542         while (*cur == ';') cur++;
543         if (!*cur) break;
544         next = strchr( cur, ';' );
545         if (next) *next++ = '\0';
546         newlen = strlen(cur) + len + 2;
547         if (newlen > currlen)
548         {
549             if (!(buffer = HeapReAlloc( GetProcessHeap(), 0, buffer, newlen)))
550                 goto done;
551             currlen = newlen;
552         }
553         strcpy( buffer, cur );
554         strcat( buffer, "\\" );
555         strcat( buffer, name );
556         ret = DOSFS_GetFullName( buffer, TRUE, full_name );
557     }
558 done:
559     HeapFree( GetProcessHeap(), 0, buffer );
560     return ret;
561 }
562
563
564 /***********************************************************************
565  *           DIR_TryEnvironmentPath
566  *
567  * Helper function for DIR_SearchPath.
568  * Search in the specified path, or in $PATH if NULL.
569  */
570 static BOOL DIR_TryEnvironmentPath( LPCSTR name, DOS_FULL_NAME *full_name, LPCSTR envpath )
571 {
572     LPSTR path;
573     BOOL ret = FALSE;
574     DWORD size;
575
576     size = envpath ? strlen(envpath)+1 : GetEnvironmentVariableA( "PATH", NULL, 0 );
577     if (!size) return FALSE;
578     if (!(path = HeapAlloc( GetProcessHeap(), 0, size ))) return FALSE;
579     if (envpath) strcpy( path, envpath );
580     else if (!GetEnvironmentVariableA( "PATH", path, size )) goto done;
581
582     ret = DIR_SearchSemicolonedPaths(name, full_name, path);
583
584 done:
585     HeapFree( GetProcessHeap(), 0, path );
586     return ret;
587 }
588
589
590 /***********************************************************************
591  *           DIR_TryModulePath
592  *
593  * Helper function for DIR_SearchPath.
594  */
595 static BOOL DIR_TryModulePath( LPCSTR name, DOS_FULL_NAME *full_name, BOOL win32 )
596 {
597     /* FIXME: for now, GetModuleFileNameA can't return more */
598     /* than OFS_MAXPATHNAME. This may change with Win32. */
599
600     char buffer[OFS_MAXPATHNAME];
601     LPSTR p;
602
603     if (!win32)
604     {
605         if (!GetCurrentTask()) return FALSE;
606         if (!GetModuleFileName16( GetCurrentTask(), buffer, sizeof(buffer) ))
607                 buffer[0]='\0';
608     } else {
609         if (!GetModuleFileNameA( 0, buffer, sizeof(buffer) ))
610                 buffer[0]='\0';
611     }
612     if (!(p = strrchr( buffer, '\\' ))) return FALSE;
613     if (sizeof(buffer) - (++p - buffer) <= strlen(name)) return FALSE;
614     strcpy( p, name );
615     return DOSFS_GetFullName( buffer, TRUE, full_name );
616 }
617
618
619 /***********************************************************************
620  *           DIR_TryAppPath
621  *
622  * Helper function for DIR_SearchPath.
623  */
624 static BOOL DIR_TryAppPath( LPCSTR name, DOS_FULL_NAME *full_name )
625 {
626     HKEY hkAppPaths, hkApp;
627     char lpAppName[MAX_PATHNAME_LEN], lpAppPaths[MAX_PATHNAME_LEN];
628     LPSTR lpFileName;
629     BOOL res = FALSE;
630     DWORD type, count;
631
632     if (RegOpenKeyA(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths", &hkAppPaths) != ERROR_SUCCESS)
633         return FALSE;
634
635     if (GetModuleFileNameA(0, lpAppName, sizeof(lpAppName)) == 0)
636     {
637         WARN("huh, module not found ??\n");
638         goto end;
639     }
640     lpFileName = strrchr(lpAppName, '\\');
641     if (!lpFileName)
642         goto end;
643     else lpFileName++; /* skip '\\' */
644     if (RegOpenKeyA(hkAppPaths, lpFileName, &hkApp) != ERROR_SUCCESS)
645         goto end;
646     count = sizeof(lpAppPaths);
647     if (RegQueryValueExA(hkApp, "Path", 0, &type, (LPBYTE)lpAppPaths, &count) != ERROR_SUCCESS)
648         goto end;
649     TRACE("successfully opened App Paths for '%s'\n", lpFileName);
650
651     res = DIR_SearchSemicolonedPaths(name, full_name, lpAppPaths);
652 end:
653     if (hkApp)
654         RegCloseKey(hkApp);
655     if (hkAppPaths)
656         RegCloseKey(hkAppPaths);
657     return res;
658 }
659
660 /***********************************************************************
661  *           DIR_SearchPath
662  *
663  * Implementation of SearchPathA. 'win32' specifies whether the search
664  * order is Win16 (module path last) or Win32 (module path first).
665  *
666  * FIXME: should return long path names.
667  */
668 DWORD DIR_SearchPath( LPCSTR path, LPCSTR name, LPCSTR ext,
669                       DOS_FULL_NAME *full_name, BOOL win32 )
670 {
671     LPCSTR p;
672     LPSTR tmp = NULL;
673     BOOL ret = TRUE;
674
675     /* First check the supplied parameters */
676
677     p = strrchr( name, '.' );
678     if (p && !strchr( p, '/' ) && !strchr( p, '\\' ))
679         ext = NULL;  /* Ignore the specified extension */
680     if (FILE_contains_path (name))
681         path = NULL;  /* Ignore path if name already contains a path */
682     if (path && !*path) path = NULL;  /* Ignore empty path */
683
684     /* Allocate a buffer for the file name and extension */
685
686     if (ext)
687     {
688         DWORD len = strlen(name) + strlen(ext);
689         if (!(tmp = HeapAlloc( GetProcessHeap(), 0, len + 1 )))
690         {
691             SetLastError( ERROR_OUTOFMEMORY );
692             return 0;
693         }
694         strcpy( tmp, name );
695         strcat( tmp, ext );
696         name = tmp;
697     }
698
699     /* If the name contains an explicit path, everything's easy */
700
701     if (FILE_contains_path(name))
702     {
703         ret = DOSFS_GetFullName( name, TRUE, full_name );
704         goto done;
705     }
706
707     /* Search in the specified path */
708
709     if (path)
710     {
711         ret = DIR_TryEnvironmentPath( name, full_name, path );
712         goto done;
713     }
714
715     /* Try the path of the current executable (for Win32 search order) */
716
717     if (win32 && DIR_TryModulePath( name, full_name, win32 )) goto done;
718
719     /* Try the current directory */
720
721     if (DOSFS_GetFullName( name, TRUE, full_name )) goto done;
722
723     /* Try the Windows system directory */
724
725     if (DIR_TryPath( &DIR_System, name, full_name ))
726         goto done;
727
728     /* Try the Windows directory */
729
730     if (DIR_TryPath( &DIR_Windows, name, full_name ))
731         goto done;
732
733     /* Try the path of the current executable (for Win16 search order) */
734
735     if (!win32 && DIR_TryModulePath( name, full_name, win32 )) goto done;
736
737     /* Try the "App Paths" entry if existing (undocumented ??) */
738     if (DIR_TryAppPath(name, full_name))
739         goto done;
740
741     /* Try all directories in path */
742
743     ret = DIR_TryEnvironmentPath( name, full_name, NULL );
744
745 done:
746     if (tmp) HeapFree( GetProcessHeap(), 0, tmp );
747     return ret;
748 }
749
750
751 /***********************************************************************
752  * SearchPathA [KERNEL32.@]
753  *
754  * Searches for a specified file in the search path.
755  *
756  * PARAMS
757  *    path      [I] Path to search
758  *    name      [I] Filename to search for.
759  *    ext       [I] File extension to append to file name. The first
760  *                  character must be a period. This parameter is
761  *                  specified only if the filename given does not
762  *                  contain an extension.
763  *    buflen    [I] size of buffer, in characters
764  *    buffer    [O] buffer for found filename
765  *    lastpart  [O] address of pointer to last used character in
766  *                  buffer (the final '\')
767  *
768  * RETURNS
769  *    Success: length of string copied into buffer, not including
770  *             terminating null character. If the filename found is
771  *             longer than the length of the buffer, the length of the
772  *             filename is returned.
773  *    Failure: Zero
774  *
775  * NOTES
776  *    If the file is not found, calls SetLastError(ERROR_FILE_NOT_FOUND)
777  *    (tested on NT 4.0)
778  */
779 DWORD WINAPI SearchPathA( LPCSTR path, LPCSTR name, LPCSTR ext, DWORD buflen,
780                             LPSTR buffer, LPSTR *lastpart )
781 {
782     LPSTR p, res;
783     DOS_FULL_NAME full_name;
784
785     if (!DIR_SearchPath( path, name, ext, &full_name, TRUE ))
786     {
787         SetLastError(ERROR_FILE_NOT_FOUND);
788         return 0;
789     }
790     lstrcpynA( buffer, full_name.short_name, buflen );
791     res = full_name.long_name +
792               strlen(DRIVE_GetRoot( full_name.short_name[0] - 'A' ));
793     while (*res == '/') res++;
794     if (buflen)
795     {
796         if (buflen > 3) lstrcpynA( buffer + 3, res, buflen - 3 );
797         for (p = buffer; *p; p++) if (*p == '/') *p = '\\';
798         if (lastpart) *lastpart = strrchr( buffer, '\\' ) + 1;
799     }
800     TRACE("Returning %d\n", strlen(res) + 3 );
801     return strlen(res) + 3;
802 }
803
804
805 /***********************************************************************
806  *           SearchPathW   (KERNEL32.@)
807  */
808 DWORD WINAPI SearchPathW( LPCWSTR path, LPCWSTR name, LPCWSTR ext,
809                             DWORD buflen, LPWSTR buffer, LPWSTR *lastpart )
810 {
811     LPWSTR p;
812     LPSTR res;
813     DOS_FULL_NAME full_name;
814
815     LPSTR pathA = HEAP_strdupWtoA( GetProcessHeap(), 0, path );
816     LPSTR nameA = HEAP_strdupWtoA( GetProcessHeap(), 0, name );
817     LPSTR extA  = HEAP_strdupWtoA( GetProcessHeap(), 0, ext );
818     DWORD ret = DIR_SearchPath( pathA, nameA, extA, &full_name, TRUE );
819     HeapFree( GetProcessHeap(), 0, extA );
820     HeapFree( GetProcessHeap(), 0, nameA );
821     HeapFree( GetProcessHeap(), 0, pathA );
822     if (!ret) return 0;
823
824     if (buflen > 0 && !MultiByteToWideChar( CP_ACP, 0, full_name.short_name, -1, buffer, buflen ))
825         buffer[buflen-1] = 0;
826     res = full_name.long_name +
827               strlen(DRIVE_GetRoot( full_name.short_name[0] - 'A' ));
828     while (*res == '/') res++;
829     if (buflen)
830     {
831         if (buflen > 3)
832         {
833             if (!MultiByteToWideChar( CP_ACP, 0, res, -1, buffer+3, buflen-3 ))
834                 buffer[buflen-1] = 0;
835         }
836         for (p = buffer; *p; p++) if (*p == '/') *p = '\\';
837         if (lastpart)
838         {
839             for (p = *lastpart = buffer; *p; p++)
840                 if (*p == '\\') *lastpart = p + 1;
841         }
842     }
843     return strlen(res) + 3;
844 }
845
846
847 /***********************************************************************
848  *           search_alternate_path
849  *
850  *
851  * FIXME: should return long path names.?
852  */
853 static BOOL search_alternate_path(LPCSTR dll_path, LPCSTR name, LPCSTR ext,
854                                   DOS_FULL_NAME *full_name)
855 {
856     LPCSTR p;
857     LPSTR tmp = NULL;
858     BOOL ret = TRUE;
859
860     /* First check the supplied parameters */
861
862     p = strrchr( name, '.' );
863     if (p && !strchr( p, '/' ) && !strchr( p, '\\' ))
864         ext = NULL;  /* Ignore the specified extension */
865
866     /* Allocate a buffer for the file name and extension */
867
868     if (ext)
869     {
870         DWORD len = strlen(name) + strlen(ext);
871         if (!(tmp = HeapAlloc( GetProcessHeap(), 0, len + 1 )))
872         {
873             SetLastError( ERROR_OUTOFMEMORY );
874             return 0;
875         }
876         strcpy( tmp, name );
877         strcat( tmp, ext );
878         name = tmp;
879     }
880
881     if (DIR_TryEnvironmentPath (name, full_name, dll_path))
882         ;
883     else if (DOSFS_GetFullName (name, TRUE, full_name)) /* current dir */
884         ;
885     else if (DIR_TryPath (&DIR_System, name, full_name)) /* System dir */
886         ;
887     else if (DIR_TryPath (&DIR_Windows, name, full_name)) /* Windows dir */
888         ;
889     else
890         ret = DIR_TryEnvironmentPath( name, full_name, NULL );
891
892     if (tmp) HeapFree( GetProcessHeap(), 0, tmp );
893     return ret;
894 }
895
896
897 /***********************************************************************
898  * DIR_SearchAlternatePath
899  *
900  * Searches for a specified file in the search path.
901  *
902  * PARAMS
903  *    dll_path  [I] Path to search
904  *    name      [I] Filename to search for.
905  *    ext       [I] File extension to append to file name. The first
906  *                  character must be a period. This parameter is
907  *                  specified only if the filename given does not
908  *                  contain an extension.
909  *    buflen    [I] size of buffer, in characters
910  *    buffer    [O] buffer for found filename
911  *    lastpart  [O] address of pointer to last used character in
912  *                  buffer (the final '\') (May be NULL)
913  *
914  * RETURNS
915  *    Success: length of string copied into buffer, not including
916  *             terminating null character. If the filename found is
917  *             longer than the length of the buffer, the length of the
918  *             filename is returned.
919  *    Failure: Zero
920  *
921  * NOTES
922  *    If the file is not found, calls SetLastError(ERROR_FILE_NOT_FOUND)
923  */
924 DWORD DIR_SearchAlternatePath( LPCSTR dll_path, LPCSTR name, LPCSTR ext,
925                                DWORD buflen, LPSTR buffer, LPSTR *lastpart )
926 {
927     LPSTR p, res;
928     DOS_FULL_NAME full_name;
929
930     if (!search_alternate_path( dll_path, name, ext, &full_name))
931     {
932         SetLastError(ERROR_FILE_NOT_FOUND);
933         return 0;
934     }
935     lstrcpynA( buffer, full_name.short_name, buflen );
936     res = full_name.long_name +
937               strlen(DRIVE_GetRoot( full_name.short_name[0] - 'A' ));
938     while (*res == '/') res++;
939     if (buflen)
940     {
941         if (buflen > 3) lstrcpynA( buffer + 3, res, buflen - 3 );
942         for (p = buffer; *p; p++) if (*p == '/') *p = '\\';
943         if (lastpart) *lastpart = strrchr( buffer, '\\' ) + 1;
944     }
945     TRACE("Returning %d\n", strlen(res) + 3 );
946     return strlen(res) + 3;
947 }