Added a few more Unicode digits from Unicode version 4.1.
[wine] / programs / winecfg / drive.c
1 /*
2  * Drive management code
3  *
4  * Copyright 2003 Mark Westcott
5  * Copyright 2003-2004 Mike Hearn
6  * Copyright 2004 Chris Morgan
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23
24 #include <assert.h>
25 #include <stdarg.h>
26 #include <stdio.h>
27 #include <string.h>
28
29 #include <windef.h>
30 #include <winbase.h>
31 #include <winreg.h>
32 #include <wine/debug.h>
33 #include <shellapi.h>
34 #include <objbase.h>
35 #include <shlguid.h>
36 #include <shlwapi.h>
37 #include <shlobj.h>
38
39 #include "winecfg.h"
40 #include "resource.h"
41
42
43 WINE_DEFAULT_DEBUG_CHANNEL(winecfg);
44
45 struct drive drives[26]; /* one for each drive letter */
46
47 static inline int letter_to_index(char letter)
48 {
49     return (toupper(letter) - 'A');
50 }
51
52 /* This function produces a mask for each drive letter that isn't
53  * currently used. Each bit of the long result represents a letter,
54  * with A being the least significant bit, and Z being the most
55  * significant.
56  *
57  * To calculate this, we loop over each letter, and see if we can get
58  * a drive entry for it. If so, we set the appropriate bit. At the
59  * end, we flip each bit, to give the desired result.
60  *
61  * The letter parameter is always marked as being available. This is
62  * so the edit dialog can display the currently used drive letter
63  * alongside the available ones.
64  */
65 long drive_available_mask(char letter)
66 {
67   long result = 0;
68   int i;
69
70   WINE_TRACE("\n");
71
72
73   for(i = 0; i < 26; i++)
74   {
75       if (!drives[i].in_use) continue;
76       result |= (1 << (toupper(drives[i].letter) - 'A'));
77   }
78
79   result = ~result;
80   if (letter) result |= DRIVE_MASK_BIT(letter);
81
82   WINE_TRACE("finished drive letter loop with %lx\n", result);
83   return result;
84 }
85
86 BOOL add_drive(const char letter, const char *targetpath, const char *label, const char *serial, unsigned int type)
87 {
88     int driveIndex = letter_to_index(letter);
89
90     if(drives[driveIndex].in_use)
91         return FALSE;
92
93     WINE_TRACE("letter == '%c', unixpath == '%s', label == '%s', serial == '%s', type == %d\n",
94                letter, targetpath, label, serial, type);
95
96     drives[driveIndex].letter   = toupper(letter);
97     drives[driveIndex].unixpath = strdupA(targetpath);
98     drives[driveIndex].label    = strdupA(label);
99     drives[driveIndex].serial   = strdupA(serial);
100     drives[driveIndex].type     = type;
101     drives[driveIndex].in_use   = TRUE;
102
103     return TRUE;
104 }
105
106 /* deallocates the contents of the drive. does not free the drive itself  */
107 void delete_drive(struct drive *d)
108 {
109     HeapFree(GetProcessHeap(), 0, d->unixpath);
110     d->unixpath = NULL;
111     HeapFree(GetProcessHeap(), 0, d->label);
112     d->label = NULL;
113     HeapFree(GetProcessHeap(), 0, d->serial);
114     d->serial = NULL;
115
116     d->in_use = FALSE;
117 }
118
119 static void set_drive_type( char letter, DWORD type )
120 {
121     HKEY hKey;
122     char driveValue[4];
123     const char *typeText = NULL;
124
125     sprintf(driveValue, "%c:", letter);
126
127     /* Set the drive type in the registry */
128     if (type == DRIVE_FIXED)
129         typeText = "hd";
130     else if (type == DRIVE_REMOTE)
131         typeText = "network";
132     else if (type == DRIVE_REMOVABLE)
133         typeText = "floppy";
134     else if (type == DRIVE_CDROM)
135         typeText = "cdrom";
136
137     if (RegCreateKey(HKEY_LOCAL_MACHINE, "Software\\Wine\\Drives", &hKey) != ERROR_SUCCESS)
138         WINE_TRACE("  Unable to open '%s'\n", "Software\\Wine\\Drives");
139     else
140     {
141         if (typeText)
142             RegSetValueEx( hKey, driveValue, 0, REG_SZ, (LPBYTE)typeText, strlen(typeText) + 1 );
143         else
144             RegDeleteValue( hKey, driveValue );
145         RegCloseKey(hKey);
146     }
147 }
148
149 static DWORD get_drive_type( char letter )
150 {
151     HKEY hKey;
152     char driveValue[4];
153     DWORD ret = DRIVE_UNKNOWN;
154
155     sprintf(driveValue, "%c:", letter);
156
157     if (RegOpenKey(HKEY_LOCAL_MACHINE, "Software\\Wine\\Drives", &hKey) != ERROR_SUCCESS)
158         WINE_TRACE("  Unable to open Software\\Wine\\Drives\n" );
159     else
160     {
161         char buffer[80];
162         DWORD size = sizeof(buffer);
163
164         if (!RegQueryValueExA( hKey, driveValue, NULL, NULL, (LPBYTE)buffer, &size ))
165         {
166             WINE_TRACE("Got type '%s' for %s\n", buffer, driveValue );
167             if (!strcasecmp( buffer, "hd" )) ret = DRIVE_FIXED;
168             else if (!strcasecmp( buffer, "network" )) ret = DRIVE_REMOTE;
169             else if (!strcasecmp( buffer, "floppy" )) ret = DRIVE_REMOVABLE;
170             else if (!strcasecmp( buffer, "cdrom" )) ret = DRIVE_CDROM;
171         }
172         RegCloseKey(hKey);
173     }
174     return ret;
175 }
176
177 #if 0
178
179 /* currently unused, but if users have this burning desire to be able to rename drives,
180    we can put it back in.
181  */
182
183 BOOL copyDrive(struct drive *pSrc, struct drive *pDst)
184 {
185     if(pDst->in_use)
186     {
187         WINE_TRACE("pDst already in use\n");
188         return FALSE;
189     }
190
191     if(!pSrc->unixpath) WINE_TRACE("!pSrc->unixpath\n");
192     if(!pSrc->label) WINE_TRACE("!pSrc->label\n");
193     if(!pSrc->serial) WINE_TRACE("!pSrc->serial\n");
194
195     pDst->unixpath = strdupA(pSrc->unixpath);
196     pDst->label = strdupA(pSrc->label);
197     pDst->serial = strdupA(pSrc->serial);
198     pDst->type = pSrc->type;
199     pDst->in_use = TRUE;
200
201     return TRUE;
202 }
203
204 BOOL moveDrive(struct drive *pSrc, struct drive *pDst)
205 {
206     WINE_TRACE("pSrc->letter == %c, pDst->letter == %c\n", pSrc->letter, pDst->letter);
207
208     if(!copyDrive(pSrc, pDst))
209     {
210         WINE_TRACE("copyDrive failed\n");
211         return FALSE;
212     }
213
214     delete_drive(pSrc);
215     return TRUE;
216 }
217
218 #endif
219
220 /* Load currently defined drives into the drives array  */
221 void load_drives()
222 {
223     char *devices, *dev;
224     int len;
225     int drivecount = 0, i;
226     int retval;
227     static const int arraysize = 512;
228
229     WINE_TRACE("\n");
230
231     /* FIXME: broken symlinks in $WINEPREFIX/dosdevices will not be
232        returned by this API, so we need to handle that  */
233
234     /* setup the drives array */
235     dev = devices = HeapAlloc(GetProcessHeap(), 0, arraysize);
236     len = GetLogicalDriveStrings(arraysize, devices);
237
238     /* make all devices unused */
239     for (i = 0; i < 26; i++)
240     {
241         drives[i].letter = 'A' + i;
242         drives[i].in_use = FALSE;
243
244         HeapFree(GetProcessHeap(), 0, drives[i].unixpath);
245         drives[i].unixpath = NULL;
246
247         HeapFree(GetProcessHeap(), 0, drives[i].label);
248         drives[i].label = NULL;
249
250         HeapFree(GetProcessHeap(), 0, drives[i].serial);
251         drives[i].serial = NULL;
252     }
253
254     /* work backwards through the result of GetLogicalDriveStrings  */
255     while (len)
256     {
257         char volname[512]; /* volume name  */
258         DWORD serial;
259         char serialstr[256];
260         char rootpath[256];
261         char simplepath[3];
262         int pathlen;
263         char targetpath[256];
264         char *c;
265
266         *devices = toupper(*devices);
267
268         WINE_TRACE("devices == '%s'\n", devices);
269
270         volname[0] = 0;
271
272         retval = GetVolumeInformation(devices,
273                                       volname,
274                                       sizeof(volname),
275                                       &serial,
276                                       NULL,
277                                       NULL,
278                                       NULL,
279                                       0);
280         if(!retval)
281         {
282             WINE_ERR("GetVolumeInformation() for '%s' failed, setting serial to 0\n", devices);
283             PRINTERROR();
284             serial = 0;
285         }
286
287         WINE_TRACE("serial: '0x%lX'\n", serial);
288
289         /* build rootpath for GetDriveType() */
290         lstrcpynA(rootpath, devices, sizeof(rootpath));
291         pathlen = strlen(rootpath);
292
293         /* ensure that we have a backslash on the root path */
294         if ((rootpath[pathlen - 1] != '\\') && (pathlen < sizeof(rootpath)))
295         {
296             rootpath[pathlen] = '\\';
297             rootpath[pathlen + 1] = 0;
298         }
299
300         /* QueryDosDevice() requires no trailing backslash */
301         lstrcpynA(simplepath, devices, 3);
302         QueryDosDevice(simplepath, targetpath, sizeof(targetpath));
303
304         /* targetpath may have forward slashes rather than backslashes, so correct */
305         c = targetpath;
306         do if (*c == '\\') *c = '/'; while (*c++);
307
308         snprintf(serialstr, sizeof(serialstr), "%lX", serial);
309         WINE_TRACE("serialstr: '%s'\n", serialstr);
310         add_drive(*devices, targetpath, volname, serialstr, get_drive_type(devices[0]) );
311
312         len -= strlen(devices);
313         devices += strlen(devices);
314
315         /* skip over any nulls */
316         while ((*devices == 0) && (len))
317         {
318             len--;
319             devices++;
320         }
321
322         drivecount++;
323     }
324
325     WINE_TRACE("found %d drives\n", drivecount);
326
327     HeapFree(GetProcessHeap(), 0, dev);
328 }
329
330 /* some of this code appears to be broken by bugs in Wine: the label
331  * setting code has no effect, for instance  */
332 void apply_drive_changes(void)
333 {
334     int i;
335     CHAR devicename[4];
336     CHAR targetpath[256];
337     BOOL foundDrive;
338     CHAR volumeNameBuffer[512];
339     DWORD serialNumber;
340     DWORD maxComponentLength;
341     DWORD fileSystemFlags;
342     CHAR fileSystemName[128];
343     int retval;
344     BOOL defineDevice;
345
346     WINE_TRACE("\n");
347
348     /* add each drive and remove as we go */
349     for(i = 0; i < 26; i++)
350     {
351         defineDevice = FALSE;
352         foundDrive = FALSE;
353         snprintf(devicename, sizeof(devicename), "%c:", 'A' + i);
354
355         /* get a drive */
356         if(QueryDosDevice(devicename, targetpath, sizeof(targetpath)))
357         {
358             char *cursor;
359             
360             /* correct the slashes in the path to be UNIX style */
361             while ((cursor = strchr(targetpath, '\\'))) *cursor = '/';
362
363             foundDrive = TRUE;
364         }
365
366         /* if we found a drive and have a drive then compare things */
367         if(foundDrive && drives[i].in_use)
368         {
369             char newSerialNumberText[256];
370
371             volumeNameBuffer[0] = 0;
372
373             WINE_TRACE("drives[i].letter: '%c'\n", drives[i].letter);
374
375             snprintf(devicename, sizeof(devicename), "%c:\\", 'A' + i);
376             retval = GetVolumeInformation(devicename,
377                          volumeNameBuffer,
378                          sizeof(volumeNameBuffer),
379                          &serialNumber,
380                          &maxComponentLength,
381                          &fileSystemFlags,
382                          fileSystemName,
383                          sizeof(fileSystemName));
384             if(!retval)
385             {
386                 WINE_TRACE("  GetVolumeInformation() for '%s' failed\n", devicename);
387                 WINE_TRACE("  Skipping this drive\n");
388                 PRINTERROR();
389                 continue; /* skip this drive */
390             }
391
392             snprintf(newSerialNumberText, sizeof(newSerialNumberText), "%lX", serialNumber);
393
394             WINE_TRACE("  current path:   '%s', new path:   '%s'\n",
395                        targetpath, drives[i].unixpath);
396             WINE_TRACE("  current label:  '%s', new label:  '%s'\n",
397                        volumeNameBuffer, drives[i].label);
398             WINE_TRACE("  current serial: '%s', new serial: '%s'\n",
399                        newSerialNumberText, drives[i].serial);
400
401             /* compare to what we have */
402             /* do we have the same targetpath? */
403             if(strcmp(drives[i].unixpath, targetpath) ||
404                strcmp(drives[i].label, volumeNameBuffer) ||
405                strcmp(drives[i].serial, newSerialNumberText))
406             {
407                 defineDevice = TRUE;
408                 WINE_TRACE("  making changes to drive '%s'\n", devicename);
409             }
410             else
411             {
412                 WINE_TRACE("  no changes to drive '%s'\n", devicename);
413             }
414         }
415         else if(foundDrive && !drives[i].in_use)
416         {
417             /* remove this drive */
418             if(!DefineDosDevice(DDD_REMOVE_DEFINITION, devicename, drives[i].unixpath))
419             {
420                 WINE_ERR("unable to remove devicename of '%s', targetpath of '%s'\n",
421                     devicename, drives[i].unixpath);
422                 PRINTERROR();
423             }
424             else
425             {
426                 WINE_TRACE("removed devicename of '%s', targetpath of '%s'\n",
427                            devicename, drives[i].unixpath);
428             }
429
430             set_drive_type( drives[i].letter, DRIVE_UNKNOWN );
431             continue;
432         }
433         else if(drives[i].in_use) /* foundDrive must be false from the above check */
434         {
435             defineDevice = TRUE;
436         }
437
438         /* adding and modifying are the same steps */
439         if(defineDevice)
440         {
441             char filename[256];
442             HANDLE hFile;
443
444             /* define this drive */
445             /* DefineDosDevice() requires that NO trailing slash be present */
446             snprintf(devicename, sizeof(devicename), "%c:", 'A' + i);
447             if(!DefineDosDevice(DDD_RAW_TARGET_PATH, devicename, drives[i].unixpath))
448             {
449                 WINE_ERR("  unable to define devicename of '%s', targetpath of '%s'\n",
450                     devicename, drives[i].unixpath);
451                 PRINTERROR();
452             }
453             else
454             {
455                 WINE_TRACE("  added devicename of '%s', targetpath of '%s'\n",
456                            devicename, drives[i].unixpath);
457
458                 /* SetVolumeLabel() requires a trailing slash */
459                 snprintf(devicename, sizeof(devicename), "%c:\\", 'A' + i);
460                 if(!SetVolumeLabel(devicename, drives[i].label))
461                 {
462                     WINE_WARN("unable to set volume label for devicename of '%s', label of '%s'\n",
463                         devicename, drives[i].label);
464                     PRINTERROR();
465                 }
466                 else
467                 {
468                     WINE_TRACE("  set volume label for devicename of '%s', label of '%s'\n",
469                         devicename, drives[i].label);
470                 }
471             }
472
473
474             /* Set the drive serial number via a .windows-serial file in */
475             /* the targetpath directory */
476             snprintf(filename, sizeof(filename), "%c:\\.windows-serial", drives[i].letter);
477             WINE_TRACE("  Putting serial number of '%ld' into file '%s'\n",
478                        serialNumber, filename);
479             hFile = CreateFile(filename,
480                        GENERIC_WRITE,
481                        FILE_SHARE_READ,
482                        NULL,
483                        CREATE_ALWAYS,
484                        FILE_ATTRIBUTE_NORMAL,
485                        NULL);
486             if (hFile != INVALID_HANDLE_VALUE)
487             {
488                 DWORD w;
489                 WINE_TRACE("  writing serial number of '%s'\n", drives[i].serial);
490                 WriteFile(hFile,
491                           drives[i].serial,
492                           strlen(drives[i].serial),
493                           &w,
494                           NULL);
495                 WriteFile(hFile,
496                           "\n",
497                           strlen("\n"),
498                           &w,
499                           NULL);
500                 CloseHandle(hFile);
501             }
502             else
503             {
504                 WINE_TRACE("  CreateFile() error with file '%s'\n", filename);
505             }
506         }
507
508         set_drive_type( drives[i].letter, drives[i].type );
509     }
510 }