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