NTFS filename namespace issues...
authorEric Biggers <ebiggers3@gmail.com>
Wed, 30 Jan 2013 04:58:55 +0000 (22:58 -0600)
committerEric Biggers <ebiggers3@gmail.com>
Wed, 30 Jan 2013 04:58:55 +0000 (22:58 -0600)
src/dentry.h
src/ntfs-apply.c
src/verify.c

index d39d223..fe53ecf 100644 (file)
@@ -252,6 +252,8 @@ struct wim_inode {
        /* %true iff verify_inode() has run on this inode. */
        u8 i_verified : 1;
 
+       u8 i_dos_name_extracted : 1;
+
        /* Number of alternate data streams associated with this inode */
        u16 i_num_ads;
 
index 998702f..39de5d6 100644 (file)
@@ -197,6 +197,12 @@ static ntfs_inode *dentry_open_parent_ni(const struct wim_dentry *dentry,
  * Or, in other words, this adds a new name @from_dentry->full_path_utf8 to an
  * existing NTFS inode which already has a name @inode->i_extracted_file.
  *
+ * The new name is made in the POSIX namespace (this is the behavior of
+ * ntfs_link()).  I am assuming this is an acceptable behavior; however, it's
+ * possible that the original name was actually in the Win32 namespace.  Note
+ * that the WIM format does not provide enough information to distinguish Win32
+ * names from POSIX names in all cases.
+ *
  * Return 0 on success, nonzero on failure.
  */
 static int apply_ntfs_hardlink(const struct wim_dentry *from_dentry,
@@ -356,57 +362,6 @@ static int apply_reparse_data(ntfs_inode *ni, const struct wim_dentry *dentry,
        return 0;
 }
 
-static int do_apply_dentry_ntfs(struct wim_dentry *dentry, ntfs_inode *dir_ni,
-                               struct apply_args *args);
-
-/*
- * If @dentry is part of a hard link group, search for hard-linked dentries in
- * the same directory that have a nonempty DOS (short) filename.  There should
- * be exactly 0 or 1 such dentries.  If there is 1, extract that dentry first,
- * so that the DOS name is correctly associated with the corresponding long name
- * in the Win32 namespace, and not any of the additional names in the POSIX
- * namespace created from hard links.
- */
-static int preapply_dentry_with_dos_name(struct wim_dentry *dentry,
-                                        ntfs_inode **dir_ni_p,
-                                        struct apply_args *args)
-{
-       struct wim_dentry *other;
-       struct wim_dentry *dentry_with_dos_name;
-
-       dentry_with_dos_name = NULL;
-       inode_for_each_dentry(other, dentry->d_inode) {
-               if (other != dentry && (dentry->parent == other->parent)
-                   && other->short_name_len)
-               {
-                       if (dentry_with_dos_name) {
-                               ERROR("Found multiple DOS names for file `%s' "
-                                     "in the same directory",
-                                     dentry_with_dos_name->full_path_utf8);
-                               return WIMLIB_ERR_INVALID_DENTRY;
-                       }
-                       dentry_with_dos_name = other;
-               }
-       }
-       /* If there's a dentry with a DOS name, extract it first */
-       if (dentry_with_dos_name && !dentry_with_dos_name->is_extracted) {
-               int ret;
-               ntfs_volume *vol = (*dir_ni_p)->vol;
-
-               DEBUG("pre-applying DOS name `%s'",
-                     dentry_with_dos_name->full_path_utf8);
-               ret = do_apply_dentry_ntfs(dentry_with_dos_name,
-                                          *dir_ni_p, args);
-               if (ret != 0)
-                       return ret;
-
-               *dir_ni_p = dentry_open_parent_ni(dentry, vol);
-               if (!*dir_ni_p)
-                       return WIMLIB_ERR_NTFS_3G;
-       }
-       return 0;
-}
-
 /*
  * Applies a WIM dentry to a NTFS filesystem.
  *
@@ -428,16 +383,6 @@ static int do_apply_dentry_ntfs(struct wim_dentry *dentry, ntfs_inode *dir_ni,
        if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
                type = S_IFDIR;
        } else {
-               /* If this dentry is hard-linked to any other dentries in the
-                * same directory, make sure to apply the one (if any) with a
-                * DOS name first.  Otherwise, NTFS-3g might not assign the file
-                * names correctly. */
-               if (dentry->short_name_len == 0) {
-                       ret = preapply_dentry_with_dos_name(dentry,
-                                                           &dir_ni, args);
-                       if (ret != 0)
-                               return ret;
-               }
                type = S_IFREG;
                if (inode->i_nlink > 1) {
                        /* Inode has multiple dentries referencing it. */
@@ -448,10 +393,7 @@ static int do_apply_dentry_ntfs(struct wim_dentry *dentry, ntfs_inode *dir_ni,
                                 * extracting the file data. */
                                ret = apply_ntfs_hardlink(dentry, inode,
                                                          &dir_ni);
-                               if (ret == 0)
-                                       goto out_set_dos_name;
-                               else
-                                       goto out_close_dir_ni;
+                               goto out_close_dir_ni;
                        } else {
                                /* None of the dentries of this inode have been
                                 * extracted yet, so go ahead and extract the
@@ -502,10 +444,8 @@ static int do_apply_dentry_ntfs(struct wim_dentry *dentry, ntfs_inode *dir_ni,
                        goto out_close_dir_ni;
        }
 
-out_set_dos_name:
        /* Set DOS (short) name if given */
        if (dentry->short_name_len != 0) {
-
                char *short_name_utf8;
                size_t short_name_utf8_len;
                ret = utf16_to_utf8(dentry->short_name,
@@ -515,20 +455,6 @@ out_set_dos_name:
                if (ret != 0)
                        goto out_close_dir_ni;
 
-               if (!ni) {
-                       /* Hardlink was made; linked inode needs to be looked up
-                        * again.  */
-                       ni = ntfs_pathname_to_inode(vol, dir_ni,
-                                                   dentry->file_name_utf8);
-                       if (!ni) {
-                               ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
-                                                dentry->full_path_utf8);
-                               FREE(short_name_utf8);
-                               ret = WIMLIB_ERR_NTFS_3G;
-                               goto out_close_dir_ni;
-                       }
-               }
-
                DEBUG("Setting short (DOS) name of `%s' to %s",
                      dentry->full_path_utf8, short_name_utf8);
 
@@ -541,9 +467,8 @@ out_set_dos_name:
                        ret = WIMLIB_ERR_NTFS_3G;
                }
                /* inodes have been closed by ntfs_set_ntfs_dos_name(). */
-               return ret;
+               goto out;
        }
-
 out_close_dir_ni:
        if (dir_ni) {
                if (ni) {
@@ -560,9 +485,8 @@ out_close_dir_ni:
                        ERROR_WITH_ERRNO("Failed to close inode of directory "
                                         "containing `%s'", dentry->full_path_utf8);
                }
-       } else {
-               wimlib_assert(ni == NULL);
        }
+out:
        return ret;
 }
 
@@ -592,16 +516,84 @@ int apply_dentry_ntfs(struct wim_dentry *dentry, void *arg)
        struct apply_args *args = arg;
        ntfs_volume *vol = args->vol;
        WIMStruct *w = args->w;
-       ntfs_inode *dir_ni;
+       struct wim_dentry *orig_dentry;
+       struct wim_dentry *other;
+       int ret;
 
+       /* Treat the root dentry specially. */
        if (dentry_is_root(dentry))
                return apply_root_dentry_ntfs(dentry, vol, w);
+       /* NTFS filename namespaces need careful consideration.  A name for a
+        * NTFS file may be in either the POSIX, Win32, DOS, or Win32+DOS
+        * namespaces.  The following list of assumptions and facts clarify the
+        * way that WIM dentries are mapped to NTFS files.  The statements
+        * marked ASSUMPTION are statements I am assuming to be true due to the
+        * lack of documentation; they are verified in verify_dentry() and
+        * verify_inode() in verify.c.
+        *
+        * - ASSUMPTION: The root WIM dentry has neither a "long name" nor a
+        *   "short name".
+        *
+        * - ASSUMPTION: Every WIM dentry other than the root directory provides
+        *   a non-empty "long name" and a possibly empty "short name".  The
+        *   "short name" corresponds to the DOS name of the file, while the
+        *   "long name" may be Win32 or POSIX.
+        *
+        *   XXX It may actually be legal to have a short name but no long name
+        *
+        * - FACT: If a dentry has a "long name" but no "short name", then it is
+        *   ambigious whether the name is POSIX or Win32+DOS, unless the name
+        *   is a valid POSIX name but not a valid Win32+DOS name.  wimlib
+        *   currently will always create POSIX names for these files, as this
+        *   is the behavior of the ntfs_create() and ntfs_link() functions.
+        *
+        * - FACT: Multiple WIM dentries may correspond to the same underlying
+        *   inode, as provided at this point in the code by the d_inode member.
+        */
+
+
+       /* Currently wimlib does not apply DOS names to hard linked files due to
+        * issues with ntfs-3g, so the following is commented out. */
+#if 0
+again:
+       /*
+        * libntfs-3g requires that for an NTFS inode with a DOS name, the
+        * corresponding long name be extracted first so that the DOS name is
+        * associated with the correct long name.  Note that by the last
+        * ASSUMPTION above, a NTFS inode can have at most one DOS name (i.e. a
+        * WIM inode can have at most one non-empty short name).
+        *
+        * Therefore, search for an alias of this dentry that has a short name,
+        * and extract it first unless it was already extracted.
+        */
+       orig_dentry = NULL;
+       if (!dentry->d_inode->i_dos_name_extracted) {
+               inode_for_each_dentry(other, dentry->d_inode) {
+                       if (other->short_name_len && other != dentry &&
+                           !other->is_extracted)
+                       {
+                               orig_dentry = dentry;
+                               dentry = other;
+                               break;
+                       }
+               }
+               dentry->d_inode->i_dos_name_extracted = 1;
+       }
+#endif
 
-       dir_ni = dentry_open_parent_ni(dentry, vol);
+       ntfs_inode *dir_ni = dentry_open_parent_ni(dentry, vol);
        if (dir_ni)
-               return do_apply_dentry_ntfs(dentry, dir_ni, arg);
+               ret = do_apply_dentry_ntfs(dentry, dir_ni, arg);
        else
-               return WIMLIB_ERR_NTFS_3G;
+               ret = WIMLIB_ERR_NTFS_3G;
+
+#if 0
+       if (ret == 0 && orig_dentry) {
+               dentry = orig_dentry;
+               goto again;
+       }
+#endif
+       return ret;
 }
 
 /* Transfers the 100-nanosecond precision timestamps from a WIM dentry to a NTFS
index 6602832..166a9e8 100644 (file)
@@ -35,14 +35,18 @@ static int verify_inode(struct wim_inode *inode, const WIMStruct *w)
        const struct wim_lookup_table *table = w->lookup_table;
        const struct wim_security_data *sd = wim_const_security_data(w);
        const struct wim_dentry *first_dentry = inode_first_dentry(inode);
+       const struct wim_dentry *dentry;
        int ret = WIMLIB_ERR_INVALID_DENTRY;
 
-       /* Check the security ID */
+       /* Check the security ID.  -1 is valid and means "no security
+        * descriptor".  Anything else has to be a valid index into the WIM
+        * image's security descriptors table. */
        if (inode->i_security_id < -1) {
                ERROR("Dentry `%s' has an invalid security ID (%d)",
                        first_dentry->full_path_utf8, inode->i_security_id);
                goto out;
        }
+
        if (inode->i_security_id >= sd->num_entries) {
                ERROR("Dentry `%s' has an invalid security ID (%d) "
                      "(there are only %u entries in the security table)",
@@ -51,9 +55,11 @@ static int verify_inode(struct wim_inode *inode, const WIMStruct *w)
                goto out;
        }
 
-       /* Check that lookup table entries for all the resources exist, except
-        * if the SHA1 message digest is all 0's, which indicates there is
-        * intentionally no resource there.  */
+       /* Check that lookup table entries for all the inode's stream exist,
+        * except if the SHA1 message digest is all 0's, which indicates an
+        * empty stream. 
+        *
+        * This check is skipped on split WIMs. */
        if (w->hdr.total_parts == 1) {
                for (unsigned i = 0; i <= inode->i_num_ads; i++) {
                        struct wim_lookup_table_entry *lte;
@@ -108,7 +114,7 @@ static int verify_inode(struct wim_inode *inode, const WIMStruct *w)
                }
        }
 
-       /* Make sure there is only one un-named stream. */
+       /* Make sure there is only one unnamed data stream. */
        unsigned num_unnamed_streams = 0;
        for (unsigned i = 0; i <= inode->i_num_ads; i++) {
                const u8 *hash;
@@ -121,6 +127,34 @@ static int verify_inode(struct wim_inode *inode, const WIMStruct *w)
                      first_dentry->full_path_utf8, num_unnamed_streams);
                goto out;
        }
+
+       /* Currently ignoring this test because wimlib does not apply DOS names
+        * to a file with hard links (see apply_dentry_ntfs()). */
+#if 0
+       /* Files cannot have multiple DOS names, even if they have multiple
+        * names in multiple directories (i.e. hard links) ??? XXX */
+       const struct wim_dentry *dentry_with_dos_name = NULL;
+       inode_for_each_dentry(dentry, inode) {
+               if (dentry->short_name_len) {
+                       if (dentry_with_dos_name) {
+                               ERROR("Hard-linked file has a DOS name at "
+                                     "both `%s' and `%s'",
+                                     dentry_with_dos_name->full_path_utf8,
+                                     dentry->full_path_utf8);
+                               goto out;
+                       }
+                       dentry_with_dos_name = dentry;
+               }
+       }
+#endif
+
+       /* Directories with multiple links have not been tested. XXX */
+       if (inode->i_nlink > 1 && inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
+               ERROR("Hard-linked directory `%s' is unsupported",
+                     first_dentry->full_path_utf8);
+               goto out;
+       }
+
        inode->i_verified = 1;
        ret = 0;
 out:
@@ -132,26 +166,30 @@ int verify_dentry(struct wim_dentry *dentry, void *wim)
 {
        int ret;
 
+       /* Verify the associated inode, but only one time no matter how many
+        * dentries it has. */
        if (!dentry->d_inode->i_verified) {
                ret = verify_inode(dentry->d_inode, wim);
                if (ret != 0)
                        return ret;
        }
 
-       /* Cannot have a short name but no long name */
-       if (dentry->short_name_len && !dentry->file_name_len) {
-               ERROR("Dentry `%s' has a short name but no long name",
-                     dentry->full_path_utf8);
-               return WIMLIB_ERR_INVALID_DENTRY;
-       }
-
-       /* Make sure root dentry is unnamed */
+       /* Make sure root dentry is unnamed, while every other dentry has at
+        * least a long name.
+        *
+        * XXX Files having only a DOS name may be acceptable. */
        if (dentry_is_root(dentry)) {
-               if (dentry->file_name_len) {
+               if (dentry->file_name_len || dentry->short_name_len) {
                        ERROR("The root dentry is named `%s', but it must "
                              "be unnamed", dentry->file_name_utf8);
                        return WIMLIB_ERR_INVALID_DENTRY;
                }
+       } else {
+               if (!dentry->file_name_len) {
+                       ERROR("Dentry `%s' has no long name",
+                             dentry->full_path_utf8);
+                       return WIMLIB_ERR_INVALID_DENTRY;
+               }
        }
 
 #if 0