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