]> wimlib.net Git - wimlib/blob - src/symlink.c
Factor code out from inode_set_symlink() into inode_set_unnamed_stream()
[wimlib] / src / symlink.c
1 /*
2  * symlink.c
3  *
4  * Code to read and set symbolic links in WIM files.
5  */
6
7 /*
8  * Copyright (C) 2012, 2013 Eric Biggers
9  *
10  * This file is part of wimlib, a library for working with WIM files.
11  *
12  * wimlib is free software; you can redistribute it and/or modify it under the
13  * terms of the GNU General Public License as published by the Free
14  * Software Foundation; either version 3 of the License, or (at your option)
15  * any later version.
16  *
17  * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
18  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
19  * A PARTICULAR PURPOSE. See the GNU General Public License for more
20  * details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with wimlib; if not, see http://www.gnu.org/licenses/.
24  */
25
26 #include "dentry.h"
27 #include "buffer_io.h"
28 #include "lookup_table.h"
29 #include "sha1.h"
30 #include <errno.h>
31
32 /* UNIX version of getting and setting the data in reparse points */
33 #if !defined(__WIN32__)
34
35 #include <sys/stat.h>
36
37 #ifdef HAVE_ALLOCA_H
38 #  include <alloca.h>
39 #endif
40
41 /*
42  * Find the symlink target of a symbolic link or junction point in the WIM.
43  *
44  * See http://msdn.microsoft.com/en-us/library/cc232006(v=prot.10).aspx for a
45  * description of the format of the so-called "reparse point data buffers".
46  *
47  * But, in the WIM format, the first 8 bytes of the reparse point data buffer
48  * are omitted, presumably because we already know the reparse tag from the
49  * dentry, and we already know the reparse tag length from the lookup table
50  * entry resource length.
51  */
52 static ssize_t
53 get_symlink_name(const void *resource, size_t resource_len, char *buf,
54                  size_t buf_len, u32 reparse_tag)
55 {
56         const void *p = resource;
57         u16 substitute_name_offset;
58         u16 substitute_name_len;
59         u16 print_name_offset;
60         u16 print_name_len;
61         char *link_target;
62         char *translated_target;
63         size_t link_target_len;
64         ssize_t ret;
65         unsigned header_size;
66         bool translate_slashes;
67
68         if (resource_len < 12)
69                 return -EIO;
70         p = get_u16(p, &substitute_name_offset);
71         p = get_u16(p, &substitute_name_len);
72         p = get_u16(p, &print_name_offset);
73         p = get_u16(p, &print_name_len);
74
75         wimlib_assert(reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
76                       reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT);
77
78         if (reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
79                 header_size = 8;
80         else {
81                 header_size = 12;
82                 p += 4;
83         }
84         if (header_size +
85             substitute_name_offset + substitute_name_len > resource_len)
86                 return -EIO;
87
88         ret = utf16le_to_tstr((const utf16lechar*)(p + substitute_name_offset),
89                               substitute_name_len,
90                               &link_target, &link_target_len);
91         if (ret)
92                 return -errno;
93
94         DEBUG("Interpeting substitute name \"%s\" (ReparseTag=0x%x)",
95               link_target, reparse_tag);
96         translate_slashes = true;
97         translated_target = link_target;
98         if (link_target_len >= 7 &&
99             translated_target[0] == '\\' &&
100             translated_target[1] == '?' &&
101             translated_target[2] == '?' &&
102             translated_target[3] == '\\' &&
103             translated_target[4] != '\0' &&
104             translated_target[5] == ':' &&
105             translated_target[6] == '\\')
106         {
107                 /* "Full" symlink or junction (\??\x:\ prefixed path) */
108                 translated_target += 6;
109                 link_target_len -= 6;
110         } else if (reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT &&
111                    link_target_len >= 12 &&
112                    memcmp(translated_target, "\\\\?\\Volume{", 11) == 0 &&
113                    translated_target[link_target_len - 1] == '\\')
114         {
115                 /* Volume junction.  Can't really do anything with it. */
116                 translate_slashes = false;
117         } else if (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK &&
118                    link_target_len >= 3 &&
119                    translated_target[0] != '\0' &&
120                    translated_target[1] == ':' &&
121                    translated_target[2] == '\\')
122         {
123                 /* "Absolute" symlink, with drive letter */
124                 translated_target += 2;
125                 link_target_len -= 2;
126         } else if (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK &&
127                    link_target_len >= 1)
128         {
129                 if (translated_target[0] == '\\')
130                         /* "Absolute" symlink, without drive letter */
131                         ;
132                 else
133                         /* "Relative" symlink, without drive letter */
134                         ;
135         } else {
136                 ERROR("Invalid reparse point substitute name: \"%s\"", translated_target);
137                 ret = -EIO;
138                 goto out;
139         }
140
141         if (translate_slashes)
142                 for (size_t i = 0; i < link_target_len; i++)
143                         if (translated_target[i] == '\\')
144                                 translated_target[i] = '/';
145
146         if (link_target_len > buf_len) {
147                 link_target_len = buf_len;
148                 ret = -ENAMETOOLONG;
149         } else {
150                 ret = link_target_len;
151         }
152         memcpy(buf, translated_target, link_target_len);
153 out:
154         FREE(link_target);
155         return ret;
156 }
157
158 #define SYMBOLIC_LINK_RELATIVE 0x00000001
159
160 /* Given a UNIX symlink target, prepare the corresponding symbolic link reparse
161  * data buffer. */
162 static int
163 make_symlink_reparse_data_buf(const char *symlink_target, void *rpdata,
164                               size_t *rplen_ret)
165 {
166         int ret;
167         utf16lechar *name_utf16le;
168         size_t name_utf16le_nbytes;
169         size_t substitute_name_nbytes;
170         size_t print_name_nbytes;
171         static const char abs_subst_name_prefix[12] = "\\\0?\0?\0\\\0C\0:\0";
172         static const char abs_print_name_prefix[4] = "C\0:\0";
173         u32 flags;
174         size_t rplen;
175         void *p;
176
177         ret = tstr_to_utf16le(symlink_target, strlen(symlink_target),
178                               &name_utf16le, &name_utf16le_nbytes);
179         if (ret)
180                 return ret;
181
182         for (size_t i = 0; i < name_utf16le_nbytes / 2; i++)
183                 if (name_utf16le[i] == cpu_to_le16('/'))
184                         name_utf16le[i] = cpu_to_le16('\\');
185
186         /* Compatability notes:
187          *
188          * On UNIX, an absolute symbolic link begins with '/'; everything else
189          * is a relative symbolic link.  (Quite simple compared to the various
190          * ways to provide Windows paths.)
191          *
192          * To change a UNIX relative symbolic link to Windows format, we only
193          * need to translate it to UTF-16LE and replace backslashes with forward
194          * slashes.  We do not make any attempt to handle filename character
195          * problems, such as a link target that itself contains backslashes on
196          * UNIX.  Then, for these relative links, we set the reparse header
197          * @flags field to SYMBOLIC_LINK_RELATIVE.
198          *
199          * For UNIX absolute symbolic links, we must set the @flags field to 0.
200          * Then, there are multiple options as to actually represent the
201          * absolute link targets:
202          *
203          * (1) An absolute path beginning with one backslash character. similar
204          * to UNIX-style, just with a different path separator.  Print name same
205          * as substitute name.
206          *
207          * (2) Absolute path beginning with drive letter followed by a
208          * backslash.  Print name same as substitute name.
209          *
210          * (3) Absolute path beginning with drive letter followed by a
211          * backslash; substitute name prefixed with \??\, otherwise same as
212          * print name.
213          *
214          * We choose option (3) here, and we just assume C: for the drive
215          * letter.  The reasoning for this is:
216          *
217          * (1) Microsoft imagex.exe has a bug where it does not attempt to do
218          * reparse point fixups for these links, even though they are valid
219          * absolute links.  (Note: in this case prefixing the substitute name
220          * with \??\ does not work; it just makes the data unable to be restored
221          * at all.)
222          * (2) Microsoft imagex.exe will fail when doing reparse point fixups
223          * for these.  It apparently contains a bug that causes it to create an
224          * invalid reparse point, which then cannot be restored.
225          * (3) This is the only option I tested for which reparse point fixups
226          * worked properly in Microsoft imagex.exe.
227          *
228          * So option (3) it is.
229          */
230
231         substitute_name_nbytes = name_utf16le_nbytes;
232         print_name_nbytes = name_utf16le_nbytes;
233         if (symlink_target[0] == '/') {
234                 substitute_name_nbytes += sizeof(abs_subst_name_prefix);
235                 print_name_nbytes += sizeof(abs_print_name_prefix);
236         }
237
238         rplen = 12 + substitute_name_nbytes + print_name_nbytes +
239                         2 * sizeof(utf16lechar);
240
241         if (rplen > REPARSE_POINT_MAX_SIZE) {
242                 ERROR("Symlink \"%s\" is too long!", symlink_target);
243                 return WIMLIB_ERR_LINK;
244         }
245
246         p = rpdata;
247
248         /* Substitute name offset */
249         p = put_u16(p, 0);
250
251         /* Substitute name length */
252         p = put_u16(p, substitute_name_nbytes);
253
254         /* Print name offset */
255         p = put_u16(p, substitute_name_nbytes + sizeof(utf16lechar));
256
257         /* Print name length */
258         p = put_u16(p, print_name_nbytes);
259
260         /* Flags */
261         flags = 0;
262         if (symlink_target[0] != '/')
263                 flags |= SYMBOLIC_LINK_RELATIVE;
264         p = put_u32(p, flags);
265
266         /* Substitute name */
267         if (symlink_target[0] == '/')
268                 p = put_bytes(p, sizeof(abs_subst_name_prefix), abs_subst_name_prefix);
269         p = put_bytes(p, name_utf16le_nbytes, name_utf16le);
270         p = put_u16(p, 0);
271
272         /* Print name */
273         if (symlink_target[0] == '/')
274                 p = put_bytes(p, sizeof(abs_print_name_prefix), abs_print_name_prefix);
275         p = put_bytes(p, name_utf16le_nbytes, name_utf16le);
276         p = put_u16(p, 0);
277
278         *rplen_ret = rplen;
279         ret = 0;
280 out_free_name_utf16le:
281         FREE(name_utf16le);
282         return ret;
283 }
284
285 /* Get the symlink target from a WIM inode.
286  *
287  * The inode may be either a "real" symlink (reparse tag
288  * WIM_IO_REPARSE_TAG_SYMLINK), or it may be a junction point (reparse tag
289  * WIM_IO_REPARSE_TAG_MOUNT_POINT).
290  */
291 ssize_t
292 inode_readlink(const struct wim_inode *inode, char *buf, size_t buf_len,
293                const WIMStruct *w, bool threadsafe)
294 {
295         const struct wim_lookup_table_entry *lte;
296         int ret;
297         u8 *res_buf;
298
299         wimlib_assert(inode_is_symlink(inode));
300
301         lte = inode_unnamed_lte(inode, w->lookup_table);
302         if (!lte)
303                 return -EIO;
304
305         if (wim_resource_size(lte) > REPARSE_POINT_MAX_SIZE)
306                 return -EIO;
307
308         res_buf = alloca(wim_resource_size(lte));
309         ret = read_full_resource_into_buf(lte, res_buf, threadsafe);
310         if (ret)
311                 return -EIO;
312         return get_symlink_name(res_buf, wim_resource_size(lte),
313                                 buf, buf_len, inode->i_reparse_tag);
314 }
315
316 /*
317  * Sets @inode to be a symbolic link pointing to @target.
318  *
319  * A lookup table entry for the symbolic link data buffer is created and
320  * inserted into @lookup_table, unless there is an existing lookup table entry
321  * for the exact same data, in which its reference count is incremented.
322  *
323  * The lookup table entry is returned in @lte_ret.
324  *
325  * On failure @dentry and @lookup_table are not modified.
326  */
327 int
328 inode_set_symlink(struct wim_inode *inode,
329                   const char *target,
330                   struct wim_lookup_table *lookup_table,
331                   struct wim_lookup_table_entry **lte_ret)
332
333 {
334         int ret;
335
336         /* Buffer for reparse point data */
337         u8 rpdata[REPARSE_POINT_MAX_SIZE];
338
339         /* Actual length of the reparse point data (to be calculated by
340          * make_symlink_reparse_data_buf()) */
341         size_t rplen;
342
343         DEBUG("Creating reparse point data buffer "
344               "for UNIX symlink target \"%s\"", target);
345
346         ret = make_symlink_reparse_data_buf(target, rpdata, &rplen);
347         if (ret)
348                 return ret;
349
350         ret = inode_set_unnamed_stream(inode, rpdata, rplen, lookup_table);
351         if (ret)
352                 return ret;
353
354         if (lte_ret)
355                 *lte_ret = inode->i_lte;
356         return 0;
357 }
358
359 static int
360 unix_get_ino_and_dev(const char *path, u64 *ino_ret, u64 *dev_ret)
361 {
362         struct stat stbuf;
363         if (stat(path, &stbuf)) {
364                 WARNING_WITH_ERRNO("Failed to stat \"%s\"", path);
365                 /* Treat as a link pointing outside the capture root (it
366                  * most likely is). */
367                 return WIMLIB_ERR_STAT;
368         } else {
369                 *ino_ret = stbuf.st_ino;
370                 *dev_ret = stbuf.st_dev;
371                 return 0;
372         }
373 }
374
375 #endif /* !defined(__WIN32__) */
376
377 #ifdef __WIN32__
378 #  include "win32.h"
379 #  define RP_PATH_SEPARATOR L'\\'
380 #  define is_rp_path_separator(c) ((c) == L'\\' || (c) == L'/')
381 #  define os_get_ino_and_dev win32_get_file_and_vol_ids
382 #else
383 #  define RP_PATH_SEPARATOR '/'
384 #  define is_rp_path_separator(c) ((c) == '/')
385 #  define os_get_ino_and_dev unix_get_ino_and_dev
386 #endif
387
388 /* Fix up absolute symbolic link targets--- mostly shared between UNIX and
389  * Windows */
390 tchar *
391 fixup_symlink(tchar *dest, u64 capture_root_ino, u64 capture_root_dev)
392 {
393         tchar *p = dest;
394
395 #ifdef __WIN32__
396         /* Skip over drive letter */
397         if (!is_rp_path_separator(*p))
398                 p += 2;
399 #endif
400
401         DEBUG("Fixing symlink or junction \"%"TS"\"", dest);
402         for (;;) {
403                 tchar save;
404                 int ret;
405                 u64 ino;
406                 u64 dev;
407
408                 while (is_rp_path_separator(*p))
409                         p++;
410
411                 save = *p;
412                 *p = T('\0');
413                 ret = os_get_ino_and_dev(dest, &ino, &dev);
414                 *p = save;
415
416                 if (ret) /* stat() failed before we got to the capture root---
417                             assume the link points outside it. */
418                         return NULL;
419
420                 if (ino == capture_root_ino && dev == capture_root_dev) {
421                         /* Link points inside capture root.  Return abbreviated
422                          * path. */
423                         if (*p == T('\0'))
424                                 *(p - 1) = RP_PATH_SEPARATOR;
425                         while (p - 1 >= dest && is_rp_path_separator(*(p - 1)))
426                                 p--;
427                 #ifdef __WIN32__
428                         /* Add back drive letter */
429                         if (!is_rp_path_separator(*dest)) {
430                                 *--p = *(dest + 1);
431                                 *--p = *dest;
432                         }
433                 #endif
434                         wimlib_assert(p >= dest);
435                         return p;
436                 }
437
438                 if (*p == T('\0')) {
439                         /* Link points outside capture root. */
440                         return NULL;
441                 }
442
443                 do {
444                         p++;
445                 } while (!is_rp_path_separator(*p) && *p != T('\0'));
446         }
447 }