wlfuzz: generate a wider range of filenames
authorEric Biggers <ebiggers3@gmail.com>
Sat, 25 Jun 2016 00:41:28 +0000 (19:41 -0500)
committerEric Biggers <ebiggers3@gmail.com>
Sat, 2 Jul 2016 14:58:24 +0000 (09:58 -0500)
src/test_support.c

index 0db98e6..313ad08 100644 (file)
@@ -468,37 +468,142 @@ set_random_streams(struct wim_inode *inode, struct generation_context *ctx,
        return 0;
 }
 
+static inline bool
+is_valid_windows_filename_char(utf16lechar c)
+{
+       return le16_to_cpu(c) > 31 &&
+               c != cpu_to_le16('/') &&
+               c != cpu_to_le16('<') &&
+               c != cpu_to_le16('>') &&
+               c != cpu_to_le16(':') &&
+               c != cpu_to_le16('"') &&
+               c != cpu_to_le16('/' ) &&
+               c != cpu_to_le16('\\') &&
+               c != cpu_to_le16('|') &&
+               c != cpu_to_le16('?') &&
+               c != cpu_to_le16('*');
+}
+
+/* Is the character valid in a filename on the current platform? */
+static inline bool
+is_valid_filename_char(utf16lechar c)
+{
+#ifdef __WIN32__
+       return is_valid_windows_filename_char(c);
+#else
+       return c != cpu_to_le16('\0') && c != cpu_to_le16('/');
+#endif
+}
+
+/* Generate a random filename and return its length. */
 static int
-generate_random_file_name(tchar name[], int max_len,
-                         struct generation_context *ctx)
+generate_random_filename(utf16lechar name[], int max_len,
+                        struct generation_context *ctx)
 {
-       int length;
+       int len;
+
+       /* Choose the length of the name. */
        switch (rand32() % 8) {
        default:
                /* short name  */
-               length = 1 + (rand32() % 6);
+               len = 1 + (rand32() % 6);
                break;
        case 2:
        case 3:
        case 4:
                /* medium-length name  */
-               length = 7 + (rand32() % 8);
+               len = 7 + (rand32() % 8);
                break;
        case 5:
        case 6:
                /* long name  */
-               length = 15 + (rand32() % 15);
+               len = 15 + (rand32() % 15);
                break;
        case 7:
                /* very long name  */
-               length = 30 + (rand32() % 90);
+               len = 30 + (rand32() % 90);
                break;
        }
-       length = min(length, max_len);
-       for (int i = 0; i < length; i++)
-               name[i] = 'a' + (rand32() % 26);
-       name[length] = 0;
-       return length;
+       len = min(len, max_len);
+
+       /* Generate the characters in the name. */
+       for (int i = 0; i < len; i++) {
+               do {
+                       name[i] = rand16();
+               } while (!is_valid_filename_char(name[i]));
+       }
+
+       /* Add a null terminator. */
+       name[len] = cpu_to_le16('\0');
+
+       return len;
+}
+
+/* The set of characters which are valid in short filenames. */
+static const char valid_short_name_chars[] = {
+       'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+       'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+       '!', '#', '$', '%', '&', '\'', '(', ')', '-', '@', '^', '_', '`', '{',
+       '}', '~',
+       /* TODO: why doesn't Windows accept these characters? */
+       /* ' ', */
+       /*128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141,*/
+       /*142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155,*/
+       /*156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169,*/
+       /*170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183,*/
+       /*184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197,*/
+       /*198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211,*/
+       /*212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225,*/
+       /*226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,*/
+       /*240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253,*/
+       /*254, 255*/
+};
+
+static int
+generate_short_name_component(utf16lechar p[], int len)
+{
+       for (int i = 0; i < len; i++) {
+               char c = valid_short_name_chars[rand32() %
+                                               ARRAY_LEN(valid_short_name_chars)];
+               p[i] = cpu_to_le16(c);
+       }
+#if 0 /* TODO: we aren't using space yet anyway */
+       while (len > 1 && p[len - 1] == cpu_to_le16(' '))
+               len--;
+       if (p[len - 1] == cpu_to_le16(' '))
+               p[len - 1] = cpu_to_le16('A');
+#endif
+       return len;
+}
+
+/* Generate a random short (8.3) filename and return its length.
+ * The @name array must have length >= 13 (8 + 1 + 3 + 1). */
+static int
+generate_random_short_name(utf16lechar name[], struct generation_context *ctx)
+{
+       /*
+        * Legal short names on Windows consist of 1 to 8 characters, optionally
+        * followed by a dot then 1 to 3 more characters.  Only certain
+        * characters are allowed.  In addition, trailing spaces are not
+        * significant.
+        */
+       int base_len = 1 + (rand32() % 8);
+       int ext_len = rand32() % 4;
+       int total_len;
+
+       base_len = generate_short_name_component(name, base_len);
+
+       if (ext_len) {
+               name[base_len] = cpu_to_le16('.');
+               ext_len = generate_short_name_component(&name[base_len + 1],
+                                                       ext_len);
+               total_len = base_len + 1 + ext_len;
+       } else {
+               total_len = base_len;
+       }
+       name[total_len] = cpu_to_le16('\0');
+       return total_len;
 }
 
 static u64
@@ -526,8 +631,10 @@ select_num_children(u32 depth, struct generation_context *ctx)
 }
 
 static bool
-is_name_forbidden_in_win32_namespace(const utf16lechar *name)
+is_name_valid_in_win32_namespace(const utf16lechar *name)
 {
+       const utf16lechar *p;
+
        static const utf16lechar forbidden_names[][5] = {
                { cpu_to_le16('C'), cpu_to_le16('O'), cpu_to_le16('N'), },
                { cpu_to_le16('P'), cpu_to_le16('R'), cpu_to_le16('N'), },
@@ -553,64 +660,58 @@ is_name_forbidden_in_win32_namespace(const utf16lechar *name)
                { cpu_to_le16('L'), cpu_to_le16('P'), cpu_to_le16('T'), cpu_to_le16('9'), },
        };
 
-       if (!name)
+       /* The name must be nonempty. */
+       if (!name || !*name)
+               return false;
+
+       /* All characters must be valid on Windows. */
+       for (p = name; *p; p++)
+               if (!is_valid_windows_filename_char(*p))
+                       return false;
+
+       /* There can't be a trailing dot or space. */
+       if (p[-1] == cpu_to_le16('.') || p[-1] == cpu_to_le16(' '))
                return false;
 
+       /* The name can't be one of the reserved names (case insensitively). */
        for (size_t i = 0; i < ARRAY_LEN(forbidden_names); i++)
                if (!cmp_utf16le_strings_z(forbidden_names[i], name, true))
-                       return true;
+                       return false;
 
-       return false;
+       return true;
 }
 
 static int
 set_random_short_name(struct wim_dentry *dir, struct wim_dentry *child,
                      struct generation_context *ctx)
 {
-       tchar name[12 + 1];
-       int ret;
-       const utf16lechar *short_name;
+       utf16lechar name[12 + 1];
+       int name_len;
        u32 hash;
        struct wim_dentry **bucket;
 
        /* If the long name is not allowed in the Win32 namespace, then it
         * cannot be assigned a corresponding short name.  */
-       if (is_name_forbidden_in_win32_namespace(child->d_name))
+       if (!is_name_valid_in_win32_namespace(child->d_name))
                return 0;
 
 retry:
        /* Don't select a short name that is already used by a long name within
         * the same directory.  */
        do {
-               int len = generate_random_file_name(name, 12, ctx);
-
-               /* Legal short names on Windows take one of the following forms:
-                *
-                *    - 1 to 8 characters
-                *    - 1 to 8 characters, then a dot, then 1 to 3 characters */
-               if (len >= 9) {
-                       if (len == 9)
-                               len--;
-                       else
-                               name[8] = T('.');
-               }
-               name[len] = 0;
-       } while (get_dentry_child_with_name(dir, name,
-                                           WIMLIB_CASE_PLATFORM_DEFAULT));
+               name_len = generate_random_short_name(name, ctx);
+       } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
+                                                   WIMLIB_CASE_INSENSITIVE));
 
 
        /* Don't select a short name that is already used by another short name
         * within the same directory.  */
        hash = 0;
-       for (const tchar *p = name; *p; p++)
-               hash = (hash * 31) + totlower(*p);
-       ret = tstr_get_utf16le(name, &short_name);
-       if (ret)
-               return ret;
+       for (const utf16lechar *p = name; *p; p++)
+               hash = (hash * 31) + *p;
        FREE(child->d_short_name);
-       child->d_short_name = utf16le_dup(short_name);
-       child->d_short_name_nbytes = utf16le_len_bytes(short_name);
-       tstr_put_utf16le(short_name);
+       child->d_short_name = memdup(name, (name_len + 1) * 2);
+       child->d_short_name_nbytes = name_len * 2;
 
        if (!child->d_short_name)
                return WIMLIB_ERR_NOMEM;
@@ -618,12 +719,15 @@ retry:
        bucket = &ctx->used_short_names[hash % ARRAY_LEN(ctx->used_short_names)];
 
        for (struct wim_dentry *d = *bucket; d != NULL;
-            d = d->d_next_extraction_alias)
-               if (!cmp_utf16le_strings_z(child->d_short_name,
-                                          d->d_short_name, true))
+            d = d->d_next_extraction_alias) {
+               if (!cmp_utf16le_strings(child->d_short_name, name_len,
+                                        d->d_short_name, d->d_short_name_nbytes / 2,
+                                        true)) {
                        goto retry;
+               }
+       }
 
-       if (is_name_forbidden_in_win32_namespace(child->d_short_name))
+       if (!is_name_valid_in_win32_namespace(child->d_short_name))
                goto retry;
 
        child->d_next_extraction_alias = *bucket;
@@ -659,34 +763,43 @@ generate_dentry_tree_recursive(struct wim_dentry *dir, u32 depth,
        for (u32 i = 0; i < num_children; i++) {
 
                /* Generate the next child dentry.  */
-
-               tchar name[128 + 1];
-               struct wim_dentry *duplicate;
                struct wim_inode *inode;
                u64 ino;
                bool is_directory;
+               utf16lechar name[63 + 1]; /* for UNIX extraction: 63 * 4 <= 255 */
+               int name_len;
+               struct wim_dentry *duplicate;
 
-               /* Choose a long filename that is unique within the directory.*/
-               do {
-                       generate_random_file_name(name, 128, ctx);
-               } while (get_dentry_child_with_name(dir, name,
-                                                   WIMLIB_CASE_PLATFORM_DEFAULT));
-
-               /* Decide whether to create a directory or not.
-                * If not a directory, also decide on the inode number (i.e. we
-                * may generate a "hard link" to an existing file).  */
+               /* Decide whether to create a directory or not.  If not a
+                * directory, also decide on the inode number (i.e. we may
+                * generate a "hard link" to an existing file).  */
                is_directory = ((rand32() % 16) <= 6);
                if (is_directory)
                        ino = 0;
                else
                        ino = select_inode_number(ctx);
 
-               /* Create the dentry and add it to the directory.  */
-               ret = inode_table_new_dentry(ctx->params->inode_table, name,
+               /* Create the dentry. */
+               ret = inode_table_new_dentry(ctx->params->inode_table, NULL,
                                             ino, 0, is_directory, &child);
                if (ret)
                        return ret;
 
+               /* Choose a filename that is unique within the directory.*/
+               do {
+                       name_len = generate_random_filename(name,
+                                                           ARRAY_LEN(name) - 1,
+                                                           ctx);
+               } while (get_dentry_child_with_utf16le_name(dir, name, name_len * 2,
+                                                           WIMLIB_CASE_PLATFORM_DEFAULT));
+
+               ret = dentry_set_name_utf16le(child, name, name_len * 2);
+               if (ret) {
+                       free_dentry(child);
+                       return ret;
+               }
+
+               /* Add the dentry to the directory. */
                duplicate = dentry_add_child(dir, child);
                wimlib_assert(!duplicate);