Implemented '--syslog' and '--syslog-facility'.
[captive.git] / src / libcaptive / client / init.c
1 /* $Id$
2  * Init and cleanup code of libcaptive to be called by client application
3  * Copyright (C) 2002-2003 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 "init.h"       /* self */
23 #include "captive/ldr.h"
24 #include "captive/ldr_exports.h"
25 #include "captive/unicode.h"
26 #include "captive/rtl-file.h"
27 #include <glib/gtypes.h>
28 #include <glib/gmessages.h>
29 #include "reactos/internal/ldr.h"
30 #include "reactos/napi/types.h"
31 #include "reactos/internal/kd.h"        /* for KDB_LOADDRIVER_HOOK */
32 #include <fcntl.h>
33 #include <sys/mman.h>   /* for PROT_READ, MAP_SHARED */
34 #include "reactos/ddk/kefuncs.h"        /* for KeInitializeSpinLock() */
35 #include "reactos/internal/ntoskrnl.h"  /* for RtlpInitNlsTables() and IoInit() */
36 #include "reactos/internal/ps.h"        /* for PsInitProcessManagment() and PsInitThreadManagment() */
37 #include "reactos/ddk/iofuncs.h"        /* for IoCreateFile() */
38 #include "captive/storage.h"
39 #include "captive/signal.h"     /* for captive_signal_init() */
40 #include "reactos/ddk/psfuncs.h"        /* for PsGetCurrentThread() */
41 #include <stdio.h>
42 #include <popt.h>
43 #include <glib/gstrfuncs.h>
44 #include <glib/glist.h>
45 #include "giochannel-blind.h"
46 #include <glib-object.h>
47 #include "reactos/internal/se.h"        /* for SeInit2() */
48 #include "captive/leave.h"
49 #include "captive/options.h"
50 #include <libgnomevfs/gnome-vfs-result.h>
51 #include "lib.h"
52 #include <reactos/ddk/obfuncs.h>
53 #include <syslog.h>
54
55
56 struct captive_options *captive_options;
57
58 /* Are we initialized? */
59 static gboolean active;
60
61 /* Module of fs module itself loaded by captive_w32_init() */
62 static PMODULE_OBJECT ModuleObject;
63
64 /* Driver in fs module loaded by captive_w32_init() */
65 DRIVER_OBJECT captive_DriverObject;
66 PDRIVER_REINITIALIZE captive_DriverObject_ReinitRoutine;
67 PVOID captive_DriverObject_ReinitRoutine_Context;
68
69 /* Structure holding the pointer to the toplevel IRP */
70 static TOP_LEVEL_IRP TopLevelIrp;       /* TODO:thread */
71
72
73 void *_local_unwind2_addr;
74
75
76 /* Acceleration hack for ntoskrnl/dbg/print.c/DbgPrint() */
77 gboolean captive_get_debug_messages(void)
78 {
79         g_return_val_if_fail(captive_options!=NULL,TRUE);
80
81         return captive_options->debug_messages;
82 }
83
84
85 static gboolean captive_w32_init(void)
86 {
87 NTSTATUS err;
88 gboolean errbool;
89
90         g_return_val_if_fail(captive_options!=NULL,FALSE);
91         g_return_val_if_fail(captive_options->filesystem.type!=CAPTIVE_OPTIONS_MODULE_TYPE_EMPTY,FALSE);
92
93         /* captive_giochannel_size() only _after_ g_io_channel_set_encoding() ! */
94         captive_image_size=captive_giochannel_size(captive_image_iochannel);
95         g_return_val_if_fail(captive_image_size>0,FALSE);
96
97         /* Part of reactos/ntoskrnl/ke/main.c/KiSystemStartup() begins. */
98         /* ExpInitializeExecutive(); */
99                 /* Part of reactos/ntoskrnl/ke/main.c/ExpInitializeExecutive() begins
100                  * here as the rest of the function does a lot of hardware initializations.
101                  */
102                 /* LdrInit1(); */
103                         /* Part of reactos/ntoskrnl/ldr/loader.c/LdrInit1() begins. */
104                         InitializeListHead(&ModuleTextListHead);
105                         /* Part of reactos/ntoskrnl/ldr/loader.c/LdrInit1() ends. */
106                 KeLowerIrql(DISPATCH_LEVEL);
107                 /*...*/
108                 /* create default nls tables */
109                 RtlpInitNlsTables();
110                 /*...*/
111                 /* KeInit2() */
112                         /*...*/
113                         KeInitializeDispatcher();
114                         /*...*/
115                 KeLowerIrql(PASSIVE_LEVEL);
116                 errbool=SeInit1();
117                 g_assert(errbool==TRUE);
118                 ObInit();
119                 errbool=SeInit2();
120                 g_assert(errbool==TRUE);
121                 /* PiInitProcessManager(); */
122                         /* Part of reactos/ntoskrnl/ps/psmgr.c/PiInitProcessManager() begins. */
123                         PsInitProcessManagment();
124                         PsInitThreadManagment();
125                         /* Part of reactos/ntoskrnl/ps/psmgr.c/PiInitProcessManager() ends. */
126                 /*...*/
127                 IoInit();
128                 /*...*/
129                 /* LdrInitModuleManagement(); */
130                         /* Part of reactos/ntoskrnl/ldr/loader.c/LdrInitModuleManagement() begins
131                          * here as the rest "Create module object for {NTOSKRNL,HAL}"
132                          * is dependent on {NTOSKRNL,HAL} PE image headers not provided by libcaptive.
133                          */
134                         /* Initialize the module list and spinlock */
135                         InitializeListHead(&ModuleListHead);
136                         KeInitializeSpinLock(&ModuleListLock);
137                         /* Part of reactos/ntoskrnl/ldr/loader.c/LdrInitModuleManagement ends. */
138                 /*...*/
139                 /* Ntinit(); */
140                         NtInitializeEventImplementation();
141                         /*...*/
142                 /* Part of reactos/ntoskrnl/ke/main.c/ExpInitializeExecutive() ends. */
143         /* Part of reactos/ntoskrnl/ke/main.c/KiSystemStartup() ends. */
144
145         /* Simulate our PE headers and export the symbols of our complete libraries */
146         captive_kernel_exports();
147
148         errbool=captive_cdrom_init();
149         g_return_val_if_fail(errbool==TRUE,FALSE);
150         errbool=captive_disk_init();
151         g_return_val_if_fail(errbool==TRUE,FALSE);
152
153         while (captive_options->load_module) {
154 PMODULE_OBJECT ModuleObject_tmp;
155 NTSTATUS err;
156
157                 /* load the module */
158                 err=captive_LdrLoadModule(
159                                 captive_options->load_module->data,
160                                 &ModuleObject_tmp);     /* ModuleObjectp */
161                 g_return_val_if_fail(NT_SUCCESS(err),FALSE);
162
163                 captive_options_module_free(captive_options->load_module->data);
164                 /* also frees 'options->load_module->data' */
165                 captive_options->load_module=g_list_delete_link(captive_options->load_module,captive_options->load_module);
166                 }
167
168         /* Patch 'ntoskrnl.exe' loaded by 'captive_options->load_module' above. */
169         {
170         CHAR *KeNumberProcessorsp=captive_Module_GetExportAddress("ntoskrnl.exe","KeNumberProcessors");
171         
172                 g_assert(*KeNumberProcessorsp==0);
173                 *KeNumberProcessorsp=KeNumberProcessors;
174                 g_assert(*KeNumberProcessorsp==1);
175                 }
176         /* Apply captive_kernel_patches() AFTER any symbols sanity checks above! */
177         captive_kernel_patches();
178
179         _local_unwind2_addr=captive_Module_GetExportAddress("ntoskrnl.exe","_local_unwind2");
180
181         /* Initialize 'FsRtlLegalAnsiCharacterArray'.
182          * It requires 'ntoskrnl.exe' loaded by 'captive_options->load_module' above;
183          * captive_kernel_patches() should not be needed.
184          */
185         captive_FsRtlLegalAnsiCharacterArray_init();
186
187         /* set TopLevelIrp() - FIXME: where is it set by native reactos? */
188         PsGetCurrentThread()->TopLevelIrp=&TopLevelIrp; /* otherwise Io{Get,Set}TopLevelIrp() would SIGSEGV */
189
190         /* Begin possible handling of foreign W32 binary code here */
191         /* If you want to disable SIGSEGV handler if not needed:
192          *      if (ModuleObject->Flags & MODULE_FLAG_PE)
193          */
194         captive_signal_init();
195
196         /* You must have already captive_signal_init() passed here as the module may
197          * call some functions from W32 ntoskrnl.exe.
198          */
199         captive_DriverObject_ReinitRoutine=NULL;
200         err=captive_LdrpLoadAndCallImage(
201                         &ModuleObject,  /* ModuleObjectp */
202                         &captive_options->filesystem,   /* options_module */
203                         &captive_DriverObject,  /* DriverEntry_DriverObject */
204                         captive_utf8_to_UnicodeString_alloca("\\captive\\filesystem")); /* DriverEntry_RegistryPath */
205         g_return_val_if_fail(NT_SUCCESS(err),FALSE);
206         if (captive_DriverObject_ReinitRoutine) {
207                 (*captive_DriverObject_ReinitRoutine)(
208                                 &captive_DriverObject,  /* DriverObject */
209                                 captive_DriverObject_ReinitRoutine_Context,     /* Context */
210                                 1);     /* Count: # of calls of ReinitRoutine incl. the current one */
211                 }
212
213         return TRUE;
214 }
215
216
217 static void     captive_log_init_g_log_func_discard
218                 (const gchar *log_domain,GLogLevelFlags log_level,const gchar *message,gpointer user_data)
219 {
220         g_return_if_fail(message!=NULL);
221
222         /* NOP */
223 }
224
225 static void captive_log_init_g_log_func
226                 (const gchar *log_domain,GLogLevelFlags log_level,const gchar *message,gpointer user_data /* unused */)
227 {
228 int priority;
229
230         g_return_if_fail(message!=NULL);
231
232                 /* unused: LOG_EMERG */
233                 /* unused: LOG_ALERT */
234         /**/ if (log_level&G_LOG_LEVEL_ERROR)
235                 priority=LOG_CRIT;
236         else if (log_level&G_LOG_LEVEL_CRITICAL)
237                 priority=LOG_ERR;
238         else if (log_level&G_LOG_LEVEL_WARNING)
239                 priority=LOG_WARNING;
240         else if (log_level&G_LOG_LEVEL_MESSAGE)
241                 priority=LOG_NOTICE;
242         else if (log_level&G_LOG_LEVEL_INFO)
243                 priority=LOG_INFO;
244         else if (log_level&G_LOG_LEVEL_DEBUG)
245                 priority=LOG_DEBUG;
246         else /* bogus? */
247                 priority=LOG_WARNING;
248
249         syslog(priority,"%s%s%s",
250                         (!(log_level&G_LOG_FLAG_RECURSION) ? "" : "RECURSION: "),
251                         (!(log_level&G_LOG_FLAG_FATAL    ) ? "" : "FATAL: "),
252                         message);
253 }
254
255 void captive_log_init(const struct captive_options *captive_options)
256 {
257         g_return_if_fail(captive_options!=NULL);
258
259         if (!captive_options->debug_messages) {
260                 /* FIXME: Save handler_id and destroy it in captive_vfs_close(). */
261                 g_log_set_handler(
262                                 G_LOG_DOMAIN,   /* log_domain; "Captive" */
263                                 0       /* log_levels */
264                                                 | G_LOG_FLAG_RECURSION
265                                                 | G_LOG_FLAG_FATAL
266                                                 /* The same mask is in:
267                                                  * libcaptive/sandbox/server-GLogFunc.c
268                                                  * libcaptive/client/init.c
269                                                  */
270                                                 | G_LOG_LEVEL_MESSAGE
271                                                 | G_LOG_LEVEL_INFO
272                                                 | G_LOG_LEVEL_DEBUG,
273                                 captive_log_init_g_log_func_discard,    /* log_func */
274                                 NULL);  /* user_data */
275                 }
276         /* We are not the sandboxed slave;
277          * 'syslog_facility' would be '-1' in slave anyway as it is not transferred through CORBA.
278          */
279         if (!captive_options->sandbox || (captive_options->sandbox_server_argv || captive_options->sandbox_server_ior)) {
280                 if (captive_options->syslog_facility!=-1) {
281                         openlog(
282                                         /* FIXME: Prefix 'ident' by device/mountpoint. */
283                                         G_LOG_DOMAIN,   /* ident; "Captive"; FIXME: lowercase it for syslog(3)? */
284                                         LOG_CONS|LOG_PID,       /* options */
285                                         captive_options->syslog_facility);      /* facility */
286                         /* FIXME: Save handler_id and destroy it in captive_vfs_close(). */
287                         g_log_set_handler(
288                                         G_LOG_DOMAIN,   /* log_domain; "Captive" */
289                                         0       /* log_levels */
290                                                         | 0     /* !G_LOG_FLAG_RECURSION */
291                                                         | G_LOG_FLAG_FATAL
292                                                         | (G_LOG_LEVEL_MASK & ~(captive_options->debug_messages ? 0 : 0
293                                                                         /* The same mask is in:
294                                                                          * libcaptive/sandbox/server-GLogFunc.c
295                                                                          * libcaptive/client/init.c
296                                                                          */
297                                                                         | G_LOG_LEVEL_MESSAGE
298                                                                         | G_LOG_LEVEL_INFO
299                                                                         | G_LOG_LEVEL_DEBUG)),
300                                         (GLogFunc)captive_log_init_g_log_func,  /* log_func */
301                                         NULL);  /* user_data */
302                         }
303                 }
304 }
305
306 /**
307  * captive_init:
308  *
309  * Expects #captive_options: Parsed by captive_options_parse().
310  * %NULL value is forbidden. Field #image_iochannel %NULL value is forbidden.
311  *
312  * Initializes %libcaptive and loads the specified filesystem.
313  *
314  * Returns: %TRUE if successfuly initialized.
315  */
316 gboolean captive_init(void)
317 {
318 gboolean errbool;
319
320         /* We are in sandbox child and we have the right to fail. */
321         g_log_set_always_fatal(~(0
322                         |G_LOG_LEVEL_MESSAGE
323                         |G_LOG_LEVEL_INFO
324                         |G_LOG_LEVEL_DEBUG
325                         ));
326
327         g_return_val_if_fail(active==FALSE,FALSE);
328
329         g_return_val_if_fail(captive_options!=NULL,FALSE);
330         g_return_val_if_fail(captive_options->image_iochannel!=NULL,FALSE);
331
332         /* Initialize GObject subsystem of GLib. */
333         g_type_init();
334
335         captive_log_init(captive_options);
336
337         if (captive_options->rwmode==CAPTIVE_OPTION_RWMODE_BLIND)
338                 captive_image_iochannel=(GIOChannel *)captive_giochannel_blind_new(captive_options->image_iochannel,
339                                 TRUE);  /* writeable */
340         else
341                 captive_image_iochannel=captive_options->image_iochannel;
342
343         /* Do not initialize 'captive_image_size' by captive_giochannel_size() here
344          * as we yet need to do g_io_channel_set_encoding().
345          */
346
347         errbool=captive_w32_init();
348         g_return_val_if_fail(errbool==TRUE,FALSE);
349
350         active=TRUE;
351         return TRUE;
352 }
353
354
355 static void dismount_volume(void)
356 {
357 IO_STATUS_BLOCK IoStatusBlock;
358 PIO_STACK_LOCATION StackPtr;
359 PIRP Irp;
360 NTSTATUS Status;
361 DEVICE_OBJECT *DeviceObject=captive_DriverObject.DeviceObject;
362 OBJECT_ATTRIBUTES dir_ObjectAttributes;
363 HANDLE dir_Handle;
364 FILE_OBJECT *FileObject;
365 GnomeVFSResult errvfsresult;
366 NTSTATUS err;
367 IO_STATUS_BLOCK dir_IoStatusBlock;
368 #if 0
369 static const ULONG fsctls[2]={ FSCTL_LOCK_VOLUME,FSCTL_DISMOUNT_VOLUME };
370 int fsctlsi;
371 #endif
372 WCHAR wzero;
373
374         errvfsresult=captive_ObjectAttributes_init("/!Captive!del",&dir_ObjectAttributes);
375         g_return_if_fail(errvfsresult==GNOME_VFS_OK);
376         
377         /* wanted: * IoCreateFile()->ObCreateObject(,,,IoFileObjectType)->
378          * ->(IoFileObjectType->Create==IopCreateFile)()->IoMountVolume()
379          */
380         err=IoCreateFile(
381                         &dir_Handle,    /* FileHandle */
382                         GENERIC_READ|GENERIC_WRITE|SYNCHRONIZE|0x80,    /* DesiredAccess; 0xC0100080=GENERIC_READ|GENERIC_WRITE|SYNCHRONIZE|0x80 */
383                         &dir_ObjectAttributes,  /* ObjectAttributes */
384                         &dir_IoStatusBlock,     /* IoStatusBlock */
385                         NULL,   /* AllocationSize; ignored for open */
386                         FILE_ATTRIBUTE_NORMAL,  /* FileAttributes; ignored for open */
387                         (FILE_SHARE_READ | FILE_SHARE_WRITE),   /* ShareAccess; 0 means exclusive */
388                         FILE_OPEN,      /* CreateDisposition */
389                         /* FILE_SYNCHRONOUS_IO_{,NON}ALERT: We need to allow W32 filesystem
390                          * any waits to not to let it return STATUS_CANT_WAIT us.
391                          * Alertability should have only effect on asynchronous events
392                          * from KeWaitForSingleObject() by setting/clearing its parameter 'Alertable'.
393                          */
394                         FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE, /* CreateOptions; FILE_DIRECTORY_FILE is forbidden */
395                         NULL,   /* EaBuffer */
396                         0,      /* EaLength */
397                         CreateFileTypeNone,     /* CreateFileType */
398                         NULL,   /* ExtraCreateParameters */
399                         0);     /* Options */
400         g_free(dir_ObjectAttributes.ObjectName);        /* left from captive_gnomevfs_uri_parent_init() */
401         g_return_if_fail(NT_SUCCESS(err)==NT_SUCCESS(dir_IoStatusBlock.Status));
402         g_return_if_fail(NT_SUCCESS(err));
403         g_return_if_fail(dir_IoStatusBlock.Information==FILE_OPENED);
404
405         Status=ObReferenceObjectByHandle(dir_Handle,FILE_LIST_DIRECTORY,IoFileObjectType,UserMode,(PVOID *)&FileObject,NULL);
406         g_assert(NT_SUCCESS(Status));
407
408         g_assert(FileObject->FileName.Length==0);
409         /* 'FileObject->FileName.MaximumLength' is not reset by IoCreateFile(). */
410         g_assert(FileObject->FileName.Buffer==NULL);
411         FileObject->FileName.MaximumLength=2;
412         FileObject->FileName.Buffer=&wzero;
413
414 #if 0
415         for (fsctlsi=0;fsctlsi<2;fsctlsi++)
416 #endif
417         {
418                 Irp=IoAllocateIrp(DeviceObject->StackSize,TRUE);
419                 g_return_if_fail(Irp!=NULL);
420
421                 Irp->UserIosb=&IoStatusBlock;
422                 Irp->UserEvent=&FileObject->Event;
423                 Irp->Tail.Overlay.Thread=PsGetCurrentThread();
424
425                 StackPtr=IoGetNextIrpStackLocation(Irp);
426 #if 0
427                 StackPtr->MajorFunction=IRP_MJ_FILE_SYSTEM_CONTROL;
428                 StackPtr->MinorFunction=IRP_MN_USER_FS_REQUEST;
429 #else
430                 StackPtr->MajorFunction=IRP_MJ_FLUSH_BUFFERS;
431 #endif
432                 StackPtr->Flags=0;
433                 StackPtr->Control=0;
434                 StackPtr->DeviceObject=DeviceObject;    /* FIXME: FileObject->Vpb->DeviceObject ? */
435                 StackPtr->FileObject=FileObject;
436                 StackPtr->CompletionRoutine=NULL;
437
438 #if 0
439                 StackPtr->Parameters.FileSystemControl.OutputBufferLength=0;
440                 StackPtr->Parameters.FileSystemControl.InputBufferLength=0;
441                 StackPtr->Parameters.FileSystemControl.FsControlCode=fsctls[fsctlsi];
442                 StackPtr->Parameters.FileSystemControl.Type3InputBuffer=NULL;
443 #endif
444
445                 /* IoCallDriver() will do one ObDereferenceObject(FileObject)
446                  * in its IoSecondStageCompletion().
447                  * Do not leave to dereference it itself as we need its 'FileObject->Event'.
448                  */
449                 ObReferenceObject(FileObject);
450
451                 Status=IoCallDriver(DeviceObject,Irp);
452                 if (Status==STATUS_PENDING) {
453                         KeWaitForSingleObject(&FileObject->Event,Executive,KernelMode,FALSE,NULL);
454                         Status=IoStatusBlock.Status;
455                         }
456                 g_assert(NT_SUCCESS(Status));
457         }
458
459         ObDereferenceObject(FileObject);
460 }
461
462
463 BOOLEAN captive_cc_FileObject_delete(FILE_OBJECT *FileObject);
464 void captive_cc_flush(void);
465
466 /**
467  * captive_shutdown:
468  *
469  * Closes down %libcaptive. It should flush all pending buffers and successfuly
470  * close the filesystem. Variable #captive_options->image_iochannel will not be set to %NULL,
471  * you should close such channel yourself.
472  *
473  * Returns: %TRUE if successfuly shutdown.
474  */
475 gboolean captive_shutdown(void)
476 {
477 GIOStatus erriostatus;
478
479         g_return_val_if_fail(active==TRUE,FALSE);
480         g_return_val_if_fail(captive_image_iochannel!=NULL,FALSE);
481         g_return_val_if_fail(captive_options->image_iochannel!=NULL,FALSE);
482
483         /* Invoke all pending idle functions just to not to forget for anything... */
484         while (g_main_context_iteration(
485                         NULL,   /* context; NULL means default one */
486                         FALSE)) /* may_block */
487                 g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: g_main_context_iteration() proceeded",G_STRLOC);
488
489         /* Do not: captive_cc_flush();  * based on captive_leave(), not g_main idle *
490          * replaced by IRP_MJ_FLUSH_BUFFERS.
491          */
492
493         dismount_volume();
494
495         /* Invoke all pending idle functions just to not to forget for anything... */
496         while (g_main_context_iteration(
497                         NULL,   /* context; NULL means default one */
498                         FALSE)) /* may_block */
499                 g_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"%s: g_main_context_iteration() proceeded",G_STRLOC);
500
501 #if 0
502         captive_PoQueueShutdownWorkItem_hooklist_invoke();
503 #endif
504
505         /* Do not: captive_cc_flush();
506          * as the dirty blocks should have been already commited by dismount_volume(),
507          * any further commits would get us just STATUS_VOLUME_DISMOUNTED.
508          */
509
510         /* Do not: captive_cc_unmounting=TRUE;
511          * Without dismount_volume() it was:
512          *      During IoShutdownRegisteredFileSystems() - IRP_MJ_SHUTDOWN to be specific
513          *      some buffers will be written but after the IofCallDriver() it will be
514          *      no longer possible to flush such buffers to their DeviceVolumeFile.
515          *      Therefore we must flush such buffers on the fly although such behaviour
516          *      would crash us in regular case as filesystems access BCBs even after their
517          *      CcUnpinData().
518          * Currently the dirty blocks should have been already commited by dismount_volume(),
519          */
520
521         /* FIXME: ntoskrnl/ex/power.c/NtShutdownSystem() does
522          * IoShutdownRegistered{Devices,FileSystems} order; is it correct?
523          */
524         IoShutdownRegisteredFileSystems();
525
526         /* Do not: captive_cc_FileObject_delete(NULL);
527          * as the dirty blocks should have been already commited by dismount_volume(),
528          * any further commits would get us just STATUS_VOLUME_DISMOUNTED.
529          */
530
531         IoShutdownRegisteredDevices();
532
533         /* libcaptive is not authorized to shutdown 'captive_image_channel'. */
534         erriostatus=g_io_channel_flush(
535                         captive_image_iochannel,        /* channel */
536                         NULL);  /* error */
537         g_assert(erriostatus==G_IO_STATUS_NORMAL);
538
539         /* 'captive_image_iochannel' may be blinded wrapper of 'captive_options->image_iochannel'. */
540         if (captive_image_iochannel!=captive_options->image_iochannel) {
541                 erriostatus=g_io_channel_flush(
542                                 captive_options->image_iochannel,       /* channel */
543                                 NULL);  /* error */
544                 g_assert(erriostatus==G_IO_STATUS_NORMAL);
545                 g_io_channel_unref(captive_image_iochannel);
546                 }
547
548         captive_image_iochannel=NULL;
549
550         active=FALSE;
551         return TRUE;
552 }