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