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