]> wimlib.net Git - wimlib/blob - src/symlink.c
Windows rpfix capture (in progress)
[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 /*
38  * Find the symlink target of a symbolic link or junction point in the WIM.
39  *
40  * See http://msdn.microsoft.com/en-us/library/cc232006(v=prot.10).aspx for a
41  * description of the format of the so-called "reparse point data buffers".
42  *
43  * But, in the WIM format, the first 8 bytes of the reparse point data buffer
44  * are omitted, presumably because we already know the reparse tag from the
45  * dentry, and we already know the reparse tag length from the lookup table
46  * entry resource length.
47  */
48 static ssize_t
49 get_symlink_name(const void *resource, size_t resource_len, char *buf,
50                  size_t buf_len, u32 reparse_tag)
51 {
52         const void *p = resource;
53         u16 substitute_name_offset;
54         u16 substitute_name_len;
55         u16 print_name_offset;
56         u16 print_name_len;
57         char *link_target;
58         char *translated_target;
59         size_t link_target_len;
60         ssize_t ret;
61         unsigned header_size;
62         bool translate_slashes;
63
64         if (resource_len < 12)
65                 return -EIO;
66         p = get_u16(p, &substitute_name_offset);
67         p = get_u16(p, &substitute_name_len);
68         p = get_u16(p, &print_name_offset);
69         p = get_u16(p, &print_name_len);
70
71         wimlib_assert(reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
72                       reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT);
73
74         if (reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
75                 header_size = 8;
76         else {
77                 header_size = 12;
78                 p += 4;
79         }
80         if (header_size + substitute_name_offset + substitute_name_len > resource_len)
81                 return -EIO;
82
83         ret = utf16le_to_tstr((const utf16lechar*)(p + substitute_name_offset),
84                               substitute_name_len,
85                               &link_target, &link_target_len);
86         if (ret)
87                 return -errno;
88
89         if (link_target_len + 1 > buf_len) {
90                 ret = -ENAMETOOLONG;
91                 goto out;
92         }
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: \"%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         memcpy(buf, translated_target, link_target_len + 1);
146         ret = link_target_len;
147 out:
148         FREE(link_target);
149         return ret;
150 }
151
152 static int
153 make_symlink_reparse_data_buf(const char *symlink_target,
154                               size_t *len_ret, void **buf_ret)
155 {
156         utf16lechar *name_utf16le;
157         size_t name_utf16le_nbytes;
158         int ret;
159
160         ret = tstr_to_utf16le(symlink_target, strlen(symlink_target),
161                               &name_utf16le, &name_utf16le_nbytes);
162         if (ret)
163                 return ret;
164
165         for (size_t i = 0; i < name_utf16le_nbytes / 2; i++)
166                 if (name_utf16le[i] == cpu_to_le16('/'))
167                         name_utf16le[i] = cpu_to_le16('\\');
168
169         size_t len = 12 + (name_utf16le_nbytes + 2) * 2;
170         void *buf = MALLOC(len);
171         if (buf) {
172                 void *p = buf;
173                 p = put_u16(p, 0); /* Substitute name offset */
174                 p = put_u16(p, name_utf16le_nbytes); /* Substitute name length */
175                 p = put_u16(p, name_utf16le_nbytes + 2); /* Print name offset */
176                 p = put_u16(p, name_utf16le_nbytes); /* Print name length */
177                 p = put_u32(p, 1); /* flags: 0 if relative link, otherwise 1 */
178                 p = put_bytes(p, name_utf16le_nbytes, name_utf16le);
179                 p = put_u16(p, 0);
180                 p = put_bytes(p, name_utf16le_nbytes, name_utf16le);
181                 p = put_u16(p, 0);
182                 *len_ret = len;
183                 *buf_ret = buf;
184                 ret = 0;
185         } else {
186                 ret = WIMLIB_ERR_NOMEM;
187         }
188         FREE(name_utf16le);
189         return ret;
190 }
191
192 /* Get the symlink target from a WIM inode.
193  *
194  * The inode may be either a "real" symlink (reparse tag
195  * WIM_IO_REPARSE_TAG_SYMLINK), or it may be a junction point (reparse tag
196  * WIM_IO_REPARSE_TAG_MOUNT_POINT).
197  */
198 ssize_t
199 inode_readlink(const struct wim_inode *inode, char *buf, size_t buf_len,
200                const WIMStruct *w, bool threadsafe)
201 {
202         const struct wim_lookup_table_entry *lte;
203         int ret;
204
205         wimlib_assert(inode_is_symlink(inode));
206
207         lte = inode_unnamed_lte(inode, w->lookup_table);
208         if (!lte)
209                 return -EIO;
210
211         if (wim_resource_size(lte) > REPARSE_POINT_MAX_SIZE)
212                 return -EIO;
213
214         u8 res_buf[wim_resource_size(lte)];
215         ret = read_full_resource_into_buf(lte, res_buf, threadsafe);
216         if (ret)
217                 return -EIO;
218         return get_symlink_name(res_buf, wim_resource_size(lte), buf,
219                                 buf_len, inode->i_reparse_tag);
220 }
221
222 /*
223  * Sets @inode to be a symbolic link pointing to @target.
224  *
225  * A lookup table entry for the symbolic link data buffer is created and
226  * inserted into @lookup_table, unless there is an existing lookup table entry
227  * for the exact same data, in which its reference count is incremented.
228  *
229  * The lookup table entry is returned in @lte_ret.
230  *
231  * On failure @dentry and @lookup_table are not modified.
232  */
233 int
234 inode_set_symlink(struct wim_inode *inode,
235                   const char *target,
236                   struct wim_lookup_table *lookup_table,
237                   struct wim_lookup_table_entry **lte_ret)
238
239 {
240         int ret;
241         size_t symlink_buf_len;
242         struct wim_lookup_table_entry *lte = NULL, *existing_lte;
243         u8 symlink_buf_hash[SHA1_HASH_SIZE];
244         void *symlink_buf;
245
246         ret = make_symlink_reparse_data_buf(target, &symlink_buf_len,
247                                             &symlink_buf);
248         if (ret)
249                 return ret;
250
251         DEBUG("Made symlink reparse data buf (len = %zu, name len = %zu)",
252                         symlink_buf_len, symlink_buf_len);
253
254         sha1_buffer(symlink_buf, symlink_buf_len, symlink_buf_hash);
255
256         existing_lte = __lookup_resource(lookup_table, symlink_buf_hash);
257
258         if (existing_lte) {
259                 lte = existing_lte;
260                 FREE(symlink_buf);
261                 symlink_buf = NULL;
262         } else {
263                 DEBUG("Creating new lookup table entry for symlink buf");
264                 lte = new_lookup_table_entry();
265                 if (!lte) {
266                         ret = WIMLIB_ERR_NOMEM;
267                         goto out_free_symlink_buf;
268                 }
269                 lte->resource_location            = RESOURCE_IN_ATTACHED_BUFFER;
270                 lte->attached_buffer              = symlink_buf;
271                 lte->resource_entry.original_size = symlink_buf_len;
272                 copy_hash(lte->hash, symlink_buf_hash);
273         }
274
275         inode->i_lte = lte;
276         inode->i_resolved = 1;
277
278         DEBUG("Loaded symlink buf");
279
280         if (existing_lte)
281                 lte->refcnt++;
282         else
283                 lookup_table_insert(lookup_table, lte);
284         if (lte_ret)
285                 *lte_ret = lte;
286         return 0;
287 out_free_symlink_buf:
288         FREE(symlink_buf);
289         return ret;
290 }
291
292 static int
293 unix_get_ino_and_dev(const char *path, u64 *ino_ret, u64 *dev_ret)
294 {
295         struct stat stbuf;
296         if (stat(path, &stbuf)) {
297                 WARNING_WITH_ERRNO("Failed to stat \"%s\"", path);
298                 /* Treat as a link pointing outside the capture root (it
299                  * most likely is). */
300                 return WIMLIB_ERR_STAT;
301         } else {
302                 *ino_ret = stbuf.st_ino;
303                 *dev_ret = stbuf.st_dev;
304                 return 0;
305         }
306 }
307
308 #endif /* !defined(__WIN32__) */
309
310 #ifdef __WIN32__
311 #  include "win32.h"
312 #  define RP_PATH_SEPARATOR L'\\'
313 #  define os_get_ino_and_dev win32_get_file_and_vol_ids
314 #else
315 #  define RP_PATH_SEPARATOR '/'
316 #  define os_get_ino_and_dev unix_get_ino_and_dev
317 #endif
318
319 /* Fix up reparse points--- mostly shared between UNIX and Windows */
320 tchar *
321 fixup_symlink(tchar *dest, u64 capture_root_ino, u64 capture_root_dev)
322 {
323         tchar *p = dest;
324
325 #ifdef __WIN32__
326         /* Skip over drive letter */
327         if (*p != RP_PATH_SEPARATOR)
328                 p += 2;
329 #endif
330
331         DEBUG("Fixing symlink or junction \"%"TS"\"", dest);
332         for (;;) {
333                 tchar save;
334                 int ret;
335                 u64 ino;
336                 u64 dev;
337
338                 while (*p == RP_PATH_SEPARATOR)
339                         p++;
340
341                 save = *p;
342                 *p = T('\0');
343                 ret = os_get_ino_and_dev(dest, &ino, &dev);
344                 *p = save;
345
346                 if (ino == capture_root_ino && dev == capture_root_dev) {
347                         /* Link points inside capture root.  Return abbreviated
348                          * path. */
349                         if (*p == T('\0'))
350                                 *(p - 1) = RP_PATH_SEPARATOR;
351                         while (p - 1 >= dest && *(p - 1) == RP_PATH_SEPARATOR)
352                                 p--;
353                 #ifdef __WIN32__
354                         /* Add back drive letter */
355                         if (*dest != RP_PATH_SEPARATOR) {
356                                 *--p = *(dest + 1);
357                                 *--p = *dest;
358                         }
359                 #endif
360                         wimlib_assert(p >= dest);
361                         return p;
362                 }
363
364                 if (*p == T('\0')) {
365                         /* Link points outside capture root. */
366                         return NULL;
367                 }
368
369                 do {
370                         p++;
371                 } while (*p != RP_PATH_SEPARATOR && *p != T('\0'));
372         }
373 }
374