07d896f571
Added a new driver for the iso9660 file system. The driver supports the current version of the disk subsystem and is intended for further translation of IDE ATAPI devices to this driver. The basic version of ISO9660 and the Joliet extension are supported. git-svn-id: svn://kolibrios.org@10053 a494cfbc-eb01-0410-851d-a64ba20cac60
1768 lines
66 KiB
PHP
1768 lines
66 KiB
PHP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; ;;
|
|
;; Copyright (C) KolibriOS team 2011-2024. All rights reserved. ;;
|
|
;; Distributed under terms of the GNU General Public License ;;
|
|
;; ;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
; =============================================================================
|
|
; ================================= Constants =================================
|
|
; =============================================================================
|
|
; Error codes for callback functions.
|
|
DISK_STATUS_OK = 0 ; success
|
|
DISK_STATUS_GENERAL_ERROR = -1; if no other code is suitable
|
|
DISK_STATUS_INVALID_CALL = 1 ; invalid input parameters
|
|
DISK_STATUS_NO_MEDIA = 2 ; no media present
|
|
DISK_STATUS_END_OF_MEDIA = 3 ; end of media while reading/writing data
|
|
DISK_STATUS_NO_MEMORY = 4 ; insufficient memory for driver operation
|
|
; Driver flags. Represent bits in DISK.DriverFlags.
|
|
DISK_NO_INSERT_NOTIFICATION = 1
|
|
; Media flags. Represent bits in DISKMEDIAINFO.Flags.
|
|
DISK_MEDIA_READONLY = 1
|
|
|
|
; If too many partitions are detected,there is probably an error on the disk.
|
|
; 256 partitions should be enough for any reasonable use.
|
|
; Also, the same number is limiting the number of MBRs to process; if
|
|
; too many MBRs are visible,there probably is a loop in the MBR structure.
|
|
MAX_NUM_PARTITIONS = 256
|
|
|
|
; =============================================================================
|
|
; ================================ Structures =================================
|
|
; =============================================================================
|
|
; This structure defines all callback functions for working with the physical
|
|
; device. They are implemented by a driver. Objects with this structure reside
|
|
; in a driver.
|
|
struct DISKFUNC
|
|
strucsize dd ?
|
|
; Size of the structure. This field is intended for possible extensions of
|
|
; this structure. If a new function is added to this structure and a driver
|
|
; implements an old version, the caller can detect this by checking .strucsize,
|
|
; so the driver remains compatible.
|
|
close dd ?
|
|
; The pointer to the function which frees all driver-specific resources for
|
|
; the disk.
|
|
; Optional, may be NULL.
|
|
; void close(void* userdata);
|
|
closemedia dd ?
|
|
; The pointer to the function which informs the driver that the kernel has
|
|
; finished all processing with the current media. If media is removed, the
|
|
; driver should decline all requests to that media with DISK_STATUS_NO_MEDIA,
|
|
; even if new media is inserted, until this function is called. If media is
|
|
; removed, a new call to 'disk_media_changed' is not allowed until this
|
|
; function is called.
|
|
; Optional, may be NULL (if media is not removable).
|
|
; void closemedia(void* userdata);
|
|
querymedia dd ?
|
|
; The pointer to the function which determines capabilities of the media.
|
|
; int querymedia(void* userdata, DISKMEDIAINFO* info);
|
|
; Return value: one of DISK_STATUS_*
|
|
read dd ?
|
|
; The pointer to the function which reads data from the device.
|
|
; int read(void* userdata, void* buffer, __int64 startsector, int* numsectors);
|
|
; input: *numsectors = number of sectors to read
|
|
; output: *numsectors = number of sectors which were successfully read
|
|
; Return value: one of DISK_STATUS_*
|
|
write dd ?
|
|
; The pointer to the function which writes data to the device.
|
|
; Optional, may be NULL.
|
|
; int write(void* userdata, void* buffer, __int64 startsector, int* numsectors);
|
|
; input: *numsectors = number of sectors to write
|
|
; output: *numsectors = number of sectors which were successfully written
|
|
; Return value: one of DISK_STATUS_*
|
|
flush dd ?
|
|
; The pointer to the function which flushes the internal device cache.
|
|
; Optional, may be NULL.
|
|
; int flush(void* userdata);
|
|
; Return value: one of DISK_STATUS_*
|
|
; Note that read/write are called by the cache manager, so a driver should not
|
|
; create a software cache. This function is implemented for flushing a hardware
|
|
; cache, if it exists.
|
|
adjust_cache_size dd ?
|
|
; The pointer to the function which returns the cache size for this device.
|
|
; Optional, may be NULL.
|
|
; unsigned int adjust_cache_size(void* userdata, unsigned int suggested_size);
|
|
; Return value: 0 = disable cache, otherwise = used cache size in bytes.
|
|
LoadTray dd ?
|
|
; This pointer to the function which load and unload tray drive.
|
|
; Optional, may be NULL
|
|
; int LoadTray(void* userdata, int flags);
|
|
; flags:
|
|
; 0 - load
|
|
; 1 - unload
|
|
; Return value: one of DISK_STATUS_*
|
|
ends
|
|
|
|
; This structure holds information on a medium.
|
|
; Objects with this structure are allocated by the kernel as a part of the DISK
|
|
; structure and are filled by a driver in the 'querymedia' callback.
|
|
struct DISKMEDIAINFO
|
|
Flags dd ?
|
|
; Combination of DISK_MEDIA_* bits.
|
|
SectorSize dd ?
|
|
; Size of the sector.
|
|
Capacity dq ?
|
|
; Size of the media in sectors.
|
|
LastSessionSector dd ?
|
|
; Number last session sectors for CDFS
|
|
ends
|
|
|
|
; This structure represents the disk cache. To follow the old implementation,
|
|
; there are two distinct caches for a disk, one for "system" data,and the other
|
|
; for "application" data.
|
|
struct DISKCACHE
|
|
; The following fields are inherited from data32.inc:cache_ideX.
|
|
pointer dd ?
|
|
data_size dd ? ; unused
|
|
data dd ?
|
|
sad_size dd ?
|
|
search_start dd ?
|
|
sector_size_log dd ?
|
|
ends
|
|
|
|
; This structure represents a disk device and its media for the kernel.
|
|
; This structure is allocated by the kernel in the 'disk_add' function,
|
|
; freed in the 'disk_dereference' function.
|
|
struct DISK
|
|
; Fields of disk object
|
|
Next dd ?
|
|
Prev dd ?
|
|
; All disk devices are linked in one list with these two fields.
|
|
; Head of the list is the 'disk_list' variable.
|
|
Functions dd ?
|
|
; Pointer to the 'DISKFUNC' structure with driver functions.
|
|
Name dd ?
|
|
; Pointer to the string used for accesses through the global filesystem.
|
|
UserData dd ?
|
|
; This field is passed to all callback functions so a driver can decide which
|
|
; physical device is addressed.
|
|
DriverFlags dd ?
|
|
; Bitfield. Currently only DISK_NO_INSERT_NOTIFICATION bit is defined.
|
|
; If it is set, the driver will never issue 'disk_media_changed' notification
|
|
; with argument set to true, so the kernel must try to detect media during
|
|
; requests from the file system.
|
|
RefCount dd ?
|
|
; Count of active references to this structure. One reference is kept during
|
|
; the lifetime of the structure between 'disk_add' and 'disk_del'.
|
|
; Another reference is taken during any filesystem operation for this disk.
|
|
; One reference is added if media is inserted.
|
|
; The structure is destroyed when the reference count decrements to zero:
|
|
; this usually occurs in 'disk_del', but can be delayed to the end of last
|
|
; filesystem operation, if one is active.
|
|
MediaLock MUTEX
|
|
; Lock to protect the MEDIA structure. See the description after
|
|
; 'disk_list_mutex' for the locking strategy.
|
|
; Fields of media object
|
|
MediaInserted db ?
|
|
; 0 if media is not inserted, nonzero otherwise.
|
|
MediaUsed db ?
|
|
; 0 if media fields are not used, nonzero otherwise. If .MediaRefCount is
|
|
; nonzero, this field is nonzero too; however, when .MediaRefCount goes
|
|
; to zero, there is some time interval during which media object is still used.
|
|
dw ? ; padding
|
|
; The following fields are not valid unless either .MediaInserted is nonzero
|
|
; or they are accessed from a code which has obtained the reference when
|
|
; .MediaInserted was nonzero.
|
|
MediaRefCount dd ?
|
|
; Count of active references to the media object. One reference is kept during
|
|
; the lifetime of the media between two calls to 'disk_media_changed'.
|
|
; Another reference is taken during any filesystem operation for this media.
|
|
; The callback 'closemedia' is called when the reference count decrements to
|
|
; zero: this usually occurs in 'disk_media_changed', but can be delayed to the
|
|
; end of the last filesystem operation, if one is active.
|
|
MediaInfo DISKMEDIAINFO
|
|
; This field keeps information on the current media.
|
|
NumPartitions dd ?
|
|
; Number of partitions on this media.
|
|
Partitions dd ?
|
|
; Pointer to array of .NumPartitions pointers to PARTITION structures.
|
|
cache_size dd ?
|
|
; inherited from cache_ideX_size
|
|
CacheLock MUTEX
|
|
; Lock to protect both caches.
|
|
SysCache DISKCACHE
|
|
AppCache DISKCACHE
|
|
; Two caches for the disk.
|
|
ends
|
|
|
|
; This structure represents one partition for the kernel. This is a base
|
|
; template, the actual contents after common fields is determined by the
|
|
; file system code for this partition.
|
|
struct PARTITION
|
|
FirstSector dq ?
|
|
; First sector of the partition.
|
|
Length dq ?
|
|
; Length of the partition in sectors.
|
|
Disk dd ?
|
|
; Pointer to parent DISK structure.
|
|
FSUserFunctions dd ?
|
|
; Handlers for the sysfunction 70h. This field is a pointer to the following
|
|
; array. The first dword is pointer to disconnect handler.
|
|
; The first dword is a number of supported subfunctions, other dwords
|
|
; point to handlers of corresponding subfunctions.
|
|
; ...fs-specific data may follow...
|
|
ends
|
|
|
|
; This is an external structure, it represents an entry in the partition table.
|
|
struct PARTITION_TABLE_ENTRY
|
|
Bootable db ?
|
|
; 80h = bootable partition, 0 = non-bootable partition, other values = invalid
|
|
FirstHead db ?
|
|
FirstSector db ?
|
|
FirstTrack db ?
|
|
; Coordinates of first sector in CHS.
|
|
Type db ?
|
|
; Partition type, one of predefined constants. 0 = empty, several types denote
|
|
; extended partition (see process_partition_table_entry), we are not interested
|
|
; in other values.
|
|
LastHead db ?
|
|
LastSector db ?
|
|
LastTrack db ?
|
|
; Coordinates of last sector in CHS.
|
|
FirstAbsSector dd ?
|
|
; Coordinate of first sector in LBA.
|
|
Length dd ?
|
|
; Length of the partition in sectors.
|
|
ends
|
|
|
|
; GUID Partition Table Header, UEFI 2.6, Table 18
|
|
struct GPTH
|
|
Signature rb 8
|
|
; 'EFI PART'
|
|
Revision dd ?
|
|
; 0x00010000
|
|
HeaderSize dd ?
|
|
; Size of this header in bytes, must fit to one sector.
|
|
HeaderCRC32 dd ?
|
|
; Set this field to zero, compute CRC32 via 0xEDB88320, compare.
|
|
Reserved dd ?
|
|
; Must be zero.
|
|
MyLBA dq ?
|
|
; LBA of the sector containing this GPT header.
|
|
AlternateLBA dq ?
|
|
; LBA of the sector containing the other GPT header.
|
|
; AlternateLBA of Primary GPTH points to Backup one and vice versa.
|
|
FirstUsableLBA dq ?
|
|
; Only sectors between first and last UsableLBA may form partitions
|
|
LastUsableLBA dq ?
|
|
DiskGUID rb 16
|
|
; Globally Unique IDentifier
|
|
PartitionEntryLBA dq ?
|
|
; First LBA of Partition Entry Array.
|
|
; Length in bytes is computed as a product of two following fields.
|
|
NumberOfPartitionEntries dd ?
|
|
; Actual number of partitions depends on the contents of Partition Entry Array.
|
|
; A partition entry is unused if zeroed.
|
|
SizeOfPartitionEntry dd ? ; in bytes
|
|
PartitionEntryArrayCRC32 dd ?
|
|
; Same CRC as for GPT header.
|
|
ends
|
|
|
|
; GPT Partition Entry, UEFI 2.6, Table 19
|
|
struct GPE
|
|
PartitionTypeGUID rb 16
|
|
UniquePartitionGUID rb 16
|
|
StartingLBA dq ?
|
|
EndingLBA dq ?
|
|
; Length in sectors is EndingLBA - StartingLBA + 1.
|
|
Attributes dq ?
|
|
PartitionName rb 72
|
|
ends
|
|
|
|
; =============================================================================
|
|
; ================================ Global data ================================
|
|
; =============================================================================
|
|
iglobal
|
|
; The pseudo-item for the list of all DISK structures.
|
|
; Initialized to the empty list.
|
|
disk_list:
|
|
dd disk_list
|
|
dd disk_list
|
|
endg
|
|
uglobal
|
|
; This mutex guards all operations with the global list of DISK structures.
|
|
disk_list_mutex MUTEX
|
|
; * There are two dependent objects, a disk and a media. In the simplest case,
|
|
; disk and media are both non-removable. However, in the general case both
|
|
; can be removed at any time, simultaneously or only media,and this makes things
|
|
; complicated.
|
|
; * For efficiency, both disk and media objects are located in the one
|
|
; structure named DISK. However, logically they are different.
|
|
; * The following operations use data of disk object: adding (disk_add);
|
|
; deleting (disk_del); filesystem (fs_lfn which eventually calls
|
|
; dyndisk_handler or dyndisk_enum_root).
|
|
; * The following operations use data of media object: adding/removing
|
|
; (disk_media_changed); filesystem (fs_lfn which eventually calls
|
|
; dyndisk_handler; dyndisk_enum_root doesn't work with media).
|
|
; * Notifications disk_add, disk_media_changed, disk_del are synchronized
|
|
; between themselves, this is a requirement for the driver. However, file
|
|
; system operations are asynchronous, can be issued at any time by any
|
|
; thread.
|
|
; * We must prevent a situation when a filesystem operation thinks that the
|
|
; object is still valid but in fact the notification has destroyed the
|
|
; object. So we keep a reference counter for both disk and media and destroy
|
|
; the object when this counter goes to zero.
|
|
; * The driver must know when it is safe to free driver-allocated resources.
|
|
; The object can be alive even after death notification has completed.
|
|
; We use special callbacks to satisfy both assertions: 'close' for the disk
|
|
; and 'closemedia' for the media. The destruction of the object includes
|
|
; calling the corresponding callback.
|
|
; * Each filesystem operation keeps one reference for the disk and one
|
|
; reference for the media. Notification disk_del forces notification on the
|
|
; media death, so the reference counter for the disk is always not less than
|
|
; the reference counter for the media.
|
|
; * Two operations "get the object" and "increment the reference counter" can
|
|
; not be done simultaneously. We use a mutex to guard the consistency here.
|
|
; It must be a part of the container for the object, so that this mutex can
|
|
; be acquired as a part of getting the object from the container. The
|
|
; container for disk object is the global list, and this list is guarded by
|
|
; 'disk_list_mutex'. The container for media object is the disk object, and
|
|
; the corresponding mutex is DISK.MediaLock.
|
|
; * Notifications do not change the data of objects, they can only remove
|
|
; objects. Thus we don't need another synchronization at this level. If two
|
|
; filesystem operations are referencing the same filesystem data, this is
|
|
; better resolved at the level of the filesystem.
|
|
endg
|
|
|
|
iglobal
|
|
; The function 'disk_scan_partitions' needs three sector-sized buffers for
|
|
; MBR, bootsector and fs-temporary sector data. It can not use the static
|
|
; buffers always, since it can be called for two or more disks in parallel.
|
|
; However, this case is not typical. We reserve three static 512-byte buffers
|
|
; and a flag that these buffers are currently used. If 'disk_scan_partitions'
|
|
; detects that the buffers are currently used, it allocates buffers from the
|
|
; heap. Also, the heap is used when sector size is other than 512.
|
|
; The flag is implemented as a global dword variable. When the static buffers
|
|
; are not used, the value is -1. When the static buffers are used, the value
|
|
; is normally 0 and temporarily can become greater. The function increments
|
|
; this value. If the resulting value is zero, it uses the buffers and
|
|
; decrements the value when the job is done. Otherwise, it immediately
|
|
; decrements the value and uses buffers from the heap, allocated in the
|
|
; beginning and freed in the end.
|
|
partition_buffer_users dd -1
|
|
endg
|
|
uglobal
|
|
; The static buffers for MBR, bootsector and fs-temporary sector data.
|
|
align 16
|
|
mbr_buffer rb 512
|
|
bootsect_buffer rb 512
|
|
fs_tmp_buffer rb 512
|
|
endg
|
|
|
|
iglobal
|
|
; This is the array of default implementations of driver callbacks.
|
|
; Same as DRIVERFUNC structure except for the first field; all functions must
|
|
; have the default implementations.
|
|
align 4
|
|
disk_default_callbacks:
|
|
dd disk_default_close
|
|
dd disk_default_closemedia
|
|
dd disk_default_querymedia
|
|
dd disk_default_read
|
|
dd disk_default_write
|
|
dd disk_default_flush
|
|
dd disk_default_adjust_cache_size
|
|
endg
|
|
|
|
; =============================================================================
|
|
; ================================= Functions =================================
|
|
; =============================================================================
|
|
|
|
; This function registers a disk device.
|
|
; This includes:
|
|
; - allocating an internal structure describing this device;
|
|
; - registering this structure in the global filesystem.
|
|
; The function initializes the disk as if there is no media. If a media is
|
|
; present, the function 'disk_media_changed' should be called after this
|
|
; function succeeds.
|
|
; Parameters:
|
|
; [esp+4] = pointer to DISKFUNC structure with the callbacks
|
|
; [esp+8] = pointer to name (ASCIIZ string)
|
|
; [esp+12] = userdata to be passed to the callbacks as is.
|
|
; [esp+16] = flags, bitfield. Currently only DISK_NO_INSERT_NOTIFICATION bit
|
|
; is defined.
|
|
; Return value:
|
|
; NULL = operation has failed
|
|
; non-NULL = handle of the disk. This handle can be used
|
|
; in the operations with other Disk* functions.
|
|
; The handle is the pointer to the internal structure DISK.
|
|
disk_add:
|
|
push ebx esi ; save used registers to be stdcall
|
|
; 1. Allocate the DISK structure.
|
|
; 1a. Call the heap manager.
|
|
movi eax, sizeof.DISK
|
|
call malloc
|
|
; 1b. Check the result. If allocation failed, return (go to 9) with eax = 0.
|
|
test eax, eax
|
|
jz .nothing
|
|
; 2. Copy the disk name to the DISK structure.
|
|
; 2a. Get length of the name, including the terminating zero.
|
|
mov ebx, [esp+8+8] ; ebx = pointer to name
|
|
push eax ; save allocated pointer to DISK
|
|
xor eax, eax ; the argument of malloc() is in eax
|
|
@@:
|
|
inc eax
|
|
cmp byte [ebx+eax-1], 0
|
|
jnz @b
|
|
; 2b. Call the heap manager.
|
|
call malloc
|
|
; 2c. Check the result. If allocation failed, go to 7.
|
|
pop esi ; restore allocated pointer to DISK
|
|
test eax, eax
|
|
jz .free
|
|
; 2d. Store the allocated pointer to the DISK structure.
|
|
mov [esi+DISK.Name], eax
|
|
; 2e. Copy the name.
|
|
@@:
|
|
mov dl, [ebx]
|
|
mov [eax], dl
|
|
inc ebx
|
|
inc eax
|
|
test dl, dl
|
|
jnz @b
|
|
; 3. Copy other arguments of the function to the DISK structure.
|
|
mov eax, [esp+4+8]
|
|
mov [esi+DISK.Functions], eax
|
|
mov eax, [esp+12+8]
|
|
mov [esi+DISK.UserData], eax
|
|
mov eax, [esp+16+8]
|
|
mov [esi+DISK.DriverFlags], eax
|
|
; 4. Initialize other fields of the DISK structure.
|
|
; Media is not inserted, reference counter is 1.
|
|
lea ecx, [esi+DISK.MediaLock]
|
|
call mutex_init
|
|
xor eax, eax
|
|
mov dword [esi+DISK.MediaInserted], eax
|
|
mov [esi+DISK.MediaRefCount], eax
|
|
inc eax
|
|
mov [esi+DISK.RefCount], eax
|
|
; The DISK structure is initialized.
|
|
; 5. Insert the new structure to the global list.
|
|
; 5a. Acquire the mutex.
|
|
mov ecx, disk_list_mutex
|
|
call mutex_lock
|
|
; 5b. Insert item to the tail of double-linked list.
|
|
mov edx, disk_list
|
|
list_add_tail esi, edx ;esi= new edx= list head
|
|
; 5c. Release the mutex.
|
|
call mutex_unlock
|
|
; 6. Return with eax = pointer to DISK.
|
|
xchg eax, esi
|
|
jmp .nothing
|
|
.free:
|
|
; Memory allocation for DISK structure succeeded, but for disk name failed.
|
|
; 7. Free the DISK structure.
|
|
xchg eax, esi
|
|
call free
|
|
; 8. Return with eax = 0.
|
|
xor eax, eax
|
|
.nothing:
|
|
; 9. Return.
|
|
pop esi ebx ; restore used registers to be stdcall
|
|
ret 16 ; purge 4 dword arguments to be stdcall
|
|
|
|
; This function deletes a disk device from the global filesystem.
|
|
; This includes:
|
|
; - removing a media including all partitions;
|
|
; - deleting this structure from the global filesystem;
|
|
; - dereferencing the DISK structure and possibly destroying it.
|
|
; Parameters:
|
|
; [esp+4] = handle of the disk, i.e. the pointer to the DISK structure.
|
|
; Return value: none.
|
|
disk_del:
|
|
push esi ; save used registers to be stdcall
|
|
; 1. Force media to be removed. If the media is already removed, the
|
|
; call does nothing.
|
|
mov esi, [esp+4+4] ; esi = handle of the disk
|
|
stdcall disk_media_changed, esi, 0
|
|
; 2. Delete the structure from the global list.
|
|
; 2a. Acquire the mutex.
|
|
mov ecx, disk_list_mutex
|
|
call mutex_lock
|
|
; 2b. Delete item from double-linked list.
|
|
mov eax, [esi+DISK.Next]
|
|
mov edx, [esi+DISK.Prev]
|
|
mov [eax+DISK.Prev], edx
|
|
mov [edx+DISK.Next], eax
|
|
; 2c. Release the mutex.
|
|
call mutex_unlock
|
|
; 3. The structure still has one reference created in disk_add. Remove this
|
|
; reference. If there are no other references, disk_dereference will free the
|
|
; structure.
|
|
call disk_dereference
|
|
; 4. Return.
|
|
pop esi ; restore used registers to be stdcall
|
|
ret 4 ; purge 1 dword argument to be stdcall
|
|
|
|
; This is an internal function which removes a previously obtained reference
|
|
; to the disk. If this is the last reference, this function lets the driver
|
|
; finalize all associated data, and afterwards frees the DISK structure.
|
|
; esi = pointer to DISK structure
|
|
disk_dereference:
|
|
; 1. Decrement reference counter. Use atomic operation to correctly handle
|
|
; possible simultaneous calls.
|
|
lock dec [esi+DISK.RefCount]
|
|
; 2. If the result is nonzero, there are other references, so nothing to do.
|
|
; In this case, return (go to 4).
|
|
jnz .nothing
|
|
; 3. If we are here, we just removed the last reference and must destroy the
|
|
; disk object.
|
|
; 3a. Call the driver.
|
|
mov al, DISKFUNC.close
|
|
stdcall disk_call_driver
|
|
; 3b. Free the structure.
|
|
xchg eax, esi
|
|
push ebx
|
|
call free
|
|
pop ebx
|
|
; 4. Return.
|
|
.nothing:
|
|
ret
|
|
|
|
; This is an internal function which removes a previously obtained reference
|
|
; to the media. If this is the last reference, this function calls 'closemedia'
|
|
; callback to signal the driver that the processing has finished and it is safe
|
|
; to inform about a new media.
|
|
; esi = pointer to DISK structure
|
|
disk_media_dereference:
|
|
; 1. Decrement reference counter. Use atomic operation to correctly handle
|
|
; possible simultaneous calls.
|
|
lock dec [esi+DISK.MediaRefCount]
|
|
; 2. If the result is nonzero, there are other references, so nothing to do.
|
|
; In this case, return (go to 4).
|
|
jnz .nothing
|
|
; 3. If we are here, we just removed the last reference and must destroy the
|
|
; media object.
|
|
; Note that the same place inside the DISK structure is reused for all media
|
|
; objects, so we must guarantee that reusing does not happen while freeing.
|
|
; Reusing is only possible when someone processes a new media. There are two
|
|
; mutually exclusive variants:
|
|
; * driver issues media insert notifications (DISK_NO_INSERT_NOTIFICATION bit
|
|
; in DISK.DriverFlags is not set). In this case, we require from the driver
|
|
; that such notification (except for the first one) can occur only after a
|
|
; call to 'closemedia' callback.
|
|
; * driver does not issue media insert notifications. In this case, the kernel
|
|
; itself must sometimes check whether media is inserted. We have the flag
|
|
; DISK.MediaUsed, visible to the kernel. This flag signals to the other parts
|
|
; of kernel that the way is free.
|
|
; In the first case other parts of the kernel do not use DISK.MediaUsed, so it
|
|
; does not matter when this flag is cleared. In the second case this flag must
|
|
; be cleared after all other actions, including call to 'closemedia'.
|
|
; 3a. Free all partitions.
|
|
push esi edi
|
|
mov edi, [esi+DISK.NumPartitions]
|
|
mov esi, [esi+DISK.Partitions]
|
|
test edi, edi
|
|
jz .nofree
|
|
.freeloop:
|
|
lodsd
|
|
mov ecx, [eax+PARTITION.FSUserFunctions]
|
|
call dword [ecx]
|
|
dec edi
|
|
jnz .freeloop
|
|
.nofree:
|
|
pop edi esi
|
|
; 3b. Free the cache.
|
|
call disk_free_cache
|
|
; 3c. Call the driver.
|
|
mov al, DISKFUNC.closemedia
|
|
stdcall disk_call_driver
|
|
; 3d. Clear the flag.
|
|
mov [esi+DISK.MediaUsed], 0
|
|
.nothing:
|
|
ret
|
|
|
|
; This function is called by the driver and informs the kernel that the media
|
|
; has changed. If the media is non-removable, it is called exactly once
|
|
; immediately after 'disk_add' and once from 'disk_del'.
|
|
; Parameters:
|
|
; [esp+4] = handle of the disk, i.e. the pointer to the DISK structure.
|
|
; [esp+8] = new status of the media: zero = no media, nonzero = media inserted.
|
|
disk_media_changed:
|
|
push ebx esi edi ; save used registers to be stdcall
|
|
; 1. Remove the existing media, if it is present.
|
|
mov esi, [esp+4+12] ; esi = pointer to DISK
|
|
; 1a. Check whether it is present. Since DISK.MediaInserted is changed only
|
|
; in this function and calls to this function are synchronized, no lock is
|
|
; required for checking.
|
|
cmp [esi+DISK.MediaInserted], 0
|
|
jz .noremove
|
|
; We really need to remove the media.
|
|
; 1b. Acquire mutex.
|
|
lea ecx, [esi+DISK.MediaLock]
|
|
call mutex_lock
|
|
; 1c. Clear the flag.
|
|
mov [esi+DISK.MediaInserted], 0
|
|
; 1d. Release mutex.
|
|
call mutex_unlock
|
|
; 1e. Remove the "lifetime" reference and possibly destroy the structure.
|
|
call disk_media_dereference
|
|
.noremove:
|
|
; 2. Test whether there is new media.
|
|
cmp dword [esp+8+12], 0
|
|
jz .noinsert
|
|
; Yep, there is.
|
|
; 3. Process the new media. We assume that all media fields are available to
|
|
; use, see comments in 'disk_media_dereference' (this covers using by previous
|
|
; media referencers) and note that calls to this function are synchronized
|
|
; (this covers using by new media referencers).
|
|
; 3a. Call the 'querymedia' callback.
|
|
; .Flags are set to zero for possible future extensions.
|
|
lea edx, [esi+DISK.MediaInfo]
|
|
and [edx+DISKMEDIAINFO.LastSessionSector], 0
|
|
and [edx+DISKMEDIAINFO.Flags], 0
|
|
mov al, DISKFUNC.querymedia
|
|
stdcall disk_call_driver, edx
|
|
; 3b. Check the result of the callback. Abort if it failed.
|
|
test eax, eax
|
|
jnz .noinsert
|
|
; 3c. Allocate the cache unless disabled by the driver. Abort if failed.
|
|
call disk_init_cache
|
|
test al, al
|
|
jz .noinsert
|
|
; 3d. Acquire the lifetime reference for the media object.
|
|
inc [esi+DISK.MediaRefCount]
|
|
; 3e. Scan for partitions. Ignore result; the list of partitions is valid even
|
|
; on errors.
|
|
call disk_scan_partitions
|
|
; 3f. Media is inserted and available for use.
|
|
inc [esi+DISK.MediaInserted]
|
|
.noinsert:
|
|
; 4. Return.
|
|
pop edi esi ebx ; restore used registers to be stdcall
|
|
ret 8 ; purge 2 dword arguments to be stdcall
|
|
|
|
; This function is a thunk for all functions of a disk driver.
|
|
; It checks whether the referenced function is implemented in the driver.
|
|
; If so, this function jumps to the function in the driver.
|
|
; Otherwise, it jumps to the default implementation.
|
|
; al = offset of function in the DISKFUNC structure;
|
|
; esi = pointer to the DISK structure;
|
|
; stack is the same as for the corresponding function except that the
|
|
; first parameter (void* userdata) is prepended automatically.
|
|
disk_call_driver:
|
|
movzx eax, al ; eax = offset of function in the DISKFUNC structure
|
|
; 1. Prepend the first argument to the stack.
|
|
pop ecx ; ecx = return address
|
|
push [esi+DISK.UserData] ; add argument
|
|
push ecx ; save return address
|
|
; 2. Check that the required function is inside the table. If not, go to 5.
|
|
mov ecx, [esi+DISK.Functions]
|
|
cmp eax, [ecx+DISKFUNC.strucsize]
|
|
jae .default
|
|
; 3. Check that the required function is implemented. If not, go to 5.
|
|
mov ecx, [ecx+eax]
|
|
test ecx, ecx
|
|
jz .default
|
|
; 4. Jump to the required function.
|
|
jmp ecx
|
|
.default:
|
|
; 5. Driver does not implement the required function; use default implementation.
|
|
jmp dword [disk_default_callbacks+eax-4]
|
|
|
|
; The default implementation of DISKFUNC.querymedia.
|
|
disk_default_querymedia:
|
|
movi eax, DISK_STATUS_INVALID_CALL
|
|
ret 8
|
|
|
|
; The default implementation of DISKFUNC.read and DISKFUNC.write.
|
|
disk_default_read:
|
|
disk_default_write:
|
|
movi eax, DISK_STATUS_INVALID_CALL
|
|
ret 20
|
|
|
|
; The default implementation of DISKFUNC.close, DISKFUNC.closemedia and
|
|
; DISKFUNC.flush.
|
|
disk_default_close:
|
|
disk_default_closemedia:
|
|
disk_default_flush:
|
|
xor eax, eax
|
|
ret 4
|
|
|
|
; The default implementation of DISKFUNC.adjust_cache_size.
|
|
disk_default_adjust_cache_size:
|
|
mov eax, [esp+8]
|
|
ret 8
|
|
|
|
; This is an internal function called from 'disk_media_changed' when a new media
|
|
; is detected. It creates the list of partitions for the media.
|
|
; If media is not partitioned, then the list consists of one partition which
|
|
; covers all the media.
|
|
; esi = pointer to the DISK structure.
|
|
disk_scan_partitions:
|
|
; 1. Initialize .NumPartitions and .Partitions fields as zeros: empty list.
|
|
and [esi+DISK.NumPartitions], 0
|
|
and [esi+DISK.Partitions], 0
|
|
; 2. Acquire the buffer for MBR and bootsector tests. See the comment before
|
|
; the 'partition_buffer_users' variable.
|
|
mov eax, [esi+DISK.MediaInfo.SectorSize]
|
|
cmp eax, 512
|
|
jnz @f
|
|
mov ebx, mbr_buffer ; assume the global buffer is free
|
|
lock inc [partition_buffer_users]
|
|
jz .buffer_acquired ; yes, it is free
|
|
lock dec [partition_buffer_users] ; no, we must allocate
|
|
@@:
|
|
lea eax, [eax*3]
|
|
stdcall kernel_alloc, eax
|
|
test eax, eax
|
|
jz .nothing
|
|
xchg eax, ebx
|
|
.buffer_acquired:
|
|
; MBR/EBRs are organized in the chain. We use a loop over MBR/EBRs, but no
|
|
; more than MAX_NUM_PARTITION times.
|
|
; 3. Prepare things for the loop.
|
|
; ebp will hold the sector number for current MBR/EBR.
|
|
; [esp] will hold the sector number for current extended partition, if there
|
|
; is one.
|
|
; [esp+4] will hold the counter that prevents long loops.
|
|
push ebp ; save ebp
|
|
push MAX_NUM_PARTITIONS ; the counter of max MBRs to process
|
|
xor ebp, ebp ; start from sector zero
|
|
push ebp ; no extended partition yet
|
|
; 4. MBR is 512 bytes long. If sector size is less than 512 bytes,
|
|
; assume no MBR, no partitions and go to 11.
|
|
cmp [esi+DISK.MediaInfo.SectorSize], 512
|
|
jb .notmbr
|
|
.new_mbr:
|
|
; 5. Read the current sector.
|
|
; Note that 'read' callback operates with 64-bit sector numbers, so we must
|
|
; push additional zero as a high dword of sector number.
|
|
mov al, DISKFUNC.read
|
|
push 1
|
|
stdcall disk_call_driver, ebx, ebp, 0, esp
|
|
pop ecx
|
|
; 6. If the read has failed, abort the loop.
|
|
dec ecx
|
|
jnz .mbr_failed
|
|
; 7. Check the MBR/EBR signature. If it is wrong, abort the loop.
|
|
; Soon we will access the partition table which starts at ebx+0x1BE,
|
|
; so we can fill its address right now. If we do it now, then the addressing
|
|
; [ecx+0x40] is shorter than [ebx+0x1fe]: one-byte offset vs 4-bytes offset.
|
|
lea ecx, [ebx+0x1be] ; ecx -> partition table
|
|
cmp word [ecx+0x40], 0xaa55
|
|
jnz .notmbr
|
|
; 8. The MBR is treated differently from EBRs. For MBR we additionally need to
|
|
; execute step 10 and possibly step 11.
|
|
test ebp, ebp
|
|
jnz .mbr
|
|
; 9. Handle GUID Partition Table
|
|
; 9a. Check if MBR is protective
|
|
call is_protective_mbr
|
|
jnz .no_gpt
|
|
; 9b. If so, try to scan GPT headers
|
|
call disk_scan_gpt
|
|
; 9c. If any GPT header is valid, ignore MBR
|
|
jz .done
|
|
; Otherwise process legacy/protective MBR
|
|
.no_gpt:
|
|
; The partition table can be present or not present. In the first case, we just
|
|
; read the MBR. In the second case, we just read the bootsector for a
|
|
; filesystem.
|
|
; The following algorithm is used to distinguish between these cases.
|
|
; A. If at least one entry of the partition table is invalid, this is
|
|
; a bootsector. See the description of 'is_partition_table_entry' for
|
|
; definition of validity.
|
|
; B. If all entries are empty (filesystem type field is zero) and the first
|
|
; byte is jmp opcode (0EBh or 0E9h), this is a bootsector which happens to
|
|
; have zeros in the place of partition table.
|
|
; C. Otherwise, this is an MBR.
|
|
; 10. Test for MBR vs bootsector.
|
|
; 10a. Check entries. If any is invalid, go to 11 (rule A).
|
|
call is_partition_table_entry
|
|
jc .notmbr
|
|
add ecx, 10h
|
|
call is_partition_table_entry
|
|
jc .notmbr
|
|
add ecx, 10h
|
|
call is_partition_table_entry
|
|
jc .notmbr
|
|
add ecx, 10h
|
|
call is_partition_table_entry
|
|
jc .notmbr
|
|
; 10b. Check types of the entries. If at least one is nonzero, go to 12 (rule C).
|
|
mov al, [ecx-30h+PARTITION_TABLE_ENTRY.Type]
|
|
or al, [ecx-20h+PARTITION_TABLE_ENTRY.Type]
|
|
or al, [ecx-10h+PARTITION_TABLE_ENTRY.Type]
|
|
or al, [ecx+PARTITION_TABLE_ENTRY.Type]
|
|
jnz .mbr
|
|
; 10c. Empty partition table or bootsector with many zeroes? (rule B)
|
|
cmp byte [ebx], 0EBh
|
|
jz .notmbr
|
|
cmp byte [ebx], 0E9h
|
|
jnz .mbr
|
|
.notmbr:
|
|
; 11. This is not an MBR. The media is not partitioned. Create one partition
|
|
; which covers all the media and abort the loop.
|
|
stdcall disk_add_partition, 0, 0, \
|
|
dword [esi+DISK.MediaInfo.Capacity], dword [esi+DISK.MediaInfo.Capacity+4], esi
|
|
jmp .done
|
|
.mbr:
|
|
; 12. Process all entries of the new MBR/EBR
|
|
lea ecx, [ebx+0x1be] ; ecx -> partition table
|
|
push 0 ; assume no extended partition
|
|
call process_partition_table_entry
|
|
add ecx, 10h
|
|
call process_partition_table_entry
|
|
add ecx, 10h
|
|
call process_partition_table_entry
|
|
add ecx, 10h
|
|
call process_partition_table_entry
|
|
pop ebp
|
|
; 13. Test whether we found a new EBR and should continue the loop.
|
|
; 13a. If there was no next EBR, return.
|
|
test ebp, ebp
|
|
jz .done
|
|
; Ok, we have EBR.
|
|
; 13b. EBRs addresses are relative to the start of extended partition.
|
|
; For simplicity, just abort if an 32-bit overflow occurs; large disks
|
|
; are most likely partitioned with GPT, not MBR scheme, since the precise
|
|
; calculation here would increase limit just twice at the price of big
|
|
; compatibility problems.
|
|
pop eax ; load extended partition
|
|
add ebp, eax
|
|
jc .mbr_failed
|
|
; 13c. If extended partition has not yet started, start it.
|
|
test eax, eax
|
|
jnz @f
|
|
mov eax, ebp
|
|
@@:
|
|
; 13d. If the limit is not exceeded, continue the loop.
|
|
dec dword [esp]
|
|
push eax ; store extended partition
|
|
jnz .new_mbr
|
|
.mbr_failed:
|
|
.done:
|
|
; 14. Cleanup after the loop.
|
|
pop eax ; not important anymore
|
|
pop eax ; not important anymore
|
|
pop ebp ; restore ebp
|
|
; 15. Release the buffer.
|
|
; 15a. Test whether it is the global buffer or we have allocated it.
|
|
cmp ebx, mbr_buffer
|
|
jz .release_partition_buffer
|
|
; 15b. If we have allocated it, free it.
|
|
xchg eax, ebx
|
|
call free
|
|
jmp .nothing
|
|
; 15c. Otherwise, release reference.
|
|
.release_partition_buffer:
|
|
lock dec [partition_buffer_users]
|
|
.nothing:
|
|
; 16. Return.
|
|
ret
|
|
|
|
|
|
; This function is called from disk_scan_partitions to validate and parse
|
|
; primary and backup GPTs.
|
|
proc disk_scan_gpt
|
|
push ecx
|
|
; Scan primary GPT (second sector)
|
|
stdcall scan_gpt, 1, 0
|
|
test eax, eax
|
|
; There is no code to restore backup GPT if it's corrupt.
|
|
; Therefore just exit if Primary GPT has been parsed successfully.
|
|
jz .exit
|
|
DEBUGF 1, 'K : Primary GPT is corrupt, trying backup one\n'
|
|
mov eax, dword[esi+DISK.MediaInfo.Capacity+0]
|
|
mov edx, dword[esi+DISK.MediaInfo.Capacity+4]
|
|
sub eax, 1
|
|
sbb edx, 0
|
|
; Scan backup GPT (last sector)
|
|
stdcall scan_gpt, eax, edx
|
|
test eax, eax
|
|
jz .exit
|
|
DEBUGF 1, 'K : Backup GPT is also corrupt, fallback to legacy MBR\n'
|
|
.exit:
|
|
; Return value is ZF
|
|
pop ecx
|
|
ret
|
|
endp
|
|
|
|
|
|
; This function is called from disk_scan_gpt to process a single GPT.
|
|
proc scan_gpt _mylba:qword
|
|
locals
|
|
GPEA_len dd ? ; Length of GPT Partition Entry Array in bytes
|
|
endl
|
|
push ebx edi
|
|
; Allocalte memory for GPT header
|
|
mov eax, [esi+DISK.MediaInfo.SectorSize]
|
|
stdcall kernel_alloc, eax
|
|
test eax, eax
|
|
jz .fail
|
|
; Save pointer to stack, just in case
|
|
push eax
|
|
mov ebx, eax
|
|
; Read GPT header
|
|
mov al, DISKFUNC.read
|
|
push 1
|
|
stdcall disk_call_driver, ebx, dword[_mylba+0], dword[_mylba+4], esp
|
|
pop ecx
|
|
test eax, eax
|
|
jnz .fail_free_gpt
|
|
; Check signature
|
|
cmp dword[ebx+GPTH.Signature+0], 'EFI '
|
|
jnz .fail_free_gpt
|
|
cmp dword[ebx+GPTH.Signature+4], 'PART'
|
|
jnz .fail_free_gpt
|
|
; Check Revision
|
|
cmp [ebx+GPTH.Revision], 0x00010000
|
|
jnz .fail_free_gpt
|
|
; Compute and check CRC32
|
|
xor edx, edx
|
|
xchg edx, [ebx+GPTH.HeaderCRC32]
|
|
mov eax, -1
|
|
stdcall crc_32, 0xEDB88320, ebx, [ebx+GPTH.HeaderSize]
|
|
xor eax, -1
|
|
cmp eax, edx
|
|
jnz .fail_free_gpt
|
|
; Reserved must be zero
|
|
cmp [ebx+GPTH.Reserved], 0
|
|
jnz .fail_free_gpt
|
|
; MyLBA of GPT header at LBA X must equal X
|
|
mov eax, dword[ebx+GPTH.MyLBA+0]
|
|
mov edx, dword[ebx+GPTH.MyLBA+4]
|
|
cmp eax, dword[_mylba+0]
|
|
jnz .fail_free_gpt
|
|
cmp edx, dword[_mylba+4]
|
|
jnz .fail_free_gpt
|
|
; Capacity - MyLBA = AlternateLBA
|
|
mov eax, dword[esi+DISK.MediaInfo.Capacity+0]
|
|
mov edx, dword[esi+DISK.MediaInfo.Capacity+4]
|
|
sub eax, dword[_mylba+0]
|
|
sbb edx, dword[_mylba+4]
|
|
cmp eax, dword[ebx+GPTH.AlternateLBA+0]
|
|
jnz .fail_free_gpt
|
|
cmp edx, dword[ebx+GPTH.AlternateLBA+4]
|
|
jnz .fail_free_gpt
|
|
|
|
; Compute GPT Partition Entry Array (GPEA) length in bytes
|
|
mov eax, [ebx+GPTH.NumberOfPartitionEntries]
|
|
mul [ebx+GPTH.SizeOfPartitionEntry]
|
|
test edx, edx ; far too big
|
|
jnz .fail_free_gpt
|
|
; Round up to sector boundary
|
|
mov ecx, [esi+DISK.MediaInfo.SectorSize] ; power of two
|
|
dec ecx
|
|
add eax, ecx
|
|
jc .fail_free_gpt ; too big
|
|
not ecx
|
|
and eax, ecx
|
|
; We will need this length to compute CRC32 of GPEA
|
|
mov [GPEA_len], eax
|
|
; Allocate memory for GPEA
|
|
stdcall kernel_alloc, eax
|
|
test eax, eax
|
|
jz .fail_free_gpt
|
|
; Save to not juggle with registers
|
|
push eax
|
|
mov edi, eax
|
|
mov eax, [GPEA_len]
|
|
xor edx, edx
|
|
; Get the number of sectors GPEA fits into
|
|
div [esi+DISK.MediaInfo.SectorSize]
|
|
push eax ; esp = pointer to the number of sectors
|
|
mov al, DISKFUNC.read
|
|
stdcall disk_call_driver, edi, dword[ebx+GPTH.PartitionEntryLBA+0], \
|
|
dword[ebx+GPTH.PartitionEntryLBA+4], esp
|
|
test eax, eax
|
|
pop eax
|
|
jnz .fail_free_gpea_gpt
|
|
; Compute and check CRC32 of GPEA
|
|
mov eax, -1
|
|
stdcall crc_32, 0xEDB88320, edi, [GPEA_len]
|
|
xor eax, -1
|
|
cmp eax, [ebx+GPTH.PartitionEntryArrayCRC32]
|
|
jnz .fail_free_gpea_gpt
|
|
|
|
; Process partitions, skip zeroed ones.
|
|
.next_gpe:
|
|
xor eax, eax
|
|
mov ecx, [ebx+GPTH.SizeOfPartitionEntry]
|
|
repz scasb
|
|
jz .skip
|
|
add edi, ecx
|
|
sub edi, [ebx+GPTH.SizeOfPartitionEntry]
|
|
; Length of a partition in sectors is EndingLBA - StartingLBA + 1
|
|
mov eax, dword[edi+GPE.EndingLBA+0]
|
|
mov edx, dword[edi+GPE.EndingLBA+4]
|
|
sub eax, dword[edi+GPE.StartingLBA+0]
|
|
sbb edx, dword[edi+GPE.StartingLBA+4]
|
|
add eax, 1
|
|
adc edx, 0
|
|
push ebx
|
|
mov ebx, [ebp-8] ; three-sectors-sized buffer
|
|
stdcall disk_add_partition, dword[edi+GPE.StartingLBA+0], \
|
|
dword[edi+GPE.StartingLBA+4], eax, edx, esi
|
|
pop ebx
|
|
add edi, [ebx+GPTH.SizeOfPartitionEntry]
|
|
.skip:
|
|
dec [ebx+GPTH.NumberOfPartitionEntries]
|
|
jnz .next_gpe
|
|
|
|
; Pointers to GPT header and GPEA are on the stack
|
|
stdcall kernel_free
|
|
stdcall kernel_free
|
|
pop edi ebx
|
|
xor eax, eax
|
|
ret
|
|
.fail_free_gpea_gpt:
|
|
stdcall kernel_free
|
|
.fail_free_gpt:
|
|
stdcall kernel_free
|
|
.fail:
|
|
pop edi ebx
|
|
xor eax, eax
|
|
inc eax
|
|
ret
|
|
endp
|
|
|
|
; ecx = pointer to partition records array (MBR + 446)
|
|
is_protective_mbr:
|
|
push ecx edi
|
|
xor eax, eax
|
|
cmp [ecx-2], ax
|
|
jnz .exit
|
|
; Partition record 0 has specific fields
|
|
cmp [ecx+0], al
|
|
jnz .exit
|
|
cmp byte[ecx+4], 0xEE
|
|
jnz .exit
|
|
cmp dword[ecx+8], 1
|
|
jnz .exit
|
|
mov edi, -1
|
|
cmp [ecx+12], edi
|
|
jz @f
|
|
add edi, dword[esi+DISK.MediaInfo.Capacity+0]
|
|
cmp [ecx+12], edi
|
|
jnz .exit
|
|
@@:
|
|
; Check that partition records 1-3 are filled with zero
|
|
lea edi, [ecx+16]
|
|
mov ecx, 16*3/2 ; 3 partitions
|
|
repz scasw
|
|
.exit:
|
|
pop edi ecx
|
|
; Return value is ZF
|
|
ret
|
|
|
|
; This is an internal function called from disk_scan_partitions. It checks
|
|
; whether the entry pointed to by ecx is a valid entry of partition table.
|
|
; The entry is valid if the first byte is 0 or 80h, the first sector plus the
|
|
; length is less than twice the size of media. Multiplication by two is
|
|
; required since the size mentioned in the partition table can be slightly
|
|
; greater than the real size.
|
|
is_partition_table_entry:
|
|
; 1. Check .Bootable field.
|
|
mov al, [ecx+PARTITION_TABLE_ENTRY.Bootable]
|
|
and al, 7Fh
|
|
jnz .invalid
|
|
; 3. Calculate first sector + length. Note that .FirstAbsSector is relative
|
|
; to the MBR/EBR, so the real sum is ebp + .FirstAbsSector + .Length.
|
|
mov eax, ebp
|
|
xor edx, edx
|
|
add eax, [ecx+PARTITION_TABLE_ENTRY.FirstAbsSector]
|
|
adc edx, 0
|
|
add eax, [ecx+PARTITION_TABLE_ENTRY.Length]
|
|
adc edx, 0
|
|
; 4. Divide by two.
|
|
shr edx, 1
|
|
rcr eax, 1
|
|
; 5. Compare with capacity. If the subtraction (edx:eax) - .Capacity does not
|
|
; overflow, this is bad.
|
|
sub eax, dword [esi+DISK.MediaInfo.Capacity]
|
|
sbb edx, dword [esi+DISK.MediaInfo.Capacity+4]
|
|
jnc .invalid
|
|
.valid:
|
|
; 5. Return success: CF is cleared.
|
|
clc
|
|
ret
|
|
.invalid:
|
|
; 6. Return fail: CF is set.
|
|
stc
|
|
ret
|
|
|
|
; This is an internal function called from disk_scan_partitions. It processes
|
|
; the entry pointed to by ecx.
|
|
; * If the entry is invalid, just ignore this entry.
|
|
; * If the type is zero, just ignore this entry.
|
|
; * If the type is one of types for extended partition, store the address
|
|
; of this partition as the new MBR in [esp+4].
|
|
; * Otherwise, add the partition to the list of partitions for this disk.
|
|
; We don't use the type from the entry to identify the file system;
|
|
; fs-specific checks do this more reliably.
|
|
process_partition_table_entry:
|
|
; 1. Check for valid entry. If invalid, return (go to 5).
|
|
call is_partition_table_entry
|
|
jc .nothing
|
|
; 2. Check for empty entry. If invalid, return (go to 5).
|
|
mov al, [ecx+PARTITION_TABLE_ENTRY.Type]
|
|
test al, al
|
|
jz .nothing
|
|
; 3. Check for extended partition. If extended, go to 6.
|
|
irp type,\
|
|
0x05,\ ; DOS: extended partition
|
|
0x0f,\ ; WIN95: extended partition, LBA-mapped
|
|
0xc5,\ ; DRDOS/secured: extended partition
|
|
0xd5 ; Old Multiuser DOS secured: extended partition
|
|
{
|
|
cmp al, type
|
|
jz .extended
|
|
}
|
|
; 4. If we are here, that is a normal partition. Add it to the list.
|
|
; Note that the first sector is relative to MBR/EBR.
|
|
mov eax, ebp
|
|
xor edx, edx
|
|
add eax, [ecx+PARTITION_TABLE_ENTRY.FirstAbsSector]
|
|
adc edx, 0
|
|
push ecx
|
|
stdcall disk_add_partition, eax, edx, \
|
|
[ecx+PARTITION_TABLE_ENTRY.Length], 0, esi
|
|
pop ecx
|
|
.nothing:
|
|
; 5. Return.
|
|
ret
|
|
.extended:
|
|
; 6. If we are here, that is an extended partition. Store the address.
|
|
mov eax, [ecx+PARTITION_TABLE_ENTRY.FirstAbsSector]
|
|
mov [esp+4], eax
|
|
ret
|
|
|
|
; This is an internal function called from disk_scan_partitions and
|
|
; process_partition_table_entry. It adds one partition to the list of
|
|
; partitions for the media.
|
|
; Important note: start, length, disk MUST be present and
|
|
; MUST be in the same order as in PARTITION structure.
|
|
; esi duplicates [disk].
|
|
proc disk_add_partition stdcall uses ebx edi, start:qword, length:qword, disk:dword
|
|
; 1. Check that this partition will not exceed the limit on total number.
|
|
cmp [esi+DISK.NumPartitions], MAX_NUM_PARTITIONS
|
|
jae .nothing
|
|
; 2. Check that this partition does not overlap with any already registered
|
|
; partition. Since any file system assumes that the disk data will not change
|
|
; outside of its control, such overlap could be destructive.
|
|
; Since the number of partitions is usually very small and is guaranteed not
|
|
; to be large, the simple linear search is sufficient.
|
|
; 2a. Prepare the loop: edi will point to the current item of .Partitions
|
|
; array, ecx will be the current item, ebx will hold number of items left.
|
|
mov edi, [esi+DISK.Partitions]
|
|
mov ebx, [esi+DISK.NumPartitions]
|
|
test ebx, ebx
|
|
jz .partitionok
|
|
.scan_existing:
|
|
; 2b. Get the next partition.
|
|
mov ecx, [edi]
|
|
add edi, 4
|
|
; The range [.FirstSector, .FirstSector+.Length) must be either entirely to
|
|
; the left of [start, start+length) or entirely to the right.
|
|
; 2c. Subtract .FirstSector - start. The possible overflow distinguish between
|
|
; cases "to the left" (2e) and "to the right" (2d).
|
|
mov eax, dword [ecx+PARTITION.FirstSector]
|
|
mov edx, dword [ecx+PARTITION.FirstSector+4]
|
|
sub eax, dword [start]
|
|
sbb edx, dword [start+4]
|
|
jb .less
|
|
; 2d. .FirstSector is greater than or equal to start. Check that .FirstSector
|
|
; is greater than or equal to start+length; the subtraction
|
|
; (.FirstSector-start) - length must not cause overflow. Go to 2g if life is
|
|
; good or to 2f in the other case.
|
|
sub eax, dword [length]
|
|
sbb edx, dword [length+4]
|
|
jb .overlap
|
|
jmp .next_existing
|
|
.less:
|
|
; 2e. .FirstSector is less than start. Check that .FirstSector+.Length is less
|
|
; than or equal to start. If the addition (.FirstSector-start) + .Length does
|
|
; not cause overflow, then .FirstSector + .Length is strictly less than start;
|
|
; since the equality is also valid, use decrement preliminarily. Go to 2g or
|
|
; 2f depending on the overflow.
|
|
sub eax, 1
|
|
sbb edx, 0
|
|
add eax, dword [ecx+PARTITION.Length]
|
|
adc edx, dword [ecx+PARTITION.Length+4]
|
|
jnc .next_existing
|
|
.overlap:
|
|
; 2f. The partition overlaps with previously registered partition. Say warning
|
|
; and return with nothing done.
|
|
dbgstr 'two partitions overlap, ignoring the last one'
|
|
jmp .nothing
|
|
.next_existing:
|
|
; 2g. The partition does not overlap with the current partition. Continue the
|
|
; loop.
|
|
dec ebx
|
|
jnz .scan_existing
|
|
.partitionok:
|
|
; 3. The partition has passed tests. Reallocate the partitions array for a new
|
|
; entry.
|
|
; 3a. Call the allocator.
|
|
mov eax, [esi+DISK.NumPartitions]
|
|
inc eax ; one more entry
|
|
shl eax, 2 ; each entry is dword
|
|
call malloc
|
|
; 3b. Test the result. If failed, return with nothing done.
|
|
test eax, eax
|
|
jz .nothing
|
|
; 3c. Copy the old array to the new array.
|
|
mov edi, eax
|
|
push esi
|
|
mov ecx, [esi+DISK.NumPartitions]
|
|
mov esi, [esi+DISK.Partitions]
|
|
rep movsd
|
|
pop esi
|
|
; 3d. Set the field in the DISK structure to the new array.
|
|
xchg [esi+DISK.Partitions], eax
|
|
; 3e. Free the old array.
|
|
call free
|
|
; 4. Recognize the file system.
|
|
; 4a. Call the filesystem recognizer. It will allocate the PARTITION structure
|
|
; with possible filesystem-specific fields.
|
|
call disk_detect_partition
|
|
; 4b. Check return value. If zero, return with list not changed; so far only
|
|
; the array was reallocated, this is ok for other code.
|
|
test eax, eax
|
|
jz .nothing
|
|
; 5. Insert the new partition to the list.
|
|
stosd
|
|
inc [esi+DISK.NumPartitions]
|
|
; 6. Return.
|
|
.nothing:
|
|
ret
|
|
endp
|
|
|
|
; This is an internal function called from disk_add_partition.
|
|
; It tries to recognize the file system on the partition and allocates the
|
|
; corresponding PARTITION structure with filesystem-specific fields.
|
|
disk_detect_partition:
|
|
; This function inherits the stack frame from disk_add_partition. In stdcall
|
|
; with ebp-based frame arguments start from ebp+8, since [ebp]=saved ebp
|
|
; and [ebp+4]=return address.
|
|
virtual at ebp+8
|
|
.start dq ?
|
|
.length dq ?
|
|
.disk dd ?
|
|
end virtual
|
|
; 1. Read the bootsector to the buffer.
|
|
; When disk_add_partition is called, ebx contains a pointer to
|
|
; a three-sectors-sized buffer. This function saves ebx in the stack
|
|
; immediately before ebp.
|
|
mov ebx, [ebp-4] ; get buffer
|
|
add ebx, [esi+DISK.MediaInfo.SectorSize] ; advance over MBR data to bootsector data
|
|
add ebp, 8 ; ebp points to part of PARTITION structure
|
|
xor eax, eax ; first sector of the partition
|
|
call fs_read32_sys
|
|
; 2. Run tests for all supported filesystems. If at least one test succeeded,
|
|
; go to 4.
|
|
; For tests:
|
|
; ebp -> first three fields of PARTITION structure, .start, .length, .disk;
|
|
; [esp] = error code after bootsector read: 0 = ok, otherwise = failed,
|
|
; ebx points to the buffer for bootsector,
|
|
; ebx+[esi+DISK.MediaInfo.SectorSize] points to sector-sized buffer that can be used for anything.
|
|
|
|
; lock fs list
|
|
|
|
mov ecx, [fs_list]
|
|
@@:
|
|
cmp ecx, fs_list
|
|
jz @f
|
|
|
|
push ecx eax
|
|
call dword[ecx + FileSystem.Creat_part]
|
|
pop ecx
|
|
test eax, eax
|
|
jnz .success
|
|
|
|
mov eax, ecx
|
|
pop ecx
|
|
mov ecx, [ecx]
|
|
jmp @b
|
|
@@:
|
|
push eax
|
|
call fat_create_partition
|
|
test eax, eax
|
|
jnz .success
|
|
call exFAT_create_partition
|
|
test eax, eax
|
|
jnz .success
|
|
call ntfs_create_partition
|
|
test eax, eax
|
|
jnz .success
|
|
call ext2_create_partition
|
|
test eax, eax
|
|
jnz .success
|
|
call xfs_create_partition
|
|
test eax, eax
|
|
jnz .success
|
|
call iso9660_create_partition
|
|
test eax, eax
|
|
jnz .success
|
|
; 3. No file system has recognized the volume, so just allocate the PARTITION
|
|
; structure without extra fields.
|
|
movi eax, sizeof.PARTITION
|
|
call malloc
|
|
test eax, eax
|
|
jz .nothing
|
|
mov edx, dword [ebp+PARTITION.FirstSector]
|
|
mov dword [eax+PARTITION.FirstSector], edx
|
|
mov edx, dword [ebp+PARTITION.FirstSector+4]
|
|
mov dword [eax+PARTITION.FirstSector+4], edx
|
|
mov edx, dword [ebp+PARTITION.Length]
|
|
mov dword [eax+PARTITION.Length], edx
|
|
mov edx, dword [ebp+PARTITION.Length+4]
|
|
mov dword [eax+PARTITION.Length+4], edx
|
|
mov [eax+PARTITION.Disk], esi
|
|
mov [eax+PARTITION.FSUserFunctions], default_fs_functions
|
|
.success:
|
|
.nothing:
|
|
sub ebp, 8 ; restore ebp
|
|
; 4. Return with eax = pointer to PARTITION or NULL.
|
|
pop ecx
|
|
|
|
; unlock fs list
|
|
ret
|
|
|
|
iglobal
|
|
align 4
|
|
default_fs_functions:
|
|
dd free
|
|
dd (default_fs_functions_end - default_fs_functions - 4) / 4
|
|
dd 0
|
|
dd 0
|
|
dd 0
|
|
dd 0
|
|
dd 0
|
|
dd default_fs_get_file_info
|
|
default_fs_functions_end:
|
|
endg
|
|
|
|
proc default_fs_get_file_info uses edi
|
|
movi eax, ERROR_UNSUPPORTED_FS
|
|
cmp byte[esi], 0
|
|
jnz .done
|
|
movi ecx, 40 ; len of BDFE without filename
|
|
cmp [ebx+f70s5arg.xflags], 0
|
|
jz @f
|
|
add ecx, 2 ; volume label requested, space for utf16 terminator
|
|
@@:
|
|
mov ebx, [ebx+f70s5arg.buf]
|
|
stdcall is_region_userspace, ebx, ecx
|
|
movi eax, ERROR_MEMORY_POINTER
|
|
jnz .done
|
|
mov edi, ebx
|
|
xor eax, eax
|
|
rep stosb
|
|
mov [ebx+bdfe.attr], 0x10 ; directory flag
|
|
mov word[ebx+bdfe.name], 0 ; word because of possible utf16
|
|
mov eax, dword[ebp+PARTITION.Length+DQ.lo]
|
|
mov edx, dword[ebp+PARTITION.Length+DQ.hi]
|
|
mov ecx, [ebp+PARTITION.Disk]
|
|
mov ecx, [ecx+DISK.MediaInfo.SectorSize]
|
|
bsf ecx, ecx
|
|
shld edx, eax, cl
|
|
shl eax, cl
|
|
mov [ebx+bdfe.size.lo], eax
|
|
mov [ebx+bdfe.size.hi], edx
|
|
xor eax, eax
|
|
.done:
|
|
ret
|
|
endp
|
|
|
|
; This function is called from file_system_lfn.
|
|
; This handler gets the control each time when fn 70 is called
|
|
; with unknown item of root subdirectory.
|
|
; in: esi = ebp -> path string
|
|
; out: if the handler processes path, it must not return in file_system_lfn,
|
|
; but instead pop return address and return directly to the caller
|
|
; otherwise simply return
|
|
dyndisk_handler:
|
|
; DEBUGF 1, "K : FS Input Path:%s\n",esi
|
|
push ebx edi ; save registers used in file_system_lfn
|
|
; 1. Acquire the mutex.
|
|
mov ecx, disk_list_mutex
|
|
call mutex_lock
|
|
; 2. Loop over the list of DISK structures.
|
|
; 2a. Initialize.
|
|
mov ebx, disk_list
|
|
.scan:
|
|
; 2b. Get the next item.
|
|
mov ebx, [ebx+DISK.Next]
|
|
; 2c. Check whether the list is done. If so, go to 3.
|
|
cmp ebx, disk_list
|
|
jz .notfound
|
|
; 2d. Compare names. If names match, go to 5.
|
|
mov edi, [ebx+DISK.Name]
|
|
push esi
|
|
@@:
|
|
; esi points to the name from fs operation; it is terminated by zero or slash.
|
|
lodsb
|
|
test al, al
|
|
jz .eoin_dec
|
|
cmp al, '/'
|
|
jz .eoin
|
|
; edi points to the disk name.
|
|
inc edi
|
|
; edi points to lowercase name, this is a requirement for the driver.
|
|
; Characters at esi can have any register. Lowercase the current character.
|
|
; This lowercasing works for latin letters and digits; since the disk name
|
|
; should not contain other symbols, this is ok.
|
|
or al, 20h
|
|
cmp al, [edi-1]
|
|
jz @b
|
|
.wrongname:
|
|
; 2f. Names don't match. Continue the loop.
|
|
pop esi
|
|
jmp .scan
|
|
.notfound:
|
|
; The loop is done and no name matches.
|
|
; 3. Release the mutex.
|
|
call mutex_unlock
|
|
; 4. Return normally.
|
|
pop edi ebx ; restore registers used in file_system_lfn
|
|
ret
|
|
; part of 2d: the name matches partially, but we must check that this is full
|
|
; equality.
|
|
.eoin_dec:
|
|
dec esi
|
|
.eoin:
|
|
cmp byte [edi], 0
|
|
jnz .wrongname
|
|
; We found the addressed DISK structure.
|
|
; 5. Reference the disk.
|
|
lock inc [ebx+DISK.RefCount]
|
|
; 6. Now we are sure that the DISK structure is not going to die at least
|
|
; while we are working with it, so release the global mutex.
|
|
call mutex_unlock
|
|
pop ecx ; pop from the stack saved value of esi
|
|
; 7. Acquire the mutex for media object.
|
|
pop edi ; restore edi
|
|
lea ecx, [ebx+DISK.MediaLock]
|
|
call mutex_lock
|
|
; 8. Get the media object. If it is not NULL, reference it.
|
|
xor edx, edx
|
|
cmp [ebx+DISK.MediaInserted], dl
|
|
jz @f
|
|
mov edx, ebx
|
|
inc [ebx+DISK.MediaRefCount]
|
|
@@:
|
|
; 9. Now we are sure that the media object, if it exists, is not going to die
|
|
; at least while we are working with it, so release the mutex for media object.
|
|
call mutex_unlock
|
|
mov ecx, ebx
|
|
pop ebx eax ; restore ebx, pop return address
|
|
; 10. Check whether the fs operation wants to enumerate partitions (go to 11)
|
|
; or work with some concrete partition (go to 12).
|
|
cmp byte [esi], 0
|
|
jnz .haspartition
|
|
; 11. The fs operation wants to enumerate partitions.
|
|
; Check whether the media is inserted.
|
|
mov esi, fs_dyndisk_next_nomedia
|
|
test edx, edx
|
|
jz @f
|
|
mov esi, fs_dyndisk_next
|
|
@@: ; Let the procedure from fs_lfn.inc do the job.
|
|
jmp file_system_lfn.maindir_noesi
|
|
|
|
.root:
|
|
pop ecx edx
|
|
xor eax, eax
|
|
cmp byte [ebx], 9
|
|
jz .cleanup_ecx
|
|
.access_denied:
|
|
movi eax, ERROR_ACCESS_DENIED
|
|
.cleanup_ecx:
|
|
mov [esp+32], eax
|
|
mov esi, ecx ; disk*dereference assume that esi points to DISK
|
|
test edx, edx ; if there are no media, we didn't reference it
|
|
jz @f
|
|
call disk_media_dereference
|
|
@@:
|
|
call disk_dereference
|
|
stdcall kernel_free, ebp
|
|
ret
|
|
|
|
.dyndisk_cleanup:
|
|
pop ecx edx
|
|
movi eax, ERROR_FILE_NOT_FOUND
|
|
jmp .cleanup_ecx
|
|
|
|
.haspartition:
|
|
; 12. The fs operation has specified some partition.
|
|
push edx ecx
|
|
xor eax, eax
|
|
lodsb
|
|
sub eax, '0'
|
|
jz .dyndisk_cleanup
|
|
cmp eax, 10
|
|
jnc .dyndisk_cleanup
|
|
mov ecx, eax
|
|
lodsb
|
|
cmp eax, '/'
|
|
jz @f
|
|
test eax, eax
|
|
jnz .dyndisk_cleanup
|
|
dec esi
|
|
@@:
|
|
cmp byte [esi], 0
|
|
jnz @f
|
|
; partition info
|
|
cmp byte [ebx], 1
|
|
jz @f
|
|
cmp byte [ebx], 5
|
|
jnz .root
|
|
@@:
|
|
dec ecx ; convert to zero-based partition index
|
|
pop edx ; edx = pointer to DISK, dword [esp] = NULL or edx
|
|
; If the driver does not support insert notifications and we are the only fs
|
|
; operation with this disk, ask the driver whether the media
|
|
; was inserted/removed/changed. Otherwise, assume that media status is valid.
|
|
test byte [edx+DISK.DriverFlags], DISK_NO_INSERT_NOTIFICATION
|
|
jz .media_accurate
|
|
|
|
push ecx esi
|
|
mov esi, edx
|
|
cmp dword [esp+8], 0
|
|
jz .test_no_media
|
|
cmp [esi+DISK.MediaRefCount], 2
|
|
jnz .media_accurate_pop
|
|
|
|
lea edx, [esi+DISK.MediaInfo]
|
|
and [edx+DISKMEDIAINFO.Flags], 0
|
|
mov al, DISKFUNC.querymedia
|
|
stdcall disk_call_driver, edx
|
|
test eax, eax
|
|
jz .media_accurate_pop
|
|
|
|
stdcall disk_media_dereference ; drop our reference so that disk_media_changed could close the media
|
|
stdcall disk_media_changed, esi, 0
|
|
and dword [esp+8], 0 ; no media
|
|
;jmp .media_accurate_pop
|
|
.test_no_media:
|
|
stdcall disk_media_changed, esi, 1 ; issue fake notification
|
|
; if querymedia() inside disk_media_changed returns error, the notification is ignored
|
|
cmp [esi+DISK.MediaInserted], 0
|
|
jz .media_accurate_pop
|
|
lock inc [esi+DISK.MediaRefCount]
|
|
mov dword [esp+8], esi
|
|
|
|
.media_accurate_pop:
|
|
mov edx, esi
|
|
pop esi ecx
|
|
|
|
.media_accurate:
|
|
pop eax
|
|
test eax, eax
|
|
jz .nomedia
|
|
cmp ecx, [edx+DISK.NumPartitions]
|
|
jae .notfound2
|
|
mov eax, [edx+DISK.Partitions]
|
|
mov eax, [eax+ecx*4]
|
|
mov edi, [eax+PARTITION.FSUserFunctions]
|
|
mov ecx, [ebx]
|
|
cmp [edi+4], ecx
|
|
jbe .unsupported
|
|
cmp dword[edi+8+ecx*4], 0 ; user function not implemented
|
|
jz .unsupported
|
|
pushd edx ebp eax [edi+8+ecx*4]
|
|
cmp ecx, 10
|
|
jnz .callFS
|
|
or ecx, -1
|
|
mov edi, esi
|
|
xor eax, eax
|
|
repnz scasb
|
|
mov edx, edi
|
|
dec edi
|
|
mov al, '/'
|
|
std
|
|
repnz scasb
|
|
cld
|
|
inc edi
|
|
mov [edi], ah
|
|
mov ebp, [current_slot]
|
|
add ebp, APPDATA.cur_dir
|
|
pushd ebx esi edx [ebp] ebp edi
|
|
sub esi, 2
|
|
mov [ebp], esi
|
|
mov edi, edx
|
|
mov esi, [ebx+16]
|
|
mov eax, [ebx+20]
|
|
cmp eax, 4
|
|
jc @f
|
|
xor eax, eax
|
|
@@:
|
|
call getFullPath
|
|
pop edi ebp
|
|
mov byte [edi], '/'
|
|
popd [ebp] edi esi ebx
|
|
add edi, 2
|
|
test eax, eax
|
|
jz .errorRename
|
|
cmp byte [edi], 0
|
|
jz .errorRename
|
|
.callFS:
|
|
pop eax ebp
|
|
call eax
|
|
pop ebp edx
|
|
mov dword [esp+20], ebx
|
|
.cleanup:
|
|
mov dword [esp+32], eax
|
|
mov esi, edx
|
|
call disk_media_dereference
|
|
@@:
|
|
call disk_dereference
|
|
stdcall kernel_free, ebp
|
|
ret
|
|
|
|
.unsupported:
|
|
movi eax, ERROR_UNKNOWN_FS
|
|
cmp edi, default_fs_functions
|
|
jz .cleanup
|
|
movi eax, ERROR_UNSUPPORTED_FS
|
|
jmp .cleanup
|
|
|
|
.errorRename:
|
|
pop eax eax ebp edx
|
|
.notfound2:
|
|
movi eax, ERROR_FILE_NOT_FOUND
|
|
jmp .cleanup
|
|
|
|
.nomedia:
|
|
test ecx, ecx
|
|
jnz .notfound2
|
|
mov dword [esp+32], ERROR_DEVICE
|
|
mov esi, edx
|
|
jmp @b
|
|
|
|
; This is a callback for enumerating partitions called from
|
|
; file_system_lfn.maindir in the case of inserted media.
|
|
; It just increments eax until DISK.NumPartitions reached and then
|
|
; cleans up.
|
|
fs_dyndisk_next:
|
|
mov ecx, [esp+8]
|
|
cmp eax, [ecx+DISK.NumPartitions]
|
|
jae .nomore
|
|
inc eax
|
|
clc
|
|
ret
|
|
.nomore:
|
|
pusha
|
|
mov esi, ecx
|
|
call disk_media_dereference
|
|
call disk_dereference
|
|
popa
|
|
stc
|
|
ret
|
|
|
|
; This is a callback for enumerating partitions called from
|
|
; file_system_lfn.maindir in the case of missing media.
|
|
; In this case we create one pseudo-partition.
|
|
fs_dyndisk_next_nomedia:
|
|
cmp eax, 1
|
|
jae .nomore
|
|
inc eax
|
|
clc
|
|
ret
|
|
.nomore:
|
|
mov ecx, [esp+8]
|
|
pusha
|
|
mov esi, ecx
|
|
call disk_dereference
|
|
popa
|
|
stc
|
|
ret
|
|
|
|
; This function is called from file_system_lfn.
|
|
; This handler is called when virtual root is enumerated
|
|
; and must return all items which can be handled by this.
|
|
; It is called several times, first time with eax=0
|
|
; in: eax = 0 for first call, previously returned value for subsequent calls
|
|
; out: eax = 0 => no more items
|
|
; eax != 0 => buffer pointed to by edi contains name of item
|
|
dyndisk_enum_root:
|
|
push edx ; save register used in file_system_lfn
|
|
mov ecx, disk_list_mutex ; it will be useful
|
|
; 1. If this is the first call, acquire the mutex and initialize.
|
|
test eax, eax
|
|
jnz .notfirst
|
|
call mutex_lock
|
|
mov eax, disk_list
|
|
.notfirst:
|
|
; 2. Get next item.
|
|
mov eax, [eax+DISK.Next]
|
|
; 3. If there are no more items, go to 6.
|
|
cmp eax, disk_list
|
|
jz .last
|
|
; 4. Copy name from the DISK structure to edi.
|
|
push eax esi
|
|
mov esi, [eax+DISK.Name]
|
|
@@:
|
|
lodsb
|
|
stosb
|
|
test al, al
|
|
jnz @b
|
|
pop esi eax
|
|
; 5. Return with eax = item.
|
|
pop edx ; restore register used in file_system_lfn
|
|
ret
|
|
.last:
|
|
; 6. Release the mutex and return with eax = 0.
|
|
call mutex_unlock
|
|
xor eax, eax
|
|
pop edx ; restore register used in file_system_lfn
|
|
ret
|
|
|
|
; void fastcall eject_disk_media(const char* str, uint32_t code);
|
|
; for load/eject drive
|
|
; ecx -> asciiz string disk name (sd0, usbhd0)
|
|
; edx = code command(0 - LoadTray 1-EjectTray)
|
|
eject_disk_media:
|
|
push esi edx ecx
|
|
|
|
mov ecx, disk_list_mutex
|
|
call mutex_lock
|
|
; find disk ejected
|
|
mov edx, disk_list
|
|
.next:
|
|
mov edx, [edx]
|
|
cmp edx, disk_list
|
|
jz .nf
|
|
|
|
mov ecx, [edx + DISK.Name]
|
|
mov esi, [esp]
|
|
@@:
|
|
lodsb
|
|
test al, al
|
|
jz @f
|
|
|
|
or al, 0x20
|
|
cmp al, [ecx]
|
|
jnz .next
|
|
inc ecx
|
|
jmp @b
|
|
@@:
|
|
cmp byte[ecx], al
|
|
jnz .next
|
|
|
|
mov ecx, disk_list_mutex
|
|
call mutex_unlock
|
|
; found disk
|
|
pop ecx edx esi
|
|
|
|
and edx, 1b
|
|
mov al, DISKFUNC.LoadTray
|
|
stdcall disk_call_driver, edx ; ecx - code command
|
|
ret
|
|
|
|
.nf:
|
|
mov ecx, disk_list_mutex
|
|
call mutex_unlock
|
|
pop ecx edx esi
|
|
ret
|
|
|
|
; for disk emulation PARTITION structure
|
|
; int32_t find_part(char* esi) esi -> name disk and partition
|
|
; return number partition or zero for disk and
|
|
;find_part:
|
|
; mov ecx, disk_list_mutex
|
|
;
|
|
; ret
|