forked from KolibriOS/kolibrios
Magomed Kostoev (mkostoevr)
360e379fc7
git-svn-id: svn://kolibrios.org@9047 a494cfbc-eb01-0410-851d-a64ba20cac60
1667 lines
64 KiB
PHP
1667 lines
64 KiB
PHP
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; ;;
|
|
;; Copyright (C) KolibriOS team 2011-2015. All rights reserved. ;;
|
|
;; Distributed under terms of the GNU General Public License ;;
|
|
;; ;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
$Revision$
|
|
|
|
; =============================================================================
|
|
; ================================= 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.
|
|
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.
|
|
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.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 .mbr_failed
|
|
; 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
|
|
push eax
|
|
; 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.
|
|
call fat_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
|
|
; 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
|
|
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:
|
|
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
|
|
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
|
|
.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
|