Subversion Repositories Kolibri OS

Rev

Rev 4457 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
4429 Serge 1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2
;;                                                              ;;
4457 Serge 3
;; Copyright (C) KolibriOS team 2011-2014. All rights reserved. ;;
4429 Serge 4
;; Distributed under terms of the GNU General Public License    ;;
5
;;                                                              ;;
6
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
7
 
4993 Serge 8
$Revision: 4437 $
4429 Serge 9
 
10
; =============================================================================
11
; ================================= Constants =================================
12
; =============================================================================
13
; Error codes for callback functions.
14
DISK_STATUS_OK              = 0 ; success
15
DISK_STATUS_GENERAL_ERROR   = -1; if no other code is suitable
16
DISK_STATUS_INVALID_CALL    = 1 ; invalid input parameters
17
DISK_STATUS_NO_MEDIA        = 2 ; no media present
18
DISK_STATUS_END_OF_MEDIA    = 3 ; end of media while reading/writing data
4457 Serge 19
DISK_STATUS_NO_MEMORY       = 4 ; insufficient memory for driver operation
4429 Serge 20
; Driver flags. Represent bits in DISK.DriverFlags.
21
DISK_NO_INSERT_NOTIFICATION = 1
22
; Media flags. Represent bits in DISKMEDIAINFO.Flags.
23
DISK_MEDIA_READONLY = 1
24
 
25
; If too many partitions are detected,there is probably an error on the disk.
26
; 256 partitions should be enough for any reasonable use.
27
; Also, the same number is limiting the number of MBRs to process; if
28
; too many MBRs are visible,there probably is a loop in the MBR structure.
29
MAX_NUM_PARTITIONS = 256
30
 
31
; =============================================================================
32
; ================================ Structures =================================
33
; =============================================================================
34
; This structure defines all callback functions for working with the physical
35
; device. They are implemented by a driver. Objects with this structure reside
36
; in a driver.
37
struct  DISKFUNC
38
        strucsize       dd ?
39
; Size of the structure. This field is intended for possible extensions of
40
; this structure. If a new function is added to this structure and a driver
41
; implements an old version, the caller can detect this by checking .strucsize,
42
; so the driver remains compatible.
43
        close           dd ?
44
; The pointer to the function which frees all driver-specific resources for
45
; the disk.
46
; Optional, may be NULL.
47
; void close(void* userdata);
48
        closemedia      dd ?
49
; The pointer to the function which informs the driver that the kernel has
50
; finished all processing with the current media. If media is removed, the
51
; driver should decline all requests to that media with DISK_STATUS_NO_MEDIA,
52
; even if new media is inserted, until this function is called. If media is
53
; removed, a new call to 'disk_media_changed' is not allowed until this
54
; function is called.
55
; Optional, may be NULL (if media is not removable).
56
; void closemedia(void* userdata);
57
        querymedia      dd ?
58
; The pointer to the function which determines capabilities of the media.
59
; int querymedia(void* userdata, DISKMEDIAINFO* info);
60
; Return value: one of DISK_STATUS_*
61
        read            dd ?
62
; The pointer to the function which reads data from the device.
63
; int read(void* userdata, void* buffer, __int64 startsector, int* numsectors);
64
; input: *numsectors = number of sectors to read
65
; output: *numsectors = number of sectors which were successfully read
66
; Return value: one of DISK_STATUS_*
67
        write           dd ?
68
; The pointer to the function which writes data to the device.
69
; Optional, may be NULL.
70
; int write(void* userdata, void* buffer, __int64 startsector, int* numsectors);
71
; input: *numsectors = number of sectors to write
72
; output: *numsectors = number of sectors which were successfully written
73
; Return value: one of DISK_STATUS_*
74
        flush           dd ?
75
; The pointer to the function which flushes the internal device cache.
76
; Optional, may be NULL.
77
; int flush(void* userdata);
78
; Return value: one of DISK_STATUS_*
79
; Note that read/write are called by the cache manager, so a driver should not
80
; create a software cache. This function is implemented for flushing a hardware
81
; cache, if it exists.
82
        adjust_cache_size       dd ?
83
; The pointer to the function which returns the cache size for this device.
84
; Optional, may be NULL.
85
; unsigned int adjust_cache_size(unsigned int suggested_size);
86
; Return value: 0 = disable cache, otherwise = used cache size in bytes.
87
ends
88
 
89
; This structure holds information on a medium.
90
; Objects with this structure are allocated by the kernel as a part of the DISK
91
; structure and are filled by a driver in the 'querymedia' callback.
92
struct  DISKMEDIAINFO
93
        Flags           dd ?
94
; Combination of DISK_MEDIA_* bits.
95
        SectorSize      dd ?
96
; Size of the sector.
97
        Capacity        dq ?
98
; Size of the media in sectors.
99
ends
100
 
101
; This structure represents the disk cache. To follow the old implementation,
102
; there are two distinct caches for a disk, one for "system" data,and the other
103
; for "application" data.
104
struct  DISKCACHE
105
; The following fields are inherited from data32.inc:cache_ideX.
106
        pointer         dd ?
107
        data_size       dd ?    ; unused
108
        data            dd ?
109
        sad_size        dd ?
110
        search_start    dd ?
111
ends
112
 
113
; This structure represents a disk device and its media for the kernel.
114
; This structure is allocated by the kernel in the 'disk_add' function,
115
; freed in the 'disk_dereference' function.
116
struct  DISK
117
; Fields of disk object
118
        Next            dd ?
119
        Prev            dd ?
120
; All disk devices are linked in one list with these two fields.
121
; Head of the list is the 'disk_list' variable.
122
        Functions       dd ?
123
; Pointer to the 'DISKFUNC' structure with driver functions.
124
        Name            dd ?
125
; Pointer to the string used for accesses through the global filesystem.
126
        UserData        dd ?
127
; This field is passed to all callback functions so a driver can decide which
128
; physical device is addressed.
129
        DriverFlags     dd ?
130
; Bitfield. Currently only DISK_NO_INSERT_NOTIFICATION bit is defined.
131
; If it is set, the driver will never issue 'disk_media_changed' notification
132
; with argument set to true, so the kernel must try to detect media during
133
; requests from the file system.
134
        RefCount        dd ?
135
; Count of active references to this structure. One reference is kept during
136
; the lifetime of the structure between 'disk_add' and 'disk_del'.
137
; Another reference is taken during any filesystem operation for this disk.
138
; One reference is added if media is inserted.
139
; The structure is destroyed when the reference count decrements to zero:
140
; this usually occurs in 'disk_del', but can be delayed to the end of last
141
; filesystem operation, if one is active.
142
        MediaLock       MUTEX
143
; Lock to protect the MEDIA structure. See the description after
144
; 'disk_list_mutex' for the locking strategy.
145
; Fields of media object
146
        MediaInserted   db ?
147
; 0 if media is not inserted, nonzero otherwise.
148
        MediaUsed       db ?
149
; 0 if media fields are not used, nonzero otherwise. If .MediaRefCount is
150
; nonzero, this field is nonzero too; however, when .MediaRefCount goes
151
; to zero, there is some time interval during which media object is still used.
152
                        dw ? ; padding
153
; The following fields are not valid unless either .MediaInserted is nonzero
154
; or they are accessed from a code which has obtained the reference when
155
; .MediaInserted was nonzero.
156
        MediaRefCount   dd ?
157
; Count of active references to the media object. One reference is kept during
158
; the lifetime of the media between two calls to 'disk_media_changed'.
159
; Another reference is taken during any filesystem operation for this media.
160
; The callback 'closemedia' is called when the reference count decrements to
161
; zero: this usually occurs in 'disk_media_changed', but can be delayed to the
162
; end of the last filesystem operation, if one is active.
163
        MediaInfo       DISKMEDIAINFO
164
; This field keeps information on the current media.
165
        NumPartitions   dd ?
166
; Number of partitions on this media.
167
        Partitions      dd ?
168
; Pointer to array of .NumPartitions pointers to PARTITION structures.
169
        cache_size      dd ?
170
; inherited from cache_ideX_size
4457 Serge 171
        CacheLock       MUTEX
172
; Lock to protect both caches.
4429 Serge 173
        SysCache        DISKCACHE
174
        AppCache        DISKCACHE
175
; Two caches for the disk.
176
ends
177
 
178
; This structure represents one partition for the kernel. This is a base
179
; template, the actual contents after common fields is determined by the
180
; file system code for this partition.
181
struct  PARTITION
182
        FirstSector     dq ?
183
; First sector of the partition.
184
        Length          dq ?
185
; Length of the partition in sectors.
186
        Disk            dd ?
187
; Pointer to parent DISK structure.
188
        FSUserFunctions dd ?
189
; Handlers for the sysfunction 70h. This field is a pointer to the following
190
; array. The first dword is pointer to disconnect handler.
191
; The first dword is a number of supported subfunctions, other dwords
192
; point to handlers of corresponding subfunctions.
193
; ...fs-specific data may follow...
194
ends
195
 
196
; This is an external structure, it represents an entry in the partition table.
197
struct  PARTITION_TABLE_ENTRY
198
        Bootable        db ?
199
; 80h = bootable partition, 0 = non-bootable partition, other values = invalid
200
        FirstHead       db ?
201
        FirstSector     db ?
202
        FirstTrack      db ?
203
; Coordinates of first sector in CHS.
204
        Type            db ?
205
; Partition type, one of predefined constants. 0 = empty, several types denote
206
; extended partition (see process_partition_table_entry), we are not interested
207
; in other values.
208
        LastHead        db ?
209
        LastSector      db ?
210
        LastTrack       db ?
211
; Coordinates of last sector in CHS.
212
        FirstAbsSector  dd ?
213
; Coordinate of first sector in LBA.
214
        Length          dd ?
215
; Length of the partition in sectors.
216
ends
217
 
218
; =============================================================================
219
; ================================ Global data ================================
220
; =============================================================================
221
iglobal
222
; The pseudo-item for the list of all DISK structures.
223
; Initialized to the empty list.
224
disk_list:
225
        dd      disk_list
226
        dd      disk_list
227
endg
228
uglobal
229
; This mutex guards all operations with the global list of DISK structures.
230
disk_list_mutex MUTEX
231
; * There are two dependent objects, a disk and a media. In the simplest case,
232
;   disk and media are both non-removable. However, in the general case both
233
;   can be removed at any time, simultaneously or only media,and this makes things
234
;   complicated.
235
; * For efficiency, both disk and media objects are located in the one
236
;   structure named DISK. However, logically they are different.
237
; * The following operations use data of disk object: adding (disk_add);
238
;   deleting (disk_del); filesystem (fs_lfn which eventually calls
239
;   dyndisk_handler or dyndisk_enum_root).
240
; * The following operations use data of media object: adding/removing
241
;   (disk_media_changed); filesystem (fs_lfn which eventually calls
242
;   dyndisk_handler; dyndisk_enum_root doesn't work with media).
243
; * Notifications disk_add, disk_media_changed, disk_del are synchronized
244
;   between themselves, this is a requirement for the driver. However, file
245
;   system operations are asynchronous, can be issued at any time by any
246
;   thread.
247
; * We must prevent a situation when a filesystem operation thinks that the
248
;   object is still valid but in fact the notification has destroyed the
249
;   object. So we keep a reference counter for both disk and media and destroy
250
;   the object when this counter goes to zero.
251
; * The driver must know when it is safe to free driver-allocated resources.
252
;   The object can be alive even after death notification has completed.
253
;   We use special callbacks to satisfy both assertions: 'close' for the disk
254
;   and 'closemedia' for the media. The destruction of the object includes
255
;   calling the corresponding callback.
256
; * Each filesystem operation keeps one reference for the disk and one
257
;   reference for the media. Notification disk_del forces notification on the
258
;   media death, so the reference counter for the disk is always not less than
259
;   the reference counter for the media.
260
; * Two operations "get the object" and "increment the reference counter" can
261
;   not be done simultaneously. We use a mutex to guard the consistency here.
262
;   It must be a part of the container for the object, so that this mutex can
263
;   be acquired as a part of getting the object from the container. The
264
;   container for disk object is the global list, and this list is guarded by
265
;   'disk_list_mutex'. The container for media object is the disk object, and
266
;   the corresponding mutex is DISK.MediaLock.
267
; * Notifications do not change the data of objects, they can only remove
268
;   objects. Thus we don't need another synchronization at this level. If two
269
;   filesystem operations are referencing the same filesystem data, this is
270
;   better resolved at the level of the filesystem.
271
endg
272
 
273
iglobal
274
; The function 'disk_scan_partitions' needs three 512-byte buffers for
275
; MBR, bootsector and fs-temporary sector data. It can not use the static
276
; buffers always, since it can be called for two or more disks in parallel.
277
; However, this case is not typical. We reserve three static 512-byte buffers
278
; and a flag that these buffers are currently used. If 'disk_scan_partitions'
279
; detects that the buffers are currently used, it allocates buffers from the
280
; heap.
281
; The flag is implemented as a global dword variable. When the static buffers
282
; are not used, the value is -1. When the static buffers are used, the value
283
; is normally 0 and temporarily can become greater. The function increments
284
; this value. If the resulting value is zero, it uses the buffers and
285
; decrements the value when the job is done. Otherwise, it immediately
286
; decrements the value and uses buffers from the heap, allocated in the
287
; beginning and freed in the end.
288
partition_buffer_users  dd      -1
289
endg
290
uglobal
291
; The static buffers for MBR, bootsector and fs-temporary sector data.
292
align 16
293
mbr_buffer      rb      512
294
bootsect_buffer rb      512
295
fs_tmp_buffer   rb      512
296
endg
297
 
298
iglobal
299
; This is the array of default implementations of driver callbacks.
300
; Same as DRIVERFUNC structure except for the first field; all functions must
301
; have the default implementations.
302
align 4
303
disk_default_callbacks:
304
        dd      disk_default_close
305
        dd      disk_default_closemedia
306
        dd      disk_default_querymedia
307
        dd      disk_default_read
308
        dd      disk_default_write
309
        dd      disk_default_flush
310
        dd      disk_default_adjust_cache_size
311
endg
312
 
313
; =============================================================================
314
; ================================= Functions =================================
315
; =============================================================================
316
 
317
; This function registers a disk device.
318
; This includes:
319
; - allocating an internal structure describing this device;
320
; - registering this structure in the global filesystem.
321
; The function initializes the disk as if there is no media. If a media is
322
; present, the function 'disk_media_changed' should be called after this
323
; function succeeds.
324
; Parameters:
325
; [esp+4] = pointer to DISKFUNC structure with the callbacks
326
; [esp+8] = pointer to name (ASCIIZ string)
327
; [esp+12] = userdata to be passed to the callbacks as is.
328
; [esp+16] = flags, bitfield. Currently only DISK_NO_INSERT_NOTIFICATION bit
329
;            is defined.
330
; Return value:
331
; NULL = operation has failed
332
; non-NULL = handle of the disk. This handle can be used
333
; in the operations with other Disk* functions.
334
; The handle is the pointer to the internal structure DISK.
335
disk_add:
336
        push    ebx esi         ; save used registers to be stdcall
337
; 1. Allocate the DISK structure.
338
; 1a. Call the heap manager.
339
        movi    eax, sizeof.DISK
340
        call    malloc
341
; 1b. Check the result. If allocation failed, return (go to 9) with eax = 0.
342
        test    eax, eax
343
        jz      .nothing
344
; 2. Copy the disk name to the DISK structure.
345
; 2a. Get length of the name, including the terminating zero.
346
        mov     ebx, [esp+8+8]  ; ebx = pointer to name
347
        push    eax             ; save allocated pointer to DISK
348
        xor     eax, eax        ; the argument of malloc() is in eax
349
@@:
350
        inc     eax
351
        cmp     byte [ebx+eax-1], 0
352
        jnz     @b
353
; 2b. Call the heap manager.
354
        call    malloc
355
; 2c. Check the result. If allocation failed, go to 7.
356
        pop     esi             ; restore allocated pointer to DISK
357
        test    eax, eax
358
        jz      .free
359
; 2d. Store the allocated pointer to the DISK structure.
360
        mov     [esi+DISK.Name], eax
361
; 2e. Copy the name.
362
@@:
363
        mov     dl, [ebx]
364
        mov     [eax], dl
365
        inc     ebx
366
        inc     eax
367
        test    dl, dl
368
        jnz     @b
369
; 3. Copy other arguments of the function to the DISK structure.
370
        mov     eax, [esp+4+8]
371
        mov     [esi+DISK.Functions], eax
372
        mov     eax, [esp+12+8]
373
        mov     [esi+DISK.UserData], eax
374
        mov     eax, [esp+16+8]
375
        mov     [esi+DISK.DriverFlags], eax
376
; 4. Initialize other fields of the DISK structure.
377
; Media is not inserted, reference counter is 1.
378
        lea     ecx, [esi+DISK.MediaLock]
379
        call    mutex_init
380
        xor     eax, eax
381
        mov     dword [esi+DISK.MediaInserted], eax
382
        mov     [esi+DISK.MediaRefCount], eax
383
        inc     eax
384
        mov     [esi+DISK.RefCount], eax
385
; The DISK structure is initialized.
386
; 5. Insert the new structure to the global list.
387
; 5a. Acquire the mutex.
388
        mov     ecx, disk_list_mutex
389
        call    mutex_lock
390
; 5b. Insert item to the tail of double-linked list.
391
        mov     edx, disk_list
392
        list_add_tail esi, edx     ;esi= new edx= list head
393
; 5c. Release the mutex.
394
        call    mutex_unlock
395
; 6. Return with eax = pointer to DISK.
396
        xchg    eax, esi
397
        jmp     .nothing
398
.free:
399
; Memory allocation for DISK structure succeeded, but for disk name failed.
400
; 7. Free the DISK structure.
401
        xchg    eax, esi
402
        call    free
403
; 8. Return with eax = 0.
404
        xor     eax, eax
405
.nothing:
406
; 9. Return.
407
        pop     esi ebx         ; restore used registers to be stdcall
408
        ret     16              ; purge 4 dword arguments to be stdcall
409
 
410
; This function deletes a disk device from the global filesystem.
411
; This includes:
412
; - removing a media including all partitions;
413
; - deleting this structure from the global filesystem;
414
; - dereferencing the DISK structure and possibly destroying it.
415
; Parameters:
416
; [esp+4] = handle of the disk, i.e. the pointer to the DISK structure.
417
; Return value: none.
418
disk_del:
419
        push    esi         ; save used registers to be stdcall
420
; 1. Force media to be removed. If the media is already removed, the
421
; call does nothing.
422
        mov     esi, [esp+4+4]  ; esi = handle of the disk
423
        stdcall disk_media_changed, esi, 0
424
; 2. Delete the structure from the global list.
425
; 2a. Acquire the mutex.
426
        mov     ecx, disk_list_mutex
427
        call    mutex_lock
428
; 2b. Delete item from double-linked list.
429
        mov     eax, [esi+DISK.Next]
430
        mov     edx, [esi+DISK.Prev]
431
        mov     [eax+DISK.Prev], edx
432
        mov     [edx+DISK.Next], eax
433
; 2c. Release the mutex.
434
        call    mutex_unlock
435
; 3. The structure still has one reference created in disk_add. Remove this
436
; reference. If there are no other references, disk_dereference will free the
437
; structure.
438
        call    disk_dereference
439
; 4. Return.
440
        pop     esi             ; restore used registers to be stdcall
441
        ret     4               ; purge 1 dword argument to be stdcall
442
 
443
; This is an internal function which removes a previously obtained reference
444
; to the disk. If this is the last reference, this function lets the driver
445
; finalize all associated data, and afterwards frees the DISK structure.
446
; esi = pointer to DISK structure
447
disk_dereference:
448
; 1. Decrement reference counter. Use atomic operation to correctly handle
449
; possible simultaneous calls.
450
        lock dec [esi+DISK.RefCount]
451
; 2. If the result is nonzero, there are other references, so nothing to do.
452
; In this case, return (go to 4).
453
        jnz     .nothing
454
; 3. If we are here, we just removed the last reference and must destroy the
455
; disk object.
456
; 3a. Call the driver.
457
        mov     al, DISKFUNC.close
458
        stdcall disk_call_driver
459
; 3b. Free the structure.
460
        xchg    eax, esi
461
        push    ebx
462
        call    free
463
        pop     ebx
464
; 4. Return.
465
.nothing:
466
        ret
467
 
468
; This is an internal function which removes a previously obtained reference
469
; to the media. If this is the last reference, this function calls 'closemedia'
470
; callback to signal the driver that the processing has finished and it is safe
471
; to inform about a new media.
472
; esi = pointer to DISK structure
473
disk_media_dereference:
474
; 1. Decrement reference counter. Use atomic operation to correctly handle
475
; possible simultaneous calls.
476
        lock dec [esi+DISK.MediaRefCount]
477
; 2. If the result is nonzero, there are other references, so nothing to do.
478
; In this case, return (go to 4).
479
        jnz     .nothing
480
; 3. If we are here, we just removed the last reference and must destroy the
481
; media object.
482
; Note that the same place inside the DISK structure is reused for all media
483
; objects, so we must guarantee that reusing does not happen while freeing.
484
; Reusing is only possible when someone processes a new media. There are two
485
; mutually exclusive variants:
486
; * driver issues media insert notifications (DISK_NO_INSERT_NOTIFICATION bit
487
;   in DISK.DriverFlags is not set). In this case, we require from the driver
488
;   that such notification (except for the first one) can occur only after a
489
;   call to 'closemedia' callback.
490
; * driver does not issue media insert notifications. In this case, the kernel
491
;   itself must sometimes check whether media is inserted. We have the flag
492
;   DISK.MediaUsed, visible to the kernel. This flag signals to the other parts
493
;   of kernel that the way is free.
494
; In the first case other parts of the kernel do not use DISK.MediaUsed, so it
495
; does not matter when this flag is cleared. In the second case this flag must
496
; be cleared after all other actions, including call to 'closemedia'.
497
; 3a. Free all partitions.
498
        push    esi edi
499
        mov     edi, [esi+DISK.NumPartitions]
500
        mov     esi, [esi+DISK.Partitions]
501
        test    edi, edi
502
        jz      .nofree
503
.freeloop:
504
        lodsd
505
        mov     ecx, [eax+PARTITION.FSUserFunctions]
506
        call    dword [ecx]
507
        dec     edi
508
        jnz     .freeloop
509
.nofree:
510
        pop     edi esi
511
; 3b. Free the cache.
512
        call    disk_free_cache
513
; 3c. Call the driver.
514
        mov     al, DISKFUNC.closemedia
515
        stdcall disk_call_driver
516
; 3d. Clear the flag.
517
        mov     [esi+DISK.MediaUsed], 0
518
.nothing:
519
        ret
520
 
521
; This function is called by the driver and informs the kernel that the media
522
; has changed. If the media is non-removable, it is called exactly once
523
; immediately after 'disk_add' and once from 'disk_del'.
524
; Parameters:
525
; [esp+4] = handle of the disk, i.e. the pointer to the DISK structure.
526
; [esp+8] = new status of the media: zero = no media, nonzero = media inserted.
527
disk_media_changed:
528
        push    ebx esi edi             ; save used registers to be stdcall
529
; 1. Remove the existing media, if it is present.
530
        mov     esi, [esp+4+12]         ; esi = pointer to DISK
531
; 1a. Check whether it is present. Since DISK.MediaInserted is changed only
532
; in this function and calls to this function are synchronized, no lock is
533
; required for checking.
534
        cmp     [esi+DISK.MediaInserted], 0
535
        jz      .noremove
536
; We really need to remove the media.
537
; 1b. Acquire mutex.
538
        lea     ecx, [esi+DISK.MediaLock]
539
        call    mutex_lock
540
; 1c. Clear the flag.
541
        mov     [esi+DISK.MediaInserted], 0
542
; 1d. Release mutex.
543
        call    mutex_unlock
544
; 1e. Remove the "lifetime" reference and possibly destroy the structure.
545
        call    disk_media_dereference
546
.noremove:
547
; 2. Test whether there is new media.
548
        cmp     dword [esp+8+12], 0
549
        jz      .noinsert
550
; Yep, there is.
551
; 3. Process the new media. We assume that all media fields are available to
552
; use, see comments in 'disk_media_dereference' (this covers using by previous
553
; media referencers) and note that calls to this function are synchronized
554
; (this covers using by new media referencers).
555
; 3a. Call the 'querymedia' callback.
556
; .Flags are set to zero for possible future extensions.
557
        lea     edx, [esi+DISK.MediaInfo]
558
        and     [edx+DISKMEDIAINFO.Flags], 0
559
        mov     al, DISKFUNC.querymedia
560
        stdcall disk_call_driver, edx
561
; 3b. Check the result of the callback. Abort if it failed.
562
        test    eax, eax
563
        jnz     .noinsert
564
; 3c. Allocate the cache unless disabled by the driver. Abort if failed.
565
        call    disk_init_cache
566
        test    al, al
567
        jz      .noinsert
568
; 3d. Acquire the lifetime reference for the media object.
569
        inc     [esi+DISK.MediaRefCount]
570
; 3e. Scan for partitions. Ignore result; the list of partitions is valid even
571
; on errors.
572
        call    disk_scan_partitions
573
; 3f. Media is inserted and available for use.
574
        inc     [esi+DISK.MediaInserted]
575
.noinsert:
576
; 4. Return.
577
        pop     edi esi ebx             ; restore used registers to be stdcall
578
        ret     8                       ; purge 2 dword arguments to be stdcall
579
 
580
; This function is a thunk for all functions of a disk driver.
581
; It checks whether the referenced function is implemented in the driver.
582
; If so, this function jumps to the function in the driver.
583
; Otherwise, it jumps to the default implementation.
584
; al = offset of function in the DISKFUNC structure;
585
; esi = pointer to the DISK structure;
586
; stack is the same as for the corresponding function except that the
587
; first parameter (void* userdata) is prepended automatically.
588
disk_call_driver:
589
        movzx   eax, al ; eax = offset of function in the DISKFUNC structure
590
; 1. Prepend the first argument to the stack.
591
        pop     ecx     ; ecx = return address
592
        push    [esi+DISK.UserData]     ; add argument
593
        push    ecx     ; save return address
594
; 2. Check that the required function is inside the table. If not, go to 5.
595
        mov     ecx, [esi+DISK.Functions]
596
        cmp     eax, [ecx+DISKFUNC.strucsize]
597
        jae     .default
598
; 3. Check that the required function is implemented. If not, go to 5.
599
        mov     ecx, [ecx+eax]
600
        test    ecx, ecx
601
        jz      .default
602
; 4. Jump to the required function.
603
        jmp     ecx
604
.default:
605
; 5. Driver does not implement the required function; use default implementation.
606
        jmp     dword [disk_default_callbacks+eax-4]
607
 
608
; The default implementation of DISKFUNC.querymedia.
609
disk_default_querymedia:
610
        movi    eax, DISK_STATUS_INVALID_CALL
611
        ret     8
612
 
613
; The default implementation of DISKFUNC.read and DISKFUNC.write.
614
disk_default_read:
615
disk_default_write:
616
        movi    eax, DISK_STATUS_INVALID_CALL
617
        ret     20
618
 
619
; The default implementation of DISKFUNC.close, DISKFUNC.closemedia and
620
; DISKFUNC.flush.
621
disk_default_close:
622
disk_default_closemedia:
623
disk_default_flush:
624
        xor     eax, eax
625
        ret     4
626
 
627
; The default implementation of DISKFUNC.adjust_cache_size.
628
disk_default_adjust_cache_size:
629
        mov     eax, [esp+8]
630
        ret     8
631
 
632
; This is an internal function called from 'disk_media_changed' when a new media
633
; is detected. It creates the list of partitions for the media.
634
; If media is not partitioned, then the list consists of one partition which
635
; covers all the media.
636
; esi = pointer to the DISK structure.
637
disk_scan_partitions:
638
; 1. Initialize .NumPartitions and .Partitions fields as zeros: empty list.
639
        and     [esi+DISK.NumPartitions], 0
640
        and     [esi+DISK.Partitions], 0
641
; 2. Currently we can work only with 512-bytes sectors. Check this restriction.
642
; The only exception is 2048-bytes CD/DVD, but they are not supported yet by
643
; this code.
644
        cmp     [esi+DISK.MediaInfo.SectorSize], 512
645
        jz      .doscan
646
        DEBUGF 1,'K : sector size is %d, only 512 is supported\n',[esi+DISK.MediaInfo.SectorSize]
647
        ret
648
.doscan:
649
; 3. Acquire the buffer for MBR and bootsector tests. See the comment before
650
; the 'partition_buffer_users' variable.
651
        mov     ebx, mbr_buffer         ; assume the global buffer is free
652
        lock inc [partition_buffer_users]
653
        jz      .buffer_acquired        ; yes, it is free
654
        lock dec [partition_buffer_users]       ; no, we must allocate
655
        stdcall kernel_alloc, 512*3
656
        test    eax, eax
657
        jz      .nothing
658
        xchg    eax, ebx
659
.buffer_acquired:
660
; MBR/EBRs are organized in the chain. We use a loop over MBR/EBRs, but no
661
; more than MAX_NUM_PARTITION times.
662
; 4. Prepare things for the loop.
663
; ebp will hold the sector number for current MBR/EBR.
664
; [esp] will hold the sector number for current extended partition, if there
665
; is one.
666
; [esp+4] will hold the counter that prevents long loops.
667
        push    ebp             ; save ebp
668
        push    MAX_NUM_PARTITIONS      ; the counter of max MBRs to process
669
        xor     ebp, ebp        ; start from sector zero
670
        push    ebp             ; no extended partition yet
671
.new_mbr:
672
; 5. Read the current sector.
673
; Note that 'read' callback operates with 64-bit sector numbers, so we must
674
; push additional zero as a high dword of sector number.
675
        mov     al, DISKFUNC.read
676
        push    1
677
        stdcall disk_call_driver, ebx, ebp, 0, esp
678
        pop     ecx
679
; 6. If the read has failed, abort the loop.
680
        dec     ecx
681
        jnz     .mbr_failed
682
; 7. Check the MBR/EBR signature. If it is wrong, abort the loop.
683
; Soon we will access the partition table which starts at ebx+0x1BE,
684
; so we can fill its address right now. If we do it now, then the addressing
685
; [ecx+0x40] is shorter than [ebx+0x1fe]: one-byte offset vs 4-bytes offset.
686
        lea     ecx, [ebx+0x1be]        ; ecx -> partition table
687
        cmp     word [ecx+0x40], 0xaa55
688
        jnz     .mbr_failed
689
; 8. The MBR is treated differently from EBRs. For MBR we additionally need to
690
; execute step 9 and possibly step 10.
691
        test    ebp, ebp
692
        jnz     .mbr
693
; The partition table can be present or not present. In the first case, we just
694
; read the MBR. In the second case, we just read the bootsector for a
695
; filesystem.
696
; The following algorithm is used to distinguish between these cases.
697
; A. If at least one entry of the partition table is invalid, this is
698
;    a bootsector. See the description of 'is_partition_table_entry' for
699
;    definition of validity.
700
; B. If all entries are empty (filesystem type field is zero) and the first
701
;    byte is jmp opcode (0EBh or 0E9h), this is a bootsector which happens to
702
;    have zeros in the place of partition table.
703
; C. Otherwise, this is an MBR.
704
; 9. Test for MBR vs bootsector.
705
; 9a. Check entries. If any is invalid, go to 10 (rule A).
706
        call    is_partition_table_entry
707
        jc      .notmbr
708
        add     ecx, 10h
709
        call    is_partition_table_entry
710
        jc      .notmbr
711
        add     ecx, 10h
712
        call    is_partition_table_entry
713
        jc      .notmbr
714
        add     ecx, 10h
715
        call    is_partition_table_entry
716
        jc      .notmbr
717
; 9b. Check types of the entries. If at least one is nonzero, go to 11 (rule C).
718
        mov     al, [ecx-30h+PARTITION_TABLE_ENTRY.Type]
719
        or      al, [ecx-20h+PARTITION_TABLE_ENTRY.Type]
720
        or      al, [ecx-10h+PARTITION_TABLE_ENTRY.Type]
721
        or      al, [ecx+PARTITION_TABLE_ENTRY.Type]
722
        jnz     .mbr
723
; 9c. Empty partition table or bootsector with many zeroes? (rule B)
724
        cmp     byte [ebx], 0EBh
725
        jz      .notmbr
726
        cmp     byte [ebx], 0E9h
727
        jnz     .mbr
728
.notmbr:
729
; 10. This is not an  MBR. The media is not partitioned. Create one partition
730
; which covers all the media and abort the loop.
731
        stdcall disk_add_partition, 0, 0, \
732
                dword [esi+DISK.MediaInfo.Capacity], dword [esi+DISK.MediaInfo.Capacity+4], esi
733
        jmp     .done
734
.mbr:
735
; 11. Process all entries of the new MBR/EBR
736
        lea     ecx, [ebx+0x1be]        ; ecx -> partition table
737
        push    0       ; assume no extended partition
738
        call    process_partition_table_entry
739
        add     ecx, 10h
740
        call    process_partition_table_entry
741
        add     ecx, 10h
742
        call    process_partition_table_entry
743
        add     ecx, 10h
744
        call    process_partition_table_entry
745
        pop     ebp
746
; 12. Test whether we found a new EBR and should continue the loop.
747
; 12a. If there was no next EBR, return.
748
        test    ebp, ebp
749
        jz      .done
750
; Ok, we have EBR.
751
; 12b. EBRs addresses are relative to the start of extended partition.
752
; For simplicity, just abort if an 32-bit overflow occurs; large disks
753
; are most likely partitioned with GPT, not MBR scheme, since the precise
754
; calculation here would increase limit just twice at the price of big
755
; compatibility problems.
756
        pop     eax     ; load extended partition
757
        add     ebp, eax
758
        jc      .mbr_failed
759
; 12c. If extended partition has not yet started, start it.
760
        test    eax, eax
761
        jnz     @f
762
        mov     eax, ebp
763
@@:
764
; 12c. If the limit is not exceeded, continue the loop.
765
        dec     dword [esp]
766
        push    eax     ; store extended partition
767
        jnz     .new_mbr
768
.mbr_failed:
769
.done:
770
; 13. Cleanup after the loop.
771
        pop     eax     ; not important anymore
772
        pop     eax     ; not important anymore
773
        pop     ebp     ; restore ebp
774
; 14. Release the buffer.
775
; 14a. Test whether it is the global buffer or we have allocated it.
776
        cmp     ebx, mbr_buffer
777
        jz      .release_partition_buffer
778
; 14b. If we have allocated it, free it.
779
        xchg    eax, ebx
780
        call    free
781
        jmp     .nothing
782
; 14c. Otherwise, release reference.
783
.release_partition_buffer:
784
        lock dec [partition_buffer_users]
785
.nothing:
786
; 15. Return.
787
        ret
788
 
789
; This is an internal function called from disk_scan_partitions. It checks
790
; whether the entry pointed to by ecx is a valid entry of partition table.
791
; The entry is valid if the first byte is 0 or 80h, the first sector plus the
792
; length is less than twice the size of media. Multiplication by two is
793
; required since the size mentioned in the partition table can be slightly
794
; greater than the real size.
795
is_partition_table_entry:
796
; 1. Check .Bootable field.
797
        mov     al, [ecx+PARTITION_TABLE_ENTRY.Bootable]
798
        and     al, 7Fh
799
        jnz     .invalid
800
; 3. Calculate first sector + length. Note that .FirstAbsSector is relative
801
; to the MBR/EBR, so the real sum is ebp + .FirstAbsSector + .Length.
802
        mov     eax, ebp
803
        xor     edx, edx
804
        add     eax, [ecx+PARTITION_TABLE_ENTRY.FirstAbsSector]
805
        adc     edx, 0
806
        add     eax, [ecx+PARTITION_TABLE_ENTRY.Length]
807
        adc     edx, 0
808
; 4. Divide by two.
809
        shr     edx, 1
810
        rcr     eax, 1
811
; 5. Compare with capacity. If the subtraction (edx:eax) - .Capacity does not
812
; overflow, this is bad.
813
        sub     eax, dword [esi+DISK.MediaInfo.Capacity]
814
        sbb     edx, dword [esi+DISK.MediaInfo.Capacity+4]
815
        jnc     .invalid
816
.valid:
817
; 5. Return success: CF is cleared.
818
        clc
819
        ret
820
.invalid:
821
; 6. Return fail: CF is set.
822
        stc
823
        ret
824
 
825
; This is an internal function called from disk_scan_partitions. It processes
826
; the entry pointed to by ecx.
827
; * If the entry is invalid, just ignore this entry.
828
; * If the type is zero, just ignore this entry.
829
; * If the type is one of types for extended partition, store the address
830
;   of this partition as the new MBR in [esp+4].
831
; * Otherwise, add the partition to the list of partitions for this disk.
832
;   We don't use the type from the entry to identify the file system;
833
;   fs-specific checks do this more reliably.
834
process_partition_table_entry:
835
; 1. Check for valid entry. If invalid, return (go to 5).
836
        call    is_partition_table_entry
837
        jc      .nothing
838
; 2. Check for empty entry. If invalid, return (go to 5).
839
        mov     al, [ecx+PARTITION_TABLE_ENTRY.Type]
840
        test    al, al
841
        jz      .nothing
842
; 3. Check for extended partition. If extended, go to 6.
843
irp type,\
844
    0x05,\                 ; DOS: extended partition
845
    0x0f,\                 ; WIN95: extended partition, LBA-mapped
846
    0xc5,\                 ; DRDOS/secured: extended partition
847
    0xd5                   ; Old Multiuser DOS secured: extended partition
848
{
849
        cmp     al, type
850
        jz      .extended
851
}
852
; 4. If we are here, that is a normal partition. Add it to the list.
853
; Note that the first sector is relative to MBR/EBR.
854
        mov     eax, ebp
855
        xor     edx, edx
856
        add     eax, [ecx+PARTITION_TABLE_ENTRY.FirstAbsSector]
857
        adc     edx, 0
858
        push    ecx
859
        stdcall disk_add_partition, eax, edx, \
860
                [ecx+PARTITION_TABLE_ENTRY.Length], 0, esi
861
        pop     ecx
862
.nothing:
863
; 5. Return.
864
        ret
865
.extended:
866
; 6. If we are here, that is an extended partition. Store the address.
867
        mov     eax, [ecx+PARTITION_TABLE_ENTRY.FirstAbsSector]
868
        mov     [esp+4], eax
869
        ret
870
 
871
; This is an internal function called from disk_scan_partitions and
872
; process_partition_table_entry. It adds one partition to the list of
873
; partitions for the media.
874
; Important note: start, length, disk MUST be present and
875
; MUST be in the same order as in PARTITION structure.
876
; esi duplicates [disk].
877
proc disk_add_partition stdcall uses ebx edi, start:qword, length:qword, disk:dword
878
; 1. Check that this partition will not exceed the limit on total number.
879
        cmp     [esi+DISK.NumPartitions], MAX_NUM_PARTITIONS
880
        jae     .nothing
881
; 2. Check that this partition does not overlap with any already registered
882
; partition. Since any file system assumes that the disk data will not change
883
; outside of its control, such overlap could be destructive.
884
; Since the number of partitions is usually very small and is guaranteed not
885
; to be large, the simple linear search is sufficient.
886
; 2a. Prepare the loop: edi will point to the current item of .Partitions
887
; array, ecx will be the current item, ebx will hold number of items left.
888
        mov     edi, [esi+DISK.Partitions]
889
        mov     ebx, [esi+DISK.NumPartitions]
890
        test    ebx, ebx
891
        jz      .partitionok
892
.scan_existing:
893
; 2b. Get the next partition.
894
        mov     ecx, [edi]
895
        add     edi, 4
896
; The range [.FirstSector, .FirstSector+.Length) must be either entirely to
897
; the left of [start, start+length) or entirely to the right.
898
; 2c. Subtract .FirstSector - start. The possible overflow distinguish between
899
; cases "to the left" (2e) and "to the right" (2d).
900
        mov     eax, dword [ecx+PARTITION.FirstSector]
901
        mov     edx, dword [ecx+PARTITION.FirstSector+4]
902
        sub     eax, dword [start]
903
        sbb     edx, dword [start+4]
904
        jb      .less
905
; 2d. .FirstSector is greater than or equal to start. Check that .FirstSector
906
; is greater than or equal to start+length; the subtraction
907
; (.FirstSector-start) - length must not cause overflow. Go to 2g if life is
908
; good or to 2f in the other case.
909
        sub     eax, dword [length]
910
        sbb     edx, dword [length+4]
911
        jb      .overlap
912
        jmp     .next_existing
913
.less:
914
; 2e. .FirstSector is less than start. Check that .FirstSector+.Length is less
915
; than or equal to start. If the addition (.FirstSector-start) + .Length does
916
; not cause overflow, then .FirstSector + .Length is strictly less than start;
917
; since the equality is also valid, use decrement preliminarily. Go to 2g or
918
; 2f depending on the overflow.
919
        sub     eax, 1
920
        sbb     edx, 0
921
        add     eax, dword [ecx+PARTITION.Length]
922
        adc     edx, dword [ecx+PARTITION.Length+4]
923
        jnc     .next_existing
924
.overlap:
925
; 2f. The partition overlaps with previously registered partition. Say warning
926
; and return with nothing done.
927
        dbgstr 'two partitions overlap, ignoring the last one'
928
        jmp     .nothing
929
.next_existing:
930
; 2g. The partition does not overlap with the current partition. Continue the
931
; loop.
932
        dec     ebx
933
        jnz     .scan_existing
934
.partitionok:
935
; 3. The partition has passed tests. Reallocate the partitions array for a new
936
; entry.
937
; 3a. Call the allocator.
938
        mov     eax, [esi+DISK.NumPartitions]
939
        inc     eax     ; one more entry
940
        shl     eax, 2  ; each entry is dword
941
        call    malloc
942
; 3b. Test the result. If failed, return with nothing done.
943
        test    eax, eax
944
        jz      .nothing
945
; 3c. Copy the old array to the new array.
946
        mov     edi, eax
947
        push    esi
948
        mov     ecx, [esi+DISK.NumPartitions]
949
        mov     esi, [esi+DISK.Partitions]
950
        rep movsd
951
        pop     esi
952
; 3d. Set the field in the DISK structure to the new array.
953
        xchg    [esi+DISK.Partitions], eax
954
; 3e. Free the old array.
955
        call    free
956
; 4. Recognize the file system.
957
; 4a. Call the filesystem recognizer. It will allocate the PARTITION structure
958
; with possible filesystem-specific fields.
959
        call    disk_detect_partition
960
; 4b. Check return value. If zero, return with list not changed; so far only
961
; the array was reallocated, this is ok for other code.
962
        test    eax, eax
963
        jz      .nothing
964
; 5. Insert the new partition to the list.
965
        stosd
966
        inc     [esi+DISK.NumPartitions]
967
; 6. Return.
968
.nothing:
969
        ret
970
endp
971
 
972
; This is an internal function called from disk_add_partition.
973
; It tries to recognize the file system on the partition and allocates the
974
; corresponding PARTITION structure with filesystem-specific fields.
975
disk_detect_partition:
976
; This function inherits the stack frame from disk_add_partition. In stdcall
977
; with ebp-based frame arguments start from ebp+8, since [ebp]=saved ebp
978
; and [ebp+4]=return address.
979
virtual at ebp+8
980
.start  dq      ?
981
.length dq      ?
982
.disk   dd      ?
983
end virtual
984
; 1. Read the bootsector to the buffer.
985
; When disk_add_partition is called, ebx contains a pointer to
986
; a three-sectors-sized buffer. This function saves ebx in the stack
987
; immediately before ebp.
988
        mov     ebx, [ebp-4] ; get buffer
989
        add     ebx, 512     ; advance over MBR data to bootsector data
990
        add     ebp, 8       ; ebp points to part of PARTITION structure
991
        xor     eax, eax     ; first sector of the partition
992
        call    fs_read32_sys
993
        push    eax
994
; 2. Run tests for all supported filesystems. If at least one test succeeded,
995
; go to 4.
996
; For tests:
997
; ebp -> first three fields of PARTITION structure, .start, .length, .disk;
998
; [esp] = error code after bootsector read: 0 = ok, otherwise = failed,
999
; ebx points to the buffer for bootsector,
1000
; ebx+512 points to 512-bytes buffer that can be used for anything.
1001
        call    fat_create_partition
1002
        test    eax, eax
1003
        jnz     .success
1004
        call    ntfs_create_partition
1005
        test    eax, eax
1006
        jnz     .success
1007
        call    ext2_create_partition
1008
        test    eax, eax
1009
        jnz     .success
1010
        call    xfs_create_partition
1011
        test    eax, eax
1012
        jnz     .success
1013
; 3. No file system has recognized the volume, so just allocate the PARTITION
1014
; structure without extra fields.
1015
        movi    eax, sizeof.PARTITION
1016
        call    malloc
1017
        test    eax, eax
1018
        jz      .nothing
1019
        mov     edx, dword [ebp+PARTITION.FirstSector]
1020
        mov     dword [eax+PARTITION.FirstSector], edx
1021
        mov     edx, dword [ebp+PARTITION.FirstSector+4]
1022
        mov     dword [eax+PARTITION.FirstSector+4], edx
1023
        mov     edx, dword [ebp+PARTITION.Length]
1024
        mov     dword [eax+PARTITION.Length], edx
1025
        mov     edx, dword [ebp+PARTITION.Length+4]
1026
        mov     dword [eax+PARTITION.Length+4], edx
1027
        mov     [eax+PARTITION.Disk], esi
1028
        mov     [eax+PARTITION.FSUserFunctions], default_fs_functions
1029
.success:
1030
.nothing:
1031
        sub     ebp, 8 ; restore ebp
1032
; 4. Return with eax = pointer to PARTITION or NULL.
1033
        pop     ecx
1034
        ret
1035
 
1036
iglobal
1037
align 4
1038
default_fs_functions:
1039
        dd      free
1040
        dd      0       ; no user functions
1041
endg
1042
 
1043
; This function is called from file_system_lfn.
1044
; This handler gets the control each time when fn 70 is called
1045
; with unknown item of root subdirectory.
1046
; in: esi -> name
1047
;     ebp = 0 or rest of name relative to esi
1048
; out: if the handler processes path, it must not return in file_system_lfn,
1049
;      but instead pop return address and return directly to the caller
1050
;      otherwise simply return
1051
dyndisk_handler:
1052
        push    ebx edi         ; save registers used in file_system_lfn
1053
; 1. Acquire the mutex.
1054
        mov     ecx, disk_list_mutex
1055
        call    mutex_lock
1056
; 2. Loop over the list of DISK structures.
1057
; 2a. Initialize.
1058
        mov     ebx, disk_list
1059
.scan:
1060
; 2b. Get the next item.
1061
        mov     ebx, [ebx+DISK.Next]
1062
; 2c. Check whether the list is done. If so, go to 3.
1063
        cmp     ebx, disk_list
1064
        jz      .notfound
1065
; 2d. Compare names. If names match, go to 5.
1066
        mov     edi, [ebx+DISK.Name]
1067
        push    esi
1068
@@:
1069
; esi points to the name from fs operation; it is terminated by zero or slash.
1070
        lodsb
1071
        test    al, al
1072
        jz      .eoin_dec
1073
        cmp     al, '/'
1074
        jz      .eoin
1075
; edi points to the disk name.
1076
        inc     edi
1077
; edi points to lowercase name, this is a requirement for the driver.
1078
; Characters at esi can have any register. Lowercase the current character.
1079
; This lowercasing works for latin letters and digits; since the disk name
1080
; should not contain other symbols, this is ok.
1081
        or      al, 20h
1082
        cmp     al, [edi-1]
1083
        jz      @b
1084
.wrongname:
1085
; 2f. Names don't match. Continue the loop.
1086
        pop     esi
1087
        jmp     .scan
1088
.notfound:
1089
; The loop is done and no name matches.
1090
; 3. Release the mutex.
1091
        call    mutex_unlock
1092
; 4. Return normally.
1093
        pop     edi ebx         ; restore registers used in file_system_lfn
1094
        ret
1095
; part of 2d: the name matches partially, but we must check that this is full
1096
; equality.
1097
.eoin_dec:
1098
        dec     esi
1099
.eoin:
1100
        cmp     byte [edi], 0
1101
        jnz     .wrongname
1102
; We found the addressed DISK structure.
1103
; 5. Reference the disk.
1104
        lock inc [ebx+DISK.RefCount]
1105
; 6. Now we are sure that the DISK structure is not going to die at least
1106
; while we are working with it, so release the global mutex.
1107
        call    mutex_unlock
1108
        pop     ecx             ; pop from the stack saved value of esi
1109
; 7. Acquire the mutex for media object.
1110
        pop     edi             ; restore edi
1111
        lea     ecx, [ebx+DISK.MediaLock]
1112
        call    mutex_lock
1113
; 8. Get the media object. If it is not NULL, reference it.
1114
        xor     edx, edx
1115
        cmp     [ebx+DISK.MediaInserted], dl
1116
        jz      @f
1117
        mov     edx, ebx
1118
        inc     [ebx+DISK.MediaRefCount]
1119
@@:
1120
; 9. Now we are sure that the media object, if it exists, is not going to die
1121
; at least while we are working with it, so release the mutex for media object.
1122
        call    mutex_unlock
1123
        mov     ecx, ebx
1124
        pop     ebx eax         ; restore ebx, pop return address
1125
; 10. Check whether the fs operation wants to enumerate partitions (go to 11)
1126
; or work with some concrete partition (go to 12).
1127
        cmp     byte [esi], 0
1128
        jnz     .haspartition
1129
; 11. The fs operation wants to enumerate partitions.
1130
; 11a. Only "list directory" operation is applicable to / path. Check
1131
; the operation code. If wrong, go to 13.
1132
        cmp     dword [ebx], 1
1133
        jnz     .access_denied
1134
; 11b. If the media is inserted, use 'fs_dyndisk_next' as an enumeration
1135
; procedure. Otherwise, use 'fs_dyndisk_next_nomedia'.
1136
        mov     esi, fs_dyndisk_next_nomedia
1137
        test    edx, edx
1138
        jz      @f
1139
        mov     esi, fs_dyndisk_next
1140
@@:
1141
; 11c. Let the procedure from fs_lfn.inc do the job.
1142
        jmp     file_system_lfn.maindir_noesi
1143
.haspartition:
1144
; 12. The fs operation has specified some partition.
1145
; 12a. Store parameters for callback functions.
1146
        push    edx
1147
        push    ecx
1148
; 12b. Store callback functions.
1149
        push    dyndisk_cleanup
1150
        push    fs_dyndisk
1151
        mov     edi, esp
1152
; 12c. Let the procedure from fs_lfn.inc do the job.
1153
        jmp     file_system_lfn.found2
1154
.access_denied:
1155
; 13. Fail the operation with the appropriate code.
1156
        mov     dword [esp+32], ERROR_ACCESS_DENIED
1157
.cleanup:
1158
; 14. Cleanup.
1159
        mov     esi, ecx        ; disk*dereference assume that esi points to DISK
1160
.cleanup_esi:
1161
        test    edx, edx        ; if there are no media, we didn't reference it
1162
        jz      @f
1163
        call    disk_media_dereference
1164
@@:
1165
        call    disk_dereference
1166
; 15. Return.
1167
        ret
1168
 
1169
; This is a callback for cleaning up things called from file_system_lfn.found2.
1170
dyndisk_cleanup:
1171
        mov     esi, [edi+8]
1172
        mov     edx, [edi+12]
1173
        jmp     dyndisk_handler.cleanup_esi
1174
 
1175
; This is a callback for enumerating partitions called from
1176
; file_system_lfn.maindir in the case of inserted media.
1177
; It just increments eax until DISK.NumPartitions reached and then
1178
; cleans up.
1179
fs_dyndisk_next:
1180
        cmp     eax, [ecx+DISK.NumPartitions]
1181
        jae     .nomore
1182
        inc     eax
1183
        clc
1184
        ret
1185
.nomore:
1186
        pusha
1187
        mov     esi, ecx
1188
        call    disk_media_dereference
1189
        call    disk_dereference
1190
        popa
1191
        stc
1192
        ret
1193
 
1194
; This is a callback for enumerating partitions called from
1195
; file_system_lfn.maindir in the case of missing media.
1196
; In this case we create one pseudo-partition.
1197
fs_dyndisk_next_nomedia:
1198
        cmp     eax, 1
1199
        jae     .nomore
1200
        inc     eax
1201
        clc
1202
        ret
1203
.nomore:
1204
        pusha
1205
        mov     esi, ecx
1206
        call    disk_dereference
1207
        popa
1208
        stc
1209
        ret
1210
 
1211
; This is a callback for doing real work with selected partition.
1212
; Currently this is just placeholder, since no file systems are supported.
1213
; edi = esp -> {dd fs_dyndisk, dd dyndisk_cleanup, dd pointer to DISK, dd media object}
1214
; ecx = partition number, esi+ebp = ASCIIZ name
1215
fs_dyndisk:
1216
        dec     ecx     ; convert to zero-based partition index
1217
        pop     edx edx edx ; edx = pointer to DISK, dword [esp] = NULL or edx
1218
; If the driver does not support insert notifications and we are the only fs
1219
; operation with this disk, ask the driver whether the media
1220
; was inserted/removed/changed. Otherwise, assume that media status is valid.
1221
        test    byte [edx+DISK.DriverFlags], DISK_NO_INSERT_NOTIFICATION
1222
        jz      .media_accurate
1223
        push    ecx esi
1224
        mov     esi, edx
1225
        cmp     dword [esp+8], 0
1226
        jz      .test_no_media
1227
        cmp     [esi+DISK.MediaRefCount], 2
1228
        jnz     .media_accurate_pop
1229
        lea     edx, [esi+DISK.MediaInfo]
1230
        and     [edx+DISKMEDIAINFO.Flags], 0
1231
        mov     al, DISKFUNC.querymedia
1232
        stdcall disk_call_driver, edx
1233
        test    eax, eax
1234
        jz      .media_accurate_pop
1235
        stdcall disk_media_dereference  ; drop our reference so that disk_media_changed could close the media
1236
        stdcall disk_media_changed, esi, 0
1237
        and     dword [esp+8], 0        ; no media
1238
.test_no_media:
1239
        stdcall disk_media_changed, esi, 1      ; issue fake notification
1240
                ; if querymedia() inside disk_media_changed returns error, the notification is ignored
1241
        cmp     [esi+DISK.MediaInserted], 0
1242
        jz      .media_accurate_pop
1243
        lock inc [esi+DISK.MediaRefCount]
1244
        mov     dword [esp+8], esi
1245
.media_accurate_pop:
1246
        mov     edx, esi
1247
        pop     esi ecx
1248
.media_accurate:
1249
        pop     eax
1250
        test    eax, eax
1251
        jz      .nomedia
1252
.main:
1253
        cmp     ecx, [edx+DISK.NumPartitions]
1254
        jae     .notfound
1255
        mov     eax, [edx+DISK.Partitions]
1256
        mov     eax, [eax+ecx*4]
1257
        mov     edi, [eax+PARTITION.FSUserFunctions]
1258
        mov     ecx, [ebx]
1259
        cmp     [edi+4], ecx
1260
        jbe     .unsupported
1261
        push    edx
1262
        push    ebp
1263
        mov     ebp, eax
1264
        call    dword [edi+8+ecx*4]
1265
        pop     ebp
1266
        pop     edx
1267
        mov     dword [esp+32], eax
1268
        mov     dword [esp+20], ebx
1269
.cleanup:
1270
        mov     esi, edx
1271
        call    disk_media_dereference
1272
        call    disk_dereference
1273
        ret
1274
.nofs:
1275
        mov     dword [esp+32], ERROR_UNKNOWN_FS
1276
        jmp     .cleanup
1277
.notfound:
1278
        mov     dword [esp+32], ERROR_FILE_NOT_FOUND
1279
        jmp     .cleanup
1280
.unsupported:
1281
        cmp     edi, default_fs_functions
1282
        jz      .nofs
1283
        mov     dword [esp+32], ERROR_UNSUPPORTED_FS
1284
        jmp     .cleanup
1285
.nomedia:
1286
        test    ecx, ecx
1287
        jnz     .notfound
1288
        mov     dword [esp+32], ERROR_DEVICE
1289
        mov     esi, edx
1290
        call    disk_dereference
1291
        ret
1292
 
1293
; This function is called from file_system_lfn.
1294
; This handler is called when virtual root is enumerated
1295
; and must return all items which can be handled by this.
1296
; It is called several times, first time with eax=0
1297
; in: eax = 0 for first call, previously returned value for subsequent calls
1298
; out: eax = 0 => no more items
1299
;      eax != 0 => buffer pointed to by edi contains name of item
1300
dyndisk_enum_root:
1301
        push    edx             ; save register used in file_system_lfn
1302
        mov     ecx, disk_list_mutex    ; it will be useful
1303
; 1. If this is the first call, acquire the mutex and initialize.
1304
        test    eax, eax
1305
        jnz     .notfirst
1306
        call    mutex_lock
1307
        mov     eax, disk_list
1308
.notfirst:
1309
; 2. Get next item.
1310
        mov     eax, [eax+DISK.Next]
1311
; 3. If there are no more items, go to 6.
1312
        cmp     eax, disk_list
1313
        jz      .last
1314
; 4. Copy name from the DISK structure to edi.
1315
        push    eax esi
1316
        mov     esi, [eax+DISK.Name]
1317
@@:
1318
        lodsb
1319
        stosb
1320
        test    al, al
1321
        jnz     @b
1322
        pop     esi eax
1323
; 5. Return with eax = item.
1324
        pop     edx             ; restore register used in file_system_lfn
1325
        ret
1326
.last:
1327
; 6. Release the mutex and return with eax = 0.
1328
        call    mutex_unlock
1329
        xor     eax, eax
1330
        pop     edx             ; restore register used in file_system_lfn
1331
        ret