Fix subclassing to support nested messages.
[wine] / files / drive.c
1 /*
2  * DOS drives handling functions
3  *
4  * Copyright 1993 Erik Bos
5  * Copyright 1996 Alexandre Julliard
6  *
7  * Label & serial number read support.
8  *  (c) 1999 Petr Tomasek <tomasek@etf.cuni.cz>
9  *  (c) 2000 Andreas Mohr (changes)
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this library; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24  */
25
26 #include "config.h"
27 #include "wine/port.h"
28
29 #include <assert.h>
30 #include <ctype.h>
31 #include <string.h>
32 #include <stdarg.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <fcntl.h>
38 #include <errno.h>
39 #ifdef HAVE_UNISTD_H
40 # include <unistd.h>
41 #endif
42 #ifdef HAVE_SYS_STATVFS_H
43 # include <sys/statvfs.h>
44 #endif
45
46 #define NONAMELESSUNION
47 #define NONAMELESSSTRUCT
48 #include "ntstatus.h"
49 #include "windef.h"
50 #include "winbase.h"
51 #include "winreg.h"
52 #include "winternl.h"
53 #include "wine/winbase16.h"   /* for GetCurrentTask */
54 #include "winerror.h"
55 #include "winioctl.h"
56 #include "ntddstor.h"
57 #include "ntddcdrm.h"
58 #include "file.h"
59 #include "wine/unicode.h"
60 #include "wine/library.h"
61 #include "wine/server.h"
62 #include "wine/debug.h"
63
64 WINE_DEFAULT_DEBUG_CHANNEL(dosfs);
65 WINE_DECLARE_DEBUG_CHANNEL(file);
66
67 typedef struct
68 {
69     char     *root;      /* root dir in Unix format without trailing / */
70     LPWSTR    dos_cwd;   /* cwd in DOS format without leading or trailing \ */
71     char     *unix_cwd;  /* cwd in Unix format without leading or trailing / */
72     char     *device;    /* raw device path */
73     UINT      type;      /* drive type */
74     UINT      flags;     /* drive flags */
75     dev_t     dev;       /* unix device number */
76     ino_t     ino;       /* unix inode number */
77 } DOSDRIVE;
78
79
80 static const WCHAR DRIVE_Types[][8] =
81 {
82     { 0 }, /* DRIVE_UNKNOWN */
83     { 0 }, /* DRIVE_NO_ROOT_DIR */
84     {'f','l','o','p','p','y',0}, /* DRIVE_REMOVABLE */
85     {'h','d',0}, /* DRIVE_FIXED */
86     {'n','e','t','w','o','r','k',0}, /* DRIVE_REMOTE */
87     {'c','d','r','o','m',0}, /* DRIVE_CDROM */
88     {'r','a','m','d','i','s','k',0} /* DRIVE_RAMDISK */
89 };
90
91 #define MAX_DOS_DRIVES  26
92
93 static DOSDRIVE DOSDrives[MAX_DOS_DRIVES];
94 static int DRIVE_CurDrive = -1;
95
96 static HTASK16 DRIVE_LastTask = 0;
97
98 /* strdup on the process heap */
99 inline static char *heap_strdup( const char *str )
100 {
101     INT len = strlen(str) + 1;
102     LPSTR p = HeapAlloc( GetProcessHeap(), 0, len );
103     if (p) memcpy( p, str, len );
104     return p;
105 }
106
107 #define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1')
108
109 extern void CDROM_InitRegistry(int dev);
110
111 /***********************************************************************
112  *           DRIVE_GetDriveType
113  */
114 static inline UINT DRIVE_GetDriveType( INT drive, LPCWSTR value )
115 {
116     int i;
117
118     for (i = 0; i < sizeof(DRIVE_Types)/sizeof(DRIVE_Types[0]); i++)
119     {
120         if (!strcmpiW( value, DRIVE_Types[i] )) return i;
121     }
122     MESSAGE("Drive %c: unknown drive type %s, defaulting to 'hd'.\n",
123             'A' + drive, debugstr_w(value) );
124     return DRIVE_FIXED;
125 }
126
127
128 /***********************************************************************
129  *           DRIVE_Init
130  */
131 int DRIVE_Init(void)
132 {
133     int i, len, count = 0;
134     WCHAR driveW[] = {'M','a','c','h','i','n','e','\\','S','o','f','t','w','a','r','e','\\',
135                       'W','i','n','e','\\','W','i','n','e','\\',
136                       'C','o','n','f','i','g','\\','D','r','i','v','e',' ','A',0};
137     WCHAR drive_env[] = {'=','A',':',0};
138     WCHAR path[MAX_PATHNAME_LEN];
139     char tmp[MAX_PATHNAME_LEN*sizeof(WCHAR) + sizeof(KEY_VALUE_PARTIAL_INFORMATION)];
140     struct stat drive_stat_buffer;
141     WCHAR *p;
142     DOSDRIVE *drive;
143     HKEY hkey;
144     DWORD dummy;
145     OBJECT_ATTRIBUTES attr;
146     UNICODE_STRING nameW;
147
148     static const WCHAR PathW[] = {'P','a','t','h',0};
149     static const WCHAR TypeW[] = {'T','y','p','e',0};
150     static const WCHAR DeviceW[] = {'D','e','v','i','c','e',0};
151     static const WCHAR FailReadOnlyW[] = {'F','a','i','l','R','e','a','d','O','n','l','y',0};
152
153     attr.Length = sizeof(attr);
154     attr.RootDirectory = 0;
155     attr.ObjectName = &nameW;
156     attr.Attributes = 0;
157     attr.SecurityDescriptor = NULL;
158     attr.SecurityQualityOfService = NULL;
159
160     for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
161     {
162         RtlInitUnicodeString( &nameW, driveW );
163         nameW.Buffer[(nameW.Length / sizeof(WCHAR)) - 1] = 'A' + i;
164         if (NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr ) != STATUS_SUCCESS) continue;
165
166         /* Get the root path */
167         RtlInitUnicodeString( &nameW, PathW );
168         if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
169         {
170             WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
171             ExpandEnvironmentStringsW( data, path, sizeof(path)/sizeof(WCHAR) );
172
173             p = path + strlenW(path) - 1;
174             while ((p > path) && (*p == '/')) *p-- = '\0';
175
176             if (path[0] == '/')
177             {
178                 len = WideCharToMultiByte(CP_UNIXCP, 0, path, -1, NULL, 0, NULL, NULL);
179                 drive->root = HeapAlloc(GetProcessHeap(), 0, len);
180                 WideCharToMultiByte(CP_UNIXCP, 0, path, -1, drive->root, len, NULL, NULL);
181             }
182             else
183             {
184                 /* relative paths are relative to config dir */
185                 const char *config = wine_get_config_dir();
186                 len = strlen(config);
187                 len += WideCharToMultiByte(CP_UNIXCP, 0, path, -1, NULL, 0, NULL, NULL) + 2;
188                 drive->root = HeapAlloc( GetProcessHeap(), 0, len );
189                 len -= sprintf( drive->root, "%s/", config );
190                 WideCharToMultiByte(CP_UNIXCP, 0, path, -1,
191                                     drive->root + strlen(drive->root), len, NULL, NULL);
192             }
193
194             if (stat( drive->root, &drive_stat_buffer ))
195             {
196                 MESSAGE("Could not stat %s (%s), ignoring drive %c:\n",
197                         drive->root, strerror(errno), 'A' + i);
198                 HeapFree( GetProcessHeap(), 0, drive->root );
199                 drive->root = NULL;
200                 goto next;
201             }
202             if (!S_ISDIR(drive_stat_buffer.st_mode))
203             {
204                 MESSAGE("%s is not a directory, ignoring drive %c:\n",
205                         drive->root, 'A' + i );
206                 HeapFree( GetProcessHeap(), 0, drive->root );
207                 drive->root = NULL;
208                 goto next;
209             }
210
211             drive->dos_cwd  = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(drive->dos_cwd[0]));
212             drive->unix_cwd = heap_strdup( "" );
213             drive->device   = NULL;
214             drive->flags    = 0;
215             drive->dev      = drive_stat_buffer.st_dev;
216             drive->ino      = drive_stat_buffer.st_ino;
217
218             /* Get the drive type */
219             RtlInitUnicodeString( &nameW, TypeW );
220             if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
221             {
222                 WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
223                 drive->type = DRIVE_GetDriveType( i, data );
224             }
225             else drive->type = DRIVE_FIXED;
226
227             /* Get the device */
228             RtlInitUnicodeString( &nameW, DeviceW );
229             if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
230             {
231                 WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
232                 len = WideCharToMultiByte(CP_UNIXCP, 0, data, -1, NULL, 0, NULL, NULL);
233                 drive->device = HeapAlloc(GetProcessHeap(), 0, len);
234                 WideCharToMultiByte(CP_UNIXCP, 0, data, -1, drive->device, len, NULL, NULL);
235
236                 if (drive->type == DRIVE_CDROM)
237                 {
238                     int cd_fd;
239                     if ((cd_fd = open(drive->device, O_RDONLY|O_NONBLOCK)) != -1)
240                     {
241                         CDROM_InitRegistry(cd_fd);
242                         close(cd_fd);
243                     }
244                 }
245             }
246
247             /* Get the FailReadOnly flag */
248             RtlInitUnicodeString( &nameW, FailReadOnlyW );
249             if (!NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, tmp, sizeof(tmp), &dummy ))
250             {
251                 WCHAR *data = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)tmp)->Data;
252                 if (IS_OPTION_TRUE(data[0])) drive->flags |= DRIVE_FAIL_READ_ONLY;
253             }
254
255             /* Make the first hard disk the current drive */
256             if ((DRIVE_CurDrive == -1) && (drive->type == DRIVE_FIXED))
257                 DRIVE_CurDrive = i;
258
259             count++;
260             TRACE("Drive %c: path=%s type=%s flags=%08x dev=%x ino=%x\n",
261                   'A' + i, drive->root, debugstr_w(DRIVE_Types[drive->type]),
262                   drive->flags, (int)drive->dev, (int)drive->ino );
263         }
264
265     next:
266         NtClose( hkey );
267     }
268
269     if (!count)
270     {
271         MESSAGE("Warning: no valid DOS drive found, check your configuration file.\n" );
272         /* Create a C drive pointing to Unix root dir */
273         DOSDrives[2].root     = heap_strdup( "/" );
274         DOSDrives[2].dos_cwd  = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DOSDrives[2].dos_cwd[0]));
275         DOSDrives[2].unix_cwd = heap_strdup( "" );
276         DOSDrives[2].type     = DRIVE_FIXED;
277         DOSDrives[2].device   = NULL;
278         DOSDrives[2].flags    = 0;
279         DRIVE_CurDrive = 2;
280     }
281
282     /* Make sure the current drive is valid */
283     if (DRIVE_CurDrive == -1)
284     {
285         for (i = 0, drive = DOSDrives; i < MAX_DOS_DRIVES; i++, drive++)
286         {
287             if (drive->root)
288             {
289                 DRIVE_CurDrive = i;
290                 break;
291             }
292         }
293     }
294
295     /* get current working directory info for all drives */
296     for (i = 0; i < MAX_DOS_DRIVES; i++, drive_env[1]++)
297     {
298         if (!GetEnvironmentVariableW(drive_env, path, MAX_PATHNAME_LEN)) continue;
299         /* sanity check */
300         if (toupperW(path[0]) != drive_env[1] || path[1] != ':') continue;
301         DRIVE_Chdir( i, path + 2 );
302     }
303     return 1;
304 }
305
306
307 /***********************************************************************
308  *           DRIVE_IsValid
309  */
310 int DRIVE_IsValid( int drive )
311 {
312     if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
313     return (DOSDrives[drive].root != NULL);
314 }
315
316
317 /***********************************************************************
318  *           DRIVE_GetCurrentDrive
319  */
320 int DRIVE_GetCurrentDrive(void)
321 {
322     TDB *pTask = GlobalLock16(GetCurrentTask());
323     if (pTask && (pTask->curdrive & 0x80)) return pTask->curdrive & ~0x80;
324     return DRIVE_CurDrive;
325 }
326
327
328 /***********************************************************************
329  *           DRIVE_SetCurrentDrive
330  */
331 int DRIVE_SetCurrentDrive( int drive )
332 {
333     TDB *pTask = GlobalLock16(GetCurrentTask());
334     if (!DRIVE_IsValid( drive ))
335     {
336         SetLastError( ERROR_INVALID_DRIVE );
337         return 0;
338     }
339     TRACE("%c:\n", 'A' + drive );
340     DRIVE_CurDrive = drive;
341     if (pTask) pTask->curdrive = drive | 0x80;
342     return 1;
343 }
344
345
346 /***********************************************************************
347  *           DRIVE_FindDriveRoot
348  *
349  * Find a drive for which the root matches the beginning of the given path.
350  * This can be used to translate a Unix path into a drive + DOS path.
351  * Return value is the drive, or -1 on error. On success, path is modified
352  * to point to the beginning of the DOS path.
353  *
354  * Note: path must be in the encoding of the underlying Unix file system.
355  */
356 int DRIVE_FindDriveRoot( const char **path )
357 {
358     /* Starting with the full path, check if the device and inode match any of
359      * the wine 'drives'. If not then remove the last path component and try
360      * again. If the last component was a '..' then skip a normal component
361      * since it's a directory that's ascended back out of.
362      */
363     int drive, level, len;
364     char buffer[MAX_PATHNAME_LEN];
365     char *p;
366     struct stat st;
367
368     strcpy( buffer, *path );
369     for (p = buffer; *p; p++) if (*p == '\\') *p = '/';
370     len = p - buffer;
371
372     /* strip off trailing slashes */
373     while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
374
375     for (;;)
376     {
377         /* Find the drive */
378         if (stat( buffer, &st ) == 0 && S_ISDIR( st.st_mode ))
379         {
380             for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
381             {
382                if (!DOSDrives[drive].root) continue;
383
384                if ((DOSDrives[drive].dev == st.st_dev) &&
385                    (DOSDrives[drive].ino == st.st_ino))
386                {
387                    if (len == 1) len = 0;  /* preserve root slash in returned path */
388                    TRACE( "%s -> drive %c:, root='%s', name='%s'\n",
389                        *path, 'A' + drive, buffer, *path + len);
390                    *path += len;
391                    if (!**path) *path = "\\";
392                    return drive;
393                }
394             }
395         }
396         if (len <= 1) return -1;  /* reached root */
397
398         level = 0;
399         while (level < 1)
400         {
401             /* find start of the last path component */
402             while (len > 1 && buffer[len - 1] != '/') len--;
403             if (!buffer[len]) break;  /* empty component -> reached root */
404             /* does removing it take us up a level? */
405             if (strcmp( buffer + len, "." ) != 0)
406                 level += strcmp( buffer + len, ".." ) ? 1 : -1;
407             buffer[len] = 0;
408             /* strip off trailing slashes */
409             while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
410         }
411     }
412 }
413
414
415 /***********************************************************************
416  *           DRIVE_FindDriveRootW
417  *
418  * Unicode version of DRIVE_FindDriveRoot.
419  */
420 int DRIVE_FindDriveRootW( LPCWSTR *path )
421 {
422     int drive, level, len;
423     WCHAR buffer[MAX_PATHNAME_LEN];
424     WCHAR *p;
425     struct stat st;
426
427     strcpyW( buffer, *path );
428     for (p = buffer; *p; p++) if (*p == '\\') *p = '/';
429     len = p - buffer;
430
431     /* strip off trailing slashes */
432     while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
433
434     for (;;)
435     {
436         char buffA[MAX_PATHNAME_LEN];
437
438         WideCharToMultiByte( CP_UNIXCP, 0, buffer, -1, buffA, sizeof(buffA), NULL, NULL );
439         if (stat( buffA, &st ) == 0 && S_ISDIR( st.st_mode ))
440         {
441             /* Find the drive */
442             for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
443             {
444                 if (!DOSDrives[drive].root) continue;
445
446                 if ((DOSDrives[drive].dev == st.st_dev) &&
447                     (DOSDrives[drive].ino == st.st_ino))
448                 {
449                     static const WCHAR rootW[] = {'\\',0};
450
451                     if (len == 1) len = 0;  /* preserve root slash in returned path */
452                     TRACE( "%s -> drive %c:, root=%s, name=%s\n",
453                            debugstr_w(*path), 'A' + drive, debugstr_w(buffer), debugstr_w(*path + len));
454                     *path += len;
455                     if (!**path) *path = rootW;
456                     return drive;
457                 }
458             }
459         }
460         if (len <= 1) return -1;  /* reached root */
461
462         level = 0;
463         while (level < 1)
464         {
465             static const WCHAR dotW[] = {'.',0};
466             static const WCHAR dotdotW[] = {'.','.',0};
467
468             /* find start of the last path component */
469             while (len > 1 && buffer[len - 1] != '/') len--;
470             if (!buffer[len]) break;  /* empty component -> reached root */
471             /* does removing it take us up a level? */
472             if (strcmpW( buffer + len, dotW ) != 0)
473                 level += strcmpW( buffer + len, dotdotW ) ? 1 : -1;
474             buffer[len] = 0;
475             /* strip off trailing slashes */
476             while (len > 1 && buffer[len - 1] == '/') buffer[--len] = 0;
477         }
478     }
479 }
480
481
482 /***********************************************************************
483  *           DRIVE_GetRoot
484  */
485 const char * DRIVE_GetRoot( int drive )
486 {
487     if (!DRIVE_IsValid( drive )) return NULL;
488     return DOSDrives[drive].root;
489 }
490
491
492 /***********************************************************************
493  *           DRIVE_GetDosCwd
494  */
495 LPCWSTR DRIVE_GetDosCwd( int drive )
496 {
497     TDB *pTask = GlobalLock16(GetCurrentTask());
498     if (!DRIVE_IsValid( drive )) return NULL;
499
500     /* Check if we need to change the directory to the new task. */
501     if (pTask && (pTask->curdrive & 0x80) &&    /* The task drive is valid */
502         ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
503         (DRIVE_LastTask != GetCurrentTask()))   /* and the task changed */
504     {
505         static const WCHAR rootW[] = {'\\',0};
506         WCHAR curdirW[MAX_PATH];
507         MultiByteToWideChar(CP_ACP, 0, pTask->curdir, -1, curdirW, MAX_PATH);
508         /* Perform the task-switch */
509         if (!DRIVE_Chdir( drive, curdirW )) DRIVE_Chdir( drive, rootW );
510         DRIVE_LastTask = GetCurrentTask();
511     }
512     return DOSDrives[drive].dos_cwd;
513 }
514
515
516 /***********************************************************************
517  *           DRIVE_GetUnixCwd
518  */
519 const char * DRIVE_GetUnixCwd( int drive )
520 {
521     TDB *pTask = GlobalLock16(GetCurrentTask());
522     if (!DRIVE_IsValid( drive )) return NULL;
523
524     /* Check if we need to change the directory to the new task. */
525     if (pTask && (pTask->curdrive & 0x80) &&    /* The task drive is valid */
526         ((pTask->curdrive & ~0x80) == drive) && /* and it's the one we want */
527         (DRIVE_LastTask != GetCurrentTask()))   /* and the task changed */
528     {
529         static const WCHAR rootW[] = {'\\',0};
530         WCHAR curdirW[MAX_PATH];
531         MultiByteToWideChar(CP_ACP, 0, pTask->curdir, -1, curdirW, MAX_PATH);
532         /* Perform the task-switch */
533         if (!DRIVE_Chdir( drive, curdirW )) DRIVE_Chdir( drive, rootW );
534         DRIVE_LastTask = GetCurrentTask();
535     }
536     return DOSDrives[drive].unix_cwd;
537 }
538
539
540 /***********************************************************************
541  *           DRIVE_GetDevice
542  */
543 const char * DRIVE_GetDevice( int drive )
544 {
545     return (DRIVE_IsValid( drive )) ? DOSDrives[drive].device : NULL;
546 }
547
548 /***********************************************************************
549  *           DRIVE_GetType
550  */
551 static UINT DRIVE_GetType( int drive )
552 {
553     if (!DRIVE_IsValid( drive )) return DRIVE_NO_ROOT_DIR;
554     return DOSDrives[drive].type;
555 }
556
557
558 /***********************************************************************
559  *           DRIVE_GetFlags
560  */
561 UINT DRIVE_GetFlags( int drive )
562 {
563     if ((drive < 0) || (drive >= MAX_DOS_DRIVES)) return 0;
564     return DOSDrives[drive].flags;
565 }
566
567 /***********************************************************************
568  *           DRIVE_Chdir
569  */
570 int DRIVE_Chdir( int drive, LPCWSTR path )
571 {
572     DOS_FULL_NAME full_name;
573     WCHAR buffer[MAX_PATHNAME_LEN];
574     LPSTR unix_cwd;
575     BY_HANDLE_FILE_INFORMATION info;
576     TDB *pTask = GlobalLock16(GetCurrentTask());
577
578     buffer[0] = 'A' + drive;
579     buffer[1] = ':';
580     buffer[2] = 0;
581     TRACE("(%s,%s)\n", debugstr_w(buffer), debugstr_w(path) );
582     strncpyW( buffer + 2, path, MAX_PATHNAME_LEN - 2 );
583     buffer[MAX_PATHNAME_LEN - 1] = 0; /* ensure 0 termination */
584
585     if (!DOSFS_GetFullName( buffer, TRUE, &full_name )) return 0;
586     if (!FILE_Stat( full_name.long_name, &info, NULL )) return 0;
587     if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
588     {
589         SetLastError( ERROR_FILE_NOT_FOUND );
590         return 0;
591     }
592     unix_cwd = full_name.long_name + strlen( DOSDrives[drive].root );
593     while (*unix_cwd == '/') unix_cwd++;
594
595     TRACE("(%c:): unix_cwd=%s dos_cwd=%s\n",
596             'A' + drive, unix_cwd, debugstr_w(full_name.short_name + 3) );
597
598     HeapFree( GetProcessHeap(), 0, DOSDrives[drive].dos_cwd );
599     HeapFree( GetProcessHeap(), 0, DOSDrives[drive].unix_cwd );
600     DOSDrives[drive].dos_cwd  = HeapAlloc(GetProcessHeap(), 0, (strlenW(full_name.short_name) - 2) * sizeof(WCHAR));
601     strcpyW(DOSDrives[drive].dos_cwd, full_name.short_name + 3);
602     DOSDrives[drive].unix_cwd = heap_strdup( unix_cwd );
603
604     if (drive == DRIVE_CurDrive)
605     {
606         UNICODE_STRING dirW;
607
608         RtlInitUnicodeString( &dirW, full_name.short_name );
609         RtlSetCurrentDirectory_U( &dirW );
610     }
611
612     if (pTask && (pTask->curdrive & 0x80) &&
613         ((pTask->curdrive & ~0x80) == drive))
614     {
615         WideCharToMultiByte(CP_ACP, 0, full_name.short_name + 2, -1,
616                             pTask->curdir, sizeof(pTask->curdir), NULL, NULL);
617         DRIVE_LastTask = GetCurrentTask();
618     }
619     return 1;
620 }
621
622
623 /***********************************************************************
624  *           DefineDosDeviceA       (KERNEL32.@)
625  */
626 BOOL WINAPI DefineDosDeviceA(DWORD flags,LPCSTR devname,LPCSTR targetpath)
627 {
628     UNICODE_STRING d, t;
629     BOOL           ret;
630
631     if (!RtlCreateUnicodeStringFromAsciiz(&d, devname))
632     {
633         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
634         return FALSE;
635     }
636     if (!RtlCreateUnicodeStringFromAsciiz(&t, targetpath))
637     {
638         RtlFreeUnicodeString(&d);
639         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
640         return FALSE;
641     }
642     ret = DefineDosDeviceW(flags, d.Buffer, t.Buffer);
643     RtlFreeUnicodeString(&d);
644     RtlFreeUnicodeString(&t);
645     return ret;
646 }
647
648
649 /***********************************************************************
650  *           DefineDosDeviceA       (KERNEL32.@)
651  */
652 BOOL WINAPI DefineDosDeviceW(DWORD flags,LPCWSTR devname,LPCWSTR targetpath) 
653 {
654     DOSDRIVE *old, *new;
655
656     /* this is a temporary hack for int21 support. better implementation has to be done */
657     if (flags != DDD_RAW_TARGET_PATH ||
658         !(toupperW(devname[0]) >= 'A' && toupperW(devname[0]) <= 'Z') ||
659         devname[1] != ':' || devname[2] != 0 ||
660         !(toupperW(targetpath[0]) >= 'A' && toupperW(targetpath[0]) <= 'Z') ||
661         targetpath[1] != ':' || targetpath[2] != '\\' || targetpath[3] != 0)
662     {
663         FIXME("(0x%08lx,%s,%s),stub!\n", flags, debugstr_w(devname), debugstr_w(targetpath));
664         SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
665         return FALSE;
666     }
667
668     old = DOSDrives + devname[0] - 'A';
669     new = DOSDrives + targetpath[0] - 'A';
670
671     if (!old->root)
672     {
673         SetLastError( ERROR_INVALID_DRIVE );
674         return 0;
675     }
676
677     if ( new->root )
678     {
679         TRACE("Can't map drive %c: to already existing drive %c:\n",
680               devname[0], targetpath[0] );
681         /* it is already mapped there, so return success */
682         if (!strcmp(old->root,new->root))
683             return 1;
684         return 0;
685     }
686
687     new->root     = heap_strdup( old->root );
688     new->dos_cwd  = HeapAlloc(GetProcessHeap(), 0, (strlenW(old->dos_cwd) + 1) * sizeof(WCHAR));
689     strcpyW(new->dos_cwd, old->dos_cwd);
690     new->unix_cwd = heap_strdup( old->unix_cwd );
691     new->device   = heap_strdup( old->device );
692     new->type = old->type;
693     new->flags = old->flags;
694     new->dev = old->dev;
695     new->ino = old->ino;
696
697     TRACE("Drive %c: is now equal to drive %c:\n",
698           targetpath[0], devname[0] );
699
700     return 1;
701 }
702
703
704 /***********************************************************************
705  *           DRIVE_GetFreeSpace
706  */
707 static int DRIVE_GetFreeSpace( int drive, PULARGE_INTEGER size,
708                                PULARGE_INTEGER available )
709 {
710     struct statvfs info;
711
712     if (!DRIVE_IsValid(drive))
713     {
714         SetLastError( ERROR_PATH_NOT_FOUND );
715         return 0;
716     }
717
718     if (statvfs( DOSDrives[drive].root, &info ) < 0)
719     {
720         FILE_SetDosError();
721         WARN("cannot do statvfs(%s)\n", DOSDrives[drive].root);
722         return 0;
723     }
724     size->QuadPart = RtlEnlargedUnsignedMultiply( info.f_frsize, info.f_blocks );
725     if (DOSDrives[drive].type == DRIVE_CDROM)
726         available->QuadPart = 0; /* ALWAYS 0, even if no real CD-ROM mounted there !! */
727     else
728         available->QuadPart = RtlEnlargedUnsignedMultiply( info.f_frsize, info.f_bavail );
729
730     return 1;
731 }
732
733 /***********************************************************************
734  *       DRIVE_GetCurrentDirectory
735  * Returns "X:\\path\\etc\\".
736  *
737  * Despite the API description, return required length including the
738  * terminating null when buffer too small. This is the real behaviour.
739 */
740 static UINT DRIVE_GetCurrentDirectory( UINT buflen, LPWSTR buf )
741 {
742     UINT ret;
743     LPCWSTR dos_cwd = DRIVE_GetDosCwd( DRIVE_GetCurrentDrive() );
744     static const WCHAR driveA_rootW[] = {'A',':','\\',0};
745
746     ret = strlenW(dos_cwd) + 3; /* length of WHOLE current directory */
747     if (ret >= buflen) return ret + 1;
748
749     strcpyW( buf, driveA_rootW );
750     buf[0] += DRIVE_GetCurrentDrive();
751     strcatW( buf, dos_cwd );
752     return ret;
753 }
754
755
756 /***********************************************************************
757  *           DRIVE_BuildEnv
758  *
759  * Build the environment array containing the drives' current directories.
760  * Resulting pointer must be freed with HeapFree.
761  */
762 WCHAR *DRIVE_BuildEnv(void)
763 {
764     int i, length = 0;
765     LPCWSTR cwd[MAX_DOS_DRIVES];
766     WCHAR *env, *p;
767
768     for (i = 0; i < MAX_DOS_DRIVES; i++)
769     {
770         if ((cwd[i] = DRIVE_GetDosCwd(i)) && cwd[i][0])
771             length += strlenW(cwd[i]) + 8;
772     }
773     if (!(env = HeapAlloc( GetProcessHeap(), 0, (length+1) * sizeof(WCHAR) ))) return NULL;
774     for (i = 0, p = env; i < MAX_DOS_DRIVES; i++)
775     {
776         if (cwd[i] && cwd[i][0])
777         {
778             *p++ = '='; *p++ = 'A' + i; *p++ = ':';
779             *p++ = '='; *p++ = 'A' + i; *p++ = ':'; *p++ = '\\';
780             strcpyW( p, cwd[i] );
781             p += strlenW(p) + 1;
782         }
783     }
784     *p = 0;
785     return env;
786 }
787
788
789 /***********************************************************************
790  *           GetDiskFreeSpaceW   (KERNEL32.@)
791  *
792  * Fails if expression resulting from current drive's dir and "root"
793  * is not a root dir of the target drive.
794  *
795  * UNDOC: setting some LPDWORDs to NULL is perfectly possible
796  * if the corresponding info is unneeded.
797  *
798  * FIXME: needs to support UNC names from Win95 OSR2 on.
799  *
800  * Behaviour under Win95a:
801  * CurrDir     root   result
802  * "E:\\TEST"  "E:"   FALSE
803  * "E:\\"      "E:"   TRUE
804  * "E:\\"      "E"    FALSE
805  * "E:\\"      "\\"   TRUE
806  * "E:\\TEST"  "\\"   TRUE
807  * "E:\\TEST"  ":\\"  FALSE
808  * "E:\\TEST"  "E:\\" TRUE
809  * "E:\\TEST"  ""     FALSE
810  * "E:\\"      ""     FALSE (!)
811  * "E:\\"      0x0    TRUE
812  * "E:\\TEST"  0x0    TRUE  (!)
813  * "E:\\TEST"  "C:"   TRUE  (when CurrDir of "C:" set to "\\")
814  * "E:\\TEST"  "C:"   FALSE (when CurrDir of "C:" set to "\\TEST")
815  */
816 BOOL WINAPI GetDiskFreeSpaceW( LPCWSTR root, LPDWORD cluster_sectors,
817                                    LPDWORD sector_bytes, LPDWORD free_clusters,
818                                    LPDWORD total_clusters )
819 {
820     int drive, sec_size;
821     ULARGE_INTEGER size,available;
822     LPCWSTR path;
823     DWORD cluster_sec;
824
825     TRACE("%s,%p,%p,%p,%p\n", debugstr_w(root), cluster_sectors, sector_bytes,
826           free_clusters, total_clusters);
827
828     if (!root || root[0] == '\\' || root[0] == '/')
829         drive = DRIVE_GetCurrentDrive();
830     else
831     if (root[0] && root[1] == ':') /* root contains drive tag */
832     {
833         drive = toupperW(root[0]) - 'A';
834         path = &root[2];
835         if (path[0] == '\0')
836         {
837             path = DRIVE_GetDosCwd(drive);
838             if (!path)
839             {
840                 SetLastError(ERROR_PATH_NOT_FOUND);
841                 return FALSE;
842             }
843         }
844         else
845         if (path[0] == '\\')
846             path++;
847
848         if (path[0]) /* oops, we are in a subdir */
849         {
850             SetLastError(ERROR_INVALID_NAME);
851             return FALSE;
852         }
853     }
854     else
855     {
856         if (!root[0])
857             SetLastError(ERROR_PATH_NOT_FOUND);
858         else
859             SetLastError(ERROR_INVALID_NAME);
860         return FALSE;
861     }
862
863     if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
864
865     /* Cap the size and available at 2GB as per specs.  */
866     if ((size.u.HighPart) ||(size.u.LowPart > 0x7fffffff))
867     {
868         size.u.HighPart = 0;
869         size.u.LowPart = 0x7fffffff;
870     }
871     if ((available.u.HighPart) ||(available.u.LowPart > 0x7fffffff))
872     {
873         available.u.HighPart =0;
874         available.u.LowPart = 0x7fffffff;
875     }
876     sec_size = (DRIVE_GetType(drive)==DRIVE_CDROM) ? 2048 : 512;
877     size.u.LowPart            /= sec_size;
878     available.u.LowPart       /= sec_size;
879     /* FIXME: probably have to adjust those variables too for CDFS */
880     cluster_sec = 1;
881     while (cluster_sec * 65536 < size.u.LowPart) cluster_sec *= 2;
882
883     if (cluster_sectors)
884         *cluster_sectors = cluster_sec;
885     if (sector_bytes)
886         *sector_bytes    = sec_size;
887     if (free_clusters)
888         *free_clusters   = available.u.LowPart / cluster_sec;
889     if (total_clusters)
890         *total_clusters  = size.u.LowPart / cluster_sec;
891     return TRUE;
892 }
893
894
895 /***********************************************************************
896  *           GetDiskFreeSpaceA   (KERNEL32.@)
897  */
898 BOOL WINAPI GetDiskFreeSpaceA( LPCSTR root, LPDWORD cluster_sectors,
899                                    LPDWORD sector_bytes, LPDWORD free_clusters,
900                                    LPDWORD total_clusters )
901 {
902     UNICODE_STRING rootW;
903     BOOL ret = FALSE;
904
905     if (root)
906     {
907         if(!RtlCreateUnicodeStringFromAsciiz(&rootW, root))
908         {
909             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
910             return FALSE;
911         }
912     }
913     else
914         rootW.Buffer = NULL;
915
916     ret = GetDiskFreeSpaceW(rootW.Buffer, cluster_sectors, sector_bytes,
917                             free_clusters, total_clusters );
918     RtlFreeUnicodeString(&rootW);
919
920     return ret;
921 }
922
923
924 /***********************************************************************
925  *           GetDiskFreeSpaceExW   (KERNEL32.@)
926  *
927  *  This function is used to acquire the size of the available and
928  *  total space on a logical volume.
929  *
930  * RETURNS
931  *
932  *  Zero on failure, nonzero upon success. Use GetLastError to obtain
933  *  detailed error information.
934  *
935  */
936 BOOL WINAPI GetDiskFreeSpaceExW( LPCWSTR root,
937                                      PULARGE_INTEGER avail,
938                                      PULARGE_INTEGER total,
939                                      PULARGE_INTEGER totalfree)
940 {
941     int drive;
942     ULARGE_INTEGER size,available;
943
944     if (!root) drive = DRIVE_GetCurrentDrive();
945     else
946     { /* C: always works for GetDiskFreeSpaceEx */
947         if ((root[1]) && ((root[1] != ':') || (root[2] && root[2] != '\\')))
948         {
949             FIXME("there are valid root names which are not supported yet\n");
950             /* ..like UNC names, for instance. */
951
952             WARN("invalid root '%s'\n", debugstr_w(root));
953             return FALSE;
954         }
955         drive = toupperW(root[0]) - 'A';
956     }
957
958     if (!DRIVE_GetFreeSpace(drive, &size, &available)) return FALSE;
959
960     if (total)
961     {
962         total->u.HighPart = size.u.HighPart;
963         total->u.LowPart = size.u.LowPart;
964     }
965
966     if (totalfree)
967     {
968         totalfree->u.HighPart = available.u.HighPart;
969         totalfree->u.LowPart = available.u.LowPart;
970     }
971
972     if (avail)
973     {
974         if (FIXME_ON(dosfs))
975         {
976             /* On Windows2000, we need to check the disk quota
977                allocated for the user owning the calling process. We
978                don't want to be more obtrusive than necessary with the
979                FIXME messages, so don't print the FIXME unless Wine is
980                actually masquerading as Windows2000. */
981
982             RTL_OSVERSIONINFOEXW ovi;
983             ovi.dwOSVersionInfoSize = sizeof(ovi);
984             if (RtlGetVersion(&ovi))
985             {
986               if (ovi.dwPlatformId == VER_PLATFORM_WIN32_NT && ovi.dwMajorVersion > 4)
987                   FIXME("no per-user quota support yet\n");
988             }
989         }
990
991         /* Quick hack, should eventually be fixed to work 100% with
992            Windows2000 (see comment above). */
993         avail->u.HighPart = available.u.HighPart;
994         avail->u.LowPart = available.u.LowPart;
995     }
996
997     return TRUE;
998 }
999
1000 /***********************************************************************
1001  *           GetDiskFreeSpaceExA   (KERNEL32.@)
1002  */
1003 BOOL WINAPI GetDiskFreeSpaceExA( LPCSTR root, PULARGE_INTEGER avail,
1004                                      PULARGE_INTEGER total,
1005                                      PULARGE_INTEGER  totalfree)
1006 {
1007     UNICODE_STRING rootW;
1008     BOOL ret;
1009
1010     if (root) RtlCreateUnicodeStringFromAsciiz(&rootW, root);
1011     else rootW.Buffer = NULL;
1012
1013     ret = GetDiskFreeSpaceExW( rootW.Buffer, avail, total, totalfree);
1014
1015     RtlFreeUnicodeString(&rootW);
1016     return ret;
1017 }
1018
1019 /***********************************************************************
1020  *           GetDriveTypeW   (KERNEL32.@)
1021  *
1022  * Returns the type of the disk drive specified. If root is NULL the
1023  * root of the current directory is used.
1024  *
1025  * RETURNS
1026  *
1027  *  Type of drive (from Win32 SDK):
1028  *
1029  *   DRIVE_UNKNOWN     unable to find out anything about the drive
1030  *   DRIVE_NO_ROOT_DIR nonexistent root dir
1031  *   DRIVE_REMOVABLE   the disk can be removed from the machine
1032  *   DRIVE_FIXED       the disk can not be removed from the machine
1033  *   DRIVE_REMOTE      network disk
1034  *   DRIVE_CDROM       CDROM drive
1035  *   DRIVE_RAMDISK     virtual disk in RAM
1036  */
1037 UINT WINAPI GetDriveTypeW(LPCWSTR root) /* [in] String describing drive */
1038 {
1039     int drive;
1040     TRACE("(%s)\n", debugstr_w(root));
1041
1042     if (NULL == root) drive = DRIVE_GetCurrentDrive();
1043     else
1044     {
1045         if ((root[1]) && (root[1] != ':'))
1046         {
1047             WARN("invalid root %s\n", debugstr_w(root));
1048             return DRIVE_NO_ROOT_DIR;
1049         }
1050         drive = toupperW(root[0]) - 'A';
1051     }
1052     return DRIVE_GetType(drive);
1053 }
1054
1055
1056 /***********************************************************************
1057  *           GetDriveTypeA   (KERNEL32.@)
1058  */
1059 UINT WINAPI GetDriveTypeA( LPCSTR root )
1060 {
1061     UNICODE_STRING rootW;
1062     UINT ret = 0;
1063
1064     if (root)
1065     {
1066         if( !RtlCreateUnicodeStringFromAsciiz(&rootW, root))
1067         {
1068             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1069             return 0;
1070         }
1071     }
1072     else
1073         rootW.Buffer = NULL;
1074
1075     ret = GetDriveTypeW(rootW.Buffer);
1076
1077     RtlFreeUnicodeString(&rootW);
1078     return ret;
1079
1080 }
1081
1082
1083 /***********************************************************************
1084  *           GetCurrentDirectory   (KERNEL.411)
1085  */
1086 UINT16 WINAPI GetCurrentDirectory16( UINT16 buflen, LPSTR buf )
1087 {
1088     WCHAR cur_dirW[MAX_PATH];
1089
1090     DRIVE_GetCurrentDirectory(MAX_PATH, cur_dirW);
1091     return (UINT16)WideCharToMultiByte(CP_ACP, 0, cur_dirW, -1, buf, buflen, NULL, NULL);
1092 }
1093
1094
1095 /***********************************************************************
1096  *           GetCurrentDirectoryW   (KERNEL32.@)
1097  */
1098 UINT WINAPI GetCurrentDirectoryW( UINT buflen, LPWSTR buf )
1099 {
1100     UINT ret;
1101     WCHAR longname[MAX_PATHNAME_LEN];
1102     WCHAR shortname[MAX_PATHNAME_LEN];
1103
1104     ret = DRIVE_GetCurrentDirectory(MAX_PATHNAME_LEN, shortname);
1105     if ( ret > MAX_PATHNAME_LEN ) {
1106       ERR_(file)("pathnamelength (%d) > MAX_PATHNAME_LEN!\n", ret );
1107       return ret;
1108     }
1109     GetLongPathNameW(shortname, longname, MAX_PATHNAME_LEN);
1110     ret = strlenW( longname ) + 1;
1111     if (ret > buflen) return ret;
1112     strcpyW(buf, longname);
1113     return ret - 1;
1114 }
1115
1116 /***********************************************************************
1117  *           GetCurrentDirectoryA   (KERNEL32.@)
1118  */
1119 UINT WINAPI GetCurrentDirectoryA( UINT buflen, LPSTR buf )
1120 {
1121     WCHAR bufferW[MAX_PATH];
1122     DWORD ret, retW;
1123
1124     retW = GetCurrentDirectoryW(MAX_PATH, bufferW);
1125
1126     if (!retW)
1127         ret = 0;
1128     else if (retW > MAX_PATH)
1129     {
1130         SetLastError(ERROR_FILENAME_EXCED_RANGE);
1131         ret = 0;
1132     }
1133     else
1134     {
1135         ret = WideCharToMultiByte(CP_ACP, 0, bufferW, -1, NULL, 0, NULL, NULL);
1136         if (buflen >= ret)
1137         {
1138             WideCharToMultiByte(CP_ACP, 0, bufferW, -1, buf, buflen, NULL, NULL);
1139             ret--; /* length without 0 */
1140         }
1141     }
1142     return ret;
1143 }
1144
1145
1146 /***********************************************************************
1147  *           SetCurrentDirectoryW   (KERNEL32.@)
1148  */
1149 BOOL WINAPI SetCurrentDirectoryW( LPCWSTR dir )
1150 {
1151     int drive, olddrive = DRIVE_GetCurrentDrive();
1152
1153     if (!dir)
1154     {
1155         SetLastError(ERROR_INVALID_PARAMETER);
1156         return FALSE;
1157     }
1158     if (dir[0] && (dir[1]==':'))
1159     {
1160         drive = toupperW( *dir ) - 'A';
1161         dir += 2;
1162     }
1163     else
1164         drive = olddrive;
1165
1166     /* WARNING: we need to set the drive before the dir, as DRIVE_Chdir
1167        sets pTask->curdir only if pTask->curdrive is drive */
1168     if (!(DRIVE_SetCurrentDrive( drive )))
1169         return FALSE;
1170
1171     /* FIXME: what about empty strings? Add a \\ ? */
1172     if (!DRIVE_Chdir( drive, dir )) {
1173         DRIVE_SetCurrentDrive(olddrive);
1174         return FALSE;
1175     }
1176     return TRUE;
1177 }
1178
1179
1180 /***********************************************************************
1181  *           SetCurrentDirectoryA   (KERNEL32.@)
1182  */
1183 BOOL WINAPI SetCurrentDirectoryA( LPCSTR dir )
1184 {
1185     UNICODE_STRING dirW;
1186     BOOL ret = FALSE;
1187
1188     if (!dir)
1189     {
1190         SetLastError(ERROR_INVALID_PARAMETER);
1191         return FALSE;
1192     }
1193
1194     if (RtlCreateUnicodeStringFromAsciiz(&dirW, dir))
1195     {
1196         ret = SetCurrentDirectoryW(dirW.Buffer);
1197         RtlFreeUnicodeString(&dirW);
1198     }
1199     else
1200         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1201     return ret;
1202 }
1203
1204
1205 /***********************************************************************
1206  *           GetLogicalDriveStringsA   (KERNEL32.@)
1207  */
1208 UINT WINAPI GetLogicalDriveStringsA( UINT len, LPSTR buffer )
1209 {
1210     int drive, count;
1211
1212     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1213         if (DRIVE_IsValid(drive)) count++;
1214     if ((count * 4) + 1 <= len)
1215     {
1216         LPSTR p = buffer;
1217         for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1218             if (DRIVE_IsValid(drive))
1219             {
1220                 *p++ = 'a' + drive;
1221                 *p++ = ':';
1222                 *p++ = '\\';
1223                 *p++ = '\0';
1224             }
1225         *p = '\0';
1226         return count * 4;
1227     }
1228     else
1229         return (count * 4) + 1; /* account for terminating null */
1230     /* The API tells about these different return values */
1231 }
1232
1233
1234 /***********************************************************************
1235  *           GetLogicalDriveStringsW   (KERNEL32.@)
1236  */
1237 UINT WINAPI GetLogicalDriveStringsW( UINT len, LPWSTR buffer )
1238 {
1239     int drive, count;
1240
1241     for (drive = count = 0; drive < MAX_DOS_DRIVES; drive++)
1242         if (DRIVE_IsValid(drive)) count++;
1243     if (count * 4 * sizeof(WCHAR) <= len)
1244     {
1245         LPWSTR p = buffer;
1246         for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1247             if (DRIVE_IsValid(drive))
1248             {
1249                 *p++ = (WCHAR)('a' + drive);
1250                 *p++ = (WCHAR)':';
1251                 *p++ = (WCHAR)'\\';
1252                 *p++ = (WCHAR)'\0';
1253             }
1254         *p = (WCHAR)'\0';
1255     }
1256     return count * 4 * sizeof(WCHAR);
1257 }
1258
1259
1260 /***********************************************************************
1261  *           GetLogicalDrives   (KERNEL32.@)
1262  */
1263 DWORD WINAPI GetLogicalDrives(void)
1264 {
1265     DWORD ret = 0;
1266     int drive;
1267
1268     for (drive = 0; drive < MAX_DOS_DRIVES; drive++)
1269     {
1270         if ( (DRIVE_IsValid(drive)) ||
1271             (DOSDrives[drive].type == DRIVE_CDROM)) /* audio CD is also valid */
1272             ret |= (1 << drive);
1273     }
1274     return ret;
1275 }