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