blob: cdabadd2f5c53c52b9c7dd3ac39772d470d6bc8a
1 | /** |
2 | * reparse.c - Processing of reparse points |
3 | * |
4 | * This module is part of ntfs-3g library |
5 | * |
6 | * Copyright (c) 2008-2013 Jean-Pierre Andre |
7 | * |
8 | * This program/include file is free software; you can redistribute it and/or |
9 | * modify it under the terms of the GNU General Public License as published |
10 | * by the Free Software Foundation; either version 2 of the License, or |
11 | * (at your option) any later version. |
12 | * |
13 | * This program/include file is distributed in the hope that it will be |
14 | * useful, but WITHOUT ANY WARRANTY; without even the implied warranty |
15 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | * GNU General Public License for more details. |
17 | * |
18 | * You should have received a copy of the GNU General Public License |
19 | * along with this program (in the main directory of the NTFS-3G |
20 | * distribution in the file COPYING); if not, write to the Free Software |
21 | * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
22 | */ |
23 | |
24 | #ifdef HAVE_CONFIG_H |
25 | #include "config.h" |
26 | #endif |
27 | |
28 | #ifdef HAVE_STDLIB_H |
29 | #include <stdlib.h> |
30 | #endif |
31 | #ifdef HAVE_ERRNO_H |
32 | #include <errno.h> |
33 | #endif |
34 | #ifdef HAVE_STRING_H |
35 | #include <string.h> |
36 | #endif |
37 | #ifdef HAVE_SYS_STAT_H |
38 | #include <sys/stat.h> |
39 | #endif |
40 | |
41 | #ifdef HAVE_SETXATTR |
42 | #include <sys/xattr.h> |
43 | #endif |
44 | |
45 | #ifdef HAVE_SYS_SYSMACROS_H |
46 | #include <sys/sysmacros.h> |
47 | #endif |
48 | |
49 | #include "compat.h" |
50 | #include "types.h" |
51 | #include "debug.h" |
52 | #include "layout.h" |
53 | #include "attrib.h" |
54 | #include "inode.h" |
55 | #include "dir.h" |
56 | #include "volume.h" |
57 | #include "mft.h" |
58 | #include "index.h" |
59 | #include "lcnalloc.h" |
60 | #include "logging.h" |
61 | #include "misc.h" |
62 | #include "reparse.h" |
63 | |
64 | struct MOUNT_POINT_REPARSE_DATA { /* reparse data for junctions */ |
65 | le16 subst_name_offset; |
66 | le16 subst_name_length; |
67 | le16 print_name_offset; |
68 | le16 print_name_length; |
69 | char path_buffer[0]; /* above data assume this is char array */ |
70 | } ; |
71 | |
72 | struct SYMLINK_REPARSE_DATA { /* reparse data for symlinks */ |
73 | le16 subst_name_offset; |
74 | le16 subst_name_length; |
75 | le16 print_name_offset; |
76 | le16 print_name_length; |
77 | le32 flags; /* 1 for full target, otherwise 0 */ |
78 | char path_buffer[0]; /* above data assume this is char array */ |
79 | } ; |
80 | |
81 | struct REPARSE_INDEX { /* index entry in $Extend/$Reparse */ |
82 | INDEX_ENTRY_HEADER header; |
83 | REPARSE_INDEX_KEY key; |
84 | le32 filling; |
85 | } ; |
86 | |
87 | static const ntfschar dir_junction_head[] = { |
88 | const_cpu_to_le16('\\'), |
89 | const_cpu_to_le16('?'), |
90 | const_cpu_to_le16('?'), |
91 | const_cpu_to_le16('\\') |
92 | } ; |
93 | |
94 | static const ntfschar vol_junction_head[] = { |
95 | const_cpu_to_le16('\\'), |
96 | const_cpu_to_le16('?'), |
97 | const_cpu_to_le16('?'), |
98 | const_cpu_to_le16('\\'), |
99 | const_cpu_to_le16('V'), |
100 | const_cpu_to_le16('o'), |
101 | const_cpu_to_le16('l'), |
102 | const_cpu_to_le16('u'), |
103 | const_cpu_to_le16('m'), |
104 | const_cpu_to_le16('e'), |
105 | const_cpu_to_le16('{'), |
106 | } ; |
107 | |
108 | static ntfschar reparse_index_name[] = { const_cpu_to_le16('$'), |
109 | const_cpu_to_le16('R') }; |
110 | |
111 | static const char mappingdir[] = ".NTFS-3G/"; |
112 | |
113 | /* |
114 | * Fix a file name with doubtful case in some directory index |
115 | * and return the name with the casing used in directory. |
116 | * |
117 | * Should only be used to translate paths stored with case insensitivity |
118 | * (such as directory junctions) when no case conflict is expected. |
119 | * If there some ambiguity, the name which collates first is returned. |
120 | * |
121 | * The name is converted to upper case and searched the usual way. |
122 | * The collation rules for file names are such that we should get the |
123 | * first candidate if any. |
124 | */ |
125 | |
126 | static u64 ntfs_fix_file_name(ntfs_inode *dir_ni, ntfschar *uname, |
127 | int uname_len) |
128 | { |
129 | ntfs_volume *vol = dir_ni->vol; |
130 | ntfs_index_context *icx; |
131 | u64 mref; |
132 | le64 lemref; |
133 | int lkup; |
134 | int olderrno; |
135 | int i; |
136 | u32 cpuchar; |
137 | INDEX_ENTRY *entry; |
138 | FILE_NAME_ATTR *found; |
139 | struct { |
140 | FILE_NAME_ATTR attr; |
141 | ntfschar file_name[NTFS_MAX_NAME_LEN + 1]; |
142 | } find; |
143 | |
144 | mref = (u64)-1; /* default return (not found) */ |
145 | icx = ntfs_index_ctx_get(dir_ni, NTFS_INDEX_I30, 4); |
146 | if (icx) { |
147 | if (uname_len > NTFS_MAX_NAME_LEN) |
148 | uname_len = NTFS_MAX_NAME_LEN; |
149 | find.attr.file_name_length = uname_len; |
150 | for (i=0; i<uname_len; i++) { |
151 | cpuchar = le16_to_cpu(uname[i]); |
152 | /* |
153 | * We need upper or lower value, whichever is smaller, |
154 | * but we can only convert to upper case, so we |
155 | * will fail when searching for an upper case char |
156 | * whose lower case is smaller (such as umlauted Y) |
157 | */ |
158 | if ((cpuchar < vol->upcase_len) |
159 | && (le16_to_cpu(vol->upcase[cpuchar]) < cpuchar)) |
160 | find.attr.file_name[i] = vol->upcase[cpuchar]; |
161 | else |
162 | find.attr.file_name[i] = uname[i]; |
163 | } |
164 | olderrno = errno; |
165 | lkup = ntfs_index_lookup((char*)&find, uname_len, icx); |
166 | if (errno == ENOENT) |
167 | errno = olderrno; |
168 | /* |
169 | * We generally only get the first matching candidate, |
170 | * so we still have to check whether this is a real match |
171 | */ |
172 | if (icx->entry && (icx->entry->ie_flags & INDEX_ENTRY_END)) |
173 | /* get next entry if reaching end of block */ |
174 | entry = ntfs_index_next(icx->entry, icx); |
175 | else |
176 | entry = icx->entry; |
177 | if (entry) { |
178 | found = &entry->key.file_name; |
179 | if (lkup |
180 | && ntfs_names_are_equal(find.attr.file_name, |
181 | find.attr.file_name_length, |
182 | found->file_name, found->file_name_length, |
183 | IGNORE_CASE, |
184 | vol->upcase, vol->upcase_len)) |
185 | lkup = 0; |
186 | if (!lkup) { |
187 | /* |
188 | * name found : |
189 | * fix original name and return inode |
190 | */ |
191 | lemref = entry->indexed_file; |
192 | mref = le64_to_cpu(lemref); |
193 | if (NVolCaseSensitive(vol) || !vol->locase) { |
194 | for (i=0; i<found->file_name_length; i++) |
195 | uname[i] = found->file_name[i]; |
196 | } else { |
197 | for (i=0; i<found->file_name_length; i++) |
198 | uname[i] = vol->locase[found->file_name[i]]; |
199 | } |
200 | } |
201 | } |
202 | ntfs_index_ctx_put(icx); |
203 | } |
204 | return (mref); |
205 | } |
206 | |
207 | /* |
208 | * Search for a directory junction or a symbolic link |
209 | * along the target path, with target defined as a full absolute path |
210 | * |
211 | * Returns the path translated to a Linux path |
212 | * or NULL if the path is not valid |
213 | */ |
214 | |
215 | static char *search_absolute(ntfs_volume *vol, ntfschar *path, |
216 | int count, BOOL isdir) |
217 | { |
218 | ntfs_inode *ni; |
219 | u64 inum; |
220 | char *target; |
221 | int start; |
222 | int len; |
223 | |
224 | target = (char*)NULL; /* default return */ |
225 | ni = ntfs_inode_open(vol, (MFT_REF)FILE_root); |
226 | if (ni) { |
227 | start = 0; |
228 | /* |
229 | * Examine and translate the path, until we reach either |
230 | * - the end, |
231 | * - an unknown item |
232 | * - a non-directory |
233 | * - another reparse point, |
234 | * A reparse point is not dereferenced, it will be |
235 | * examined later when the translated path is dereferenced, |
236 | * however the final part of the path will not be adjusted |
237 | * to correct case. |
238 | */ |
239 | do { |
240 | len = 0; |
241 | while (((start + len) < count) |
242 | && (path[start + len] != const_cpu_to_le16('\\'))) |
243 | len++; |
244 | inum = ntfs_fix_file_name(ni, &path[start], len); |
245 | ntfs_inode_close(ni); |
246 | ni = (ntfs_inode*)NULL; |
247 | if (inum != (u64)-1) { |
248 | inum = MREF(inum); |
249 | ni = ntfs_inode_open(vol, inum); |
250 | start += len; |
251 | if (start < count) |
252 | path[start++] = const_cpu_to_le16('/'); |
253 | } |
254 | } while (ni |
255 | && (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) |
256 | && !(ni->flags & FILE_ATTR_REPARSE_POINT) |
257 | && (start < count)); |
258 | if (ni |
259 | && ((ni->mrec->flags & MFT_RECORD_IS_DIRECTORY ? isdir : !isdir) |
260 | || (ni->flags & FILE_ATTR_REPARSE_POINT))) |
261 | if (ntfs_ucstombs(path, count, &target, 0) < 0) { |
262 | if (target) { |
263 | free(target); |
264 | target = (char*)NULL; |
265 | } |
266 | } |
267 | if (ni) |
268 | ntfs_inode_close(ni); |
269 | } |
270 | return (target); |
271 | } |
272 | |
273 | /* |
274 | * Search for a symbolic link along the target path, |
275 | * with the target defined as a relative path |
276 | * |
277 | * Note : the path used to access the current inode, may be |
278 | * different from the one implied in the target definition, |
279 | * when an inode has names in several directories. |
280 | * |
281 | * Returns the path translated to a Linux path |
282 | * or NULL if the path is not valid |
283 | */ |
284 | |
285 | static char *search_relative(ntfs_inode *ni, ntfschar *path, int count) |
286 | { |
287 | char *target = (char*)NULL; |
288 | ntfs_inode *curni; |
289 | ntfs_inode *newni; |
290 | u64 inum; |
291 | int pos; |
292 | int lth; |
293 | BOOL ok; |
294 | BOOL morelinks; |
295 | int max = 32; /* safety */ |
296 | |
297 | pos = 0; |
298 | ok = TRUE; |
299 | morelinks = FALSE; |
300 | curni = ntfs_dir_parent_inode(ni); |
301 | /* |
302 | * Examine and translate the path, until we reach either |
303 | * - the end, |
304 | * - an unknown item |
305 | * - a non-directory |
306 | * - another reparse point, |
307 | * A reparse point is not dereferenced, it will be |
308 | * examined later when the translated path is dereferenced, |
309 | * however the final part of the path will not be adjusted |
310 | * to correct case. |
311 | */ |
312 | while (curni && ok && !morelinks && (pos < (count - 1)) && --max) { |
313 | if ((count >= (pos + 2)) |
314 | && (path[pos] == const_cpu_to_le16('.')) |
315 | && (path[pos+1] == const_cpu_to_le16('\\'))) { |
316 | path[pos+1] = const_cpu_to_le16('/'); |
317 | pos += 2; |
318 | } else { |
319 | if ((count >= (pos + 3)) |
320 | && (path[pos] == const_cpu_to_le16('.')) |
321 | &&(path[pos+1] == const_cpu_to_le16('.')) |
322 | && (path[pos+2] == const_cpu_to_le16('\\'))) { |
323 | path[pos+2] = const_cpu_to_le16('/'); |
324 | pos += 3; |
325 | newni = ntfs_dir_parent_inode(curni); |
326 | if (curni != ni) |
327 | ntfs_inode_close(curni); |
328 | curni = newni; |
329 | if (!curni) |
330 | ok = FALSE; |
331 | } else { |
332 | lth = 0; |
333 | while (((pos + lth) < count) |
334 | && (path[pos + lth] != const_cpu_to_le16('\\'))) |
335 | lth++; |
336 | if (lth > 0) |
337 | inum = ntfs_fix_file_name(curni,&path[pos],lth); |
338 | else |
339 | inum = (u64)-1; |
340 | if (!lth |
341 | || ((curni != ni) |
342 | && ntfs_inode_close(curni)) |
343 | || (inum == (u64)-1)) |
344 | ok = FALSE; |
345 | else { |
346 | curni = ntfs_inode_open(ni->vol, MREF(inum)); |
347 | if (!curni) |
348 | ok = FALSE; |
349 | else { |
350 | if (curni->flags & FILE_ATTR_REPARSE_POINT) |
351 | morelinks = TRUE; |
352 | if (ok && ((pos + lth) < count)) { |
353 | path[pos + lth] = const_cpu_to_le16('/'); |
354 | pos += lth + 1; |
355 | if (morelinks |
356 | && ntfs_inode_close(curni)) |
357 | ok = FALSE; |
358 | } else { |
359 | pos += lth; |
360 | if (!morelinks |
361 | && (ni->mrec->flags ^ curni->mrec->flags) |
362 | & MFT_RECORD_IS_DIRECTORY) |
363 | ok = FALSE; |
364 | if (ntfs_inode_close(curni)) |
365 | ok = FALSE; |
366 | } |
367 | } |
368 | } |
369 | } |
370 | } |
371 | } |
372 | |
373 | if (ok && (ntfs_ucstombs(path, count, &target, 0) < 0)) { |
374 | free(target); // needed ? |
375 | target = (char*)NULL; |
376 | } |
377 | return (target); |
378 | } |
379 | |
380 | /* |
381 | * Check whether a drive letter has been defined in .NTFS-3G |
382 | * |
383 | * Returns 1 if found, |
384 | * 0 if not found, |
385 | * -1 if there was an error (described by errno) |
386 | */ |
387 | |
388 | static int ntfs_drive_letter(ntfs_volume *vol, ntfschar letter) |
389 | { |
390 | char defines[NTFS_MAX_NAME_LEN + 5]; |
391 | char *drive; |
392 | int ret; |
393 | int sz; |
394 | int olderrno; |
395 | ntfs_inode *ni; |
396 | |
397 | ret = -1; |
398 | drive = (char*)NULL; |
399 | sz = ntfs_ucstombs(&letter, 1, &drive, 0); |
400 | if (sz > 0) { |
401 | strcpy(defines,mappingdir); |
402 | if ((*drive >= 'a') && (*drive <= 'z')) |
403 | *drive += 'A' - 'a'; |
404 | strcat(defines,drive); |
405 | strcat(defines,":"); |
406 | olderrno = errno; |
407 | ni = ntfs_pathname_to_inode(vol, NULL, defines); |
408 | if (ni && !ntfs_inode_close(ni)) |
409 | ret = 1; |
410 | else |
411 | if (errno == ENOENT) { |
412 | ret = 0; |
413 | /* avoid errno pollution */ |
414 | errno = olderrno; |
415 | } |
416 | } |
417 | if (drive) |
418 | free(drive); |
419 | return (ret); |
420 | } |
421 | |
422 | /* |
423 | * Do some sanity checks on reparse data |
424 | * |
425 | * The only general check is about the size (at least the tag must |
426 | * be present) |
427 | * If the reparse data looks like a junction point or symbolic |
428 | * link, more checks can be done. |
429 | * |
430 | */ |
431 | |
432 | static BOOL valid_reparse_data(ntfs_inode *ni, |
433 | const REPARSE_POINT *reparse_attr, size_t size) |
434 | { |
435 | BOOL ok; |
436 | unsigned int offs; |
437 | unsigned int lth; |
438 | const struct MOUNT_POINT_REPARSE_DATA *mount_point_data; |
439 | const struct SYMLINK_REPARSE_DATA *symlink_data; |
440 | |
441 | ok = ni && reparse_attr |
442 | && (size >= sizeof(REPARSE_POINT)) |
443 | && (((size_t)le16_to_cpu(reparse_attr->reparse_data_length) |
444 | + sizeof(REPARSE_POINT)) == size); |
445 | if (ok) { |
446 | switch (reparse_attr->reparse_tag) { |
447 | case IO_REPARSE_TAG_MOUNT_POINT : |
448 | mount_point_data = (const struct MOUNT_POINT_REPARSE_DATA*) |
449 | reparse_attr->reparse_data; |
450 | offs = le16_to_cpu(mount_point_data->subst_name_offset); |
451 | lth = le16_to_cpu(mount_point_data->subst_name_length); |
452 | /* consistency checks */ |
453 | if (!(ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) |
454 | || ((size_t)((sizeof(REPARSE_POINT) |
455 | + sizeof(struct MOUNT_POINT_REPARSE_DATA) |
456 | + offs + lth)) > size)) |
457 | ok = FALSE; |
458 | break; |
459 | case IO_REPARSE_TAG_SYMLINK : |
460 | symlink_data = (const struct SYMLINK_REPARSE_DATA*) |
461 | reparse_attr->reparse_data; |
462 | offs = le16_to_cpu(symlink_data->subst_name_offset); |
463 | lth = le16_to_cpu(symlink_data->subst_name_length); |
464 | if ((size_t)((sizeof(REPARSE_POINT) |
465 | + sizeof(struct SYMLINK_REPARSE_DATA) |
466 | + offs + lth)) > size) |
467 | ok = FALSE; |
468 | break; |
469 | default : |
470 | break; |
471 | } |
472 | } |
473 | if (!ok) |
474 | errno = EINVAL; |
475 | return (ok); |
476 | } |
477 | |
478 | /* |
479 | * Check and translate the target of a junction point or |
480 | * a full absolute symbolic link. |
481 | * |
482 | * A full target definition begins with "\??\" or "\\?\" |
483 | * |
484 | * The fully defined target is redefined as a relative link, |
485 | * - either to the target if found on the same device. |
486 | * - or into the /.NTFS-3G directory for the user to define |
487 | * In the first situation, the target is translated to case-sensitive path. |
488 | * |
489 | * returns the target converted to a relative symlink |
490 | * or NULL if there were some problem, as described by errno |
491 | */ |
492 | |
493 | static char *ntfs_get_fulllink(ntfs_volume *vol, ntfschar *junction, |
494 | int count, const char *mnt_point, BOOL isdir) |
495 | { |
496 | char *target; |
497 | char *fulltarget; |
498 | int sz; |
499 | char *q; |
500 | enum { DIR_JUNCTION, VOL_JUNCTION, NO_JUNCTION } kind; |
501 | |
502 | target = (char*)NULL; |
503 | fulltarget = (char*)NULL; |
504 | /* |
505 | * For a valid directory junction we want \??\x:\ |
506 | * where \ is an individual char and x a non-null char |
507 | */ |
508 | if ((count >= 7) |
509 | && !memcmp(junction,dir_junction_head,8) |
510 | && junction[4] |
511 | && (junction[5] == const_cpu_to_le16(':')) |
512 | && (junction[6] == const_cpu_to_le16('\\'))) |
513 | kind = DIR_JUNCTION; |
514 | else |
515 | /* |
516 | * For a valid volume junction we want \\?\Volume{ |
517 | * and a final \ (where \ is an individual char) |
518 | */ |
519 | if ((count >= 12) |
520 | && !memcmp(junction,vol_junction_head,22) |
521 | && (junction[count-1] == const_cpu_to_le16('\\'))) |
522 | kind = VOL_JUNCTION; |
523 | else |
524 | kind = NO_JUNCTION; |
525 | /* |
526 | * Directory junction with an explicit path and |
527 | * no specific definition for the drive letter : |
528 | * try to interpret as a target on the same volume |
529 | */ |
530 | if ((kind == DIR_JUNCTION) |
531 | && (count >= 7) |
532 | && junction[7] |
533 | && !ntfs_drive_letter(vol, junction[4])) { |
534 | target = search_absolute(vol,&junction[7],count - 7, isdir); |
535 | if (target) { |
536 | fulltarget = (char*)ntfs_malloc(strlen(mnt_point) |
537 | + strlen(target) + 2); |
538 | if (fulltarget) { |
539 | strcpy(fulltarget,mnt_point); |
540 | strcat(fulltarget,"/"); |
541 | strcat(fulltarget,target); |
542 | } |
543 | free(target); |
544 | } |
545 | } |
546 | /* |
547 | * Volume junctions or directory junctions with |
548 | * target not found on current volume : |
549 | * link to /.NTFS-3G/target which the user can |
550 | * define as a symbolic link to the real target |
551 | */ |
552 | if (((kind == DIR_JUNCTION) && !fulltarget) |
553 | || (kind == VOL_JUNCTION)) { |
554 | sz = ntfs_ucstombs(&junction[4], |
555 | (kind == VOL_JUNCTION ? count - 5 : count - 4), |
556 | &target, 0); |
557 | if ((sz > 0) && target) { |
558 | /* reverse slashes */ |
559 | for (q=target; *q; q++) |
560 | if (*q == '\\') |
561 | *q = '/'; |
562 | /* force uppercase drive letter */ |
563 | if ((target[1] == ':') |
564 | && (target[0] >= 'a') |
565 | && (target[0] <= 'z')) |
566 | target[0] += 'A' - 'a'; |
567 | fulltarget = (char*)ntfs_malloc(strlen(mnt_point) |
568 | + sizeof(mappingdir) + strlen(target) + 1); |
569 | if (fulltarget) { |
570 | strcpy(fulltarget,mnt_point); |
571 | strcat(fulltarget,"/"); |
572 | strcat(fulltarget,mappingdir); |
573 | strcat(fulltarget,target); |
574 | } |
575 | } |
576 | if (target) |
577 | free(target); |
578 | } |
579 | return (fulltarget); |
580 | } |
581 | |
582 | /* |
583 | * Check and translate the target of an absolute symbolic link. |
584 | * |
585 | * An absolute target definition begins with "\" or "x:\" |
586 | * |
587 | * The absolute target is redefined as a relative link, |
588 | * - either to the target if found on the same device. |
589 | * - or into the /.NTFS-3G directory for the user to define |
590 | * In the first situation, the target is translated to case-sensitive path. |
591 | * |
592 | * returns the target converted to a relative symlink |
593 | * or NULL if there were some problem, as described by errno |
594 | */ |
595 | |
596 | static char *ntfs_get_abslink(ntfs_volume *vol, ntfschar *junction, |
597 | int count, const char *mnt_point, BOOL isdir) |
598 | { |
599 | char *target; |
600 | char *fulltarget; |
601 | int sz; |
602 | char *q; |
603 | enum { FULL_PATH, ABS_PATH, REJECTED_PATH } kind; |
604 | |
605 | target = (char*)NULL; |
606 | fulltarget = (char*)NULL; |
607 | /* |
608 | * For a full valid path we want x:\ |
609 | * where \ is an individual char and x a non-null char |
610 | */ |
611 | if ((count >= 3) |
612 | && junction[0] |
613 | && (junction[1] == const_cpu_to_le16(':')) |
614 | && (junction[2] == const_cpu_to_le16('\\'))) |
615 | kind = FULL_PATH; |
616 | else |
617 | /* |
618 | * For an absolute path we want an initial \ |
619 | */ |
620 | if ((count >= 0) |
621 | && (junction[0] == const_cpu_to_le16('\\'))) |
622 | kind = ABS_PATH; |
623 | else |
624 | kind = REJECTED_PATH; |
625 | /* |
626 | * Full path, with a drive letter and |
627 | * no specific definition for the drive letter : |
628 | * try to interpret as a target on the same volume. |
629 | * Do the same for an abs path with no drive letter. |
630 | */ |
631 | if (((kind == FULL_PATH) |
632 | && (count >= 3) |
633 | && junction[3] |
634 | && !ntfs_drive_letter(vol, junction[0])) |
635 | || (kind == ABS_PATH)) { |
636 | if (kind == ABS_PATH) |
637 | target = search_absolute(vol, &junction[1], |
638 | count - 1, isdir); |
639 | else |
640 | target = search_absolute(vol, &junction[3], |
641 | count - 3, isdir); |
642 | if (target) { |
643 | fulltarget = (char*)ntfs_malloc(strlen(mnt_point) |
644 | + strlen(target) + 2); |
645 | if (fulltarget) { |
646 | strcpy(fulltarget,mnt_point); |
647 | strcat(fulltarget,"/"); |
648 | strcat(fulltarget,target); |
649 | } |
650 | free(target); |
651 | } |
652 | } |
653 | /* |
654 | * full path with target not found on current volume : |
655 | * link to /.NTFS-3G/target which the user can |
656 | * define as a symbolic link to the real target |
657 | */ |
658 | if ((kind == FULL_PATH) && !fulltarget) { |
659 | sz = ntfs_ucstombs(&junction[0], |
660 | count,&target, 0); |
661 | if ((sz > 0) && target) { |
662 | /* reverse slashes */ |
663 | for (q=target; *q; q++) |
664 | if (*q == '\\') |
665 | *q = '/'; |
666 | /* force uppercase drive letter */ |
667 | if ((target[1] == ':') |
668 | && (target[0] >= 'a') |
669 | && (target[0] <= 'z')) |
670 | target[0] += 'A' - 'a'; |
671 | fulltarget = (char*)ntfs_malloc(strlen(mnt_point) |
672 | + sizeof(mappingdir) + strlen(target) + 1); |
673 | if (fulltarget) { |
674 | strcpy(fulltarget,mnt_point); |
675 | strcat(fulltarget,"/"); |
676 | strcat(fulltarget,mappingdir); |
677 | strcat(fulltarget,target); |
678 | } |
679 | } |
680 | if (target) |
681 | free(target); |
682 | } |
683 | return (fulltarget); |
684 | } |
685 | |
686 | /* |
687 | * Check and translate the target of a relative symbolic link. |
688 | * |
689 | * A relative target definition does not begin with "\" |
690 | * |
691 | * The original definition of relative target is kept, it is just |
692 | * translated to a case-sensitive path. |
693 | * |
694 | * returns the target converted to a relative symlink |
695 | * or NULL if there were some problem, as described by errno |
696 | */ |
697 | |
698 | static char *ntfs_get_rellink(ntfs_inode *ni, ntfschar *junction, int count) |
699 | { |
700 | char *target; |
701 | |
702 | target = search_relative(ni,junction,count); |
703 | return (target); |
704 | } |
705 | |
706 | /* |
707 | * Get the target for a junction point or symbolic link |
708 | * Should only be called for files or directories with reparse data |
709 | * |
710 | * returns the target converted to a relative path, or NULL |
711 | * if some error occurred, as described by errno |
712 | * errno is EOPNOTSUPP if the reparse point is not a valid |
713 | * symbolic link or directory junction |
714 | */ |
715 | |
716 | char *ntfs_make_symlink(ntfs_inode *ni, const char *mnt_point, |
717 | int *pattr_size) |
718 | { |
719 | s64 attr_size = 0; |
720 | char *target; |
721 | unsigned int offs; |
722 | unsigned int lth; |
723 | ntfs_volume *vol; |
724 | REPARSE_POINT *reparse_attr; |
725 | struct MOUNT_POINT_REPARSE_DATA *mount_point_data; |
726 | struct SYMLINK_REPARSE_DATA *symlink_data; |
727 | enum { FULL_TARGET, ABS_TARGET, REL_TARGET } kind; |
728 | ntfschar *p; |
729 | BOOL bad; |
730 | BOOL isdir; |
731 | |
732 | target = (char*)NULL; |
733 | bad = TRUE; |
734 | isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) |
735 | != const_cpu_to_le16(0); |
736 | vol = ni->vol; |
737 | reparse_attr = (REPARSE_POINT*)ntfs_attr_readall(ni, |
738 | AT_REPARSE_POINT,(ntfschar*)NULL, 0, &attr_size); |
739 | if (reparse_attr && attr_size |
740 | && valid_reparse_data(ni, reparse_attr, attr_size)) { |
741 | switch (reparse_attr->reparse_tag) { |
742 | case IO_REPARSE_TAG_MOUNT_POINT : |
743 | mount_point_data = (struct MOUNT_POINT_REPARSE_DATA*) |
744 | reparse_attr->reparse_data; |
745 | offs = le16_to_cpu(mount_point_data->subst_name_offset); |
746 | lth = le16_to_cpu(mount_point_data->subst_name_length); |
747 | /* reparse data consistency has been checked */ |
748 | target = ntfs_get_fulllink(vol, |
749 | (ntfschar*)&mount_point_data->path_buffer[offs], |
750 | lth/2, mnt_point, isdir); |
751 | if (target) |
752 | bad = FALSE; |
753 | break; |
754 | case IO_REPARSE_TAG_SYMLINK : |
755 | symlink_data = (struct SYMLINK_REPARSE_DATA*) |
756 | reparse_attr->reparse_data; |
757 | offs = le16_to_cpu(symlink_data->subst_name_offset); |
758 | lth = le16_to_cpu(symlink_data->subst_name_length); |
759 | p = (ntfschar*)&symlink_data->path_buffer[offs]; |
760 | /* |
761 | * Predetermine the kind of target, |
762 | * the called function has to make a full check |
763 | */ |
764 | if (*p++ == const_cpu_to_le16('\\')) { |
765 | if ((*p == const_cpu_to_le16('?')) |
766 | || (*p == const_cpu_to_le16('\\'))) |
767 | kind = FULL_TARGET; |
768 | else |
769 | kind = ABS_TARGET; |
770 | } else |
771 | if (*p == const_cpu_to_le16(':')) |
772 | kind = ABS_TARGET; |
773 | else |
774 | kind = REL_TARGET; |
775 | p--; |
776 | /* reparse data consistency has been checked */ |
777 | switch (kind) { |
778 | case FULL_TARGET : |
779 | if (!(symlink_data->flags |
780 | & const_cpu_to_le32(1))) { |
781 | target = ntfs_get_fulllink(vol, |
782 | p, lth/2, |
783 | mnt_point, isdir); |
784 | if (target) |
785 | bad = FALSE; |
786 | } |
787 | break; |
788 | case ABS_TARGET : |
789 | if (symlink_data->flags |
790 | & const_cpu_to_le32(1)) { |
791 | target = ntfs_get_abslink(vol, |
792 | p, lth/2, |
793 | mnt_point, isdir); |
794 | if (target) |
795 | bad = FALSE; |
796 | } |
797 | break; |
798 | case REL_TARGET : |
799 | if (symlink_data->flags |
800 | & const_cpu_to_le32(1)) { |
801 | target = ntfs_get_rellink(ni, |
802 | p, lth/2); |
803 | if (target) |
804 | bad = FALSE; |
805 | } |
806 | break; |
807 | } |
808 | break; |
809 | } |
810 | free(reparse_attr); |
811 | } |
812 | *pattr_size = attr_size; |
813 | if (bad) |
814 | errno = EOPNOTSUPP; |
815 | return (target); |
816 | } |
817 | |
818 | /* |
819 | * Check whether a reparse point looks like a junction point |
820 | * or a symbolic link. |
821 | * Should only be called for files or directories with reparse data |
822 | * |
823 | * The validity of the target is not checked. |
824 | */ |
825 | |
826 | BOOL ntfs_possible_symlink(ntfs_inode *ni) |
827 | { |
828 | s64 attr_size = 0; |
829 | REPARSE_POINT *reparse_attr; |
830 | BOOL possible; |
831 | |
832 | possible = FALSE; |
833 | reparse_attr = (REPARSE_POINT*)ntfs_attr_readall(ni, |
834 | AT_REPARSE_POINT,(ntfschar*)NULL, 0, &attr_size); |
835 | if (reparse_attr && attr_size) { |
836 | switch (reparse_attr->reparse_tag) { |
837 | case IO_REPARSE_TAG_MOUNT_POINT : |
838 | case IO_REPARSE_TAG_SYMLINK : |
839 | possible = TRUE; |
840 | default : ; |
841 | } |
842 | free(reparse_attr); |
843 | } |
844 | return (possible); |
845 | } |
846 | |
847 | #ifdef HAVE_SETXATTR /* extended attributes interface required */ |
848 | |
849 | /* |
850 | * Set the index for new reparse data |
851 | * |
852 | * Returns 0 if success |
853 | * -1 if failure, explained by errno |
854 | */ |
855 | |
856 | static int set_reparse_index(ntfs_inode *ni, ntfs_index_context *xr, |
857 | le32 reparse_tag) |
858 | { |
859 | struct REPARSE_INDEX indx; |
860 | u64 file_id_cpu; |
861 | le64 file_id; |
862 | le16 seqn; |
863 | |
864 | seqn = ni->mrec->sequence_number; |
865 | file_id_cpu = MK_MREF(ni->mft_no,le16_to_cpu(seqn)); |
866 | file_id = cpu_to_le64(file_id_cpu); |
867 | indx.header.data_offset = const_cpu_to_le16( |
868 | sizeof(INDEX_ENTRY_HEADER) |
869 | + sizeof(REPARSE_INDEX_KEY)); |
870 | indx.header.data_length = const_cpu_to_le16(0); |
871 | indx.header.reservedV = const_cpu_to_le32(0); |
872 | indx.header.length = const_cpu_to_le16( |
873 | sizeof(struct REPARSE_INDEX)); |
874 | indx.header.key_length = const_cpu_to_le16( |
875 | sizeof(REPARSE_INDEX_KEY)); |
876 | indx.header.flags = const_cpu_to_le16(0); |
877 | indx.header.reserved = const_cpu_to_le16(0); |
878 | indx.key.reparse_tag = reparse_tag; |
879 | /* danger on processors which require proper alignment ! */ |
880 | memcpy(&indx.key.file_id, &file_id, 8); |
881 | indx.filling = const_cpu_to_le32(0); |
882 | ntfs_index_ctx_reinit(xr); |
883 | return (ntfs_ie_add(xr,(INDEX_ENTRY*)&indx)); |
884 | } |
885 | |
886 | #endif /* HAVE_SETXATTR */ |
887 | |
888 | /* |
889 | * Remove a reparse data index entry if attribute present |
890 | * |
891 | * Returns the size of existing reparse data |
892 | * (the existing reparse tag is returned) |
893 | * -1 if failure, explained by errno |
894 | */ |
895 | |
896 | static int remove_reparse_index(ntfs_attr *na, ntfs_index_context *xr, |
897 | le32 *preparse_tag) |
898 | { |
899 | REPARSE_INDEX_KEY key; |
900 | u64 file_id_cpu; |
901 | le64 file_id; |
902 | s64 size; |
903 | le16 seqn; |
904 | int ret; |
905 | |
906 | ret = na->data_size; |
907 | if (ret) { |
908 | /* read the existing reparse_tag */ |
909 | size = ntfs_attr_pread(na, 0, 4, preparse_tag); |
910 | if (size == 4) { |
911 | seqn = na->ni->mrec->sequence_number; |
912 | file_id_cpu = MK_MREF(na->ni->mft_no,le16_to_cpu(seqn)); |
913 | file_id = cpu_to_le64(file_id_cpu); |
914 | key.reparse_tag = *preparse_tag; |
915 | /* danger on processors which require proper alignment ! */ |
916 | memcpy(&key.file_id, &file_id, 8); |
917 | if (!ntfs_index_lookup(&key, sizeof(REPARSE_INDEX_KEY), xr) |
918 | && ntfs_index_rm(xr)) |
919 | ret = -1; |
920 | } else { |
921 | ret = -1; |
922 | errno = ENODATA; |
923 | } |
924 | } |
925 | return (ret); |
926 | } |
927 | |
928 | /* |
929 | * Open the $Extend/$Reparse file and its index |
930 | * |
931 | * Return the index context if opened |
932 | * or NULL if an error occurred (errno tells why) |
933 | * |
934 | * The index has to be freed and inode closed when not needed any more. |
935 | */ |
936 | |
937 | static ntfs_index_context *open_reparse_index(ntfs_volume *vol) |
938 | { |
939 | u64 inum; |
940 | ntfs_inode *ni; |
941 | ntfs_inode *dir_ni; |
942 | ntfs_index_context *xr; |
943 | |
944 | /* do not use path_name_to inode - could reopen root */ |
945 | dir_ni = ntfs_inode_open(vol, FILE_Extend); |
946 | ni = (ntfs_inode*)NULL; |
947 | if (dir_ni) { |
948 | inum = ntfs_inode_lookup_by_mbsname(dir_ni,"$Reparse"); |
949 | if (inum != (u64)-1) |
950 | ni = ntfs_inode_open(vol, inum); |
951 | ntfs_inode_close(dir_ni); |
952 | } |
953 | if (ni) { |
954 | xr = ntfs_index_ctx_get(ni, reparse_index_name, 2); |
955 | if (!xr) { |
956 | ntfs_inode_close(ni); |
957 | } |
958 | } else |
959 | xr = (ntfs_index_context*)NULL; |
960 | return (xr); |
961 | } |
962 | |
963 | #ifdef HAVE_SETXATTR /* extended attributes interface required */ |
964 | |
965 | /* |
966 | * Update the reparse data and index |
967 | * |
968 | * The reparse data attribute should have been created, and |
969 | * an existing index is expected if there is an existing value. |
970 | * |
971 | * Returns 0 if success |
972 | * -1 if failure, explained by errno |
973 | * If could not remove the existing index, nothing is done, |
974 | * If could not write the new data, no index entry is inserted |
975 | * If failed to insert the index, data is removed |
976 | */ |
977 | |
978 | static int update_reparse_data(ntfs_inode *ni, ntfs_index_context *xr, |
979 | const char *value, size_t size) |
980 | { |
981 | int res; |
982 | int written; |
983 | int oldsize; |
984 | ntfs_attr *na; |
985 | le32 reparse_tag; |
986 | |
987 | res = 0; |
988 | na = ntfs_attr_open(ni, AT_REPARSE_POINT, AT_UNNAMED, 0); |
989 | if (na) { |
990 | /* remove the existing reparse data */ |
991 | oldsize = remove_reparse_index(na,xr,&reparse_tag); |
992 | if (oldsize < 0) |
993 | res = -1; |
994 | else { |
995 | /* resize attribute */ |
996 | res = ntfs_attr_truncate(na, (s64)size); |
997 | /* overwrite value if any */ |
998 | if (!res && value) { |
999 | written = (int)ntfs_attr_pwrite(na, |
1000 | (s64)0, (s64)size, value); |
1001 | if (written != (s64)size) { |
1002 | ntfs_log_error("Failed to update " |
1003 | "reparse data\n"); |
1004 | errno = EIO; |
1005 | res = -1; |
1006 | } |
1007 | } |
1008 | if (!res |
1009 | && set_reparse_index(ni,xr, |
1010 | ((const REPARSE_POINT*)value)->reparse_tag) |
1011 | && (oldsize > 0)) { |
1012 | /* |
1013 | * If cannot index, try to remove the reparse |
1014 | * data and log the error. There will be an |
1015 | * inconsistency if removal fails. |
1016 | */ |
1017 | ntfs_attr_rm(na); |
1018 | ntfs_log_error("Failed to index reparse data." |
1019 | " Possible corruption.\n"); |
1020 | } |
1021 | } |
1022 | ntfs_attr_close(na); |
1023 | NInoSetDirty(ni); |
1024 | } else |
1025 | res = -1; |
1026 | return (res); |
1027 | } |
1028 | |
1029 | #endif /* HAVE_SETXATTR */ |
1030 | |
1031 | /* |
1032 | * Delete a reparse index entry |
1033 | * |
1034 | * Returns 0 if success |
1035 | * -1 if failure, explained by errno |
1036 | */ |
1037 | |
1038 | int ntfs_delete_reparse_index(ntfs_inode *ni) |
1039 | { |
1040 | ntfs_index_context *xr; |
1041 | ntfs_inode *xrni; |
1042 | ntfs_attr *na; |
1043 | le32 reparse_tag; |
1044 | int res; |
1045 | |
1046 | res = 0; |
1047 | na = ntfs_attr_open(ni, AT_REPARSE_POINT, AT_UNNAMED, 0); |
1048 | if (na) { |
1049 | /* |
1050 | * read the existing reparse data (the tag is enough) |
1051 | * and un-index it |
1052 | */ |
1053 | xr = open_reparse_index(ni->vol); |
1054 | if (xr) { |
1055 | if (remove_reparse_index(na,xr,&reparse_tag) < 0) |
1056 | res = -1; |
1057 | xrni = xr->ni; |
1058 | ntfs_index_entry_mark_dirty(xr); |
1059 | NInoSetDirty(xrni); |
1060 | ntfs_index_ctx_put(xr); |
1061 | ntfs_inode_close(xrni); |
1062 | } |
1063 | ntfs_attr_close(na); |
1064 | } |
1065 | return (res); |
1066 | } |
1067 | |
1068 | #ifdef HAVE_SETXATTR /* extended attributes interface required */ |
1069 | |
1070 | /* |
1071 | * Get the ntfs reparse data into an extended attribute |
1072 | * |
1073 | * Returns the reparse data size |
1074 | * and the buffer is updated if it is long enough |
1075 | */ |
1076 | |
1077 | int ntfs_get_ntfs_reparse_data(ntfs_inode *ni, char *value, size_t size) |
1078 | { |
1079 | REPARSE_POINT *reparse_attr; |
1080 | s64 attr_size; |
1081 | |
1082 | attr_size = 0; /* default to no data and no error */ |
1083 | if (ni) { |
1084 | if (ni->flags & FILE_ATTR_REPARSE_POINT) { |
1085 | reparse_attr = (REPARSE_POINT*)ntfs_attr_readall(ni, |
1086 | AT_REPARSE_POINT,(ntfschar*)NULL, 0, &attr_size); |
1087 | if (reparse_attr) { |
1088 | if (attr_size <= (s64)size) { |
1089 | if (value) |
1090 | memcpy(value,reparse_attr, |
1091 | attr_size); |
1092 | else |
1093 | errno = EINVAL; |
1094 | } |
1095 | free(reparse_attr); |
1096 | } |
1097 | } else |
1098 | errno = ENODATA; |
1099 | } |
1100 | return (attr_size ? (int)attr_size : -errno); |
1101 | } |
1102 | |
1103 | /* |
1104 | * Set the reparse data from an extended attribute |
1105 | * |
1106 | * Warning : the new data is not checked |
1107 | * |
1108 | * Returns 0, or -1 if there is a problem |
1109 | */ |
1110 | |
1111 | int ntfs_set_ntfs_reparse_data(ntfs_inode *ni, |
1112 | const char *value, size_t size, int flags) |
1113 | { |
1114 | int res; |
1115 | u8 dummy; |
1116 | ntfs_inode *xrni; |
1117 | ntfs_index_context *xr; |
1118 | |
1119 | res = 0; |
1120 | if (ni && valid_reparse_data(ni, (const REPARSE_POINT*)value, size)) { |
1121 | xr = open_reparse_index(ni->vol); |
1122 | if (xr) { |
1123 | if (!ntfs_attr_exist(ni,AT_REPARSE_POINT, |
1124 | AT_UNNAMED,0)) { |
1125 | if (!(flags & XATTR_REPLACE)) { |
1126 | /* |
1127 | * no reparse data attribute : add one, |
1128 | * apparently, this does not feed the new value in |
1129 | * Note : NTFS version must be >= 3 |
1130 | */ |
1131 | if (ni->vol->major_ver >= 3) { |
1132 | res = ntfs_attr_add(ni, |
1133 | AT_REPARSE_POINT, |
1134 | AT_UNNAMED,0,&dummy, |
1135 | (s64)0); |
1136 | if (!res) { |
1137 | ni->flags |= |
1138 | FILE_ATTR_REPARSE_POINT; |
1139 | NInoFileNameSetDirty(ni); |
1140 | } |
1141 | NInoSetDirty(ni); |
1142 | } else { |
1143 | errno = EOPNOTSUPP; |
1144 | res = -1; |
1145 | } |
1146 | } else { |
1147 | errno = ENODATA; |
1148 | res = -1; |
1149 | } |
1150 | } else { |
1151 | if (flags & XATTR_CREATE) { |
1152 | errno = EEXIST; |
1153 | res = -1; |
1154 | } |
1155 | } |
1156 | if (!res) { |
1157 | /* update value and index */ |
1158 | res = update_reparse_data(ni,xr,value,size); |
1159 | } |
1160 | xrni = xr->ni; |
1161 | ntfs_index_entry_mark_dirty(xr); |
1162 | NInoSetDirty(xrni); |
1163 | ntfs_index_ctx_put(xr); |
1164 | ntfs_inode_close(xrni); |
1165 | } else { |
1166 | res = -1; |
1167 | } |
1168 | } else { |
1169 | errno = EINVAL; |
1170 | res = -1; |
1171 | } |
1172 | return (res ? -1 : 0); |
1173 | } |
1174 | |
1175 | /* |
1176 | * Remove the reparse data |
1177 | * |
1178 | * Returns 0, or -1 if there is a problem |
1179 | */ |
1180 | |
1181 | int ntfs_remove_ntfs_reparse_data(ntfs_inode *ni) |
1182 | { |
1183 | int res; |
1184 | int olderrno; |
1185 | ntfs_attr *na; |
1186 | ntfs_inode *xrni; |
1187 | ntfs_index_context *xr; |
1188 | le32 reparse_tag; |
1189 | |
1190 | res = 0; |
1191 | if (ni) { |
1192 | /* |
1193 | * open and delete the reparse data |
1194 | */ |
1195 | na = ntfs_attr_open(ni, AT_REPARSE_POINT, |
1196 | AT_UNNAMED,0); |
1197 | if (na) { |
1198 | /* first remove index (reparse data needed) */ |
1199 | xr = open_reparse_index(ni->vol); |
1200 | if (xr) { |
1201 | if (remove_reparse_index(na,xr, |
1202 | &reparse_tag) < 0) { |
1203 | res = -1; |
1204 | } else { |
1205 | /* now remove attribute */ |
1206 | res = ntfs_attr_rm(na); |
1207 | if (!res) { |
1208 | ni->flags &= |
1209 | ~FILE_ATTR_REPARSE_POINT; |
1210 | NInoFileNameSetDirty(ni); |
1211 | } else { |
1212 | /* |
1213 | * If we could not remove the |
1214 | * attribute, try to restore the |
1215 | * index and log the error. There |
1216 | * will be an inconsistency if |
1217 | * the reindexing fails. |
1218 | */ |
1219 | set_reparse_index(ni, xr, |
1220 | reparse_tag); |
1221 | ntfs_log_error( |
1222 | "Failed to remove reparse data." |
1223 | " Possible corruption.\n"); |
1224 | } |
1225 | } |
1226 | xrni = xr->ni; |
1227 | ntfs_index_entry_mark_dirty(xr); |
1228 | NInoSetDirty(xrni); |
1229 | ntfs_index_ctx_put(xr); |
1230 | ntfs_inode_close(xrni); |
1231 | } |
1232 | olderrno = errno; |
1233 | ntfs_attr_close(na); |
1234 | /* avoid errno pollution */ |
1235 | if (errno == ENOENT) |
1236 | errno = olderrno; |
1237 | } else { |
1238 | errno = ENODATA; |
1239 | res = -1; |
1240 | } |
1241 | NInoSetDirty(ni); |
1242 | } else { |
1243 | errno = EINVAL; |
1244 | res = -1; |
1245 | } |
1246 | return (res ? -1 : 0); |
1247 | } |
1248 | |
1249 | #endif /* HAVE_SETXATTR */ |
1250 |