captive_cdrom_init(): 'const gchar *image_pathname' -> 'GIOChannel *image_iochannel'
[captive.git] / src / libcaptive / storage / cdrom.c
1 /* $Id$
2  * "\Device\CdRom%d" storage emulation driver for reactos of libcaptive
3  * Copyright (C) 2002 Jan Kratochvil <project-captive@jankratochvil.net>
4  * 
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; exactly version 2 of June 1991 is required
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19
20 #include "config.h"
21
22 #include "captive/storage.h"    /* self */
23 #include "reactos/ddk/iotypes.h"        /* for DRIVER_OBJECT */
24 #include <glib/gmessages.h>
25 #include <sys/types.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include "reactos/ddk/ntddscsi.h"       /* for IO_SCSI_CAPABILITIES */
29 #include "reactos/ddk/class2.h" /* for PDEVICE_EXTENSION */
30 #include "reactos/ddk/status.h" /* for STATUS_INVALID_PARAMETER */
31 #include "reactos/ddk/iofuncs.h"        /* for IoCreateDevice() */
32 #include "captive/unicode.h"
33 #include "captive/macros.h"
34 #include "reactos/ddk/mmfuncs.h"        /* for MmGetSystemAddressForMdl() */
35
36
37 static DRIVER_OBJECT cdrom_DriverObject;
38 static DISK_GEOMETRY cdrom_DiskGeometry;
39 static DISK_GEOMETRY cdrom_DiskGeometry_check;  /* for g_assert() checking against foreign modifications */
40 static IO_SCSI_CAPABILITIES cdrom_PortCapabilities;
41 static IO_SCSI_CAPABILITIES cdrom_PortCapabilities_check;       /* for g_assert() checking against foreign modifications */
42 static GIOChannel *Image_iochannel=NULL;
43 static guint64 Image_size;      /* FIXME: lseek64() */
44
45
46 static gboolean validate_DeviceObject(DEVICE_OBJECT *DeviceObject)
47 {
48 DEVICE_EXTENSION *DeviceExtension;
49 DISK_GEOMETRY *DiskGeometry;
50 IO_SCSI_CAPABILITIES *PortCapabilities;
51
52         g_return_val_if_fail(DeviceObject!=NULL,FALSE);
53         g_return_val_if_fail(DeviceObject->DriverObject==&cdrom_DriverObject,FALSE);
54
55         DeviceExtension=DeviceObject->DeviceExtension;
56
57         DiskGeometry=DeviceExtension->DiskGeometry;
58         g_return_val_if_fail(DiskGeometry==&cdrom_DiskGeometry,FALSE);
59         g_return_val_if_fail(DiskGeometry->MediaType==cdrom_DiskGeometry_check.MediaType,FALSE);
60         g_return_val_if_fail(DiskGeometry->TracksPerCylinder==cdrom_DiskGeometry_check.TracksPerCylinder,FALSE);
61         g_return_val_if_fail(DiskGeometry->SectorsPerTrack==cdrom_DiskGeometry_check.SectorsPerTrack,FALSE);
62         g_return_val_if_fail(DiskGeometry->BytesPerSector==cdrom_DiskGeometry_check.BytesPerSector,FALSE);
63         g_return_val_if_fail(DiskGeometry->Cylinders.QuadPart==cdrom_DiskGeometry_check.Cylinders.QuadPart,FALSE);
64         g_return_val_if_fail(DeviceExtension->PartitionLength.QuadPart==(gint64)Image_size,FALSE);
65
66         PortCapabilities=DeviceExtension->PortCapabilities;
67         g_return_val_if_fail(PortCapabilities==&cdrom_PortCapabilities,FALSE);
68         g_return_val_if_fail(PortCapabilities->Length==cdrom_PortCapabilities_check.Length,FALSE);
69         g_return_val_if_fail(PortCapabilities->MaximumTransferLength==cdrom_PortCapabilities_check.MaximumTransferLength,FALSE);
70         g_return_val_if_fail(PortCapabilities->MaximumPhysicalPages==cdrom_PortCapabilities_check.MaximumPhysicalPages,FALSE);
71         g_return_val_if_fail(PortCapabilities->SupportedAsynchronousEvents==cdrom_PortCapabilities_check.SupportedAsynchronousEvents,FALSE);
72         g_return_val_if_fail(PortCapabilities->AlignmentMask==cdrom_PortCapabilities_check.AlignmentMask,FALSE);
73         g_return_val_if_fail(PortCapabilities->TaggedQueuing==cdrom_PortCapabilities_check.TaggedQueuing,FALSE);
74         g_return_val_if_fail(PortCapabilities->AdapterScansDown==cdrom_PortCapabilities_check.AdapterScansDown,FALSE);
75         g_return_val_if_fail(PortCapabilities->AdapterUsesPio==cdrom_PortCapabilities_check.AdapterUsesPio,FALSE);
76
77         return TRUE;
78 }
79
80
81 static NTSTATUS MajorFunction_Irp_finish(DEVICE_OBJECT *DeviceObject,IRP *Irp)
82 {
83 NTSTATUS r;
84
85         g_return_val_if_fail(TRUE==validate_DeviceObject(DeviceObject),STATUS_INVALID_PARAMETER);
86         g_return_val_if_fail(Irp!=NULL,STATUS_INVALID_PARAMETER);
87
88         /* required for removable media only */
89         if (!NT_SUCCESS(Irp->IoStatus.Status) && IoIsErrorUserInduced(Irp->IoStatus.Status)) {
90                 g_assert(Irp->Tail.Overlay.Thread!=NULL);       /* FIXME: Error should be postponed to first !=NULL Irp later */
91                 IoSetHardErrorOrVerifyDevice(Irp,DeviceObject);
92                 Irp->IoStatus.Information=0;    /* may got set during some processing before error occured */
93                 }
94
95         /* IoCompleteRequest() will do 'IoFreeIrp(Irp);'!
96          * 'IoStatus.Status' must be saved before its invocation!
97          */
98         r=Irp->IoStatus.Status;
99         IoCompleteRequest(Irp,IO_NO_INCREMENT);
100         return r;
101 }
102
103
104 /* FIXME: We should comply with PDRIVER_DISPATCH prototype but unfortunately
105  * CAPTIVE_STDCALL prevents us to do so at least in RedHat gcc-3.2-4 (gcc bug?).
106  */
107 #define MajorFunction_DEVICE_CONTROL ((PDRIVER_DISPATCH)MajorFunction_DEVICE_CONTROL_func)
108 static NTSTATUS CAPTIVE_STDCALL MajorFunction_DEVICE_CONTROL_func(IN DEVICE_OBJECT *DeviceObject,IN IRP *Irp)
109 {
110 IO_STACK_LOCATION *IrpStack;
111
112         g_return_val_if_fail(TRUE==validate_DeviceObject(DeviceObject),STATUS_INVALID_PARAMETER);
113         g_return_val_if_fail(Irp!=NULL,STATUS_INVALID_PARAMETER);
114
115         Irp->IoStatus.Information=0;    /* request-specific, may get overriden later */
116         IrpStack=IoGetCurrentIrpStackLocation(Irp);
117         g_assert(IrpStack->MajorFunction==IRP_MJ_DEVICE_CONTROL);
118         g_assert(IrpStack->MinorFunction==0);
119
120         switch (IrpStack->Parameters.DeviceIoControl.IoControlCode) {
121
122                 case IOCTL_CDROM_GET_LAST_SESSION:
123                         /* Nothing interesting to see, move along.
124                          * FIXME: This call is somehow multisession related - must we care?
125                          */
126                         /* PASSTHRU */
127
128                 case IOCTL_CDROM_READ_TOC: {
129 CDROM_TOC *CdromToc;
130
131                         if (IrpStack->Parameters.DeviceIoControl.OutputBufferLength<sizeof(CDROM_TOC)) {
132                                 Irp->IoStatus.Status=STATUS_BUFFER_TOO_SMALL;
133                                 Irp->IoStatus.Information=sizeof(CDROM_TOC);
134                                 g_assert_not_reached();
135                                 goto done;
136                                 }
137                         CdromToc=(CDROM_TOC *)Irp->AssociatedIrp.SystemBuffer;
138                         CAPTIVE_MEMZERO(CdromToc);
139                         CdromToc->Length[0]=((sizeof(*CdromToc)-2)>>0U)&0xFFU;  /* little-endian */
140                         CdromToc->Length[1]=((sizeof(*CdromToc)-2)>>8U)&0xFFU;
141                         CdromToc->FirstTrack=0; /* one track; TOC_LAST_TRACK does not count */
142                         CdromToc->LastTrack =0; /* one track; TOC_LAST_TRACK does not count */
143                         CdromToc->TrackData[0].Control=TOC_DATA_TRACK;
144                         CdromToc->TrackData[0].Adr=0;   /* Q-subchannel subinfo */
145                         CdromToc->TrackData[0].TrackNumber=0;
146                         CdromToc->TrackData[0].Address[0]=0>>24U;       /* LBA offset; big-endian */
147                         CdromToc->TrackData[0].Address[1]=0>>16U;
148                         CdromToc->TrackData[0].Address[2]=0>> 8U;
149                         CdromToc->TrackData[0].Address[3]=0>> 0U;
150                         CdromToc->TrackData[1].Control=0;
151                         CdromToc->TrackData[1].Adr=0;   /* Q-subchannel subinfo */
152                         CdromToc->TrackData[1].TrackNumber=TOC_LAST_TRACK;
153                         /* FIXME: should we put the Image_size to TOC_LAST_TRACK? */
154                         CdromToc->TrackData[1].Address[0]=(Image_size/512)>>24U;        /* LBA offset; big-endian */
155                         CdromToc->TrackData[1].Address[1]=(Image_size/512)>>16U;
156                         CdromToc->TrackData[1].Address[2]=(Image_size/512)>> 8U;
157                         CdromToc->TrackData[1].Address[3]=(Image_size/512)>> 0U;
158
159                         Irp->IoStatus.Information=sizeof(DISK_GEOMETRY);
160                         Irp->IoStatus.Status=STATUS_SUCCESS;
161                         } break;
162
163                 case IOCTL_CDROM_GET_DRIVE_GEOMETRY:
164                         if (IrpStack->Parameters.DeviceIoControl.OutputBufferLength<sizeof(DISK_GEOMETRY)) {
165                                 Irp->IoStatus.Status=STATUS_BUFFER_TOO_SMALL;
166                                 Irp->IoStatus.Information=sizeof(DISK_GEOMETRY);
167                                 g_assert_not_reached();
168                                 goto done;
169                                 }
170                         *(DISK_GEOMETRY *)Irp->AssociatedIrp.SystemBuffer=cdrom_DiskGeometry;
171                         Irp->IoStatus.Information=sizeof(DISK_GEOMETRY);
172                         Irp->IoStatus.Status=STATUS_SUCCESS;
173                         break;
174
175                 case IOCTL_CDROM_CHECK_VERIFY:
176                         if (IrpStack->Parameters.DeviceIoControl.OutputBufferLength) {
177                                 if (IrpStack->Parameters.DeviceIoControl.OutputBufferLength<sizeof(ULONG)) {
178                                         Irp->IoStatus.Status=STATUS_BUFFER_TOO_SMALL;
179                                         Irp->IoStatus.Information=sizeof(ULONG);
180                                         g_assert_not_reached();
181                                         goto done;
182                                         }
183                                 *(ULONG *)Irp->AssociatedIrp.SystemBuffer=0;    /* MediaChangeCount */
184                                 Irp->IoStatus.Information=sizeof(ULONG);
185                                 }
186                         else {
187                                 Irp->IoStatus.Information=0;
188                                 }
189                         Irp->IoStatus.Status=STATUS_SUCCESS;
190                         break;
191
192                 case IOCTL_SCSI_GET_CAPABILITIES:
193                         if (IrpStack->Parameters.DeviceIoControl.OutputBufferLength<sizeof(IO_SCSI_CAPABILITIES)) {
194                                 Irp->IoStatus.Status=STATUS_BUFFER_TOO_SMALL;
195                                 Irp->IoStatus.Information=sizeof(IO_SCSI_CAPABILITIES);
196                                 g_assert_not_reached();
197                                 goto done;
198                                 }
199                         *(IO_SCSI_CAPABILITIES *)Irp->AssociatedIrp.SystemBuffer=cdrom_PortCapabilities;
200                         Irp->IoStatus.Information=sizeof(IO_SCSI_CAPABILITIES);
201                         Irp->IoStatus.Status=STATUS_SUCCESS;
202                         break;
203
204                 default:
205                         Irp->IoStatus.Status=STATUS_INVALID_DEVICE_REQUEST;
206                         g_assert_not_reached();
207                         goto done;
208                 }
209         /* PASSTHRU */
210
211 done:   /* 'err:' but we flow here even during success */
212         return MajorFunction_Irp_finish(DeviceObject,Irp);
213 }
214
215
216 /* FIXME: We should comply with PDRIVER_DISPATCH prototype but unfortunately
217  * CAPTIVE_STDCALL prevents us to do so at least in RedHat gcc-3.2-4 (gcc bug?).
218  */
219 #define MajorFunction_READ ((PDRIVER_DISPATCH)MajorFunction_READ_func)
220 static NTSTATUS CAPTIVE_STDCALL MajorFunction_READ_func(IN DEVICE_OBJECT *DeviceObject,IN IRP *Irp)
221 {
222 IO_STACK_LOCATION *IrpStack;
223 gsize bytesread;
224 gpointer buffer=NULL;
225 GIOStatus erriostatus;
226
227         g_return_val_if_fail(TRUE==validate_DeviceObject(DeviceObject),STATUS_INVALID_PARAMETER);
228         g_return_val_if_fail(Irp!=NULL,STATUS_INVALID_PARAMETER);
229
230         Irp->IoStatus.Information=0;    /* request-specific, may get overriden later */
231         IrpStack=IoGetCurrentIrpStackLocation(Irp);
232         g_assert(IrpStack->MajorFunction==IRP_MJ_READ);
233         g_assert(IrpStack->MinorFunction==0);
234
235         /* What is 'IrpStack->Parameters.Read.Key'? */
236         g_assert(0==(IrpStack->Parameters.Read.Length%2048));
237         g_assert(0==(IrpStack->Parameters.Read.ByteOffset.QuadPart%2048));
238         g_assert(IrpStack->Parameters.Read.ByteOffset.QuadPart>=0);
239
240         /* Autodetect 'buffer' as we are !DO_BUFFERED_IO && !DO_DIRECT_IO hybrid */
241         if (Irp->UserBuffer) {
242                 g_assert(buffer==NULL);
243                 buffer=Irp->UserBuffer;
244                 }
245         if (Irp->AssociatedIrp.SystemBuffer) {
246                 g_assert(buffer==NULL);
247                 buffer=Irp->AssociatedIrp.SystemBuffer;
248                 }
249         if (Irp->MdlAddress) {
250                 g_assert(buffer==NULL);
251                 g_assert(IrpStack->Parameters.Read.Length<=MmGetMdlByteCount(Irp->MdlAddress));
252                 buffer=MmGetSystemAddressForMdl(Irp->MdlAddress);
253                 }
254         g_assert(buffer!=NULL);
255
256         erriostatus=g_io_channel_seek_position(Image_iochannel,
257                         IrpStack->Parameters.Read.ByteOffset.QuadPart,  /* offset */
258                         G_SEEK_SET,     /* type */
259                         NULL);  /* error */
260         g_assert(erriostatus==G_IO_STATUS_NORMAL);
261
262         erriostatus=g_io_channel_read_chars(Image_iochannel,
263                         buffer, /* buf */
264                         IrpStack->Parameters.Read.Length,       /* count */
265                         &bytesread,     /* bytesread */
266                         NULL);  /* error */
267         g_assert(erriostatus==G_IO_STATUS_NORMAL);
268         g_assert(bytesread==IrpStack->Parameters.Read.Length);
269
270         Irp->IoStatus.Information=IrpStack->Parameters.Read.Length;
271         Irp->IoStatus.Status=STATUS_SUCCESS;
272
273         /* PASSTHRU */
274 /* done: */     /* 'err:' but we flow here even during success */
275         return MajorFunction_Irp_finish(DeviceObject,Irp);
276 }
277
278
279 /* similiar to drivers/storage/cdrom/cdrom.c/DriverEntry()->...
280  * ...->CdromClassCreateDeviceObject()->
281  * ->reactos/drivers/storage/class2/class2.c/ScsiClassCreateDeviceObject()
282  * We should be driving a lower layer PortDevice but currently we
283  * do not provide it, I hope W32 filesystems don't touch it.
284  */
285 static NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
286 {
287 PDEVICE_OBJECT DeviceObject;
288 PDEVICE_EXTENSION DeviceExtension;
289 NTSTATUS err;
290
291         g_return_val_if_fail(DriverObject!=NULL,STATUS_INVALID_PARAMETER);
292         g_return_val_if_fail(RegistryPath!=NULL,STATUS_INVALID_PARAMETER);
293
294         err=IoCreateDevice(
295                         DriverObject,   /* DriverObject */
296                         sizeof(DEVICE_EXTENSION),       /* DeviceExtensionSize; additional storage not used */
297                         captive_utf8_to_UnicodeString_alloca("\\Device\\CdRom0"),       /* DeviceName */
298                         FILE_DEVICE_CD_ROM,     /* DeviceType */
299                         FILE_REMOVABLE_MEDIA|FILE_READ_ONLY_DEVICE,     /* DeviceCharacteristics */
300                         FALSE,  /* Exclusive */
301                         &DeviceObject); /* DeviceObject */
302         g_return_val_if_fail(NT_SUCCESS(err),FALSE);
303
304         /* Currently we are !DO_BUFFERED_IO && !DO_DIRECT_IO and we must solve
305          * being called with 'Irp->UserBuffer', 'Irp->AssociatedIrp.SystemBuffer' or 'Irp->MdlAddress'.
306          */
307         /* should be left from IoCreateDevice(DeviceCharacteristics) above: */
308         g_assert(DeviceObject->Characteristics & FILE_REMOVABLE_MEDIA);
309         /* ignored: DeviceObject->StackSize */
310         /* ignored: DeviceObject->AlignmentRequirement */
311
312         /* from reactos/drivers/storage/scsiport/scsiport.c/ScsiPortCreatePortDevice() */
313         cdrom_PortCapabilities.Length=sizeof(cdrom_PortCapabilities);
314         cdrom_PortCapabilities.MaximumTransferLength=0x10000;   /* 64KB */
315         g_assert((cdrom_PortCapabilities.MaximumTransferLength%PAGE_SIZE)==0);
316         cdrom_PortCapabilities.MaximumPhysicalPages=cdrom_PortCapabilities.MaximumTransferLength/PAGE_SIZE;
317         cdrom_PortCapabilities.SupportedAsynchronousEvents=0;
318         cdrom_PortCapabilities.AlignmentMask=1; /* no alignment required by us; speced as "integer multiple" */
319         cdrom_PortCapabilities.TaggedQueuing=FALSE;
320         cdrom_PortCapabilities.AdapterScansDown=FALSE;
321         cdrom_PortCapabilities.AdapterUsesPio=TRUE;
322         cdrom_PortCapabilities_check=cdrom_PortCapabilities;    /* for g_assert() checking against foreign modifications */
323
324         DeviceExtension=DeviceObject->DeviceExtension;
325         DeviceExtension->MediaChangeCount=0;
326         DeviceExtension->PhysicalDevice=DeviceObject;   /* no real PhysicalDeviceObject */
327         DeviceExtension->LockCount=0;
328         DeviceExtension->DeviceNumber=0;        /* corresponds to the # in "\\Device\\CdRom0" */
329         /* ignored DeviceExtension->PortDeviceObject
330          * as we are the final driver and we don't have any PortDeviceObject
331          */
332         DeviceExtension->PortCapabilities=&cdrom_PortCapabilities;
333         DeviceExtension->StartingOffset.QuadPart=0;
334         DeviceExtension->PartitionLength.QuadPart=Image_size;
335         DeviceExtension->PortNumber=0;
336         DeviceExtension->PathId=0;
337         DeviceExtension->TargetId=0;
338         DeviceExtension->Lun=0;
339
340         cdrom_DiskGeometry.MediaType=RemovableMedia;
341         cdrom_DiskGeometry.TracksPerCylinder=64;
342         cdrom_DiskGeometry.SectorsPerTrack=32;
343         cdrom_DiskGeometry.BytesPerSector=2048;
344         cdrom_DiskGeometry.Cylinders.QuadPart=Image_size
345                         /cdrom_DiskGeometry.BytesPerSector
346                         /cdrom_DiskGeometry.SectorsPerTrack
347                         /cdrom_DiskGeometry.TracksPerCylinder;
348         /* 'DeviceExtension->DiskGeometry' is NULL! */
349         cdrom_DiskGeometry_check=cdrom_DiskGeometry;    /* for g_assert() checking against foreign modifications */
350         DeviceExtension->DiskGeometry=&cdrom_DiskGeometry;
351
352         DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=MajorFunction_DEVICE_CONTROL;
353         DriverObject->MajorFunction[IRP_MJ_READ          ]=MajorFunction_READ;
354
355         return STATUS_SUCCESS;
356 }
357
358 /**
359  * captive_cdrom_init:
360  * @image_channel: Host OS file of the disk image to provide.
361  * %NULL value is forbidden.
362  *
363  * Creates system device "\Device\CdRom%d" providing readonly access
364  * to the given @image_pathname as emulation of CD-ROM driver.
365  *
366  * captive currently supports just one drive and thus "\Device\CdRom0"
367  * is always created. It is forbidden to call this function twice.
368  *
369  * Returns: %TRUE if the initialization was successful.
370  */
371 gboolean captive_cdrom_init(GIOChannel *image_iochannel)
372 {
373 NTSTATUS err;
374
375         g_return_val_if_fail(image_iochannel!=NULL,FALSE);
376
377         Image_iochannel=image_iochannel;
378         Image_size=captive_giochannel_size(Image_iochannel);
379
380         err=DriverEntry(
381                         &cdrom_DriverObject,    /* DriverEntry_DriverObject */
382                         captive_utf8_to_UnicodeString_alloca("\\captive\\storage\\cdrom"));     /* DriverEntry_RegistryPath; ignored */
383         g_return_val_if_fail(NT_SUCCESS(err),FALSE);
384
385         return TRUE;
386
387 /* err_close: */
388         g_io_channel_close(Image_iochannel);
389         Image_iochannel=NULL;
390 /* err: */
391         g_return_val_if_reached(FALSE);
392 }