winhelp: Specify the lex options directly in the source file.
[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, 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 << (letter_to_index(drives[i].letter)));
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
178 static void set_drive_label( char letter, const char *label )
179 {
180     char device[] = "a:\\";  /* SetVolumeLabel() requires a trailing slash */
181     device[0] = letter;
182
183     if(!SetVolumeLabel(device, label))
184     {
185         WINE_WARN("unable to set volume label for devicename of '%s', label of '%s'\n",
186                   device, label);
187         PRINTERROR();
188     }
189     else
190     {
191         WINE_TRACE("  set volume label for devicename of '%s', label of '%s'\n",
192                    device, label);
193     }
194 }
195
196 /* set the drive serial number via a .windows-serial file */
197 static void set_drive_serial( char letter, const char *serial )
198 {
199     char filename[] = "a:\\.windows-serial";
200     HANDLE hFile;
201
202     filename[0] = letter;
203     WINE_TRACE("Putting serial number of '%s' into file '%s'\n", serial, filename);
204     hFile = CreateFile(filename, GENERIC_WRITE, FILE_SHARE_READ, NULL,
205                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
206     if (hFile != INVALID_HANDLE_VALUE)
207     {
208         DWORD w;
209         WriteFile(hFile, serial, strlen(serial), &w, NULL);
210         WriteFile(hFile, "\n", 1, &w, NULL);
211         CloseHandle(hFile);
212     }
213 }
214
215 #if 0
216
217 /* currently unused, but if users have this burning desire to be able to rename drives,
218    we can put it back in.
219  */
220
221 BOOL copyDrive(struct drive *pSrc, struct drive *pDst)
222 {
223     if(pDst->in_use)
224     {
225         WINE_TRACE("pDst already in use\n");
226         return FALSE;
227     }
228
229     if(!pSrc->unixpath) WINE_TRACE("!pSrc->unixpath\n");
230     if(!pSrc->label) WINE_TRACE("!pSrc->label\n");
231     if(!pSrc->serial) WINE_TRACE("!pSrc->serial\n");
232
233     pDst->unixpath = strdupA(pSrc->unixpath);
234     pDst->label = strdupA(pSrc->label);
235     pDst->serial = strdupA(pSrc->serial);
236     pDst->type = pSrc->type;
237     pDst->in_use = TRUE;
238
239     return TRUE;
240 }
241
242 BOOL moveDrive(struct drive *pSrc, struct drive *pDst)
243 {
244     WINE_TRACE("pSrc->letter == %c, pDst->letter == %c\n", pSrc->letter, pDst->letter);
245
246     if(!copyDrive(pSrc, pDst))
247     {
248         WINE_TRACE("copyDrive failed\n");
249         return FALSE;
250     }
251
252     delete_drive(pSrc);
253     return TRUE;
254 }
255
256 #endif
257
258 /* Load currently defined drives into the drives array  */
259 void load_drives()
260 {
261     char *devices, *dev;
262     int len;
263     int drivecount = 0, i;
264     int retval;
265     static const int arraysize = 512;
266
267     WINE_TRACE("\n");
268
269     /* FIXME: broken symlinks in $WINEPREFIX/dosdevices will not be
270        returned by this API, so we need to handle that  */
271
272     /* setup the drives array */
273     dev = devices = HeapAlloc(GetProcessHeap(), 0, arraysize);
274     len = GetLogicalDriveStrings(arraysize, devices);
275
276     /* make all devices unused */
277     for (i = 0; i < 26; i++)
278     {
279         drives[i].letter = 'A' + i;
280         drives[i].in_use = FALSE;
281
282         HeapFree(GetProcessHeap(), 0, drives[i].unixpath);
283         drives[i].unixpath = NULL;
284
285         HeapFree(GetProcessHeap(), 0, drives[i].label);
286         drives[i].label = NULL;
287
288         HeapFree(GetProcessHeap(), 0, drives[i].serial);
289         drives[i].serial = NULL;
290     }
291
292     /* work backwards through the result of GetLogicalDriveStrings  */
293     while (len)
294     {
295         char volname[512]; /* volume name  */
296         DWORD serial;
297         char serialstr[256];
298         char rootpath[256];
299         char simplepath[3];
300         int pathlen;
301         char targetpath[256];
302         char *c;
303
304         *devices = toupper(*devices);
305
306         WINE_TRACE("devices == '%s'\n", devices);
307
308         volname[0] = 0;
309
310         retval = GetVolumeInformation(devices,
311                                       volname,
312                                       sizeof(volname),
313                                       &serial,
314                                       NULL,
315                                       NULL,
316                                       NULL,
317                                       0);
318         if(!retval)
319         {
320             WINE_ERR("GetVolumeInformation() for '%s' failed, setting serial to 0\n", devices);
321             PRINTERROR();
322             serial = 0;
323         }
324
325         WINE_TRACE("serial: '0x%lX'\n", serial);
326
327         /* build rootpath for GetDriveType() */
328         lstrcpynA(rootpath, devices, sizeof(rootpath));
329         pathlen = strlen(rootpath);
330
331         /* ensure that we have a backslash on the root path */
332         if ((rootpath[pathlen - 1] != '\\') && (pathlen < sizeof(rootpath)))
333         {
334             rootpath[pathlen] = '\\';
335             rootpath[pathlen + 1] = 0;
336         }
337
338         /* QueryDosDevice() requires no trailing backslash */
339         lstrcpynA(simplepath, devices, 3);
340         QueryDosDevice(simplepath, targetpath, sizeof(targetpath));
341
342         /* targetpath may have forward slashes rather than backslashes, so correct */
343         c = targetpath;
344         do if (*c == '\\') *c = '/'; while (*c++);
345
346         snprintf(serialstr, sizeof(serialstr), "%lX", serial);
347         WINE_TRACE("serialstr: '%s'\n", serialstr);
348         add_drive(*devices, targetpath, volname, serialstr, get_drive_type(devices[0]) );
349
350         len -= strlen(devices);
351         devices += strlen(devices);
352
353         /* skip over any nulls */
354         while ((*devices == 0) && (len))
355         {
356             len--;
357             devices++;
358         }
359
360         drivecount++;
361     }
362
363     WINE_TRACE("found %d drives\n", drivecount);
364
365     HeapFree(GetProcessHeap(), 0, dev);
366 }
367
368 /* some of this code appears to be broken by bugs in Wine: the label
369  * setting code has no effect, for instance  */
370 void apply_drive_changes(void)
371 {
372     int i;
373     CHAR devicename[4];
374     CHAR targetpath[256];
375     BOOL foundDrive;
376     CHAR volumeNameBuffer[512];
377     DWORD serialNumber;
378     DWORD maxComponentLength;
379     DWORD fileSystemFlags;
380     CHAR fileSystemName[128];
381     char newSerialNumberText[32];
382     int retval;
383     BOOL defineDevice;
384
385     WINE_TRACE("\n");
386
387     /* add each drive and remove as we go */
388     for(i = 0; i < 26; i++)
389     {
390         defineDevice = FALSE;
391         foundDrive = FALSE;
392         volumeNameBuffer[0] = 0;
393         serialNumber = 0;
394         snprintf(devicename, sizeof(devicename), "%c:", 'A' + i);
395
396         /* get a drive */
397         if(QueryDosDevice(devicename, targetpath, sizeof(targetpath)))
398         {
399             char *cursor;
400             
401             /* correct the slashes in the path to be UNIX style */
402             while ((cursor = strchr(targetpath, '\\'))) *cursor = '/';
403
404             foundDrive = TRUE;
405         }
406
407         /* if we found a drive and have a drive then compare things */
408         if(foundDrive && drives[i].in_use)
409         {
410             WINE_TRACE("drives[i].letter: '%c'\n", drives[i].letter);
411
412             snprintf(devicename, sizeof(devicename), "%c:\\", 'A' + i);
413             retval = GetVolumeInformation(devicename,
414                          volumeNameBuffer,
415                          sizeof(volumeNameBuffer),
416                          &serialNumber,
417                          &maxComponentLength,
418                          &fileSystemFlags,
419                          fileSystemName,
420                          sizeof(fileSystemName));
421             if(!retval)
422             {
423                 WINE_TRACE("  GetVolumeInformation() for '%s' failed\n", devicename);
424                 WINE_TRACE("  Skipping this drive\n");
425                 PRINTERROR();
426                 continue; /* skip this drive */
427             }
428
429             WINE_TRACE("  current path:   '%s', new path:   '%s'\n",
430                        targetpath, drives[i].unixpath);
431
432             /* compare to what we have */
433             /* do we have the same targetpath? */
434             if(strcmp(drives[i].unixpath, targetpath))
435             {
436                 defineDevice = TRUE;
437                 WINE_TRACE("  making changes to drive '%s'\n", devicename);
438             }
439             else
440             {
441                 WINE_TRACE("  no changes to drive '%s'\n", devicename);
442             }
443         }
444         else if(foundDrive && !drives[i].in_use)
445         {
446             /* remove this drive */
447             if(!DefineDosDevice(DDD_REMOVE_DEFINITION, devicename, drives[i].unixpath))
448             {
449                 WINE_ERR("unable to remove devicename of '%s', targetpath of '%s'\n",
450                     devicename, drives[i].unixpath);
451                 PRINTERROR();
452             }
453             else
454             {
455                 WINE_TRACE("removed devicename of '%s', targetpath of '%s'\n",
456                            devicename, drives[i].unixpath);
457             }
458
459             set_drive_type( drives[i].letter, DRIVE_UNKNOWN );
460             continue;
461         }
462         else if(drives[i].in_use) /* foundDrive must be false from the above check */
463         {
464             defineDevice = TRUE;
465         }
466
467         /* adding and modifying are the same steps */
468         if(defineDevice)
469         {
470             /* define this drive */
471             /* DefineDosDevice() requires that NO trailing slash be present */
472             snprintf(devicename, sizeof(devicename), "%c:", 'A' + i);
473             if(!DefineDosDevice(DDD_RAW_TARGET_PATH, devicename, drives[i].unixpath))
474             {
475                 WINE_ERR("  unable to define devicename of '%s', targetpath of '%s'\n",
476                     devicename, drives[i].unixpath);
477                 PRINTERROR();
478             }
479             else
480             {
481                 WINE_TRACE("  added devicename of '%s', targetpath of '%s'\n",
482                            devicename, drives[i].unixpath);
483             }
484         }
485
486         if (drives[i].label && strcmp(drives[i].label, volumeNameBuffer))
487             set_drive_label( drives[i].letter, drives[i].label );
488
489         snprintf(newSerialNumberText, sizeof(newSerialNumberText), "%lX", serialNumber);
490         if (drives[i].serial && drives[i].serial[0] && strcmp(drives[i].serial, newSerialNumberText))
491             set_drive_serial( drives[i].letter, drives[i].serial );
492
493         set_drive_type( drives[i].letter, drives[i].type );
494     }
495 }