+static const utf16lechar volume_junction_prefix[11] = {
+ cpu_to_le16('\\'),
+ cpu_to_le16('?'),
+ cpu_to_le16('?'),
+ cpu_to_le16('\\'),
+ cpu_to_le16('V'),
+ cpu_to_le16('o'),
+ cpu_to_le16('l'),
+ cpu_to_le16('u'),
+ cpu_to_le16('m'),
+ cpu_to_le16('e'),
+ cpu_to_le16('{'),
+};
+
+enum {
+ SUBST_NAME_IS_RELATIVE_LINK = -1,
+ SUBST_NAME_IS_VOLUME_JUNCTION = -2,
+ SUBST_NAME_IS_UNKNOWN = -3,
+};
+
+/* Parse the "substitute name" (link target) from a symbolic link or junction
+ * reparse point.
+ *
+ * Return value is:
+ *
+ * Non-negative integer:
+ * The name is an absolute symbolic link in one of several formats,
+ * and the return value is the number of UTF-16LE characters that need to
+ * be advanced to reach a simple "absolute" path starting with a backslash
+ * (i.e. skip over \??\ and/or drive letter)
+ * Negative integer:
+ * SUBST_NAME_IS_VOLUME_JUNCTION:
+ * The name is a volume junction.
+ * SUBST_NAME_IS_RELATIVE_LINK:
+ * The name is a relative symbolic link.
+ * SUBST_NAME_IS_UNKNOWN:
+ * The name does not appear to be a valid symbolic link, junction,
+ * or mount point.
+ */
+static int
+parse_substitute_name(const utf16lechar *substitute_name,
+ u16 substitute_name_nbytes, u32 rptag)
+{
+ u16 substitute_name_nchars = substitute_name_nbytes / 2;
+
+ if (substitute_name_nchars >= 7 &&
+ substitute_name[0] == cpu_to_le16('\\') &&
+ substitute_name[1] == cpu_to_le16('?') &&
+ substitute_name[2] == cpu_to_le16('?') &&
+ substitute_name[3] == cpu_to_le16('\\') &&
+ substitute_name[4] != cpu_to_le16('\0') &&
+ substitute_name[5] == cpu_to_le16(':') &&
+ substitute_name[6] == cpu_to_le16('\\'))
+ {
+ /* "Full" symlink or junction (\??\x:\ prefixed path) */
+ return 6;
+ } else if (rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT &&
+ substitute_name_nchars >= 12 &&
+ memcmp(substitute_name, volume_junction_prefix,
+ sizeof(volume_junction_prefix)) == 0 &&
+ substitute_name[substitute_name_nchars - 1] == cpu_to_le16('\\'))
+ {
+ /* Volume junction. Can't really do anything with it. */
+ return SUBST_NAME_IS_VOLUME_JUNCTION;
+ } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
+ substitute_name_nchars >= 3 &&
+ substitute_name[0] != cpu_to_le16('\0') &&
+ substitute_name[1] == cpu_to_le16(':') &&
+ substitute_name[2] == cpu_to_le16('\\'))
+ {
+ /* "Absolute" symlink, with drive letter */
+ return 2;
+ } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
+ substitute_name_nchars >= 1)
+ {
+ if (substitute_name[0] == cpu_to_le16('\\'))
+ /* "Absolute" symlink, without drive letter */
+ return 0;
+ else
+ /* "Relative" symlink, without drive letter */
+ return SUBST_NAME_IS_RELATIVE_LINK;
+ } else {
+ return SUBST_NAME_IS_UNKNOWN;
+ }
+}
+
+/*
+ * Get the UNIX-style symlink target from the WIM inode for a reparse point.
+ * Specifically, this translates the target from UTF-16 to the current multibyte
+ * encoding, strips the drive prefix if present, and replaces backslashes with
+ * forward slashes.
+ *
+ * @inode
+ * The inode to read the symlink from. It must be a reparse point with
+ * tag WIM_IO_REPARSE_TAG_SYMLINK (a real symlink) or
+ * WIM_IO_REPARSE_TAG_MOUNT_POINT (a mount point or junction point).