Describe tabs being linked together.
[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 #if 0
120
121 /* currently unused, but if users have this burning desire to be able to rename drives,
122    we can put it back in.
123  */
124
125 BOOL copyDrive(struct drive *pSrc, struct drive *pDst)
126 {
127     if(pDst->in_use)
128     {
129         WINE_TRACE("pDst already in use\n");
130         return FALSE;
131     }
132
133     if(!pSrc->unixpath) WINE_TRACE("!pSrc->unixpath\n");
134     if(!pSrc->label) WINE_TRACE("!pSrc->label\n");
135     if(!pSrc->serial) WINE_TRACE("!pSrc->serial\n");
136
137     pDst->unixpath = strdupA(pSrc->unixpath);
138     pDst->label = strdupA(pSrc->label);
139     pDst->serial = strdupA(pSrc->serial);
140     pDst->type = pSrc->type;
141     pDst->in_use = TRUE;
142
143     return TRUE;
144 }
145
146 BOOL moveDrive(struct drive *pSrc, struct drive *pDst)
147 {
148     WINE_TRACE("pSrc->letter == %c, pDst->letter == %c\n", pSrc->letter, pDst->letter);
149
150     if(!copyDrive(pSrc, pDst))
151     {
152         WINE_TRACE("copyDrive failed\n");
153         return FALSE;
154     }
155
156     delete_drive(pSrc);
157     return TRUE;
158 }
159
160 #endif
161
162 /* Load currently defined drives into the drives array  */
163 void load_drives()
164 {
165     char *devices, *dev;
166     int len;
167     int drivecount = 0, i;
168     int retval;
169     static const int arraysize = 512;
170
171     WINE_TRACE("\n");
172
173     /* FIXME: broken symlinks in $WINEPREFIX/dosdevices will not be
174        returned by this API, so we need to handle that  */
175
176     /* setup the drives array */
177     dev = devices = HeapAlloc(GetProcessHeap(), 0, arraysize);
178     len = GetLogicalDriveStrings(arraysize, devices);
179
180     /* make all devices unused */
181     for (i = 0; i < 26; i++)
182     {
183         drives[i].letter = 'A' + i;
184         drives[i].in_use = FALSE;
185
186         HeapFree(GetProcessHeap(), 0, drives[i].unixpath);
187         drives[i].unixpath = NULL;
188
189         HeapFree(GetProcessHeap(), 0, drives[i].label);
190         drives[i].label = NULL;
191
192         HeapFree(GetProcessHeap(), 0, drives[i].serial);
193         drives[i].serial = NULL;
194     }
195
196     /* work backwards through the result of GetLogicalDriveStrings  */
197     while (len)
198     {
199         char volname[512]; /* volume name  */
200         DWORD serial;
201         char serialstr[256];
202         char rootpath[256];
203         char simplepath[3];
204         int pathlen;
205         char targetpath[256];
206         char *c;
207
208         *devices = toupper(*devices);
209
210         WINE_TRACE("devices == '%s'\n", devices);
211
212         volname[0] = 0;
213
214         retval = GetVolumeInformation(devices,
215                                       volname,
216                                       sizeof(volname),
217                                       &serial,
218                                       NULL,
219                                       NULL,
220                                       NULL,
221                                       0);
222         if(!retval)
223         {
224             WINE_ERR("GetVolumeInformation() for '%s' failed, setting serial to 0\n", devices);
225             PRINTERROR();
226             serial = 0;
227         }
228
229         WINE_TRACE("serial: '0x%lX'\n", serial);
230
231         /* build rootpath for GetDriveType() */
232         lstrcpynA(rootpath, devices, sizeof(rootpath));
233         pathlen = strlen(rootpath);
234
235         /* ensure that we have a backslash on the root path */
236         if ((rootpath[pathlen - 1] != '\\') && (pathlen < sizeof(rootpath)))
237         {
238             rootpath[pathlen] = '\\';
239             rootpath[pathlen + 1] = 0;
240         }
241
242         /* QueryDosDevice() requires no trailing backslash */
243         lstrcpynA(simplepath, devices, 3);
244         QueryDosDevice(simplepath, targetpath, sizeof(targetpath));
245
246         /* targetpath may have forward slashes rather than backslashes, so correct */
247         c = targetpath;
248         do if (*c == '\\') *c = '/'; while (*c++);
249
250         snprintf(serialstr, sizeof(serialstr), "%lX", serial);
251         WINE_TRACE("serialstr: '%s'\n", serialstr);
252         add_drive(*devices, targetpath, volname, serialstr, GetDriveType(rootpath));
253
254         len -= strlen(devices);
255         devices += strlen(devices);
256
257         /* skip over any nulls */
258         while ((*devices == 0) && (len))
259         {
260             len--;
261             devices++;
262         }
263
264         drivecount++;
265     }
266
267     WINE_TRACE("found %d drives\n", drivecount);
268
269     HeapFree(GetProcessHeap(), 0, dev);
270 }
271
272 /* some of this code appears to be broken by bugs in Wine: the label
273  * setting code has no effect, for instance  */
274 void apply_drive_changes(void)
275 {
276     int i;
277     CHAR devicename[4];
278     CHAR targetpath[256];
279     BOOL foundDrive;
280     CHAR volumeNameBuffer[512];
281     DWORD serialNumber;
282     DWORD maxComponentLength;
283     DWORD fileSystemFlags;
284     CHAR fileSystemName[128];
285     int retval;
286     BOOL defineDevice;
287
288     WINE_TRACE("\n");
289
290     /* add each drive and remove as we go */
291     for(i = 0; i < 26; i++)
292     {
293         defineDevice = FALSE;
294         foundDrive = FALSE;
295         snprintf(devicename, sizeof(devicename), "%c:", 'A' + i);
296
297         /* get a drive */
298         if(QueryDosDevice(devicename, targetpath, sizeof(targetpath)))
299         {
300             char *cursor;
301             
302             /* correct the slashes in the path to be UNIX style */
303             while ((cursor = strchr(targetpath, '\\'))) *cursor = '/';
304
305             foundDrive = TRUE;
306         }
307
308         /* if we found a drive and have a drive then compare things */
309         if(foundDrive && drives[i].in_use)
310         {
311             char newSerialNumberText[256];
312
313             volumeNameBuffer[0] = 0;
314
315             WINE_TRACE("drives[i].letter: '%c'\n", drives[i].letter);
316
317             snprintf(devicename, sizeof(devicename), "%c:\\", 'A' + i);
318             retval = GetVolumeInformation(devicename,
319                          volumeNameBuffer,
320                          sizeof(volumeNameBuffer),
321                          &serialNumber,
322                          &maxComponentLength,
323                          &fileSystemFlags,
324                          fileSystemName,
325                          sizeof(fileSystemName));
326             if(!retval)
327             {
328                 WINE_TRACE("  GetVolumeInformation() for '%s' failed\n", devicename);
329                 WINE_TRACE("  Skipping this drive\n");
330                 PRINTERROR();
331                 continue; /* skip this drive */
332             }
333
334             snprintf(newSerialNumberText, sizeof(newSerialNumberText), "%lX", serialNumber);
335
336             WINE_TRACE("  current path:   '%s', new path:   '%s'\n",
337                        targetpath, drives[i].unixpath);
338             WINE_TRACE("  current label:  '%s', new label:  '%s'\n",
339                        volumeNameBuffer, drives[i].label);
340             WINE_TRACE("  current serial: '%s', new serial: '%s'\n",
341                        newSerialNumberText, drives[i].serial);
342
343             /* compare to what we have */
344             /* do we have the same targetpath? */
345             if(strcmp(drives[i].unixpath, targetpath) ||
346                strcmp(drives[i].label, volumeNameBuffer) ||
347                strcmp(drives[i].serial, newSerialNumberText))
348             {
349                 defineDevice = TRUE;
350                 WINE_TRACE("  making changes to drive '%s'\n", devicename);
351             }
352             else
353             {
354                 WINE_TRACE("  no changes to drive '%s'\n", devicename);
355             }
356         }
357         else if(foundDrive && !drives[i].in_use)
358         {
359             HKEY hKey;
360             char driveValue[256];
361
362             /* remove this drive */
363             if(!DefineDosDevice(DDD_REMOVE_DEFINITION, devicename, drives[i].unixpath))
364             {
365                 WINE_ERR("unable to remove devicename of '%s', targetpath of '%s'\n",
366                     devicename, drives[i].unixpath);
367                 PRINTERROR();
368             }
369             else
370             {
371                 WINE_TRACE("removed devicename of '%s', targetpath of '%s'\n",
372                            devicename, drives[i].unixpath);
373             }
374
375             retval = RegOpenKey(HKEY_LOCAL_MACHINE, "Software\\Wine\\Drives", &hKey);
376             if (retval != ERROR_SUCCESS)
377             {
378                 WINE_TRACE("Unable to open '%s'\n", "Software\\Wine\\Drives");
379             }
380             else
381             {
382                 snprintf(driveValue, sizeof(driveValue), "%c:", toupper(drives[i].letter));    
383
384                 retval = RegDeleteValue(hKey, driveValue);
385             }
386         }
387         else if(drives[i].in_use) /* foundDrive must be false from the above check */
388         {
389             defineDevice = TRUE;
390         }
391
392         /* adding and modifying are the same steps */
393         if(defineDevice)
394         {
395             char filename[256];
396             HANDLE hFile;
397
398             HKEY hKey;
399             const char *typeText;
400             char driveValue[256];
401
402             /* define this drive */
403             /* DefineDosDevice() requires that NO trailing slash be present */
404             snprintf(devicename, sizeof(devicename), "%c:", 'A' + i);
405             if(!DefineDosDevice(DDD_RAW_TARGET_PATH, devicename, drives[i].unixpath))
406             {
407                 WINE_ERR("  unable to define devicename of '%s', targetpath of '%s'\n",
408                     devicename, drives[i].unixpath);
409                 PRINTERROR();
410             }
411             else
412             {
413                 WINE_TRACE("  added devicename of '%s', targetpath of '%s'\n",
414                            devicename, drives[i].unixpath);
415
416                 /* SetVolumeLabel() requires a trailing slash */
417                 snprintf(devicename, sizeof(devicename), "%c:\\", 'A' + i);
418                 if(!SetVolumeLabel(devicename, drives[i].label))
419                 {
420                     WINE_WARN("unable to set volume label for devicename of '%s', label of '%s'\n",
421                         devicename, drives[i].label);
422                     PRINTERROR();
423                 }
424                 else
425                 {
426                     WINE_TRACE("  set volume label for devicename of '%s', label of '%s'\n",
427                         devicename, drives[i].label);
428                 }
429             }
430
431             /* Set the drive type in the registry */
432             if(drives[i].type == DRIVE_FIXED)
433                 typeText = "hd";
434             else if(drives[i].type == DRIVE_REMOTE)
435                 typeText = "network";
436             else if(drives[i].type == DRIVE_REMOVABLE)
437                 typeText = "floppy";
438             else /* must be DRIVE_CDROM */
439                 typeText = "cdrom";
440
441
442             snprintf(driveValue, sizeof(driveValue), "%c:", toupper(drives[i].letter));
443
444             retval = RegOpenKey(HKEY_LOCAL_MACHINE,
445                        "Software\\Wine\\Drives",
446                        &hKey);
447
448             if(retval != ERROR_SUCCESS)
449             {
450                 WINE_TRACE("  Unable to open '%s'\n", "Software\\Wine\\Drives");
451             }
452             else
453             {
454                 retval = RegSetValueEx(
455                               hKey,
456                               driveValue,
457                               0,
458                               REG_SZ,
459                               typeText,
460                               strlen(typeText) + 1);
461                 if(retval != ERROR_SUCCESS)
462                 {
463                     WINE_TRACE("  Unable to set value of '%s' to '%s'\n",
464                                driveValue, typeText);
465                 }
466                 else
467                 {
468                     WINE_TRACE("  Finished setting value of '%s' to '%s'\n",
469                                driveValue, typeText);
470                     RegCloseKey(hKey);
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 }