Reimplemented GetVolumeInformation and SetVolumeLabel; volume label
[wine] / dlls / kernel / volume.c
1 /*
2  * Volume management functions
3  *
4  * Copyright 1993 Erik Bos
5  * Copyright 1996, 2004 Alexandre Julliard
6  * Copyright 1999 Petr Tomasek
7  * Copyright 2000 Andreas Mohr
8  * Copyright 2003 Eric Pouech
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23  */
24
25 #include "config.h"
26 #include "wine/port.h"
27
28 #include <stdarg.h>
29 #include <stdlib.h>
30
31 #include "windef.h"
32 #include "winbase.h"
33 #include "winreg.h"
34 #include "winnls.h"
35 #include "winternl.h"
36 #include "ntstatus.h"
37 #include "winioctl.h"
38 #include "ntddstor.h"
39 #include "ntddcdrm.h"
40 #include "wine/unicode.h"
41 #include "wine/debug.h"
42
43 WINE_DEFAULT_DEBUG_CHANNEL(volume);
44
45 #define SUPERBLOCK_SIZE 2048
46
47 #define CDFRAMES_PERSEC         75
48 #define CDFRAMES_PERMIN         (CDFRAMES_PERSEC * 60)
49 #define FRAME_OF_ADDR(a)        ((a)[1] * CDFRAMES_PERMIN + (a)[2] * CDFRAMES_PERSEC + (a)[3])
50 #define FRAME_OF_TOC(toc, idx)  FRAME_OF_ADDR((toc)->TrackData[(idx) - (toc)->FirstTrack].Address)
51
52 #define GETWORD(buf,off)  MAKEWORD(buf[(off)],buf[(off+1)])
53 #define GETLONG(buf,off)  MAKELONG(GETWORD(buf,off),GETWORD(buf,off+2))
54
55 enum fs_type
56 {
57     FS_ERROR,    /* error accessing the device */
58     FS_UNKNOWN,  /* unknown file system */
59     FS_FAT1216,
60     FS_FAT32,
61     FS_ISO9660
62 };
63
64
65 /******************************************************************
66  *              VOLUME_FindCdRomDataBestVoldesc
67  */
68 static DWORD VOLUME_FindCdRomDataBestVoldesc( HANDLE handle )
69 {
70     BYTE cur_vd_type, max_vd_type = 0;
71     BYTE buffer[16];
72     DWORD size, offs, best_offs = 0, extra_offs = 0;
73
74     for (offs = 0x8000; offs <= 0x9800; offs += 0x800)
75     {
76         /* if 'CDROM' occurs at position 8, this is a pre-iso9660 cd, and
77          * the volume label is displaced forward by 8
78          */
79         if (SetFilePointer( handle, offs, NULL, FILE_BEGIN ) != offs) break;
80         if (!ReadFile( handle, buffer, sizeof(buffer), &size, NULL )) break;
81         if (size != sizeof(buffer)) break;
82         /* check for non-ISO9660 signature */
83         if (!memcmp( buffer + 11, "ROM", 3 )) extra_offs = 8;
84         cur_vd_type = buffer[extra_offs];
85         if (cur_vd_type == 0xff) /* voldesc set terminator */
86             break;
87         if (cur_vd_type > max_vd_type)
88         {
89             max_vd_type = cur_vd_type;
90             best_offs = offs + extra_offs;
91         }
92     }
93     return best_offs;
94 }
95
96
97 /***********************************************************************
98  *           VOLUME_ReadFATSuperblock
99  */
100 static enum fs_type VOLUME_ReadFATSuperblock( HANDLE handle, BYTE *buff )
101 {
102     DWORD size;
103
104     /* try a fixed disk, with a FAT partition */
105     if (SetFilePointer( handle, 0, NULL, FILE_BEGIN ) != 0 ||
106         !ReadFile( handle, buff, SUPERBLOCK_SIZE, &size, NULL ) ||
107         size != SUPERBLOCK_SIZE)
108         return FS_ERROR;
109
110     if (buff[0] == 0xE9 || (buff[0] == 0xEB && buff[2] == 0x90))
111     {
112         /* guess which type of FAT we have */
113         unsigned int sz, nsect, nclust;
114         sz = GETWORD(buff, 0x16);
115         if (!sz) sz = GETLONG(buff, 0x24);
116         nsect = GETWORD(buff, 0x13);
117         if (!nsect) nsect = GETLONG(buff, 0x20);
118         nsect -= GETWORD(buff, 0x0e) + buff[0x10] * sz +
119             (GETWORD(buff, 0x11) * 32 + (GETWORD(buff, 0x0b) - 1)) / GETWORD(buff, 0x0b);
120         nclust = nsect / buff[0x0d];
121
122         if (nclust < 65525)
123         {
124             if (buff[0x26] == 0x29 && !memcmp(buff+0x36, "FAT", 3))
125             {
126                 /* FIXME: do really all FAT have their name beginning with
127                  * "FAT" ? (At least FAT12, FAT16 and FAT32 have :)
128                  */
129                 return FS_FAT1216;
130             }
131         }
132         else if (!memcmp(buff+0x52, "FAT", 3)) return FS_FAT32;
133     }
134     return FS_UNKNOWN;
135 }
136
137
138 /***********************************************************************
139  *           VOLUME_ReadCDSuperblock
140  */
141 static enum fs_type VOLUME_ReadCDSuperblock( HANDLE handle, BYTE *buff )
142 {
143     DWORD size, offs = VOLUME_FindCdRomDataBestVoldesc( handle );
144
145     if (!offs) return FS_UNKNOWN;
146
147     if (SetFilePointer( handle, offs, NULL, FILE_BEGIN ) != offs ||
148         !ReadFile( handle, buff, SUPERBLOCK_SIZE, &size, NULL ) ||
149         size != SUPERBLOCK_SIZE)
150         return FS_ERROR;
151
152     /* check for iso9660 present */
153     if (!memcmp(&buff[1], "CD001", 5)) return FS_ISO9660;
154     return FS_UNKNOWN;
155 }
156
157
158 /**************************************************************************
159  *                              VOLUME_GetSuperblockLabel
160  */
161 static void VOLUME_GetSuperblockLabel( enum fs_type type, const BYTE *superblock,
162                                        WCHAR *label, DWORD len )
163 {
164     const BYTE *label_ptr = NULL;
165     DWORD label_len;
166
167     switch(type)
168     {
169     case FS_ERROR:
170     case FS_UNKNOWN:
171         label_len = 0;
172         break;
173     case FS_FAT1216:
174         label_ptr = superblock + 0x2b;
175         label_len = 11;
176         break;
177     case FS_FAT32:
178         label_ptr = superblock + 0x47;
179         label_len = 11;
180         break;
181     case FS_ISO9660:
182         {
183             BYTE ver = superblock[0x5a];
184
185             if (superblock[0x58] == 0x25 && superblock[0x59] == 0x2f &&  /* Unicode ID */
186                 ((ver == 0x40) || (ver == 0x43) || (ver == 0x45)))
187             { /* yippee, unicode */
188                 int i;
189
190                 if (len > 17) len = 17;
191                 for (i = 0; i < len-1; i++)
192                     label[i] = (superblock[40+2*i] << 8) | superblock[41+2*i];
193                 label[i] = 0;
194                 while (i && label[i-1] == ' ') label[--i] = 0;
195                 return;
196             }
197             label_ptr = superblock + 40;
198             label_len = 32;
199             break;
200         }
201     }
202     if (label_len) RtlMultiByteToUnicodeN( label, (len-1) * sizeof(WCHAR),
203                                            &label_len, label_ptr, label_len );
204     label_len /= sizeof(WCHAR);
205     label[label_len] = 0;
206     while (label_len && label[label_len-1] == ' ') label[--label_len] = 0;
207 }
208
209
210 /**************************************************************************
211  *                              VOLUME_SetSuperblockLabel
212  */
213 static BOOL VOLUME_SetSuperblockLabel( enum fs_type type, HANDLE handle, const WCHAR *label )
214 {
215     BYTE label_data[11];
216     DWORD offset, len;
217
218     switch(type)
219     {
220     case FS_FAT1216:
221         offset = 0x2b;
222         break;
223     case FS_FAT32:
224         offset = 0x47;
225         break;
226     default:
227         SetLastError( ERROR_ACCESS_DENIED );
228         return FALSE;
229     }
230     RtlUnicodeToMultiByteN( label_data, sizeof(label_data), &len,
231                             label, strlenW(label) * sizeof(WCHAR) );
232     if (len < sizeof(label_data))
233         memset( label_data + len, ' ', sizeof(label_data) - len );
234
235     return (SetFilePointer( handle, offset, NULL, FILE_BEGIN ) == offset &&
236             WriteFile( handle, label_data, sizeof(label_data), &len, NULL ));
237 }
238
239
240 /**************************************************************************
241  *                              VOLUME_GetSuperblockSerial
242  */
243 static DWORD VOLUME_GetSuperblockSerial( enum fs_type type, const BYTE *superblock )
244 {
245     switch(type)
246     {
247     case FS_ERROR:
248     case FS_UNKNOWN:
249         break;
250     case FS_FAT1216:
251         return GETLONG( superblock, 0x27 );
252     case FS_FAT32:
253         return GETLONG( superblock, 0x33 );
254     case FS_ISO9660:
255         {
256             BYTE sum[4];
257             int i;
258
259             sum[0] = sum[1] = sum[2] = sum[3] = 0;
260             for (i = 0; i < 2048; i += 4)
261             {
262                 /* DON'T optimize this into DWORD !! (breaks overflow) */
263                 sum[0] += superblock[i+0];
264                 sum[1] += superblock[i+1];
265                 sum[2] += superblock[i+2];
266                 sum[3] += superblock[i+3];
267             }
268             /*
269              * OK, another braindead one... argh. Just believe it.
270              * Me$$ysoft chose to reverse the serial number in NT4/W2K.
271              * It's true and nobody will ever be able to change it.
272              */
273             if (GetVersion() & 0x80000000)
274                 return (sum[3] << 24) | (sum[2] << 16) | (sum[1] << 8) | sum[0];
275             else
276                 return (sum[0] << 24) | (sum[1] << 16) | (sum[2] << 8) | sum[3];
277         }
278     }
279     return 0;
280 }
281
282
283 /**************************************************************************
284  *                              VOLUME_GetAudioCDSerial
285  */
286 static DWORD VOLUME_GetAudioCDSerial( const CDROM_TOC *toc )
287 {
288     DWORD serial = 0;
289     int i;
290
291     for (i = 0; i <= toc->LastTrack - toc->FirstTrack; i++)
292         serial += ((toc->TrackData[i].Address[1] << 16) |
293                    (toc->TrackData[i].Address[2] << 8) |
294                    toc->TrackData[i].Address[3]);
295
296     /*
297      * dwStart, dwEnd collect the beginning and end of the disc respectively, in
298      * frames.
299      * There it is collected for correcting the serial when there are less than
300      * 3 tracks.
301      */
302     if (toc->LastTrack - toc->FirstTrack + 1 < 3)
303     {
304         DWORD dwStart = FRAME_OF_TOC(toc, toc->FirstTrack);
305         DWORD dwEnd = FRAME_OF_TOC(toc, toc->LastTrack + 1);
306         serial += dwEnd - dwStart;
307     }
308     return serial;
309 }
310
311
312 /***********************************************************************
313  *           GetVolumeInformationW   (KERNEL32.@)
314  */
315 BOOL WINAPI GetVolumeInformationW( LPCWSTR root, LPWSTR label, DWORD label_len,
316                                    DWORD *serial, DWORD *filename_len, DWORD *flags,
317                                    LPWSTR fsname, DWORD fsname_len )
318 {
319     static const WCHAR audiocdW[] = {'A','u','d','i','o',' ','C','D',0};
320     static const WCHAR fatW[] = {'F','A','T',0};
321     static const WCHAR cdfsW[] = {'C','D','F','S',0};
322
323     WCHAR device[] = {'\\','\\','.','\\','A',':',0};
324     HANDLE handle;
325     enum fs_type type = FS_UNKNOWN;
326
327     if (!root)
328     {
329         WCHAR path[MAX_PATH];
330         GetCurrentDirectoryW( MAX_PATH, path );
331         device[4] = path[0];
332     }
333     else
334     {
335         if (!root[0] || root[1] != ':')
336         {
337             SetLastError( ERROR_INVALID_NAME );
338             return FALSE;
339         }
340         device[4] = root[0];
341     }
342
343     /* try to open the device */
344
345     handle = CreateFileW( device, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,
346                           NULL, OPEN_EXISTING, 0, 0 );
347     if (handle != INVALID_HANDLE_VALUE)
348     {
349         BYTE superblock[SUPERBLOCK_SIZE];
350         CDROM_TOC toc;
351         DWORD br;
352
353         /* check for audio CD */
354         /* FIXME: we only check the first track for now */
355         if (DeviceIoControl( handle, IOCTL_CDROM_READ_TOC, NULL, 0, &toc, sizeof(toc), &br, 0 ))
356         {
357             if (!(toc.TrackData[0].Control & 0x04))  /* audio track */
358             {
359                 TRACE( "%s: found audio CD\n", debugstr_w(device) );
360                 if (label) lstrcpynW( label, audiocdW, label_len );
361                 if (serial) *serial = VOLUME_GetAudioCDSerial( &toc );
362                 CloseHandle( handle );
363                 type = FS_ISO9660;
364                 goto fill_fs_info;
365             }
366             type = VOLUME_ReadCDSuperblock( handle, superblock );
367         }
368         else
369         {
370             type = VOLUME_ReadFATSuperblock( handle, superblock );
371             if (type == FS_UNKNOWN) type = VOLUME_ReadCDSuperblock( handle, superblock );
372         }
373         CloseHandle( handle );
374         TRACE( "%s: found fs type %d\n", debugstr_w(device), type );
375         if (type == FS_ERROR) return FALSE;
376
377         if (label && label_len) VOLUME_GetSuperblockLabel( type, superblock, label, label_len );
378         if (serial) *serial = VOLUME_GetSuperblockSerial( type, superblock );
379         goto fill_fs_info;
380     }
381     else
382     {
383         TRACE( "cannot open device %s: err %ld\n", debugstr_w(device), GetLastError() );
384         if (GetLastError() != ERROR_ACCESS_DENIED) return FALSE;
385     }
386
387     /* we couldn't open the device, fallback to default strategy */
388
389     switch(GetDriveTypeW( root ))
390     {
391     case DRIVE_UNKNOWN:
392     case DRIVE_NO_ROOT_DIR:
393         SetLastError( ERROR_NOT_READY );
394         return FALSE;
395     case DRIVE_REMOVABLE:
396     case DRIVE_FIXED:
397     case DRIVE_REMOTE:
398     case DRIVE_RAMDISK:
399         type = FS_UNKNOWN;
400         break;
401     case DRIVE_CDROM:
402         type = FS_ISO9660;
403         break;
404     }
405
406     if (label && label_len)
407     {
408         WCHAR labelW[] = {'A',':','\\','.','w','i','n','d','o','w','s','-','l','a','b','e','l',0};
409
410         labelW[0] = device[4];
411         handle = CreateFileW( labelW, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
412                               OPEN_EXISTING, 0, 0 );
413         if (handle != INVALID_HANDLE_VALUE)
414         {
415             char buffer[256], *p;
416             DWORD size;
417
418             if (!ReadFile( handle, buffer, sizeof(buffer)-1, &size, NULL )) size = 0;
419             CloseHandle( handle );
420             p = buffer + size;
421             while (p > buffer && (p[-1] == ' ' || p[-1] == '\r' || p[-1] == '\n')) p--;
422             *p = 0;
423             if (!MultiByteToWideChar( CP_UNIXCP, 0, buffer, -1, label, label_len ))
424                 label[label_len-1] = 0;
425         }
426         else label[0] = 0;
427     }
428     if (serial)
429     {
430         WCHAR serialW[] = {'A',':','\\','.','w','i','n','d','o','w','s','-','s','e','r','i','a','l',0};
431
432         serialW[0] = device[4];
433         handle = CreateFileW( serialW, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
434                               OPEN_EXISTING, 0, 0 );
435         if (handle != INVALID_HANDLE_VALUE)
436         {
437             char buffer[32];
438             DWORD size;
439
440             if (!ReadFile( handle, buffer, sizeof(buffer)-1, &size, NULL )) size = 0;
441             CloseHandle( handle );
442             buffer[size] = 0;
443             *serial = strtoul( buffer, NULL, 16 );
444         }
445         else *serial = 0;
446     }
447
448 fill_fs_info:  /* now fill in the information that depends on the file system type */
449
450     switch(type)
451     {
452     case FS_ISO9660:
453         if (fsname) lstrcpynW( fsname, cdfsW, fsname_len );
454         if (filename_len) *filename_len = 221;
455         if (flags) *flags = FILE_READ_ONLY_VOLUME;
456         break;
457     case FS_FAT1216:
458     case FS_FAT32:
459     default:  /* default to FAT file system (FIXME) */
460         if (fsname) lstrcpynW( fsname, fatW, fsname_len );
461         if (filename_len) *filename_len = 255;
462         if (flags) *flags = FILE_CASE_PRESERVED_NAMES;  /* FIXME */
463         break;
464     }
465     return TRUE;
466 }
467
468
469 /***********************************************************************
470  *           GetVolumeInformationA   (KERNEL32.@)
471  */
472 BOOL WINAPI GetVolumeInformationA( LPCSTR root, LPSTR label,
473                                        DWORD label_len, DWORD *serial,
474                                        DWORD *filename_len, DWORD *flags,
475                                        LPSTR fsname, DWORD fsname_len )
476 {
477     UNICODE_STRING rootW;
478     LPWSTR labelW, fsnameW;
479     BOOL ret;
480
481     if (root) RtlCreateUnicodeStringFromAsciiz(&rootW, root);
482     else rootW.Buffer = NULL;
483     labelW = label ? HeapAlloc(GetProcessHeap(), 0, label_len * sizeof(WCHAR)) : NULL;
484     fsnameW = fsname ? HeapAlloc(GetProcessHeap(), 0, fsname_len * sizeof(WCHAR)) : NULL;
485
486     if ((ret = GetVolumeInformationW(rootW.Buffer, labelW, label_len, serial,
487                                     filename_len, flags, fsnameW, fsname_len)))
488     {
489         if (label) WideCharToMultiByte(CP_ACP, 0, labelW, -1, label, label_len, NULL, NULL);
490         if (fsname) WideCharToMultiByte(CP_ACP, 0, fsnameW, -1, fsname, fsname_len, NULL, NULL);
491     }
492
493     RtlFreeUnicodeString(&rootW);
494     if (labelW) HeapFree( GetProcessHeap(), 0, labelW );
495     if (fsnameW) HeapFree( GetProcessHeap(), 0, fsnameW );
496     return ret;
497 }
498
499
500
501 /***********************************************************************
502  *           SetVolumeLabelW   (KERNEL32.@)
503  */
504 BOOL WINAPI SetVolumeLabelW( LPCWSTR root, LPCWSTR label )
505 {
506     WCHAR device[] = {'\\','\\','.','\\','A',':',0};
507     HANDLE handle;
508     enum fs_type type = FS_UNKNOWN;
509
510     if (!root)
511     {
512         WCHAR path[MAX_PATH];
513         GetCurrentDirectoryW( MAX_PATH, path );
514         device[4] = path[0];
515     }
516     else
517     {
518         if (!root[0] || root[1] != ':')
519         {
520             SetLastError( ERROR_INVALID_NAME );
521             return FALSE;
522         }
523         device[4] = root[0];
524     }
525
526     /* try to open the device */
527
528     handle = CreateFileW( device, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
529                           NULL, OPEN_EXISTING, 0, 0 );
530     if (handle == INVALID_HANDLE_VALUE)
531     {
532         /* try read-only */
533         handle = CreateFileW( device, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,
534                               NULL, OPEN_EXISTING, 0, 0 );
535         if (handle != INVALID_HANDLE_VALUE)
536         {
537             /* device can be read but not written, return error */
538             CloseHandle( handle );
539             SetLastError( ERROR_ACCESS_DENIED );
540             return FALSE;
541         }
542     }
543
544     if (handle != INVALID_HANDLE_VALUE)
545     {
546         BYTE superblock[SUPERBLOCK_SIZE];
547         BOOL ret;
548
549         type = VOLUME_ReadFATSuperblock( handle, superblock );
550         ret = VOLUME_SetSuperblockLabel( type, handle, label );
551         CloseHandle( handle );
552         return ret;
553     }
554     else
555     {
556         TRACE( "cannot open device %s: err %ld\n", debugstr_w(device), GetLastError() );
557         if (GetLastError() != ERROR_ACCESS_DENIED) return FALSE;
558     }
559
560     /* we couldn't open the device, fallback to default strategy */
561
562     switch(GetDriveTypeW( root ))
563     {
564     case DRIVE_UNKNOWN:
565     case DRIVE_NO_ROOT_DIR:
566         SetLastError( ERROR_NOT_READY );
567         break;
568     case DRIVE_REMOVABLE:
569     case DRIVE_FIXED:
570         {
571             WCHAR labelW[] = {'A',':','\\','.','w','i','n','d','o','w','s','-','l','a','b','e','l',0};
572
573             labelW[0] = device[4];
574             handle = CreateFileW( labelW, GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
575                                   CREATE_ALWAYS, 0, 0 );
576             if (handle != INVALID_HANDLE_VALUE)
577             {
578                 char buffer[64];
579                 DWORD size;
580
581                 if (!WideCharToMultiByte( CP_UNIXCP, 0, label, -1, buffer, sizeof(buffer), NULL, NULL ))
582                     buffer[sizeof(buffer)-1] = 0;
583                 WriteFile( handle, buffer, strlen(buffer), &size, NULL );
584                 CloseHandle( handle );
585                 return TRUE;
586             }
587             break;
588         }
589     case DRIVE_REMOTE:
590     case DRIVE_RAMDISK:
591     case DRIVE_CDROM:
592         SetLastError( ERROR_ACCESS_DENIED );
593         break;
594     }
595     return FALSE;
596 }
597
598 /***********************************************************************
599  *           SetVolumeLabelA   (KERNEL32.@)
600  */
601 BOOL WINAPI SetVolumeLabelA(LPCSTR root, LPCSTR volname)
602 {
603     UNICODE_STRING rootW, volnameW;
604     BOOL ret;
605
606     if (root) RtlCreateUnicodeStringFromAsciiz(&rootW, root);
607     else rootW.Buffer = NULL;
608     if (volname) RtlCreateUnicodeStringFromAsciiz(&volnameW, volname);
609     else volnameW.Buffer = NULL;
610
611     ret = SetVolumeLabelW( rootW.Buffer, volnameW.Buffer );
612
613     RtlFreeUnicodeString(&rootW);
614     RtlFreeUnicodeString(&volnameW);
615     return ret;
616 }
617
618
619 /***********************************************************************
620  *           GetVolumeNameForVolumeMountPointW   (KERNEL32.@)
621  */
622 BOOL WINAPI GetVolumeNameForVolumeMountPointW(LPCWSTR str, LPWSTR dst, DWORD size)
623 {
624     FIXME("(%s, %p, %lx): stub\n", debugstr_w(str), dst, size);
625     return 0;
626 }