OSDN Git Service

* fhandler.h (struct part_t): New type.
authorcorinna <corinna>
Wed, 12 Jan 2011 09:16:49 +0000 (09:16 +0000)
committercorinna <corinna>
Wed, 12 Jan 2011 09:16:49 +0000 (09:16 +0000)
(class fhandler_dev_floppy): Convert partitions to part_t pointer.
Add lock_partition method.
* fhandler_floppy.cc (fhandler_dev_floppy::lock_partition): New method
to implement ondemand partition locking.
(fhandler_dev_floppy::write_file): Call lock_partition from here if
writing failed due to a potential write restriction on a disk
partition.
(fhandler_dev_floppy::open): Don't lock partitions here.
(fhandler_dev_floppy::close): Keep track of partition handle reference
count.  Close handles and remove partitions pointer ony if count is 0.
(fhandler_dev_floppy::dup): Just copy partitions pointer and increment
reference count.

winsup/cygwin/ChangeLog
winsup/cygwin/fhandler.h
winsup/cygwin/fhandler_floppy.cc

index 95fcb53..5d6e4be 100644 (file)
@@ -1,3 +1,19 @@
+2011-01-12  Corinna Vinschen  <corinna@vinschen.de>
+
+       * fhandler.h (struct part_t): New type.
+       (class fhandler_dev_floppy): Convert partitions to part_t pointer.
+       Add lock_partition method.
+       * fhandler_floppy.cc (fhandler_dev_floppy::lock_partition): New method
+       to implement ondemand partition locking.
+       (fhandler_dev_floppy::write_file): Call lock_partition from here if
+       writing failed due to a potential write restriction on a disk
+       partition.  
+       (fhandler_dev_floppy::open): Don't lock partitions here.
+       (fhandler_dev_floppy::close): Keep track of partition handle reference
+       count.  Close handles and remove partitions pointer ony if count is 0.
+       (fhandler_dev_floppy::dup): Just copy partitions pointer and increment
+       reference count.
+
 2011-01-11  Corinna Vinschen  <corinna@vinschen.de>
 
        * fhandler.h (MAX_PARTITIONS): New definition.
index 0561990..74eebf5 100644 (file)
@@ -689,12 +689,18 @@ class fhandler_dev_raw: public fhandler_base
 
 #define MAX_PARTITIONS 15
 
+struct part_t
+{
+  LONG refcnt;
+  HANDLE hdl[MAX_PARTITIONS];
+};
+
 class fhandler_dev_floppy: public fhandler_dev_raw
 {
  private:
   _off64_t drive_size;
   unsigned long bytes_per_sector;
-  HANDLE partitions[MAX_PARTITIONS];
+  part_t *partitions;
   struct status_flags
   {
     unsigned eom_detected    : 1;
@@ -707,6 +713,8 @@ class fhandler_dev_floppy: public fhandler_dev_raw
   inline _off64_t get_current_position ();
   int get_drive_info (struct hd_geometry *geo);
 
+  int lock_partition (DWORD to_write);
+
   BOOL write_file (const void *buf, DWORD to_write, DWORD *written, int *err);
   BOOL read_file (void *buf, DWORD to_read, DWORD *read, int *err);
 
index af8b7d2..5ebcd14 100644 (file)
@@ -11,6 +11,7 @@ Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
 details. */
 
 #include "winsup.h"
+#include <alloca.h>
 #include <unistd.h>
 #include <winioctl.h>
 #include <cygwin/rdevio.h>
@@ -32,7 +33,6 @@ details. */
 fhandler_dev_floppy::fhandler_dev_floppy ()
   : fhandler_dev_raw (), status ()
 {
-  memset (partitions, 0, sizeof partitions);
 }
 
 int
@@ -180,6 +180,143 @@ fhandler_dev_floppy::read_file (void *buf, DWORD to_read, DWORD *read, int *err)
   return ret;
 }
 
+/* See comment in write_file below. */
+BOOL
+fhandler_dev_floppy::lock_partition (DWORD to_write)
+{
+  DWORD bytes_read;
+
+  /* The simple case.  We have only a single partition open anyway. 
+     Try to lock the partition so that a subsequent write succeeds.
+     If there's some file handle open on one of the affected partitions,
+     this fails, but that's how it works on Vista and later... */
+  if (get_minor () % 16 != 0)
+    {
+      if (!DeviceIoControl (get_handle (), FSCTL_LOCK_VOLUME,
+                          NULL, 0, NULL, 0, &bytes_read, NULL))
+       {
+         debug_printf ("DeviceIoControl (FSCTL_LOCK_VOLUME) failed, %E");
+         return FALSE;
+       }
+      return TRUE;
+    }
+
+  /* The tricky case.  We're writing to the entire disk.  What this code
+     basically does is to find out if the current write operation affects
+     one or more partitions on the disk.  If so, it tries to lock all these
+     partitions and stores the handles for a subsequent close(). */
+  NTSTATUS status;
+  IO_STATUS_BLOCK io;
+  FILE_POSITION_INFORMATION fpi;
+  /* Allocate space for 4 times the maximum partition count we can handle.
+     The reason is that for *every* single logical drive in an extended
+     partition on an MBR drive, 3 filler entries with partition number set
+     to 0 are added into the partition table returned by
+     IOCTL_DISK_GET_DRIVE_LAYOUT_EX.  The first of them reproduces the data
+     of the next partition entry, if any, except for the partiton number.
+     Then two entries with everything set to 0 follow.  Well, the
+     documentation states that for MBR drives the number of partition entries
+     in the PARTITION_INFORMATION_EX array is always a multiple of 4, but,
+     nevertheless, how crappy is that layout? */
+  const DWORD size = sizeof (DRIVE_LAYOUT_INFORMATION_EX)
+                    + 4 * MAX_PARTITIONS * sizeof (PARTITION_INFORMATION_EX);
+  PDRIVE_LAYOUT_INFORMATION_EX pdlix = (PDRIVE_LAYOUT_INFORMATION_EX)
+                                      alloca (size);
+  BOOL found = FALSE;
+  
+  /* Fetch current file pointer position on disk. */
+  status = NtQueryInformationFile (get_handle (), &io, &fpi, sizeof fpi,
+                                  FilePositionInformation);
+  if (!NT_SUCCESS (status))
+    {
+      debug_printf ("NtQueryInformationFile(FilePositionInformation): %p",
+                   status);
+      return FALSE;
+    }
+  /* Fetch drive layout to get start and end positions of partitions on disk. */
+  if (!DeviceIoControl (get_handle (), IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0,
+                       pdlix, size, &bytes_read, NULL))
+    {
+      debug_printf ("DeviceIoControl(IOCTL_DISK_GET_DRIVE_LAYOUT_EX): %E");
+      return FALSE;
+    }
+  /* Scan through partition info to find the partition(s) into which we're
+     currently trying to write. */
+  PARTITION_INFORMATION_EX *ppie = pdlix->PartitionEntry;
+  for (DWORD i = 0; i < pdlix->PartitionCount; ++i, ++ppie)
+    {
+      /* A partition number of 0 denotes an extended partition or one of the
+        aforementioned filler entries.  Just skip. */
+      if (ppie->PartitionNumber == 0)
+       continue;
+      /* Check if our writing range affects this partition. */
+      if (fpi.CurrentByteOffset.QuadPart   < ppie->StartingOffset.QuadPart
+                                            + ppie->PartitionLength.QuadPart
+         && ppie->StartingOffset.QuadPart < fpi.CurrentByteOffset.QuadPart
+                                            + to_write)
+       {
+         /* Yes.  Now check if we can handle it.  We can only handle
+            up to MAX_PARTITIONS partitions.  The partition numbering is
+            one-based, so we decrement the partition number by 1 when using
+            as index into the partition array. */
+         DWORD &part_no = ppie->PartitionNumber;
+         if (part_no >= MAX_PARTITIONS)
+           return FALSE;
+         found = TRUE;
+         debug_printf ("%d %D->%D : %D->%D", part_no,
+                       ppie->StartingOffset.QuadPart,
+                       ppie->StartingOffset.QuadPart
+                       + ppie->PartitionLength.QuadPart,
+                       fpi.CurrentByteOffset.QuadPart,
+                       fpi.CurrentByteOffset.QuadPart + to_write);
+         /* Do we already have partitions?  If not, create it. */
+         if (!partitions)
+           {
+             partitions = (part_t *) ccalloc_abort (HEAP_FHANDLER, 1,
+                                                    sizeof (part_t));
+             partitions->refcnt = 1;
+           }
+         /* Next, check if the partition is already open.  If so, skip it. */
+         if (partitions->hdl[part_no - 1])
+           continue;
+         /* Now open the partition and lock it. */
+         WCHAR part[MAX_PATH], *p;
+         NTSTATUS status;
+         UNICODE_STRING upart;
+         OBJECT_ATTRIBUTES attr;
+         IO_STATUS_BLOCK io;
+
+         sys_mbstowcs (part, MAX_PATH, get_win32_name ());
+         p = wcschr (part, L'\0') - 1;
+         __small_swprintf (p, L"%d", part_no);
+         RtlInitUnicodeString (&upart, part);
+         InitializeObjectAttributes (&attr, &upart,
+                                     OBJ_CASE_INSENSITIVE
+                                     | ((get_flags () & O_CLOEXEC)
+                                        ? 0 : OBJ_INHERIT),
+                                     NULL, NULL);
+         status = NtOpenFile (&partitions->hdl[part_no - 1],
+                              GENERIC_READ | GENERIC_WRITE, &attr,
+                              &io, FILE_SHARE_READ | FILE_SHARE_WRITE, 0);
+         if (!NT_SUCCESS (status))
+           {
+             debug_printf ("NtCreateFile(%W): %p", part, status);
+             return FALSE;
+           }
+         if (!DeviceIoControl (partitions->hdl[part_no - 1], FSCTL_LOCK_VOLUME,
+                               NULL, 0, NULL, 0, &bytes_read, NULL))
+           {
+             debug_printf ("DeviceIoControl (%W, FSCTL_LOCK_VOLUME) "
+                           "failed, %E", part);
+             return FALSE;
+           }
+       }
+    }
+  /* If we didn't find a single matching partition, the "Access denied"
+     had another reason, so return FALSE in that case. */
+  return found;
+}
+
 BOOL
 fhandler_dev_floppy::write_file (const void *buf, DWORD to_write,
                                 DWORD *written, int *err)
@@ -189,6 +326,24 @@ fhandler_dev_floppy::write_file (const void *buf, DWORD to_write,
   *err = 0;
   if (!(ret = WriteFile (get_handle (), buf, to_write, written, 0)))
     *err = GetLastError ();
+  /* When writing to a disk or partition on Vista, an "Access denied" error
+     is potentially a result of the raw disk write restriction.  See
+     http://support.microsoft.com/kb/942448 for details.  What we have to
+     do here is to lock the partition and retry.  The previous solution
+     locked one or all partitions immediately in open.  Which is overly
+     wasteful, given that the user might only want to change, say, the boot
+     sector. */
+  if (*err == ERROR_ACCESS_DENIED
+      && wincap.has_restricted_raw_disk_access ()
+      && get_major () != DEV_FLOPPY_MAJOR
+      && get_major () != DEV_CDROM_MAJOR
+      && (get_flags () & O_ACCMODE) != O_RDONLY
+      && lock_partition (to_write))
+    {
+      *err = 0;
+      if (!(ret = WriteFile (get_handle (), buf, to_write, written, 0)))
+       *err = GetLastError ();
+    }
   syscall_printf ("%d (err %d) = WriteFile (%d, %d, write %d, written %d, 0)",
                  ret, *err, get_handle (), buf, to_write, *written);
   return ret;
@@ -227,52 +382,6 @@ fhandler_dev_floppy::open (int flags, mode_t)
                               NULL, 0, NULL, 0, &bytes_read, NULL))
        debug_printf ("DeviceIoControl (FSCTL_ALLOW_EXTENDED_DASD_IO) "
                      "failed, %E");
-      /* If we're trying to write to a disk partition, lock the partition,
-        otherwise we will get "Access denied" starting with Vista. */
-      if (wincap.has_restricted_raw_disk_access ()
-         && get_major () != DEV_FLOPPY_MAJOR
-         && get_major () != DEV_CDROM_MAJOR
-         && (flags & O_ACCMODE) != O_RDONLY)
-       {
-         /* Special case: If we try to write to the entire disk, we have to
-            lock all partitions, otherwise writing fails as soon as we cross
-            a partition boundary. */
-         if (get_minor () % 16 == 0)
-           {
-             WCHAR part[MAX_PATH], *p;
-
-             sys_mbstowcs (part, MAX_PATH, get_win32_name ());
-             p = wcschr (part, L'\0') - 1;
-             for (int i = 0; i < MAX_PARTITIONS; ++i)
-               {
-                 NTSTATUS status;
-                 UNICODE_STRING upart;
-                 OBJECT_ATTRIBUTES attr;
-                 IO_STATUS_BLOCK io;
-
-                 __small_swprintf (p, L"%d", i + 1);
-                 RtlInitUnicodeString (&upart, part);
-                 InitializeObjectAttributes (&attr, &upart,
-                                             OBJ_INHERIT|OBJ_CASE_INSENSITIVE,
-                                             NULL, NULL);
-                 status = NtOpenFile (&partitions[i], GENERIC_WRITE, &attr,
-                                      &io, FILE_SHARE_VALID_FLAGS, 0);
-                 if (status == STATUS_OBJECT_NAME_NOT_FOUND ||
-                     status == STATUS_OBJECT_PATH_NOT_FOUND)
-                   break;
-                 else if (!NT_SUCCESS (status))
-                   debug_printf ("NtCreateFile(%W): status %p", part, status);
-                 else if (!DeviceIoControl (partitions[i], FSCTL_LOCK_VOLUME,
-                                            NULL, 0, NULL, 0,
-                                            &bytes_read, NULL))
-                   debug_printf ("DeviceIoControl (%W, FSCTL_LOCK_VOLUME) "
-                                 "failed, %E", part);
-               }
-           }
-         else if (!DeviceIoControl (get_handle (), FSCTL_LOCK_VOLUME,
-                                    NULL, 0, NULL, 0, &bytes_read, NULL))
-           debug_printf ("DeviceIoControl (FSCTL_LOCK_VOLUME) failed, %E");
-       }
     }
 
   return ret;
@@ -283,11 +392,13 @@ fhandler_dev_floppy::close ()
 {
   int ret = fhandler_dev_raw::close ();
 
-  /* See "Special case" comment in fhandler_dev_floppy::open. */
-  if (wincap.has_restricted_raw_disk_access ())
-    for (int i = 0; i < MAX_PARTITIONS && partitions[i]; ++i)
-      NtClose (partitions[i]);
-
+  if (partitions && InterlockedDecrement (&partitions->refcnt) == 0)
+    {
+      for (int i = 0; i < MAX_PARTITIONS; ++i)
+       if (partitions->hdl[i])
+         NtClose (partitions->hdl[i]);
+      cfree (partitions);
+    }
   return ret;
 }
 
@@ -296,26 +407,17 @@ fhandler_dev_floppy::dup (fhandler_base *child)
 {
   fhandler_dev_floppy *fhc = (fhandler_dev_floppy *) child;
 
-  /* See "Special case" comment in fhandler_dev_floppy::open. */
-  memset (fhc->partitions, 0, sizeof fhc->partitions);
-  if (wincap.has_restricted_raw_disk_access ())
-    for (int i = 0; i < MAX_PARTITIONS && partitions[i]; ++i)
-      if (!DuplicateHandle (GetCurrentProcess (), partitions[i],
-                           GetCurrentProcess (), &fhc->partitions[i],
-                           0, TRUE, DUPLICATE_SAME_ACCESS))
-       {
-         __seterrno ();
-         while (--i >= 0)
-           NtClose (partitions[i]);
-         return -1;
-       }
-
   int ret = fhandler_dev_raw::dup (child);
 
   if (!ret)
     {
       fhc->drive_size = drive_size;
       fhc->bytes_per_sector = bytes_per_sector;
+      if (partitions)
+       {
+         InterlockedIncrement (&partitions->refcnt);
+         fhc->partitions = partitions;
+       }
       fhc->eom_detected (eom_detected ());
     }
   return ret;