rpfix extract on UNIX
[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 /* None of this file is ever needed in Win32 builds because the reparse point
33  * buffers are not parsed. */
34 #if !defined(__WIN32__)
35
36 /*
37  * Find the symlink target of a symbolic link or junction point in the WIM.
38  *
39  * See http://msdn.microsoft.com/en-us/library/cc232006(v=prot.10).aspx for a
40  * description of the format of the so-called "reparse point data buffers".
41  *
42  * But, in the WIM format, the first 8 bytes of the reparse point data buffer
43  * are omitted, presumably because we already know the reparse tag from the
44  * dentry, and we already know the reparse tag length from the lookup table
45  * entry resource length.
46  */
47 static ssize_t
48 get_symlink_name(const void *resource, size_t resource_len, char *buf,
49                  size_t buf_len, u32 reparse_tag)
50 {
51         const void *p = resource;
52         u16 substitute_name_offset;
53         u16 substitute_name_len;
54         u16 print_name_offset;
55         u16 print_name_len;
56         char *link_target;
57         size_t link_target_len;
58         ssize_t ret;
59         unsigned header_size;
60         bool translate_slashes;
61
62         if (resource_len < 12)
63                 return -EIO;
64         p = get_u16(p, &substitute_name_offset);
65         p = get_u16(p, &substitute_name_len);
66         p = get_u16(p, &print_name_offset);
67         p = get_u16(p, &print_name_len);
68
69         wimlib_assert(reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
70                       reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT);
71
72         if (reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
73                 header_size = 8;
74         else {
75                 header_size = 12;
76                 p += 4;
77         }
78         if (header_size + substitute_name_offset + substitute_name_len > resource_len)
79                 return -EIO;
80
81         ret = utf16le_to_tstr((const utf16lechar*)(p + substitute_name_offset),
82                               substitute_name_len,
83                               &link_target, &link_target_len);
84         if (ret)
85                 return -errno;
86
87         if (link_target_len + 1 > buf_len) {
88                 ret = -ENAMETOOLONG;
89                 goto out;
90         }
91
92         DEBUG("Interpeting substitute name \"%s\" (ReparseTag=0x%x)",
93               link_target, reparse_tag);
94         translate_slashes = true;
95         if (link_target_len >= 7 &&
96             link_target[0] == '\\' &&
97             link_target[1] == '?' &&
98             link_target[2] == '?' &&
99             link_target[3] == '\\' &&
100             link_target[4] != '\0' &&
101             link_target[5] == ':' &&
102             link_target[6] == '\\')
103         {
104                 /* "Full" symlink or junction (\??\x:\ prefixed path) */
105                 link_target += 6;
106                 link_target_len -= 6;
107         } else if (reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT &&
108                    link_target_len >= 12 &&
109                    memcmp(link_target, "\\\\?\\Volume{", 11) == 0 &&
110                    link_target[link_target_len - 1] == '\\')
111         {
112                 /* Volume junction.  Can't really do anything with it. */
113                 translate_slashes = false;
114         } else if (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK &&
115                    link_target_len >= 3 &&
116                    link_target[0] != '\0' &&
117                    link_target[1] == ':' &&
118                    link_target[2] == '/')
119         {
120                 /* "Absolute" symlink, with drive letter */
121                 link_target += 2;
122                 link_target_len -= 2;
123         } else if (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK &&
124                    link_target_len >= 1)
125         {
126                 if (link_target[0] == '/')
127                         /* "Absolute" symlink, without drive letter */
128                         ;
129                 else
130                         /* "Relative" symlink, without drive letter */
131                         ;
132         } else {
133                 ERROR("Invalid reparse point: \"%s\"", link_target);
134                 ret = -EIO;
135                 goto out;
136         }
137
138         if (translate_slashes)
139                 for (size_t i = 0; i < link_target_len; i++)
140                         if (link_target[i] == '\\')
141                                 link_target[i] = '/';
142         memcpy(buf, link_target, link_target_len + 1);
143         ret = link_target_len;
144 out:
145         FREE(link_target);
146         return ret;
147 }
148
149 static int
150 make_symlink_reparse_data_buf(const char *symlink_target,
151                               size_t *len_ret, void **buf_ret)
152 {
153         utf16lechar *name_utf16le;
154         size_t name_utf16le_nbytes;
155         int ret;
156
157         ret = tstr_to_utf16le(symlink_target, strlen(symlink_target),
158                               &name_utf16le, &name_utf16le_nbytes);
159         if (ret != 0)
160                 return ret;
161
162         for (size_t i = 0; i < name_utf16le_nbytes / 2; i++)
163                 if (name_utf16le[i] == cpu_to_le16('/'))
164                         name_utf16le[i] = cpu_to_le16('\\');
165
166         size_t len = 12 + name_utf16le_nbytes * 2;
167         void *buf = MALLOC(len);
168         if (buf) {
169                 void *p = buf;
170                 p = put_u16(p, name_utf16le_nbytes); /* Substitute name offset */
171                 p = put_u16(p, name_utf16le_nbytes); /* Substitute name length */
172                 p = put_u16(p, 0); /* Print name offset */
173                 p = put_u16(p, name_utf16le_nbytes); /* Print name length */
174                 p = put_u32(p, 1); /* flags: 0 iff *full* target, including drive letter??? */
175                 p = put_bytes(p, name_utf16le_nbytes, name_utf16le);
176                 p = put_bytes(p, name_utf16le_nbytes, name_utf16le);
177                 *len_ret = len;
178                 *buf_ret = buf;
179                 ret = 0;
180         } else {
181                 ret = WIMLIB_ERR_NOMEM;
182         }
183         FREE(name_utf16le);
184         return ret;
185 }
186
187 /* Get the symlink target from a WIM inode.
188  *
189  * The inode may be either a "real" symlink (reparse tag
190  * WIM_IO_REPARSE_TAG_SYMLINK), or it may be a junction point (reparse tag
191  * WIM_IO_REPARSE_TAG_MOUNT_POINT).
192  */
193 ssize_t
194 inode_readlink(const struct wim_inode *inode, char *buf, size_t buf_len,
195                const WIMStruct *w, bool threadsafe)
196 {
197         const struct wim_lookup_table_entry *lte;
198         int ret;
199
200         wimlib_assert(inode_is_symlink(inode));
201
202         lte = inode_unnamed_lte(inode, w->lookup_table);
203         if (!lte)
204                 return -EIO;
205
206         if (wim_resource_size(lte) > REPARSE_POINT_MAX_SIZE)
207                 return -EIO;
208
209         u8 res_buf[wim_resource_size(lte)];
210         ret = read_full_resource_into_buf(lte, res_buf, threadsafe);
211         if (ret)
212                 return -EIO;
213         return get_symlink_name(res_buf, wim_resource_size(lte), buf,
214                                 buf_len, inode->i_reparse_tag);
215 }
216
217 /*
218  * Sets @inode to be a symbolic link pointing to @target.
219  *
220  * A lookup table entry for the symbolic link data buffer is created and
221  * inserted into @lookup_table, unless there is an existing lookup table entry
222  * for the exact same data, in which its reference count is incremented.
223  *
224  * The lookup table entry is returned in @lte_ret.
225  *
226  * On failure @dentry and @lookup_table are not modified.
227  */
228 int
229 inode_set_symlink(struct wim_inode *inode,
230                   const char *target,
231                   struct wim_lookup_table *lookup_table,
232                   struct wim_lookup_table_entry **lte_ret)
233
234 {
235         int ret;
236         size_t symlink_buf_len;
237         struct wim_lookup_table_entry *lte = NULL, *existing_lte;
238         u8 symlink_buf_hash[SHA1_HASH_SIZE];
239         void *symlink_buf;
240
241         ret = make_symlink_reparse_data_buf(target, &symlink_buf_len,
242                                             &symlink_buf);
243         if (ret)
244                 return ret;
245
246         DEBUG("Made symlink reparse data buf (len = %zu, name len = %zu)",
247                         symlink_buf_len, symlink_buf_len);
248
249         sha1_buffer(symlink_buf, symlink_buf_len, symlink_buf_hash);
250
251         existing_lte = __lookup_resource(lookup_table, symlink_buf_hash);
252
253         if (existing_lte) {
254                 lte = existing_lte;
255                 FREE(symlink_buf);
256                 symlink_buf = NULL;
257         } else {
258                 DEBUG("Creating new lookup table entry for symlink buf");
259                 lte = new_lookup_table_entry();
260                 if (!lte) {
261                         ret = WIMLIB_ERR_NOMEM;
262                         goto out_free_symlink_buf;
263                 }
264                 lte->resource_location            = RESOURCE_IN_ATTACHED_BUFFER;
265                 lte->attached_buffer              = symlink_buf;
266                 lte->resource_entry.original_size = symlink_buf_len;
267                 copy_hash(lte->hash, symlink_buf_hash);
268         }
269
270         inode->i_lte = lte;
271         inode->i_resolved = 1;
272
273         DEBUG("Loaded symlink buf");
274
275         if (existing_lte)
276                 lte->refcnt++;
277         else
278                 lookup_table_insert(lookup_table, lte);
279         if (lte_ret)
280                 *lte_ret = lte;
281         return 0;
282 out_free_symlink_buf:
283         FREE(symlink_buf);
284         return ret;
285 }
286
287 #endif /* !defined(__WIN32__) */