NTFS: Detect the case when Windows has been suspended to disk on the volume
[pandora-kernel.git] / fs / ntfs / super.c
index 455cbe0..41aa8eb 100644 (file)
@@ -34,6 +34,7 @@
 #include "sysctl.h"
 #include "logfile.h"
 #include "quota.h"
+#include "usnjrnl.h"
 #include "dir.h"
 #include "debug.h"
 #include "index.h"
@@ -497,6 +498,12 @@ static int ntfs_remount(struct super_block *sb, int *flags, char *opt)
                        NVolSetErrors(vol);
                        return -EROFS;
                }
+               if (!ntfs_stamp_usnjrnl(vol)) {
+                       ntfs_error(sb, "Failed to stamp transation log "
+                                       "($UsnJrnl)%s", es);
+                       NVolSetErrors(vol);
+                       return -EROFS;
+               }
        } else if (!(sb->s_flags & MS_RDONLY) && (*flags & MS_RDONLY)) {
                /* Remounting read-only. */
                if (!NVolErrors(vol)) {
@@ -1149,6 +1156,124 @@ static BOOL load_and_check_logfile(ntfs_volume *vol)
        return TRUE;
 }
 
+#define NTFS_HIBERFIL_HEADER_SIZE      4096
+
+/**
+ * check_windows_hibernation_status - check if Windows is suspended on a volume
+ * @vol:       ntfs super block of device to check
+ *
+ * Check if Windows is hibernated on the ntfs volume @vol.  This is done by
+ * looking for the file hiberfil.sys in the root directory of the volume.  If
+ * the file is not present Windows is definitely not suspended.
+ *
+ * If hiberfil.sys exists and is less than 4kiB in size it means Windows is
+ * definitely suspended (this volume is not the system volume).  Caveat:  on a
+ * system with many volumes it is possible that the < 4kiB check is bogus but
+ * for now this should do fine.
+ *
+ * If hiberfil.sys exists and is larger than 4kiB in size, we need to read the
+ * hiberfil header (which is the first 4kiB).  If this begins with "hibr",
+ * Windows is definitely suspended.  If it is completely full of zeroes,
+ * Windows is definitely not hibernated.  Any other case is treated as if
+ * Windows is suspended.  This caters for the above mentioned caveat of a
+ * system with many volumes where no "hibr" magic would be present and there is
+ * no zero header.
+ *
+ * Return 0 if Windows is not hibernated on the volume, >0 if Windows is
+ * hibernated on the volume, and -errno on error.
+ */
+static int check_windows_hibernation_status(ntfs_volume *vol)
+{
+       MFT_REF mref;
+       struct inode *vi;
+       ntfs_inode *ni;
+       struct page *page;
+       u32 *kaddr, *kend;
+       ntfs_name *name = NULL;
+       int ret = 1;
+       static const ntfschar hiberfil[13] = { const_cpu_to_le16('h'),
+                       const_cpu_to_le16('i'), const_cpu_to_le16('b'),
+                       const_cpu_to_le16('e'), const_cpu_to_le16('r'),
+                       const_cpu_to_le16('f'), const_cpu_to_le16('i'),
+                       const_cpu_to_le16('l'), const_cpu_to_le16('.'),
+                       const_cpu_to_le16('s'), const_cpu_to_le16('y'),
+                       const_cpu_to_le16('s'), 0 };
+
+       ntfs_debug("Entering.");
+       /*
+        * Find the inode number for the hibernation file by looking up the
+        * filename hiberfil.sys in the root directory.
+        */
+       down(&vol->root_ino->i_sem);
+       mref = ntfs_lookup_inode_by_name(NTFS_I(vol->root_ino), hiberfil, 12,
+                       &name);
+       up(&vol->root_ino->i_sem);
+       if (IS_ERR_MREF(mref)) {
+               ret = MREF_ERR(mref);
+               /* If the file does not exist, Windows is not hibernated. */
+               if (ret == -ENOENT) {
+                       ntfs_debug("hiberfil.sys not present.  Windows is not "
+                                       "hibernated on the volume.");
+                       return 0;
+               }
+               /* A real error occured. */
+               ntfs_error(vol->sb, "Failed to find inode number for "
+                               "hiberfil.sys.");
+               return ret;
+       }
+       /* We do not care for the type of match that was found. */
+       kfree(name);
+       /* Get the inode. */
+       vi = ntfs_iget(vol->sb, MREF(mref));
+       if (IS_ERR(vi) || is_bad_inode(vi)) {
+               if (!IS_ERR(vi))
+                       iput(vi);
+               ntfs_error(vol->sb, "Failed to load hiberfil.sys.");
+               return IS_ERR(vi) ? PTR_ERR(vi) : -EIO;
+       }
+       if (unlikely(i_size_read(vi) < NTFS_HIBERFIL_HEADER_SIZE)) {
+               ntfs_debug("hiberfil.sys is smaller than 4kiB (0x%llx).  "
+                               "Windows is hibernated on the volume.  This "
+                               "is not the system volume.", i_size_read(vi));
+               goto iput_out;
+       }
+       ni = NTFS_I(vi);
+       page = ntfs_map_page(vi->i_mapping, 0);
+       if (IS_ERR(page)) {
+               ntfs_error(vol->sb, "Failed to read from hiberfil.sys.");
+               ret = PTR_ERR(page);
+               goto iput_out;
+       }
+       kaddr = (u32*)page_address(page);
+       if (*(le32*)kaddr == const_cpu_to_le32(0x72626968)/*'hibr'*/) {
+               ntfs_debug("Magic \"hibr\" found in hiberfil.sys.  Windows is "
+                               "hibernated on the volume.  This is the "
+                               "system volume.");
+               goto unm_iput_out;
+       }
+       kend = kaddr + NTFS_HIBERFIL_HEADER_SIZE/sizeof(*kaddr);
+       do {
+               if (unlikely(*kaddr)) {
+                       ntfs_debug("hiberfil.sys is larger than 4kiB "
+                                       "(0x%llx), does not contain the "
+                                       "\"hibr\" magic, and does not have a "
+                                       "zero header.  Windows is hibernated "
+                                       "on the volume.  This is not the "
+                                       "system volume.", i_size_read(vi));
+                       goto unm_iput_out;
+               }
+       } while (++kaddr < kend);
+       ntfs_debug("hiberfil.sys contains a zero header.  Windows is not "
+                       "hibernated on the volume.  This is the system "
+                       "volume.");
+       ret = 0;
+unm_iput_out:
+       ntfs_unmap_page(page);
+iput_out:
+       iput(vi);
+       return ret;
+}
+
 /**
  * load_and_init_quota - load and setup the quota file for a volume if present
  * @vol:       ntfs super block describing device whose quota file to load
@@ -1218,6 +1343,167 @@ static BOOL load_and_init_quota(ntfs_volume *vol)
        return TRUE;
 }
 
+/**
+ * load_and_init_usnjrnl - load and setup the transaction log if present
+ * @vol:       ntfs super block describing device whose usnjrnl file to load
+ *
+ * Return TRUE on success or FALSE on error.
+ *
+ * If $UsnJrnl is not present or in the process of being disabled, we set
+ * NVolUsnJrnlStamped() and return success.
+ *
+ * If the $UsnJrnl $DATA/$J attribute has a size equal to the lowest valid usn,
+ * i.e. transaction logging has only just been enabled or the journal has been
+ * stamped and nothing has been logged since, we also set NVolUsnJrnlStamped()
+ * and return success.
+ */
+static BOOL load_and_init_usnjrnl(ntfs_volume *vol)
+{
+       MFT_REF mref;
+       struct inode *tmp_ino;
+       ntfs_inode *tmp_ni;
+       struct page *page;
+       ntfs_name *name = NULL;
+       USN_HEADER *uh;
+       static const ntfschar UsnJrnl[9] = { const_cpu_to_le16('$'),
+                       const_cpu_to_le16('U'), const_cpu_to_le16('s'),
+                       const_cpu_to_le16('n'), const_cpu_to_le16('J'),
+                       const_cpu_to_le16('r'), const_cpu_to_le16('n'),
+                       const_cpu_to_le16('l'), 0 };
+       static ntfschar Max[5] = { const_cpu_to_le16('$'),
+                       const_cpu_to_le16('M'), const_cpu_to_le16('a'),
+                       const_cpu_to_le16('x'), 0 };
+       static ntfschar J[3] = { const_cpu_to_le16('$'),
+                       const_cpu_to_le16('J'), 0 };
+
+       ntfs_debug("Entering.");
+       /*
+        * Find the inode number for the transaction log file by looking up the
+        * filename $UsnJrnl in the extended system files directory $Extend.
+        */
+       down(&vol->extend_ino->i_sem);
+       mref = ntfs_lookup_inode_by_name(NTFS_I(vol->extend_ino), UsnJrnl, 8,
+                       &name);
+       up(&vol->extend_ino->i_sem);
+       if (IS_ERR_MREF(mref)) {
+               /*
+                * If the file does not exist, transaction logging is disabled,
+                * just return success.
+                */
+               if (MREF_ERR(mref) == -ENOENT) {
+                       ntfs_debug("$UsnJrnl not present.  Volume does not "
+                                       "have transaction logging enabled.");
+not_enabled:
+                       /*
+                        * No need to try to stamp the transaction log if
+                        * transaction logging is not enabled.
+                        */
+                       NVolSetUsnJrnlStamped(vol);
+                       return TRUE;
+               }
+               /* A real error occured. */
+               ntfs_error(vol->sb, "Failed to find inode number for "
+                               "$UsnJrnl.");
+               return FALSE;
+       }
+       /* We do not care for the type of match that was found. */
+       kfree(name);
+       /* Get the inode. */
+       tmp_ino = ntfs_iget(vol->sb, MREF(mref));
+       if (unlikely(IS_ERR(tmp_ino) || is_bad_inode(tmp_ino))) {
+               if (!IS_ERR(tmp_ino))
+                       iput(tmp_ino);
+               ntfs_error(vol->sb, "Failed to load $UsnJrnl.");
+               return FALSE;
+       }
+       vol->usnjrnl_ino = tmp_ino;
+       /*
+        * If the transaction log is in the process of being deleted, we can
+        * ignore it.
+        */
+       if (unlikely(vol->vol_flags & VOLUME_DELETE_USN_UNDERWAY)) {
+               ntfs_debug("$UsnJrnl in the process of being disabled.  "
+                               "Volume does not have transaction logging "
+                               "enabled.");
+               goto not_enabled;
+       }
+       /* Get the $DATA/$Max attribute. */
+       tmp_ino = ntfs_attr_iget(vol->usnjrnl_ino, AT_DATA, Max, 4);
+       if (IS_ERR(tmp_ino)) {
+               ntfs_error(vol->sb, "Failed to load $UsnJrnl/$DATA/$Max "
+                               "attribute.");
+               return FALSE;
+       }
+       vol->usnjrnl_max_ino = tmp_ino;
+       if (unlikely(i_size_read(tmp_ino) < sizeof(USN_HEADER))) {
+               ntfs_error(vol->sb, "Found corrupt $UsnJrnl/$DATA/$Max "
+                               "attribute (size is 0x%llx but should be at "
+                               "least 0x%x bytes).", i_size_read(tmp_ino),
+                               sizeof(USN_HEADER));
+               return FALSE;
+       }
+       /* Get the $DATA/$J attribute. */
+       tmp_ino = ntfs_attr_iget(vol->usnjrnl_ino, AT_DATA, J, 2);
+       if (IS_ERR(tmp_ino)) {
+               ntfs_error(vol->sb, "Failed to load $UsnJrnl/$DATA/$J "
+                               "attribute.");
+               return FALSE;
+       }
+       vol->usnjrnl_j_ino = tmp_ino;
+       /* Verify $J is non-resident and sparse. */
+       tmp_ni = NTFS_I(vol->usnjrnl_j_ino);
+       if (unlikely(!NInoNonResident(tmp_ni) || !NInoSparse(tmp_ni))) {
+               ntfs_error(vol->sb, "$UsnJrnl/$DATA/$J attribute is resident "
+                               "and/or not sparse.");
+               return FALSE;
+       }
+       /* Read the USN_HEADER from $DATA/$Max. */
+       page = ntfs_map_page(vol->usnjrnl_max_ino->i_mapping, 0);
+       if (IS_ERR(page)) {
+               ntfs_error(vol->sb, "Failed to read from $UsnJrnl/$DATA/$Max "
+                               "attribute.");
+               return FALSE;
+       }
+       uh = (USN_HEADER*)page_address(page);
+       /* Sanity check the $Max. */
+       if (unlikely(sle64_to_cpu(uh->allocation_delta) >
+                       sle64_to_cpu(uh->maximum_size))) {
+               ntfs_error(vol->sb, "Allocation delta (0x%llx) exceeds "
+                               "maximum size (0x%llx).  $UsnJrnl is corrupt.",
+                               (long long)sle64_to_cpu(uh->allocation_delta),
+                               (long long)sle64_to_cpu(uh->maximum_size));
+               ntfs_unmap_page(page);
+               return FALSE;
+       }
+       /*
+        * If the transaction log has been stamped and nothing has been written
+        * to it since, we do not need to stamp it.
+        */
+       if (unlikely(sle64_to_cpu(uh->lowest_valid_usn) >=
+                       i_size_read(vol->usnjrnl_j_ino))) {
+               if (likely(sle64_to_cpu(uh->lowest_valid_usn) ==
+                               i_size_read(vol->usnjrnl_j_ino))) {
+                       ntfs_unmap_page(page);
+                       ntfs_debug("$UsnJrnl is enabled but nothing has been "
+                                       "logged since it was last stamped.  "
+                                       "Treating this as if the volume does "
+                                       "not have transaction logging "
+                                       "enabled.");
+                       goto not_enabled;
+               }
+               ntfs_error(vol->sb, "$UsnJrnl has lowest valid usn (0x%llx) "
+                               "which is out of bounds (0x%llx).  $UsnJrnl "
+                               "is corrupt.",
+                               (long long)sle64_to_cpu(uh->lowest_valid_usn),
+                               i_size_read(vol->usnjrnl_j_ino));
+               ntfs_unmap_page(page);
+               return FALSE;
+       }
+       ntfs_unmap_page(page);
+       ntfs_debug("Done.");
+       return TRUE;
+}
+
 /**
  * load_and_init_attrdef - load the attribute definitions table for a volume
  * @vol:       ntfs super block describing device whose attrdef to load
@@ -1402,6 +1688,9 @@ static BOOL load_system_files(ntfs_volume *vol)
        MFT_RECORD *m;
        VOLUME_INFORMATION *vi;
        ntfs_attr_search_ctx *ctx;
+#ifdef NTFS_RW
+       int err;
+#endif /* NTFS_RW */
 
        ntfs_debug("Entering.");
 #ifdef NTFS_RW
@@ -1578,6 +1867,50 @@ get_ctx_vol_failed:
                /* This will prevent a read-write remount. */
                NVolSetErrors(vol);
        }
+#endif /* NTFS_RW */
+       /* Get the root directory inode so we can do path lookups. */
+       vol->root_ino = ntfs_iget(sb, FILE_root);
+       if (IS_ERR(vol->root_ino) || is_bad_inode(vol->root_ino)) {
+               if (!IS_ERR(vol->root_ino))
+                       iput(vol->root_ino);
+               ntfs_error(sb, "Failed to load root directory.");
+               goto iput_logfile_err_out;
+       }
+#ifdef NTFS_RW
+       /*
+        * Check if Windows is suspended to disk on the target volume.  If it
+        * is hibernated, we must not write *anything* to the disk so set
+        * NVolErrors() without setting the dirty volume flag and mount
+        * read-only.  This will prevent read-write remounting and it will also
+        * prevent all writes.
+        */
+       err = check_windows_hibernation_status(vol);
+       if (unlikely(err)) {
+               static const char *es1a = "Failed to determine if Windows is "
+                               "hibernated";
+               static const char *es1b = "Windows is hibernated";
+               static const char *es2 = ".  Run chkdsk.";
+               const char *es1;
+
+               es1 = err < 0 ? es1a : es1b;
+               /* If a read-write mount, convert it to a read-only mount. */
+               if (!(sb->s_flags & MS_RDONLY)) {
+                       if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
+                                       ON_ERRORS_CONTINUE))) {
+                               ntfs_error(sb, "%s and neither on_errors="
+                                               "continue nor on_errors="
+                                               "remount-ro was specified%s",
+                                               es1, es2);
+                               goto iput_root_err_out;
+                       }
+                       sb->s_flags |= MS_RDONLY | MS_NOATIME | MS_NODIRATIME;
+                       ntfs_error(sb, "%s.  Mounting read-only%s", es1, es2);
+               } else
+                       ntfs_warning(sb, "%s.  Will not be able to remount "
+                                       "read-write%s", es1, es2);
+               /* This will prevent a read-write remount. */
+               NVolSetErrors(vol);
+       }
        /* If (still) a read-write mount, mark the volume dirty. */
        if (!(sb->s_flags & MS_RDONLY) &&
                        ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY)) {
@@ -1591,7 +1924,7 @@ get_ctx_vol_failed:
                        ntfs_error(sb, "%s and neither on_errors=continue nor "
                                        "on_errors=remount-ro was specified%s",
                                        es1, es2);
-                       goto iput_logfile_err_out;
+                       goto iput_root_err_out;
                }
                ntfs_error(sb, "%s.  Mounting read-only%s", es1, es2);
                sb->s_flags |= MS_RDONLY | MS_NOATIME | MS_NODIRATIME;
@@ -1618,7 +1951,7 @@ get_ctx_vol_failed:
                        ntfs_error(sb, "%s and neither on_errors=continue nor "
                                        "on_errors=remount-ro was specified%s",
                                        es1, es2);
-                       goto iput_logfile_err_out;
+                       goto iput_root_err_out;
                }
                ntfs_error(sb, "%s.  Mounting read-only%s", es1, es2);
                sb->s_flags |= MS_RDONLY | MS_NOATIME | MS_NODIRATIME;
@@ -1637,23 +1970,15 @@ get_ctx_vol_failed:
                        ntfs_error(sb, "%s and neither on_errors=continue nor "
                                        "on_errors=remount-ro was specified%s",
                                        es1, es2);
-                       goto iput_logfile_err_out;
+                       goto iput_root_err_out;
                }
                ntfs_error(sb, "%s.  Mounting read-only%s", es1, es2);
                sb->s_flags |= MS_RDONLY | MS_NOATIME | MS_NODIRATIME;
                NVolSetErrors(vol);
        }
 #endif /* NTFS_RW */
-       /* Get the root directory inode. */
-       vol->root_ino = ntfs_iget(sb, FILE_root);
-       if (IS_ERR(vol->root_ino) || is_bad_inode(vol->root_ino)) {
-               if (!IS_ERR(vol->root_ino))
-                       iput(vol->root_ino);
-               ntfs_error(sb, "Failed to load root directory.");
-               goto iput_logfile_err_out;
-       }
        /* If on NTFS versions before 3.0, we are done. */
-       if (vol->major_ver < 3)
+       if (unlikely(vol->major_ver < 3))
                return TRUE;
        /* NTFS 3.0+ specific initialization. */
        /* Get the security descriptors inode. */
@@ -1664,7 +1989,7 @@ get_ctx_vol_failed:
                ntfs_error(sb, "Failed to load $Secure.");
                goto iput_root_err_out;
        }
-       // FIXME: Initialize security.
+       // TODO: Initialize security.
        /* Get the extended system files' directory inode. */
        vol->extend_ino = ntfs_iget(sb, FILE_Extend);
        if (IS_ERR(vol->extend_ino) || is_bad_inode(vol->extend_ino)) {
@@ -1715,10 +2040,60 @@ get_ctx_vol_failed:
                sb->s_flags |= MS_RDONLY | MS_NOATIME | MS_NODIRATIME;
                NVolSetErrors(vol);
        }
-       // TODO: Delete or checkpoint the $UsnJrnl if it exists.
+       /*
+        * Find the transaction log file ($UsnJrnl), load it if present, check
+        * it, and set it up.
+        */
+       if (!load_and_init_usnjrnl(vol)) {
+               static const char *es1 = "Failed to load $UsnJrnl";
+               static const char *es2 = ".  Run chkdsk.";
+
+               /* If a read-write mount, convert it to a read-only mount. */
+               if (!(sb->s_flags & MS_RDONLY)) {
+                       if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
+                                       ON_ERRORS_CONTINUE))) {
+                               ntfs_error(sb, "%s and neither on_errors="
+                                               "continue nor on_errors="
+                                               "remount-ro was specified%s",
+                                               es1, es2);
+                               goto iput_usnjrnl_err_out;
+                       }
+                       sb->s_flags |= MS_RDONLY | MS_NOATIME | MS_NODIRATIME;
+                       ntfs_error(sb, "%s.  Mounting read-only%s", es1, es2);
+               } else
+                       ntfs_warning(sb, "%s.  Will not be able to remount "
+                                       "read-write%s", es1, es2);
+               /* This will prevent a read-write remount. */
+               NVolSetErrors(vol);
+       }
+       /* If (still) a read-write mount, stamp the transaction log. */
+       if (!(sb->s_flags & MS_RDONLY) && !ntfs_stamp_usnjrnl(vol)) {
+               static const char *es1 = "Failed to stamp transaction log "
+                               "($UsnJrnl)";
+               static const char *es2 = ".  Run chkdsk.";
+
+               /* Convert to a read-only mount. */
+               if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
+                               ON_ERRORS_CONTINUE))) {
+                       ntfs_error(sb, "%s and neither on_errors=continue nor "
+                                       "on_errors=remount-ro was specified%s",
+                                       es1, es2);
+                       goto iput_usnjrnl_err_out;
+               }
+               ntfs_error(sb, "%s.  Mounting read-only%s", es1, es2);
+               sb->s_flags |= MS_RDONLY | MS_NOATIME | MS_NODIRATIME;
+               NVolSetErrors(vol);
+       }
 #endif /* NTFS_RW */
        return TRUE;
 #ifdef NTFS_RW
+iput_usnjrnl_err_out:
+       if (vol->usnjrnl_j_ino)
+               iput(vol->usnjrnl_j_ino);
+       if (vol->usnjrnl_max_ino)
+               iput(vol->usnjrnl_max_ino);
+       if (vol->usnjrnl_ino)
+               iput(vol->usnjrnl_ino);
 iput_quota_err_out:
        if (vol->quota_q_ino)
                iput(vol->quota_q_ino);
@@ -1792,6 +2167,12 @@ static void ntfs_put_super(struct super_block *sb)
 
        /* NTFS 3.0+ specific. */
        if (vol->major_ver >= 3) {
+               if (vol->usnjrnl_j_ino)
+                       ntfs_commit_inode(vol->usnjrnl_j_ino);
+               if (vol->usnjrnl_max_ino)
+                       ntfs_commit_inode(vol->usnjrnl_max_ino);
+               if (vol->usnjrnl_ino)
+                       ntfs_commit_inode(vol->usnjrnl_ino);
                if (vol->quota_q_ino)
                        ntfs_commit_inode(vol->quota_q_ino);
                if (vol->quota_ino)
@@ -1847,6 +2228,18 @@ static void ntfs_put_super(struct super_block *sb)
        /* NTFS 3.0+ specific clean up. */
        if (vol->major_ver >= 3) {
 #ifdef NTFS_RW
+               if (vol->usnjrnl_j_ino) {
+                       iput(vol->usnjrnl_j_ino);
+                       vol->usnjrnl_j_ino = NULL;
+               }
+               if (vol->usnjrnl_max_ino) {
+                       iput(vol->usnjrnl_max_ino);
+                       vol->usnjrnl_max_ino = NULL;
+               }
+               if (vol->usnjrnl_ino) {
+                       iput(vol->usnjrnl_ino);
+                       vol->usnjrnl_ino = NULL;
+               }
                if (vol->quota_q_ino) {
                        iput(vol->quota_q_ino);
                        vol->quota_q_ino = NULL;
@@ -2463,6 +2856,18 @@ static int ntfs_fill_super(struct super_block *sb, void *opt, const int silent)
        /* NTFS 3.0+ specific clean up. */
        if (vol->major_ver >= 3) {
 #ifdef NTFS_RW
+               if (vol->usnjrnl_j_ino) {
+                       iput(vol->usnjrnl_j_ino);
+                       vol->usnjrnl_j_ino = NULL;
+               }
+               if (vol->usnjrnl_max_ino) {
+                       iput(vol->usnjrnl_max_ino);
+                       vol->usnjrnl_max_ino = NULL;
+               }
+               if (vol->usnjrnl_ino) {
+                       iput(vol->usnjrnl_ino);
+                       vol->usnjrnl_ino = NULL;
+               }
                if (vol->quota_q_ino) {
                        iput(vol->quota_q_ino);
                        vol->quota_q_ino = NULL;