/* * win32_io.c - A stdio-like disk I/O implementation for low-level disk access * on Win32. Can access an NTFS volume while it is mounted. * Originated from the Linux-NTFS project. * * Copyright (c) 2003-2004 Lode Leroy * Copyright (c) 2003-2006 Anton Altaparmakov * Copyright (c) 2004-2005 Yuval Fledel * Copyright (c) 2012-2014 Jean-Pierre Andre * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program/include file is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (in the main directory of the NTFS-3G * distribution in the file COPYING); if not, write to the Free Software * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #ifdef HAVE_WINDOWS_H #define BOOL WINBOOL /* avoid conflicting definitions of BOOL */ #include #undef BOOL #endif #ifdef HAVE_STDLIB_H #include #endif /* * Definitions needed for */ #ifndef _ANONYMOUS_UNION #define _ANONYMOUS_UNION #define _ANONYMOUS_STRUCT typedef unsigned long long DWORD64; #endif typedef struct { DWORD data1; /* The first eight hexadecimal digits of the GUID. */ WORD data2; /* The first group of four hexadecimal digits. */ WORD data3; /* The second group of four hexadecimal digits. */ char data4[8]; /* The first two bytes are the third group of four hexadecimal digits. The remaining six bytes are the final 12 hexadecimal digits. */ } GUID; #include #ifdef HAVE_STDIO_H #include #endif #ifdef HAVE_CTYPE_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_SYS_STAT_H #include #define stat stat64 #define st_blocks st_rdev /* emulate st_blocks, missing in Windows */ #endif /* Prevent volume.h from being be loaded, as it conflicts with winnt.h. */ #define _NTFS_VOLUME_H struct ntfs_volume; typedef struct ntfs_volume ntfs_volume; #include "debug.h" #include "types.h" #include "device.h" #include "misc.h" #define cpu_to_le16(x) (x) #define const_cpu_to_le16(x) (x) #ifndef MAX_PATH #define MAX_PATH 1024 #endif #ifndef NTFS_BLOCK_SIZE #define NTFS_BLOCK_SIZE 512 #define NTFS_BLOCK_SIZE_BITS 9 #endif #ifndef INVALID_SET_FILE_POINTER #define INVALID_SET_FILE_POINTER ((DWORD)-1) #endif #ifndef IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS #define IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS 5636096 #endif #ifndef IOCTL_DISK_GET_DRIVE_GEOMETRY #define IOCTL_DISK_GET_DRIVE_GEOMETRY 0x70000 #endif #ifndef IOCTL_GET_DISK_LENGTH_INFO #define IOCTL_GET_DISK_LENGTH_INFO 0x7405c #endif #ifndef FSCTL_ALLOW_EXTENDED_DASD_IO #define FSCTL_ALLOW_EXTENDED_DASD_IO 0x90083 #endif /* Windows 2k+ imports. */ typedef HANDLE (WINAPI *LPFN_FINDFIRSTVOLUME)(LPTSTR, DWORD); typedef BOOL (WINAPI *LPFN_FINDNEXTVOLUME)(HANDLE, LPTSTR, DWORD); typedef BOOL (WINAPI *LPFN_FINDVOLUMECLOSE)(HANDLE); typedef BOOL (WINAPI *LPFN_SETFILEPOINTEREX)(HANDLE, LARGE_INTEGER, PLARGE_INTEGER, DWORD); static LPFN_FINDFIRSTVOLUME fnFindFirstVolume = NULL; static LPFN_FINDNEXTVOLUME fnFindNextVolume = NULL; static LPFN_FINDVOLUMECLOSE fnFindVolumeClose = NULL; static LPFN_SETFILEPOINTEREX fnSetFilePointerEx = NULL; #ifdef UNICODE #define FNPOSTFIX "W" #else #define FNPOSTFIX "A" #endif enum { /* see http://msdn.microsoft.com/en-us/library/cc704588(v=prot.10).aspx */ STATUS_UNKNOWN = -1, STATUS_SUCCESS = 0x00000000, STATUS_BUFFER_OVERFLOW = 0x80000005, STATUS_INVALID_HANDLE = 0xC0000008, STATUS_INVALID_PARAMETER = 0xC000000D, STATUS_INVALID_DEVICE_REQUEST = 0xC0000010, STATUS_END_OF_FILE = 0xC0000011, STATUS_CONFLICTING_ADDRESSES = 0xC0000018, STATUS_NO_MATCH = 0xC000001E, STATUS_ACCESS_DENIED = 0xC0000022, STATUS_BUFFER_TOO_SMALL = 0xC0000023, STATUS_OBJECT_TYPE_MISMATCH = 0xC0000024, STATUS_FILE_NOT_FOUND = 0xC0000028, STATUS_OBJECT_NAME_INVALID = 0xC0000033, STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034, STATUS_SHARING_VIOLATION = 0xC0000043, STATUS_INVALID_PARAMETER_1 = 0xC00000EF, STATUS_IO_DEVICE_ERROR = 0xC0000185, STATUS_GUARD_PAGE_VIOLATION = 0x80000001 } ; typedef u32 NTSTATUS; /* do not let the compiler choose the size */ #ifdef __x86_64__ typedef unsigned long long ULONG_PTR; /* an integer the same size as a pointer */ #else typedef unsigned long ULONG_PTR; /* an integer the same size as a pointer */ #endif HANDLE get_osfhandle(int); /* from msvcrt.dll */ /* * A few needed definitions not included in */ typedef struct _IO_STATUS_BLOCK { union { NTSTATUS Status; PVOID Pointer; }; ULONG_PTR Information; } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; #ifdef __x86_64__ u32 padding; #endif PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING; typedef struct _OBJECT_ATTRIBUTES { ULONG Length; #ifdef __x86_64__ u32 padding1; HANDLE RootDirectory; PUNICODE_STRING ObjectName; ULONG Attributes; u32 padding2; #else HANDLE RootDirectory; PUNICODE_STRING ObjectName; ULONG Attributes; #endif PVOID SecurityDescriptor; PVOID SecurityQualityOfService; } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; #define FILE_OPEN 1 #define FILE_CREATE 2 #define FILE_OVERWRITE 4 #define FILE_SYNCHRONOUS_IO_ALERT 0x10 #define FILE_SYNCHRONOUS_IO_NONALERT 0x20 #define OBJ_CASE_INSENSITIVE 0x40 typedef void (WINAPI *PIO_APC_ROUTINE)(void*, PIO_STATUS_BLOCK, ULONG); extern WINAPI NTSTATUS NtOpenFile( PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, ULONG ShareAccess, ULONG OpenOptions ); extern WINAPI NTSTATUS NtReadFile( HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key ); extern WINAPI NTSTATUS NtWriteFile( HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, LPCVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key ); extern NTSTATUS WINAPI NtClose( HANDLE Handle ); extern NTSTATUS WINAPI NtDeviceIoControlFile( HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, ULONG IoControlCode, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength ); extern NTSTATUS WINAPI NtFsControlFile( HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, ULONG FsControlCode, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength ); /** * struct win32_fd - */ typedef struct { HANDLE handle; s64 pos; /* Logical current position on the volume. */ s64 part_start; s64 part_length; int part_hidden_sectors; s64 geo_size, geo_cylinders; s32 geo_sector_size; s64 volume_size; DWORD geo_sectors, geo_heads; HANDLE vol_handle; BOOL ntdll; } win32_fd; /** * ntfs_w32error_to_errno - convert a win32 error code to the unix one * @w32error: the win32 error code * * Limited to a relatively small but useful number of codes. */ static int ntfs_w32error_to_errno(unsigned int w32error) { ntfs_log_trace("Converting w32error 0x%x.\n",w32error); switch (w32error) { case ERROR_INVALID_FUNCTION: return EBADRQC; case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: case ERROR_INVALID_NAME: return ENOENT; case ERROR_TOO_MANY_OPEN_FILES: return EMFILE; case ERROR_ACCESS_DENIED: return EACCES; case ERROR_INVALID_HANDLE: return EBADF; case ERROR_NOT_ENOUGH_MEMORY: return ENOMEM; case ERROR_OUTOFMEMORY: return ENOSPC; case ERROR_INVALID_DRIVE: case ERROR_BAD_UNIT: return ENODEV; case ERROR_WRITE_PROTECT: return EROFS; case ERROR_NOT_READY: case ERROR_SHARING_VIOLATION: return EBUSY; case ERROR_BAD_COMMAND: return EINVAL; case ERROR_SEEK: case ERROR_NEGATIVE_SEEK: return ESPIPE; case ERROR_NOT_SUPPORTED: return EOPNOTSUPP; case ERROR_BAD_NETPATH: return ENOSHARE; default: /* generic message */ return ENOMSG; } } static int ntfs_ntstatus_to_errno(NTSTATUS status) { ntfs_log_trace("Converting w32error 0x%x.\n",w32error); switch (status) { case STATUS_INVALID_HANDLE : case STATUS_INVALID_PARAMETER : case STATUS_OBJECT_NAME_INVALID : case STATUS_INVALID_DEVICE_REQUEST : return (EINVAL); case STATUS_ACCESS_DENIED : return (EACCES); case STATUS_IO_DEVICE_ERROR : case STATUS_END_OF_FILE : return (EIO); case STATUS_SHARING_VIOLATION : return (EBUSY); default: /* generic message */ return ENOMSG; } } /** * libntfs_SetFilePointerEx - emulation for SetFilePointerEx() * * We use this to emulate SetFilePointerEx() when it is not present. This can * happen since SetFilePointerEx() only exists in Win2k+. */ static BOOL WINAPI libntfs_SetFilePointerEx(HANDLE hFile, LARGE_INTEGER liDistanceToMove, PLARGE_INTEGER lpNewFilePointer, DWORD dwMoveMethod) { liDistanceToMove.u.LowPart = SetFilePointer(hFile, liDistanceToMove.u.LowPart, &liDistanceToMove.u.HighPart, dwMoveMethod); SetLastError(NO_ERROR); if (liDistanceToMove.u.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) { if (lpNewFilePointer) lpNewFilePointer->QuadPart = -1; return FALSE; } if (lpNewFilePointer) lpNewFilePointer->QuadPart = liDistanceToMove.QuadPart; return TRUE; } /** * ntfs_device_win32_init_imports - initialize the function pointers * * The Find*Volume and SetFilePointerEx functions exist only on win2k+, as such * we cannot just staticly import them. * * This function initializes the imports if the functions do exist and in the * SetFilePointerEx case, we emulate the function ourselves if it is not * present. * * Note: The values are cached, do be afraid to run it more than once. */ static void ntfs_device_win32_init_imports(void) { HMODULE kernel32 = GetModuleHandle("kernel32"); if (!kernel32) { errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("kernel32.dll could not be imported.\n"); } if (!fnSetFilePointerEx) { if (kernel32) fnSetFilePointerEx = (LPFN_SETFILEPOINTEREX) GetProcAddress(kernel32, "SetFilePointerEx"); /* * If we did not get kernel32.dll or it is not Win2k+, emulate * SetFilePointerEx(). */ if (!fnSetFilePointerEx) { ntfs_log_debug("SetFilePointerEx() not found in " "kernel32.dll: Enabling emulation.\n"); fnSetFilePointerEx = libntfs_SetFilePointerEx; } } /* Cannot do lookups if we could not get kernel32.dll... */ if (!kernel32) return; if (!fnFindFirstVolume) fnFindFirstVolume = (LPFN_FINDFIRSTVOLUME) GetProcAddress(kernel32, "FindFirstVolume" FNPOSTFIX); if (!fnFindNextVolume) fnFindNextVolume = (LPFN_FINDNEXTVOLUME) GetProcAddress(kernel32, "FindNextVolume" FNPOSTFIX); if (!fnFindVolumeClose) fnFindVolumeClose = (LPFN_FINDVOLUMECLOSE) GetProcAddress(kernel32, "FindVolumeClose"); } /** * ntfs_device_unix_status_flags_to_win32 - convert unix->win32 open flags * @flags: unix open status flags * * Supported flags are O_RDONLY, O_WRONLY and O_RDWR. */ static __inline__ int ntfs_device_unix_status_flags_to_win32(int flags) { int win_mode; switch (flags & O_ACCMODE) { case O_RDONLY: win_mode = GENERIC_READ; break; case O_WRONLY: win_mode = GENERIC_WRITE; break; case O_RDWR: win_mode = GENERIC_READ | GENERIC_WRITE; break; default: /* error */ ntfs_log_trace("Unknown status flags.\n"); win_mode = 0; } return win_mode; } /** * ntfs_device_win32_simple_open_file - just open a file via win32 API * @filename: name of the file to open * @handle: pointer the a HANDLE in which to put the result * @flags: unix open status flags * @locking: will the function gain an exclusive lock on the file? * * Supported flags are O_RDONLY, O_WRONLY and O_RDWR. * * Return 0 if o.k. * -1 if not, and errno set. In this case handle is trashed. */ static int ntfs_device_win32_simple_open_file(const char *filename, HANDLE *handle, int flags, BOOL locking) { *handle = CreateFile(filename, ntfs_device_unix_status_flags_to_win32(flags), locking ? 0 : (FILE_SHARE_WRITE | FILE_SHARE_READ), NULL, (flags & O_CREAT ? OPEN_ALWAYS : OPEN_EXISTING), 0, NULL); if (*handle == INVALID_HANDLE_VALUE) { errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("CreateFile(%s) failed.\n", filename); return -1; } return 0; } /** * ntfs_device_win32_lock - lock the volume * @handle: a win32 HANDLE for a volume to lock * * Locking a volume means no one can access its contents. * Exiting the process automatically unlocks the volume, except in old NT4s. * * Return 0 if o.k. * -1 if not, and errno set. */ static int ntfs_device_win32_lock(HANDLE handle) { DWORD i; if (!DeviceIoControl(handle, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &i, NULL)) { errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("Couldn't lock volume.\n"); return -1; } ntfs_log_debug("Volume locked.\n"); return 0; } /** * ntfs_device_win32_unlock - unlock the volume * @handle: the win32 HANDLE which the volume was locked with * * Return 0 if o.k. * -1 if not, and errno set. */ static int ntfs_device_win32_unlock(HANDLE handle) { DWORD i; if (!DeviceIoControl(handle, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &i, NULL)) { errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("Couldn't unlock volume.\n"); return -1; } ntfs_log_debug("Volume unlocked.\n"); return 0; } static int ntfs_device_win32_setlock(HANDLE handle, ULONG code) { IO_STATUS_BLOCK io_status; NTSTATUS res; io_status.Status = STATUS_SUCCESS; io_status.Information = 0; res = NtFsControlFile(handle,(HANDLE)NULL, (PIO_APC_ROUTINE)NULL,(void*)NULL, &io_status, code, (char*)NULL,0,(char*)NULL,0); if (res != STATUS_SUCCESS) errno = ntfs_ntstatus_to_errno(res); return (res == STATUS_SUCCESS ? 0 : -1); } /** * ntfs_device_win32_dismount - dismount a volume * @handle: a win32 HANDLE for a volume to dismount * * Dismounting means the system will refresh the volume in the first change it * gets. Usefull after altering the file structures. * The volume must be locked by the current process while dismounting. * A side effect is that the volume is also unlocked, but you must not rely om * this. * * Return 0 if o.k. * -1 if not, and errno set. */ static int ntfs_device_win32_dismount(HANDLE handle) { DWORD i; if (!DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &i, NULL)) { errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("Couldn't dismount volume.\n"); return -1; } ntfs_log_debug("Volume dismounted.\n"); return 0; } /** * ntfs_device_win32_getsize - get file size via win32 API * @handle: pointer the file HANDLE obtained via open * * Only works on ordinary files. * * Return The file size if o.k. * -1 if not, and errno set. */ static s64 ntfs_device_win32_getsize(HANDLE handle) { LONG loword, hiword; SetLastError(NO_ERROR); hiword = 0; loword = SetFilePointer(handle, 0, &hiword, 2); if ((loword == INVALID_SET_FILE_POINTER) && (GetLastError() != NO_ERROR)) { errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("Couldn't get file size.\n"); return -1; } return ((s64)hiword << 32) + (ULONG)loword; } /** * ntfs_device_win32_getdisklength - get disk size via win32 API * @handle: pointer the file HANDLE obtained via open * @argp: pointer to result buffer * * Only works on PhysicalDriveX type handles. * * Return The disk size if o.k. * -1 if not, and errno set. */ static s64 ntfs_device_win32_getdisklength(HANDLE handle) { GET_LENGTH_INFORMATION buf; DWORD i; if (!DeviceIoControl(handle, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &buf, sizeof(buf), &i, NULL)) { errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("Couldn't get disk length.\n"); return -1; } ntfs_log_debug("Disk length: %lld.\n", buf.Length.QuadPart); return buf.Length.QuadPart; } /** * ntfs_device_win32_getntfssize - get NTFS volume size via win32 API * @handle: pointer the file HANDLE obtained via open * @argp: pointer to result buffer * * Only works on NTFS volume handles. * An annoying bug in windows is that an NTFS volume does not occupy the entire * partition, namely not the last sector (which holds the backup boot sector, * and normally not interesting). * Use this function to get the length of the accessible space through a given * volume handle. * * Return The volume size if o.k. * -1 if not, and errno set. */ static s64 ntfs_device_win32_getntfssize(HANDLE handle) { s64 rvl; #ifdef FSCTL_GET_NTFS_VOLUME_DATA DWORD i; NTFS_VOLUME_DATA_BUFFER buf; if (!DeviceIoControl(handle, FSCTL_GET_NTFS_VOLUME_DATA, NULL, 0, &buf, sizeof(buf), &i, NULL)) { errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("Couldn't get NTFS volume length.\n"); return -1; } rvl = buf.NumberSectors.QuadPart * buf.BytesPerSector; ntfs_log_debug("NTFS volume length: 0x%llx.\n", (long long)rvl); #else errno = EINVAL; rvl = -1; #endif return rvl; } /** * ntfs_device_win32_getgeo - get CHS information of a drive * @handle: an open handle to the PhysicalDevice * @fd: a win_fd structure that will be filled * * Return 0 if o.k. * -1 if not, and errno set. * * In Windows NT+: fills size, sectors, and cylinders and sets heads to -1. * In Windows XP+: fills size, sectors, cylinders, and heads. * * Note: In pre XP, this requires write permission, even though nothing is * actually written. * * If fails, sets sectors, cylinders, heads, and size to -1. */ static int ntfs_device_win32_getgeo(HANDLE handle, win32_fd *fd) { DWORD i; BOOL rvl; BYTE b[sizeof(DISK_GEOMETRY) + sizeof(DISK_PARTITION_INFO) + sizeof(DISK_DETECTION_INFO) + 512]; rvl = DeviceIoControl(handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, NULL, 0, &b, sizeof(b), &i, NULL); if (rvl) { ntfs_log_debug("GET_DRIVE_GEOMETRY_EX detected.\n"); DISK_DETECTION_INFO *ddi = (PDISK_DETECTION_INFO) (((PBYTE)(&((PDISK_GEOMETRY_EX)b)->Data)) + (((PDISK_PARTITION_INFO) (&((PDISK_GEOMETRY_EX)b)->Data))-> SizeOfPartitionInfo)); fd->geo_cylinders = ((DISK_GEOMETRY*)&b)->Cylinders.QuadPart; fd->geo_sectors = ((DISK_GEOMETRY*)&b)->SectorsPerTrack; fd->geo_size = ((DISK_GEOMETRY_EX*)&b)->DiskSize.QuadPart; fd->geo_sector_size = NTFS_BLOCK_SIZE; switch (ddi->DetectionType) { case DetectInt13: fd->geo_cylinders = ddi->Int13.MaxCylinders; fd->geo_sectors = ddi->Int13.SectorsPerTrack; fd->geo_heads = ddi->Int13.MaxHeads; return 0; case DetectExInt13: fd->geo_cylinders = ddi->ExInt13.ExCylinders; fd->geo_sectors = ddi->ExInt13.ExSectorsPerTrack; fd->geo_heads = ddi->ExInt13.ExHeads; return 0; case DetectNone: default: break; } } else fd->geo_heads = -1; rvl = DeviceIoControl(handle, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &b, sizeof(b), &i, NULL); if (rvl) { ntfs_log_debug("GET_DRIVE_GEOMETRY detected.\n"); fd->geo_cylinders = ((DISK_GEOMETRY*)&b)->Cylinders.QuadPart; fd->geo_sectors = ((DISK_GEOMETRY*)&b)->SectorsPerTrack; fd->geo_size = fd->geo_cylinders * fd->geo_sectors * ((DISK_GEOMETRY*)&b)->TracksPerCylinder * ((DISK_GEOMETRY*)&b)->BytesPerSector; fd->geo_sector_size = ((DISK_GEOMETRY*)&b)->BytesPerSector; return 0; } errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("Couldn't retrieve disk geometry.\n"); fd->geo_cylinders = -1; fd->geo_sectors = -1; fd->geo_size = -1; fd->geo_sector_size = NTFS_BLOCK_SIZE; return -1; } static int ntfs_device_win32_getntgeo(HANDLE handle, win32_fd *fd) { DISK_GEOMETRY geo; NTSTATUS st; IO_STATUS_BLOCK status; u64 bytes; int res; res = -1; fd->geo_cylinders = 0; fd->geo_sectors = 0; fd->geo_size = 1073741824; fd->geo_sectors = fd->geo_size >> 9; fd->geo_sector_size = NTFS_BLOCK_SIZE; st = NtDeviceIoControlFile(handle, (HANDLE)NULL, (PIO_APC_ROUTINE)NULL, (void*)NULL, &status, IOCTL_DISK_GET_DRIVE_GEOMETRY, (void*)NULL, 0, (void*)&geo, sizeof(geo)); if (st == STATUS_SUCCESS) { /* over-estimate the (rounded) number of cylinders */ fd->geo_cylinders = geo.Cylinders.QuadPart + 1; fd->geo_sectors = fd->geo_cylinders *geo.TracksPerCylinder*geo.SectorsPerTrack; fd->geo_size = fd->geo_sectors*geo.BytesPerSector; fd->geo_sector_size = geo.BytesPerSector; res = 0; /* try to get the exact sector count */ st = NtDeviceIoControlFile(handle, (HANDLE)NULL, (PIO_APC_ROUTINE)NULL, (void*)NULL, &status, IOCTL_GET_DISK_LENGTH_INFO, (void*)NULL, 0, (void*)&bytes, sizeof(bytes)); if (st == STATUS_SUCCESS) { fd->geo_size = bytes; fd->geo_sectors = bytes/geo.BytesPerSector; } } return (res); } /** * ntfs_device_win32_open_file - open a file via win32 API * @filename: name of the file to open * @fd: pointer to win32 file device in which to put the result * @flags: unix open status flags * * Return 0 if o.k. * -1 if not, and errno set. */ static __inline__ int ntfs_device_win32_open_file(char *filename, win32_fd *fd, int flags) { HANDLE handle; int mode; if (ntfs_device_win32_simple_open_file(filename, &handle, flags, FALSE)) { /* open error */ return -1; } mode = flags & O_ACCMODE; if ((mode == O_RDWR) || (mode == O_WRONLY)) { DWORD bytes; /* try making sparse (but ignore errors) */ DeviceIoControl(handle, FSCTL_SET_SPARSE, (void*)NULL, 0, (void*)NULL, 0, &bytes, (LPOVERLAPPED)NULL); } /* fill fd */ fd->handle = handle; fd->part_start = 0; fd->part_length = ntfs_device_win32_getsize(handle); fd->pos = 0; fd->part_hidden_sectors = -1; fd->geo_size = -1; /* used as a marker that this is a file */ fd->vol_handle = INVALID_HANDLE_VALUE; fd->geo_sector_size = 512; /* will be adjusted from the boot sector */ fd->ntdll = FALSE; return 0; } /** * ntfs_device_win32_open_drive - open a drive via win32 API * @drive_id: drive to open * @fd: pointer to win32 file device in which to put the result * @flags: unix open status flags * * return 0 if o.k. * -1 if not, and errno set. */ static __inline__ int ntfs_device_win32_open_drive(int drive_id, win32_fd *fd, int flags) { HANDLE handle; int err; char filename[MAX_PATH]; sprintf(filename, "\\\\.\\PhysicalDrive%d", drive_id); if ((err = ntfs_device_win32_simple_open_file(filename, &handle, flags, TRUE))) { /* open error */ return err; } /* store the drive geometry */ ntfs_device_win32_getgeo(handle, fd); /* Just to be sure */ if (fd->geo_size == -1) fd->geo_size = ntfs_device_win32_getdisklength(handle); /* fill fd */ fd->ntdll = FALSE; fd->handle = handle; fd->part_start = 0; fd->part_length = fd->geo_size; fd->pos = 0; fd->part_hidden_sectors = -1; fd->vol_handle = INVALID_HANDLE_VALUE; return 0; } /** * ntfs_device_win32_open_lowlevel - open a drive via low level win32 API * @drive_id: drive to open * @fd: pointer to win32 file device in which to put the result * @flags: unix open status flags * * return 0 if o.k. * -1 if not, and errno set. */ static __inline__ int ntfs_device_win32_open_lowlevel(int drive_id, win32_fd *fd, int flags) { HANDLE handle; NTSTATUS st; ACCESS_MASK access; ULONG share; OBJECT_ATTRIBUTES attr; IO_STATUS_BLOCK io_status; UNICODE_STRING unicode_name; ntfschar unicode_buffer[7]; int mode; static const ntfschar unicode_init[] = { const_cpu_to_le16('\\'), const_cpu_to_le16('?'), const_cpu_to_le16('?'), const_cpu_to_le16('\\'), const_cpu_to_le16(' '), const_cpu_to_le16(':'), const_cpu_to_le16(0) }; memcpy(unicode_buffer, unicode_init, sizeof(unicode_buffer)); unicode_buffer[4] = cpu_to_le16(drive_id + 'A'); unicode_name.Buffer = unicode_buffer; unicode_name.Length = 6*sizeof(ntfschar); unicode_name.MaximumLength = 6*sizeof(ntfschar); attr.Length = sizeof(OBJECT_ATTRIBUTES); attr.RootDirectory = (HANDLE*)NULL; attr.ObjectName = &unicode_name; attr.Attributes = OBJ_CASE_INSENSITIVE; attr.SecurityDescriptor = (void*)NULL; attr.SecurityQualityOfService = (void*)NULL; io_status.Status = 0; io_status.Information = 0; mode = flags & O_ACCMODE; share = (mode == O_RDWR ? 0 : FILE_SHARE_READ | FILE_SHARE_WRITE); access = (mode == O_RDWR ? FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE : FILE_READ_DATA | SYNCHRONIZE); st = NtOpenFile(&handle, access, &attr, &io_status, share, FILE_SYNCHRONOUS_IO_ALERT); if (st != STATUS_SUCCESS) { errno = ntfs_ntstatus_to_errno(st); return (-1); } ntfs_device_win32_setlock(handle,FSCTL_LOCK_VOLUME); /* store the drive geometry */ ntfs_device_win32_getntgeo(handle, fd); fd->ntdll = TRUE; /* allow accessing the full partition */ st = NtFsControlFile(handle, (HANDLE)NULL, (PIO_APC_ROUTINE)NULL, (PVOID)NULL, &io_status, FSCTL_ALLOW_EXTENDED_DASD_IO, NULL, 0, NULL, 0); if (st != STATUS_SUCCESS) { errno = ntfs_ntstatus_to_errno(st); NtClose(handle); return (-1); } /* fill fd */ fd->handle = handle; fd->part_start = 0; fd->part_length = fd->geo_size; fd->pos = 0; fd->part_hidden_sectors = -1; fd->vol_handle = INVALID_HANDLE_VALUE; return 0; } /** * ntfs_device_win32_open_volume_for_partition - find and open a volume * * Windows NT/2k/XP handles volumes instead of partitions. * This function gets the partition details and return an open volume handle. * That volume is the one whose only physical location on disk is the described * partition. * * The function required Windows 2k/XP, otherwise it fails (gracefully). * * Return success: a valid open volume handle. * fail : INVALID_HANDLE_VALUE */ static HANDLE ntfs_device_win32_open_volume_for_partition(unsigned int drive_id, s64 part_offset, s64 part_length, int flags) { HANDLE vol_find_handle; TCHAR vol_name[MAX_PATH]; /* Make sure all the required imports exist. */ if (!fnFindFirstVolume || !fnFindNextVolume || !fnFindVolumeClose) { ntfs_log_trace("Required dll imports not found.\n"); return INVALID_HANDLE_VALUE; } /* Start iterating through volumes. */ ntfs_log_trace("Entering with drive_id=%d, part_offset=%lld, " "path_length=%lld, flags=%d.\n", drive_id, (unsigned long long)part_offset, (unsigned long long)part_length, flags); vol_find_handle = fnFindFirstVolume(vol_name, MAX_PATH); /* If a valid handle could not be aquired, reply with "don't know". */ if (vol_find_handle == INVALID_HANDLE_VALUE) { ntfs_log_trace("FindFirstVolume failed.\n"); return INVALID_HANDLE_VALUE; } do { int vol_name_length; HANDLE handle; /* remove trailing '/' from vol_name */ #ifdef UNICODE vol_name_length = wcslen(vol_name); #else vol_name_length = strlen(vol_name); #endif if (vol_name_length>0) vol_name[vol_name_length-1]=0; ntfs_log_debug("Processing %s.\n", vol_name); /* open the file */ handle = CreateFile(vol_name, ntfs_device_unix_status_flags_to_win32(flags), FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (handle != INVALID_HANDLE_VALUE) { DWORD bytesReturned; #define EXTENTS_SIZE sizeof(VOLUME_DISK_EXTENTS) + 9 * sizeof(DISK_EXTENT) char extents[EXTENTS_SIZE]; /* Check physical locations. */ if (DeviceIoControl(handle, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0, extents, EXTENTS_SIZE, &bytesReturned, NULL)) { if (((VOLUME_DISK_EXTENTS *)extents)-> NumberOfDiskExtents == 1) { DISK_EXTENT *extent = &(( VOLUME_DISK_EXTENTS *) extents)->Extents[0]; if ((extent->DiskNumber==drive_id) && (extent->StartingOffset. QuadPart==part_offset) && (extent-> ExtentLength.QuadPart == part_length)) { /* * Eureka! (Archimedes, 287 BC, * "I have found it!") */ fnFindVolumeClose( vol_find_handle); return handle; } } } } else ntfs_log_trace("getExtents() Failed.\n"); } while (fnFindNextVolume(vol_find_handle, vol_name, MAX_PATH)); /* End of iteration through volumes. */ ntfs_log_trace("Closing, volume was not found.\n"); fnFindVolumeClose(vol_find_handle); return INVALID_HANDLE_VALUE; } /** * ntfs_device_win32_find_partition - locates partition details by id. * @handle: HANDLE to the PhysicalDrive * @partition_id: the partition number to locate * @part_offset: pointer to where to put the offset to the partition * @part_length: pointer to where to put the length of the partition * @hidden_sectors: pointer to where to put the hidden sectors * * This function requires an open PhysicalDrive handle and a partition_id. * If a partition with the required id is found on the supplied device, * the partition attributes are returned back. * * Returns: TRUE if found, and sets the output parameters. * FALSE if not and errno is set to the error code. */ static BOOL ntfs_device_win32_find_partition(HANDLE handle, DWORD partition_id, s64 *part_offset, s64 *part_length, int *hidden_sectors) { DRIVE_LAYOUT_INFORMATION *drive_layout; unsigned int err, buf_size, part_count; DWORD i; /* * There is no way to know the required buffer, so if the ioctl fails, * try doubling the buffer size each time until the ioctl succeeds. */ part_count = 8; do { buf_size = sizeof(DRIVE_LAYOUT_INFORMATION) + part_count * sizeof(PARTITION_INFORMATION); drive_layout = (DRIVE_LAYOUT_INFORMATION*)ntfs_malloc(buf_size); if (!drive_layout) { errno = ENOMEM; return FALSE; } if (DeviceIoControl(handle, IOCTL_DISK_GET_DRIVE_LAYOUT, NULL, 0, (BYTE*)drive_layout, buf_size, &i, NULL)) break; err = GetLastError(); free(drive_layout); if (err != ERROR_INSUFFICIENT_BUFFER) { ntfs_log_trace("GetDriveLayout failed.\n"); errno = ntfs_w32error_to_errno(err); return FALSE; } ntfs_log_debug("More than %u partitions.\n", part_count); part_count <<= 1; if (part_count > 512) { ntfs_log_trace("GetDriveLayout failed: More than 512 " "partitions?\n"); errno = ENOBUFS; return FALSE; } } while (1); for (i = 0; i < drive_layout->PartitionCount; i++) { if (drive_layout->PartitionEntry[i].PartitionNumber == partition_id) { *part_offset = drive_layout->PartitionEntry[i]. StartingOffset.QuadPart; *part_length = drive_layout->PartitionEntry[i]. PartitionLength.QuadPart; *hidden_sectors = drive_layout->PartitionEntry[i]. HiddenSectors; free(drive_layout); return TRUE; } } free(drive_layout); errno = ENOENT; return FALSE; } /** * ntfs_device_win32_open_partition - open a partition via win32 API * @drive_id: drive to open * @partition_id: partition to open * @fd: win32 file device to return * @flags: unix open status flags * * Return 0 if o.k. * -1 if not, and errno set. * * When fails, fd contents may have not been preserved. */ static int ntfs_device_win32_open_partition(int drive_id, unsigned int partition_id, win32_fd *fd, int flags) { s64 part_start, part_length; HANDLE handle; int err, hidden_sectors; char drive_name[MAX_PATH]; sprintf(drive_name, "\\\\.\\PhysicalDrive%d", drive_id); /* Open the entire device without locking, ask questions later */ if ((err = ntfs_device_win32_simple_open_file(drive_name, &handle, flags, FALSE))) { /* error */ return err; } if (ntfs_device_win32_find_partition(handle, partition_id, &part_start, &part_length, &hidden_sectors)) { s64 tmp; HANDLE vol_handle = ntfs_device_win32_open_volume_for_partition( drive_id, part_start, part_length, flags); /* Store the drive geometry. */ ntfs_device_win32_getgeo(handle, fd); fd->handle = handle; fd->pos = 0; fd->part_start = part_start; fd->part_length = part_length; fd->part_hidden_sectors = hidden_sectors; fd->geo_sector_size = 512; fd->ntdll = FALSE; tmp = ntfs_device_win32_getntfssize(vol_handle); if (tmp > 0) fd->geo_size = tmp; else fd->geo_size = fd->part_length; if (vol_handle != INVALID_HANDLE_VALUE) { if (((flags & O_RDWR) == O_RDWR) && ntfs_device_win32_lock(vol_handle)) { CloseHandle(vol_handle); CloseHandle(handle); return -1; } fd->vol_handle = vol_handle; } else { if ((flags & O_RDWR) == O_RDWR) { /* Access if read-write, no volume found. */ ntfs_log_trace("Partitions containing Spanned/" "Mirrored volumes are not " "supported in R/W status " "yet.\n"); CloseHandle(handle); errno = EOPNOTSUPP; return -1; } fd->vol_handle = INVALID_HANDLE_VALUE; } return 0; } else { ntfs_log_debug("Partition %u not found on drive %d.\n", partition_id, drive_id); CloseHandle(handle); errno = ENODEV; return -1; } } /** * ntfs_device_win32_open - open a device * @dev: a pointer to the NTFS_DEVICE to open * @flags: unix open status flags * * @dev->d_name must hold the device name, the rest is ignored. * Supported flags are O_RDONLY, O_WRONLY and O_RDWR. * * If name is in format "(hd[0-9],[0-9])" then open a partition. * If name is in format "(hd[0-9])" then open a volume. * Otherwise open a file. */ static int ntfs_device_win32_open(struct ntfs_device *dev, int flags) { int drive_id = 0, numparams; unsigned int part = 0; char drive_char; win32_fd fd; int err; if (NDevOpen(dev)) { errno = EBUSY; return -1; } ntfs_device_win32_init_imports(); numparams = sscanf(dev->d_name, "/dev/hd%c%u", &drive_char, &part); if (!numparams && (dev->d_name[1] == ':') && (dev->d_name[2] == '\0')) { drive_char = dev->d_name[0]; numparams = 3; drive_id = toupper(drive_char) - 'A'; } switch (numparams) { case 0: ntfs_log_debug("win32_open(%s) -> file.\n", dev->d_name); err = ntfs_device_win32_open_file(dev->d_name, &fd, flags); break; case 1: ntfs_log_debug("win32_open(%s) -> drive %d.\n", dev->d_name, drive_id); err = ntfs_device_win32_open_drive(drive_id, &fd, flags); break; case 2: ntfs_log_debug("win32_open(%s) -> drive %d, part %u.\n", dev->d_name, drive_id, part); err = ntfs_device_win32_open_partition(drive_id, part, &fd, flags); break; case 3: ntfs_log_debug("win32_open(%s) -> drive %c:\n", dev->d_name, drive_char); err = ntfs_device_win32_open_lowlevel(drive_id, &fd, flags); break; default: ntfs_log_debug("win32_open(%s) -> unknwon file format.\n", dev->d_name); err = -1; } if (err) return err; ntfs_log_debug("win32_open(%s) -> %p, offset 0x%llx.\n", dev->d_name, dev, fd.part_start); /* Setup our read-only flag. */ if ((flags & O_RDWR) != O_RDWR) NDevSetReadOnly(dev); dev->d_private = (win32_fd*)ntfs_malloc(sizeof(win32_fd)); memcpy(dev->d_private, &fd, sizeof(win32_fd)); NDevSetOpen(dev); NDevClearDirty(dev); return 0; } /** * ntfs_device_win32_seek - change current logical file position * @dev: ntfs device obtained via ->open * @offset: required offset from the whence anchor * @whence: whence anchor specifying what @offset is relative to * * Return the new position on the volume on success and -1 on error with errno * set to the error code. * * @whence may be one of the following: * SEEK_SET - Offset is relative to file start. * SEEK_CUR - Offset is relative to current position. * SEEK_END - Offset is relative to end of file. */ static s64 ntfs_device_win32_seek(struct ntfs_device *dev, s64 offset, int whence) { s64 abs_ofs; win32_fd *fd = (win32_fd *)dev->d_private; ntfs_log_trace("seek offset = 0x%llx, whence = %d.\n", offset, whence); switch (whence) { case SEEK_SET: abs_ofs = offset; break; case SEEK_CUR: abs_ofs = fd->pos + offset; break; case SEEK_END: /* End of partition != end of disk. */ if (fd->part_length == -1) { ntfs_log_trace("Position relative to end of disk not " "implemented.\n"); errno = EOPNOTSUPP; return -1; } abs_ofs = fd->part_length + offset; break; default: ntfs_log_trace("Wrong mode %d.\n", whence); errno = EINVAL; return -1; } if ((abs_ofs < 0) || (fd->ntdll && (abs_ofs > fd->part_length))) { ntfs_log_trace("Seeking outsize seekable area.\n"); errno = EINVAL; return -1; } fd->pos = abs_ofs; return abs_ofs; } /** * ntfs_device_win32_pio - positioned low level i/o * @fd: win32 device descriptor obtained via ->open * @pos: at which position to do i/o from/to * @count: how many bytes should be transfered * @b: source/destination buffer * @write: TRUE if write transfer and FALSE if read transfer * * On success returns the number of bytes transfered (can be < @count) and on * error returns -1 and errno set. Transfer starts from position @pos on @fd. * * Notes: * - @pos, @buf, and @count must be aligned to geo_sector_size * - When dealing with volumes, a single call must not span both volume * and disk extents. * - Does not use/set @fd->pos. */ static s64 ntfs_device_win32_pio(win32_fd *fd, const s64 pos, const s64 count, void *rbuf, const void *wbuf) { LARGE_INTEGER li; HANDLE handle; DWORD bt; BOOL res; s64 bytes; ntfs_log_trace("pos = 0x%llx, count = 0x%llx, direction = %s.\n", (long long)pos, (long long)count, write ? "write" : "read"); li.QuadPart = pos; if (fd->vol_handle != INVALID_HANDLE_VALUE && pos < fd->geo_size) { ntfs_log_debug("Transfering via vol_handle.\n"); handle = fd->vol_handle; } else { ntfs_log_debug("Transfering via handle.\n"); handle = fd->handle; li.QuadPart += fd->part_start; } if (fd->ntdll) { IO_STATUS_BLOCK io_status; NTSTATUS res; LARGE_INTEGER offset; io_status.Status = STATUS_SUCCESS; io_status.Information = 0; offset.QuadPart = pos; if (wbuf) { res = NtWriteFile(fd->handle,(HANDLE)NULL, (PIO_APC_ROUTINE)NULL,(void*)NULL, &io_status, wbuf, count, &offset, (PULONG)NULL); } else { res = NtReadFile(fd->handle,(HANDLE)NULL, (PIO_APC_ROUTINE)NULL,(void*)NULL, &io_status, rbuf, count, &offset, (PULONG)NULL); } if (res == STATUS_SUCCESS) { bytes = io_status.Information; } else { bytes = -1; errno = ntfs_ntstatus_to_errno(res); } } else { if (!fnSetFilePointerEx(handle, li, NULL, FILE_BEGIN)) { errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("SetFilePointer failed.\n"); return -1; } if (wbuf) res = WriteFile(handle, wbuf, count, &bt, NULL); else res = ReadFile(handle, rbuf, count, &bt, NULL); bytes = bt; if (!res) { errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("%sFile() failed.\n", write ? "Write" : "Read"); return -1; } if (rbuf && !pos) { /* get the sector size from the boot sector */ char *boot = (char*)rbuf; fd->geo_sector_size = (boot[11] & 255) + ((boot[12] & 255) << 8); } } return bytes; } /** * ntfs_device_win32_pread_simple - positioned simple read * @fd: win32 device descriptor obtained via ->open * @pos: at which position to read from * @count: how many bytes should be read * @b: a pointer to where to put the contents * * On success returns the number of bytes read (can be < @count) and on error * returns -1 and errno set. Read starts from position @pos. * * Notes: * - @pos, @buf, and @count must be aligned to geo_sector_size. * - When dealing with volumes, a single call must not span both volume * and disk extents. * - Does not use/set @fd->pos. */ static inline s64 ntfs_device_win32_pread_simple(win32_fd *fd, const s64 pos, const s64 count, void *b) { return ntfs_device_win32_pio(fd, pos, count, b, (void*)NULL); } /** * ntfs_device_win32_read - read bytes from an ntfs device * @dev: ntfs device obtained via ->open * @b: pointer to where to put the contents * @count: how many bytes should be read * * On success returns the number of bytes actually read (can be < @count). * On error returns -1 with errno set. */ static s64 ntfs_device_win32_read(struct ntfs_device *dev, void *b, s64 count) { s64 old_pos, to_read, i, br = 0; win32_fd *fd = (win32_fd *)dev->d_private; BYTE *alignedbuffer; int old_ofs, ofs; old_pos = fd->pos; old_ofs = ofs = old_pos & (fd->geo_sector_size - 1); to_read = (ofs + count + fd->geo_sector_size - 1) & ~(s64)(fd->geo_sector_size - 1); /* Impose maximum of 2GB to be on the safe side. */ if (to_read > 0x80000000) { int delta = to_read - count; to_read = 0x80000000; count = to_read - delta; } ntfs_log_trace("fd = %p, b = %p, count = 0x%llx, pos = 0x%llx, " "ofs = %i, to_read = 0x%llx.\n", fd, b, (long long)count, (long long)old_pos, ofs, (long long)to_read); if (!((unsigned long)b & (fd->geo_sector_size - 1)) && !old_ofs && !(count & (fd->geo_sector_size - 1))) alignedbuffer = b; else { alignedbuffer = (BYTE *)VirtualAlloc(NULL, to_read, MEM_COMMIT, PAGE_READWRITE); if (!alignedbuffer) { errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("VirtualAlloc failed for read.\n"); return -1; } } if (fd->vol_handle != INVALID_HANDLE_VALUE && old_pos < fd->geo_size) { s64 vol_to_read = fd->geo_size - old_pos; if (count > vol_to_read) { br = ntfs_device_win32_pread_simple(fd, old_pos & ~(s64)(fd->geo_sector_size - 1), ofs + vol_to_read, alignedbuffer); if (br == -1) goto read_error; to_read -= br; if (br < ofs) { br = 0; goto read_partial; } br -= ofs; fd->pos += br; ofs = fd->pos & (fd->geo_sector_size - 1); if (br != vol_to_read) goto read_partial; } } i = ntfs_device_win32_pread_simple(fd, fd->pos & ~(s64)(fd->geo_sector_size - 1), to_read, alignedbuffer + br); if (i == -1) { if (br) goto read_partial; goto read_error; } if (i < ofs) goto read_partial; i -= ofs; br += i; if (br > count) br = count; fd->pos = old_pos + br; read_partial: if (alignedbuffer != b) { memcpy((void*)b, alignedbuffer + old_ofs, br); VirtualFree(alignedbuffer, 0, MEM_RELEASE); } return br; read_error: if (alignedbuffer != b) VirtualFree(alignedbuffer, 0, MEM_RELEASE); return -1; } /** * ntfs_device_win32_close - close an open ntfs deivce * @dev: ntfs device obtained via ->open * * Return 0 if o.k. * -1 if not, and errno set. Note if error fd->vol_handle is trashed. */ static int ntfs_device_win32_close(struct ntfs_device *dev) { win32_fd *fd = (win32_fd *)dev->d_private; BOOL rvl; ntfs_log_trace("Closing device %p.\n", dev); if (!NDevOpen(dev)) { errno = EBADF; return -1; } if (fd->vol_handle != INVALID_HANDLE_VALUE) { if (!NDevReadOnly(dev)) { ntfs_device_win32_dismount(fd->vol_handle); ntfs_device_win32_unlock(fd->vol_handle); } if (!CloseHandle(fd->vol_handle)) ntfs_log_trace("CloseHandle() failed for volume.\n"); } if (fd->ntdll) { ntfs_device_win32_setlock(fd->handle,FSCTL_UNLOCK_VOLUME); rvl = NtClose(fd->handle) == STATUS_SUCCESS; } else rvl = CloseHandle(fd->handle); NDevClearOpen(dev); free(fd); if (!rvl) { errno = ntfs_w32error_to_errno(GetLastError()); if (fd->ntdll) ntfs_log_trace("NtClose() failed.\n"); else ntfs_log_trace("CloseHandle() failed.\n"); return -1; } return 0; } /** * ntfs_device_win32_sync - flush write buffers to disk * @dev: ntfs device obtained via ->open * * Return 0 if o.k. * -1 if not, and errno set. * * Note: Volume syncing works differently in windows. * Disk cannot be synced in windows. */ static int ntfs_device_win32_sync(struct ntfs_device *dev) { int err = 0; BOOL to_clear = TRUE; if (!NDevReadOnly(dev) && NDevDirty(dev)) { win32_fd *fd = (win32_fd *)dev->d_private; if ((fd->vol_handle != INVALID_HANDLE_VALUE) && !FlushFileBuffers(fd->vol_handle)) { to_clear = FALSE; err = ntfs_w32error_to_errno(GetLastError()); } if (!FlushFileBuffers(fd->handle)) { to_clear = FALSE; if (!err) err = ntfs_w32error_to_errno(GetLastError()); } if (!to_clear) { ntfs_log_trace("Could not sync.\n"); errno = err; return -1; } NDevClearDirty(dev); } return 0; } /** * ntfs_device_win32_pwrite_simple - positioned simple write * @fd: win32 device descriptor obtained via ->open * @pos: at which position to write to * @count: how many bytes should be written * @b: a pointer to the data to write * * On success returns the number of bytes written and on error returns -1 and * errno set. Write starts from position @pos. * * Notes: * - @pos, @buf, and @count must be aligned to geo_sector_size. * - When dealing with volumes, a single call must not span both volume * and disk extents. * - Does not use/set @fd->pos. */ static inline s64 ntfs_device_win32_pwrite_simple(win32_fd *fd, const s64 pos, const s64 count, const void *b) { return ntfs_device_win32_pio(fd, pos, count, (void*)NULL, b); } /** * ntfs_device_win32_write - write bytes to an ntfs device * @dev: ntfs device obtained via ->open * @b: pointer to the data to write * @count: how many bytes should be written * * On success returns the number of bytes actually written. * On error returns -1 with errno set. */ static s64 ntfs_device_win32_write(struct ntfs_device *dev, const void *b, s64 count) { s64 old_pos, to_write, i, bw = 0; win32_fd *fd = (win32_fd *)dev->d_private; const BYTE *alignedbuffer; BYTE *readbuffer; int old_ofs, ofs; old_pos = fd->pos; old_ofs = ofs = old_pos & (fd->geo_sector_size - 1); to_write = (ofs + count + fd->geo_sector_size - 1) & ~(s64)(fd->geo_sector_size - 1); /* Impose maximum of 2GB to be on the safe side. */ if (to_write > 0x80000000) { int delta = to_write - count; to_write = 0x80000000; count = to_write - delta; } ntfs_log_trace("fd = %p, b = %p, count = 0x%llx, pos = 0x%llx, " "ofs = %i, to_write = 0x%llx.\n", fd, b, (long long)count, (long long)old_pos, ofs, (long long)to_write); if (NDevReadOnly(dev)) { ntfs_log_trace("Can't write on a R/O device.\n"); errno = EROFS; return -1; } if (!count) return 0; NDevSetDirty(dev); readbuffer = (BYTE*)NULL; if (!((unsigned long)b & (fd->geo_sector_size - 1)) && !old_ofs && !(count & (fd->geo_sector_size - 1))) alignedbuffer = (const BYTE *)b; else { s64 end; readbuffer = (BYTE *)VirtualAlloc(NULL, to_write, MEM_COMMIT, PAGE_READWRITE); if (!readbuffer) { errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("VirtualAlloc failed for write.\n"); return -1; } /* Read first sector if start of write not sector aligned. */ if (ofs) { i = ntfs_device_win32_pread_simple(fd, old_pos & ~(s64)(fd->geo_sector_size - 1), fd->geo_sector_size, readbuffer); if (i != fd->geo_sector_size) { if (i >= 0) errno = EIO; goto write_error; } } /* * Read last sector if end of write not sector aligned and last * sector is either not the same as the first sector or it is * the same as the first sector but this has not been read in * yet, i.e. the start of the write is sector aligned. */ end = old_pos + count; if ((end & (fd->geo_sector_size - 1)) && ((to_write > fd->geo_sector_size) || !ofs)) { i = ntfs_device_win32_pread_simple(fd, end & ~(s64)(fd->geo_sector_size - 1), fd->geo_sector_size, readbuffer + to_write - fd->geo_sector_size); if (i != fd->geo_sector_size) { if (i >= 0) errno = EIO; goto write_error; } } /* Copy the data to be written into @readbuffer. */ memcpy(readbuffer + ofs, b, count); alignedbuffer = readbuffer; } if (fd->vol_handle != INVALID_HANDLE_VALUE && old_pos < fd->geo_size) { s64 vol_to_write = fd->geo_size - old_pos; if (count > vol_to_write) { bw = ntfs_device_win32_pwrite_simple(fd, old_pos & ~(s64)(fd->geo_sector_size - 1), ofs + vol_to_write, alignedbuffer); if (bw == -1) goto write_error; to_write -= bw; if (bw < ofs) { bw = 0; goto write_partial; } bw -= ofs; fd->pos += bw; ofs = fd->pos & (fd->geo_sector_size - 1); if (bw != vol_to_write) goto write_partial; } } i = ntfs_device_win32_pwrite_simple(fd, fd->pos & ~(s64)(fd->geo_sector_size - 1), to_write, alignedbuffer + bw); if (i == -1) { if (bw) goto write_partial; goto write_error; } if (i < ofs) goto write_partial; i -= ofs; bw += i; if (bw > count) bw = count; fd->pos = old_pos + bw; write_partial: if (readbuffer) VirtualFree(readbuffer, 0, MEM_RELEASE); return bw; write_error: bw = -1; goto write_partial; } /** * ntfs_device_win32_stat - get a unix-like stat structure for an ntfs device * @dev: ntfs device obtained via ->open * @buf: pointer to the stat structure to fill * * Note: Only st_mode, st_size, and st_blocks are filled. * * Return 0 if o.k. * -1 if not and errno set. in this case handle is trashed. */ static int ntfs_device_win32_stat(struct ntfs_device *dev, struct stat *buf) { win32_fd *fd = (win32_fd *)dev->d_private; mode_t st_mode; if ((dev->d_name[1] == ':') && (dev->d_name[2] == '\0')) st_mode = S_IFBLK; else switch (GetFileType(fd->handle)) { case FILE_TYPE_CHAR: st_mode = S_IFCHR; break; case FILE_TYPE_DISK: st_mode = S_IFREG; break; case FILE_TYPE_PIPE: st_mode = S_IFIFO; break; default: st_mode = 0; } memset(buf, 0, sizeof(struct stat)); buf->st_mode = st_mode; buf->st_size = fd->part_length; if (buf->st_size != -1) buf->st_blocks = buf->st_size >> 9; else buf->st_size = 0; return 0; } #ifdef HDIO_GETGEO /** * ntfs_win32_hdio_getgeo - get drive geometry * @dev: ntfs device obtained via ->open * @argp: pointer to where to put the output * * Note: Works on windows NT/2k/XP only. * * Return 0 if o.k. * -1 if not, and errno set. Note if error fd->handle is trashed. */ static __inline__ int ntfs_win32_hdio_getgeo(struct ntfs_device *dev, struct hd_geometry *argp) { win32_fd *fd = (win32_fd *)dev->d_private; argp->heads = fd->geo_heads; argp->sectors = fd->geo_sectors; argp->cylinders = fd->geo_cylinders; argp->start = fd->part_hidden_sectors; return 0; } #endif /** * ntfs_win32_blksszget - get block device sector size * @dev: ntfs device obtained via ->open * @argp: pointer to where to put the output * * Note: Works on windows NT/2k/XP only. * * Return 0 if o.k. * -1 if not, and errno set. Note if error fd->handle is trashed. */ static __inline__ int ntfs_win32_blksszget(struct ntfs_device *dev,int *argp) { win32_fd *fd = (win32_fd *)dev->d_private; DWORD bytesReturned; DISK_GEOMETRY dg; if (DeviceIoControl(fd->handle, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &dg, sizeof(DISK_GEOMETRY), &bytesReturned, NULL)) { /* success */ *argp = dg.BytesPerSector; return 0; } errno = ntfs_w32error_to_errno(GetLastError()); ntfs_log_trace("GET_DRIVE_GEOMETRY failed.\n"); return -1; } static int ntfs_device_win32_ioctl(struct ntfs_device *dev, unsigned long request, void *argp) { #if defined(BLKGETSIZE) | defined(BLKGETSIZE64) win32_fd *fd = (win32_fd *)dev->d_private; #endif ntfs_log_trace("win32_ioctl(0x%lx) called.\n", request); switch (request) { #if defined(BLKGETSIZE) case BLKGETSIZE: ntfs_log_debug("BLKGETSIZE detected.\n"); if (fd->part_length >= 0) { *(int *)argp = (int)(fd->part_length / 512); return 0; } errno = EOPNOTSUPP; return -1; #endif #if defined(BLKGETSIZE64) case BLKGETSIZE64: ntfs_log_debug("BLKGETSIZE64 detected.\n"); if (fd->part_length >= 0) { *(s64 *)argp = fd->part_length; return 0; } errno = EOPNOTSUPP; return -1; #endif #ifdef HDIO_GETGEO case HDIO_GETGEO: ntfs_log_debug("HDIO_GETGEO detected.\n"); return ntfs_win32_hdio_getgeo(dev, (struct hd_geometry *)argp); #endif #ifdef BLKSSZGET case BLKSSZGET: ntfs_log_debug("BLKSSZGET detected.\n"); if (fd && !fd->ntdll) { *(int*)argp = fd->geo_sector_size; return (0); } else return ntfs_win32_blksszget(dev, (int *)argp); #endif #ifdef BLKBSZSET case BLKBSZSET: ntfs_log_debug("BLKBSZSET detected.\n"); /* Nothing to do on Windows. */ return 0; #endif default: ntfs_log_debug("unimplemented ioctl 0x%lx.\n", request); errno = EOPNOTSUPP; return -1; } } static s64 ntfs_device_win32_pread(struct ntfs_device *dev, void *b, s64 count, s64 offset) { s64 got; win32_fd *fd; /* read the fast way if sector aligned */ fd = (win32_fd*)dev->d_private; if (!((count | offset) & (fd->geo_sector_size - 1))) { got = ntfs_device_win32_pio(fd, offset, count, b, (void*)NULL); } else { if (ntfs_device_win32_seek(dev, offset, 0) == -1) got = 0; else got = ntfs_device_win32_read(dev, b, count); } return (got); } static s64 ntfs_device_win32_pwrite(struct ntfs_device *dev, const void *b, s64 count, s64 offset) { s64 put; win32_fd *fd; /* write the fast way if sector aligned */ fd = (win32_fd*)dev->d_private; if (!((count | offset) & (fd->geo_sector_size - 1))) { put = ntfs_device_win32_pio(fd, offset, count, (void*)NULL, b); } else { if (ntfs_device_win32_seek(dev, offset, 0) == -1) put = 0; else put = ntfs_device_win32_write(dev, b, count); } return (put); } struct ntfs_device_operations ntfs_device_win32_io_ops = { .open = ntfs_device_win32_open, .close = ntfs_device_win32_close, .seek = ntfs_device_win32_seek, .read = ntfs_device_win32_read, .write = ntfs_device_win32_write, .pread = ntfs_device_win32_pread, .pwrite = ntfs_device_win32_pwrite, .sync = ntfs_device_win32_sync, .stat = ntfs_device_win32_stat, .ioctl = ntfs_device_win32_ioctl }; /* * Mark an open file as sparse * * This is only called by ntfsclone when cloning a volume to a file. * The argument is the target file, not a volume. * * Returns 0 if successful. */ int ntfs_win32_set_sparse(int fd) { BOOL ok; HANDLE handle; DWORD bytes; handle = get_osfhandle(fd); if (handle == INVALID_HANDLE_VALUE) ok = FALSE; else ok = DeviceIoControl(handle, FSCTL_SET_SPARSE, (void*)NULL, 0, (void*)NULL, 0, &bytes, (LPOVERLAPPED)NULL); return (!ok); } /* * Resize an open file * * This is only called by ntfsclone when cloning a volume to a file. * The argument must designate a file, not a volume. * * Returns 0 if successful. */ static int win32_ftruncate(HANDLE handle, s64 size) { BOOL ok; LONG hsize, lsize; LONG ohsize, olsize; if (handle == INVALID_HANDLE_VALUE) ok = FALSE; else { SetLastError(NO_ERROR); /* save original position */ ohsize = 0; olsize = SetFilePointer(handle, 0, &ohsize, 1); hsize = size >> 32; lsize = size & 0xffffffff; ok = (SetFilePointer(handle, lsize, &hsize, 0) == (DWORD)lsize) && (GetLastError() == NO_ERROR) && SetEndOfFile(handle); /* restore original position, even if above failed */ SetFilePointer(handle, olsize, &ohsize, 0); if (GetLastError() != NO_ERROR) ok = FALSE; } if (!ok) errno = EINVAL; return (ok ? 0 : -1); } int ntfs_device_win32_ftruncate(struct ntfs_device *dev, s64 size) { win32_fd *fd; int ret; ret = -1; fd = (win32_fd*)dev->d_private; if (fd && !fd->ntdll) ret = win32_ftruncate(fd->handle, size); return (ret); } int ntfs_win32_ftruncate(int fd, s64 size) { int ret; HANDLE handle; handle = get_osfhandle(fd); ret = win32_ftruncate(handle, size); return (ret); }