1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS FS utility tool
4  * FILE:            modules/rosapps/applications/cmdutils/vcdcli/vcdcli.c
5  * PURPOSE:         Virtual CD-ROM management application
6  * PROGRAMMERS:     Pierre Schweitzer <pierre@reactos.org>
7  */
8 
9 #define WIN32_NO_STATUS
10 #include <windef.h>
11 #include <winbase.h>
12 #include <winsvc.h>
13 #include <winreg.h>
14 #include <ndk/rtltypes.h>
15 #include <ndk/rtlfuncs.h>
16 #include <tchar.h>
17 #include <stdio.h>
18 
19 #include <vcdioctl.h>
20 
21 #define IOCTL_CDROM_BASE FILE_DEVICE_CD_ROM
22 #define IOCTL_CDROM_EJECT_MEDIA CTL_CODE(IOCTL_CDROM_BASE, 0x0202, METHOD_BUFFERED, FILE_READ_ACCESS)
23 
24 void
25 PrintUsage(int type)
26 {
27     if (type == 0)
28     {
29         _ftprintf(stdout, _T("vcdcli usage:\n"));
30         _ftprintf(stdout, _T("\tlist [/a]: list all the virtual drives\n"));
31         _ftprintf(stdout, _T("\tcreate: create a virtual drive\n"));
32         _ftprintf(stdout, _T("\tmount X path: mount path image on X virtual drive\n"));
33         _ftprintf(stdout, _T("\tremount X: remount image on X virtual drive\n"));
34         _ftprintf(stdout, _T("\tremount X: remount image on X virtual drive\n"));
35         _ftprintf(stdout, _T("\teject X: eject image on X virtual drive\n"));
36         _ftprintf(stdout, _T("\tremove X: remove virtual drive X\n"));
37     }
38     else if (type == 1)
39     {
40         _ftprintf(stdout, _T("mount usage:\n"));
41         _ftprintf(stdout, _T("\tmount <drive letter> <path.iso> [/u] [/j] [/p]\n"));
42         _ftprintf(stdout, _T("\tMount the ISO image given in <path.iso> on the previously created virtual drive <drive letter>\n"));
43         _ftprintf(stdout, _T("\t\tDo not use colon for drive letter\n"));
44         _ftprintf(stdout, _T("\t\tUse /u to make UDF volumes not appear as such\n"));
45         _ftprintf(stdout, _T("\t\tUse /j to make Joliet volumes not appear as such\n"));
46         _ftprintf(stdout, _T("\t\tUse /p to make the mounting persistent\n"));
47     }
48     else if (type == 2)
49     {
50         _ftprintf(stdout, _T("remount usage:\n"));
51         _ftprintf(stdout, _T("\tremount <drive letter>\n"));
52         _ftprintf(stdout, _T("\tRemount the ISO image that was previously mounted on the virtual drive <drive letter>\n"));
53         _ftprintf(stdout, _T("\t\tDo not use colon for drive letter\n"));
54     }
55     else if (type == 3)
56     {
57         _ftprintf(stdout, _T("eject usage:\n"));
58         _ftprintf(stdout, _T("\teject <drive letter>\n"));
59         _ftprintf(stdout, _T("\tEjects the ISO image that is mounted on the virtual drive <drive letter>\n"));
60         _ftprintf(stdout, _T("\t\tDo not use colon for drive letter\n"));
61     }
62     else if (type == 4)
63     {
64         _ftprintf(stdout, _T("remove usage:\n"));
65         _ftprintf(stdout, _T("\tremove <drive letter>\n"));
66         _ftprintf(stdout, _T("\tRemoves the virtual drive <drive letter> making it no longer usable\n"));
67         _ftprintf(stdout, _T("\t\tDo not use colon for drive letter\n"));
68     }
69 }
70 
71 HANDLE
72 OpenLetter(WCHAR Letter)
73 {
74     TCHAR Device[255];
75 
76     /* Make name */
77     _stprintf(Device, _T("\\\\.\\%c:"), Letter);
78 
79     /* And open */
80     return CreateFile(Device, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
81                       NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
82 }
83 
84 BOOLEAN
85 StartDriver(VOID)
86 {
87     SC_HANDLE hMgr, hSvc;
88 
89     /* Open the SC manager */
90     hMgr = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
91     if (hMgr == NULL)
92     {
93         _ftprintf(stderr, _T("Failed opening service manager: %x\n"), GetLastError());
94         return FALSE;
95     }
96 
97     /* Open the service matching our driver */
98     hSvc = OpenService(hMgr, _T("Vcdrom"), SERVICE_START);
99     if (hSvc == NULL)
100     {
101         _ftprintf(stderr, _T("Failed opening service: %x\n"), GetLastError());
102         CloseServiceHandle(hMgr);
103         return FALSE;
104     }
105 
106     /* Start it */
107     /* FIXME: improve */
108     StartService(hSvc, 0, NULL);
109 
110     /* Cleanup */
111     CloseServiceHandle(hSvc);
112     CloseServiceHandle(hMgr);
113 
114     /* Always return true when service exists
115      * We don't care whether it was running or not
116      * We just need it
117      */
118     return TRUE;
119 }
120 
121 HANDLE
122 OpenMaster(VOID)
123 {
124     /* We'll always talk to master first, so we start it here */
125     if (!StartDriver())
126     {
127         return INVALID_HANDLE_VALUE;
128     }
129 
130     /* And then, open it */
131     return CreateFile(_T("\\\\.\\\\VirtualCdRom"), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
132                       NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
133 }
134 
135 BOOLEAN
136 IsLetterOwned(WCHAR Letter)
137 {
138     HANDLE hDev;
139     BOOLEAN Res;
140     DRIVES_LIST Drives;
141     DWORD i, BytesRead;
142 
143     /* We've to deal with driver */
144     hDev = OpenMaster();
145     if (hDev == INVALID_HANDLE_VALUE)
146     {
147         _ftprintf(stderr, _T("Failed to open VCD: %x\n"), GetLastError());
148         return FALSE;
149     }
150 
151     /* Get the list of the managed drives */
152     Res = DeviceIoControl(hDev, IOCTL_VCDROM_ENUMERATE_DRIVES, NULL, 0, &Drives, sizeof(Drives), &BytesRead, NULL);
153     if (!Res)
154     {
155         _ftprintf(stderr, _T("Failed to enumerate drives: %x\n"), GetLastError());
156         CloseHandle(hDev);
157         return FALSE;
158     }
159 
160     /* Don't leak ;-) */
161     CloseHandle(hDev);
162 
163     /* Do we find our letter in the list? */
164     for (i = 0; i < Drives.Count; ++i)
165     {
166         if (Drives.Drives[i] == Letter)
167         {
168             break;
169         }
170     }
171 
172     /* No? Fail */
173     if (i == Drives.Count)
174     {
175         _ftprintf(stderr, _T("%c is not a drive owned by VCD\n"), Letter);
176         return FALSE;
177     }
178 
179     /* Otherwise, that's fine! */
180     return TRUE;
181 }
182 
183 FORCEINLINE
184 DWORD
185 Min(DWORD a, DWORD b)
186 {
187     return (a > b ? b : a);
188 }
189 
190 int
191 __cdecl
192 _tmain(int argc, const TCHAR *argv[])
193 {
194     HANDLE hDev;
195     BOOLEAN Res;
196     DWORD BytesRead;
197 
198     /* We need a command, at least */
199     if (argc < 2)
200     {
201         PrintUsage(0);
202         return 1;
203     }
204 
205     /* List will display all the managed drives */
206     if (_tcscmp(argv[1], _T("list")) == 0)
207     {
208         DWORD i;
209         BOOLEAN All;
210         DRIVES_LIST Drives;
211 
212         /* Open the driver for query */
213         hDev = OpenMaster();
214         if (hDev == INVALID_HANDLE_VALUE)
215         {
216             _ftprintf(stderr, _T("Failed to open VCD: %x\n"), GetLastError());
217             return 1;
218         }
219 
220         /* Query the virtual drives */
221         Res = DeviceIoControl(hDev, IOCTL_VCDROM_ENUMERATE_DRIVES, NULL, 0, &Drives, sizeof(Drives), &BytesRead, NULL);
222         if (!Res)
223         {
224             _ftprintf(stderr, _T("Failed to create VCD: %x\n"), GetLastError());
225             CloseHandle(hDev);
226             return 1;
227         }
228 
229         /* Done with master */
230         CloseHandle(hDev);
231 
232         /* No drives? Display a pretty message */
233         if (Drives.Count == 0)
234         {
235             _ftprintf(stdout, _T("No virtual drives\n"));
236         }
237         else
238         {
239             /* Do we have to display all the information? That's '/a' */
240             All = FALSE;
241             if (argc > 2)
242             {
243                 if (_tcscmp(argv[2], _T("/a")) == 0)
244                 {
245                     All = TRUE;
246                 }
247             }
248 
249             if (All)
250             {
251                 _ftprintf(stdout, _T("Managed drives:\n"));
252                 /* For each virtual drive... */
253                 for (i = 0; i < Drives.Count; ++i)
254                 {
255                     HANDLE hLet;
256                     IMAGE_PATH Image;
257 
258                     /* Display its letter */
259                     _ftprintf(stdout, _T("%c: "), Drives.Drives[i]);
260 
261                     /* And open it to query more data */
262                     hLet = OpenLetter(Drives.Drives[i]);
263                     if (hLet != INVALID_HANDLE_VALUE)
264                     {
265                         /* Get the image path along with mount status */
266                         Res = DeviceIoControl(hLet, IOCTL_VCDROM_GET_IMAGE_PATH, NULL, 0, &Image, sizeof(Image), &BytesRead, NULL);
267                         /* If it succeed */
268                         if (Res)
269                         {
270                             UNICODE_STRING Path;
271 
272                             /* Display image if any, otherwise display "no image" */
273                             if (Image.Length != 0)
274                             {
275                                 Path.Length = Image.Length;
276                                 Path.MaximumLength = Image.Length;
277                                 Path.Buffer = Image.Path;
278                             }
279                             else
280                             {
281                                 Path.Length = sizeof(L"no image") - sizeof(UNICODE_NULL);
282                                 Path.MaximumLength = sizeof(L"no image");
283                                 Path.Buffer = L"no image";
284                             }
285 
286                             /* Print everything including mount status */
287                             _ftprintf(stdout, _T("%wZ, %s"), &Path, (Image.Mounted == 0 ? _T("not mounted") : _T("mounted")));
288                         }
289 
290                         /* Close drive and move to the next one */
291                         CloseHandle(hLet);
292                     }
293 
294                     /* EOL! */
295                     _ftprintf(stdout, _T("\n"));
296                 }
297             }
298             else
299             {
300                 /* Basic display, just display drives on a single line */
301                 _ftprintf(stdout, _T("Virtual drives:\n"));
302                 for (i = 0; i < Drives.Count; ++i)
303                 {
304                     _ftprintf(stdout, _T("%c: "), Drives.Drives[i]);
305                 }
306                 _ftprintf(stdout, _T("\n"));
307             }
308         }
309     }
310     else if (_tcscmp(argv[1], _T("create")) == 0)
311     {
312         WCHAR Letter;
313 
314         /* Open driver */
315         hDev = OpenMaster();
316         if (hDev == INVALID_HANDLE_VALUE)
317         {
318             _ftprintf(stderr, _T("Failed to open VCD: %x\n"), GetLastError());
319             return 1;
320         }
321 
322         /* Issue the IOCTL */
323         Res = DeviceIoControl(hDev, IOCTL_VCDROM_CREATE_DRIVE, NULL, 0, &Letter, sizeof(WCHAR), &BytesRead, NULL);
324         if (!Res)
325         {
326             _ftprintf(stderr, _T("Failed to create drive: %x\n"), GetLastError());
327             CloseHandle(hDev);
328             return 1;
329         }
330 
331         /* And display the create drive letter to the user */
332         _ftprintf(stdout, _T("The virtual drive '%c' has been created\n"), Letter);
333 
334         CloseHandle(hDev);
335     }
336     else if (_tcscmp(argv[1], _T("mount")) == 0)
337     {
338         DWORD i;
339         HKEY hKey;
340         HANDLE hFile;
341         WCHAR Letter;
342         BOOLEAN bPersist;
343         TCHAR szBuffer[260];
344         UNICODE_STRING NtPathName;
345         MOUNT_PARAMETERS MountParams;
346 
347         /* We need two args */
348         if (argc < 4)
349         {
350             PrintUsage(1);
351             return 1;
352         }
353 
354         /* First, check letter is OK */
355         if (!_istalpha(argv[2][0]) || argv[2][1] != 0)
356         {
357             PrintUsage(1);
358             return 1;
359         }
360 
361         /* Now, check the ISO image is OK and reachable by the user */
362         hFile = CreateFile(argv[3], FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
363         if (hFile == INVALID_HANDLE_VALUE)
364         {
365             _ftprintf(stderr, _T("Failed to open file: %s\n"), GetLastError());
366             return 1;
367         }
368 
369         /* Validate the drive is owned by vcdrom */
370         Letter = _totupper(argv[2][0]);
371         if (!IsLetterOwned(Letter))
372         {
373             CloseHandle(hFile);
374             return 1;
375         }
376 
377         /* Get NT path for the driver */
378         if (!RtlDosPathNameToNtPathName_U(argv[3], &NtPathName, NULL, NULL))
379         {
380             _ftprintf(stderr, _T("Failed to convert path\n"));
381             CloseHandle(hFile);
382             return 1;
383         }
384 
385         /* Copy it in the parameter structure */
386         _tcsncpy(MountParams.Path, NtPathName.Buffer, 255);
387         MountParams.Length = Min(NtPathName.Length, 255 * sizeof(WCHAR));
388         MountParams.Flags = 0;
389 
390         /* Do we have to suppress anything? */
391         bPersist = FALSE;
392         for (i = 4; i < argc; ++i)
393         {
394             /* Make UDF uneffective */
395             if (_tcscmp(argv[i], _T("/u")) == 0)
396             {
397                 MountParams.Flags |= MOUNT_FLAG_SUPP_UDF;
398             }
399             /* Make Joliet uneffective */
400             else if (_tcscmp(argv[i], _T("/j")) == 0)
401             {
402                 MountParams.Flags |= MOUNT_FLAG_SUPP_JOLIET;
403             }
404             /* Should it be persistent? */
405             else if (_tcscmp(argv[i], _T("/p")) == 0)
406             {
407                 bPersist = TRUE;
408             }
409         }
410 
411         /* No longer needed */
412         RtlFreeHeap(RtlGetProcessHeap(), 0, NtPathName.Buffer);
413 
414         /* Open the drive */
415         hDev = OpenLetter(Letter);
416         if (hDev == INVALID_HANDLE_VALUE)
417         {
418             _ftprintf(stderr, _T("Failed to open VCD %c: %x\n"), Letter, GetLastError());
419             CloseHandle(hFile);
420             return 1;
421         }
422 
423         /* We have to release image now, the driver will attempt to open it */
424         CloseHandle(hFile);
425 
426         /* Issue the mount IOCTL */
427         Res = DeviceIoControl(hDev, IOCTL_VCDROM_MOUNT_IMAGE, &MountParams, sizeof(MountParams), NULL, 0, &BytesRead, NULL);
428         if (!Res)
429         {
430             _ftprintf(stderr, _T("Failed to mount %s on %c: %x\n"), argv[3], Letter, GetLastError());
431             CloseHandle(hDev);
432             return 1;
433         }
434 
435         /* Pretty print in case of a success */
436         _ftprintf(stdout, _T("%s mounted on %c\n"), argv[3], Letter);
437 
438         CloseHandle(hDev);
439 
440         /* Should it persistent? */
441         if (bPersist)
442         {
443             /* Create the registry key Device<Letter> */
444             _stprintf(szBuffer, _T("SYSTEM\\CurrentControlSet\\Services\\Vcdrom\\Parameters\\Device%c"), Letter);
445             if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, szBuffer, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_CREATE_SUB_KEY | KEY_SET_VALUE, NULL, &hKey, NULL) == ERROR_SUCCESS)
446             {
447                 /* Set the image path */
448                 _tcsncpy(szBuffer, MountParams.Path, MountParams.Length);
449                 szBuffer[MountParams.Length / sizeof(TCHAR)] = 0;
450                 RegSetValueExW(hKey, _T("IMAGE"), 0, REG_SZ, (BYTE *)szBuffer, MountParams.Length);
451 
452                 /* Set the drive letter */
453                 szBuffer[0] = Letter;
454                 szBuffer[1] = _T(':');
455                 szBuffer[2] = 0;
456                 RegSetValueExW(hKey, _T("DRIVE"), 0, REG_SZ, (BYTE *)szBuffer, 3 * sizeof(TCHAR));
457 
458                 RegCloseKey(hKey);
459             }
460             else
461             {
462                 _ftprintf(stderr, _T("Failed to make mounting persistent: %x\n"), GetLastError());
463             }
464         }
465     }
466     else if (_tcscmp(argv[1], _T("remount")) == 0)
467     {
468         WCHAR Letter;
469 
470         /* We need an arg */
471         if (argc < 3)
472         {
473             PrintUsage(2);
474             return 1;
475         }
476 
477         /* First, check letter is OK */
478         if (!_istalpha(argv[2][0]) || argv[2][1] != 0)
479         {
480             PrintUsage(2);
481             return 1;
482         }
483 
484         /* Validate the drive is owned by vcdrom */
485         Letter = _totupper(argv[2][0]);
486         if (!IsLetterOwned(Letter))
487         {
488             return 1;
489         }
490 
491         /* Open the drive */
492         hDev = OpenLetter(Letter);
493         if (hDev == INVALID_HANDLE_VALUE)
494         {
495             _ftprintf(stderr, _T("Failed to open VCD %c: %x\n"), Letter, GetLastError());
496             return 1;
497         }
498 
499         /* Issue the remount IOCTL */
500         Res = DeviceIoControl(hDev, IOCTL_STORAGE_LOAD_MEDIA, NULL, 0, NULL, 0, &BytesRead, NULL);
501         if (!Res)
502         {
503             _ftprintf(stderr, _T("Failed to remount media on %c: %x\n"), Letter, GetLastError());
504             CloseHandle(hDev);
505             return 1;
506         }
507 
508         /* Pretty print in case of a success */
509         _ftprintf(stdout, _T("Media remounted on %c\n"), Letter);
510 
511         CloseHandle(hDev);
512     }
513     else if (_tcscmp(argv[1], _T("eject")) == 0)
514     {
515         WCHAR Letter;
516 
517         /* We need an arg */
518         if (argc < 3)
519         {
520             PrintUsage(3);
521             return 1;
522         }
523 
524         /* First, check letter is OK */
525         if (!_istalpha(argv[2][0]) || argv[2][1] != 0)
526         {
527             PrintUsage(3);
528             return 1;
529         }
530 
531         /* Validate the drive is owned by vcdrom */
532         Letter = _totupper(argv[2][0]);
533         if (!IsLetterOwned(Letter))
534         {
535             return 1;
536         }
537 
538         /* Open the drive */
539         hDev = OpenLetter(Letter);
540         if (hDev == INVALID_HANDLE_VALUE)
541         {
542             _ftprintf(stderr, _T("Failed to open VCD %c: %x\n"), Letter, GetLastError());
543             return 1;
544         }
545 
546         /* Issue the eject IOCTL */
547         Res = DeviceIoControl(hDev, IOCTL_CDROM_EJECT_MEDIA, NULL, 0, NULL, 0, &BytesRead, NULL);
548         if (!Res)
549         {
550             _ftprintf(stderr, _T("Failed to eject media on %c: %x\n"), Letter, GetLastError());
551             CloseHandle(hDev);
552             return 1;
553         }
554 
555         /* Pretty print in case of a success */
556         _ftprintf(stdout, _T("Media ejected on %c\n"), Letter);
557 
558         CloseHandle(hDev);
559     }
560     else if (_tcscmp(argv[1], _T("remove")) == 0)
561     {
562         WCHAR Letter;
563 
564         /* We need an arg */
565         if (argc < 3)
566         {
567             PrintUsage(4);
568             return 1;
569         }
570 
571         /* First, check letter is OK */
572         if (!_istalpha(argv[2][0]) || argv[2][1] != 0)
573         {
574             PrintUsage(4);
575             return 1;
576         }
577 
578         /* Validate the drive is owned by vcdrom */
579         Letter = _totupper(argv[2][0]);
580         if (!IsLetterOwned(Letter))
581         {
582             return 1;
583         }
584 
585         /* Open the drive */
586         hDev = OpenLetter(Letter);
587         if (hDev == INVALID_HANDLE_VALUE)
588         {
589             _ftprintf(stderr, _T("Failed to open VCD %c: %x\n"), Letter, GetLastError());
590             return 1;
591         }
592 
593         /* Issue the remove IOCTL */
594         Res = DeviceIoControl(hDev, IOCTL_VCDROM_DELETE_DRIVE, NULL, 0, NULL, 0, &BytesRead, NULL);
595         if (!Res)
596         {
597             _ftprintf(stderr, _T("Failed to remove virtual drive %c: %x\n"), Letter, GetLastError());
598             CloseHandle(hDev);
599             return 1;
600         }
601 
602         /* Pretty print in case of a success */
603         _ftprintf(stdout, _T("Virtual drive %c removed\n"), Letter);
604 
605         CloseHandle(hDev);
606     }
607     else
608     {
609         PrintUsage(0);
610     }
611 
612     return 0;
613 }
614