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