blob: 299357e56241408b6882b599c6b334382d4a487e
1 | /** |
2 | * object_id.c - Processing of object ids |
3 | * |
4 | * This module is part of ntfs-3g library |
5 | * |
6 | * Copyright (c) 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 "compat.h" |
50 | #include "types.h" |
51 | #include "debug.h" |
52 | #include "attrib.h" |
53 | #include "inode.h" |
54 | #include "dir.h" |
55 | #include "volume.h" |
56 | #include "mft.h" |
57 | #include "index.h" |
58 | #include "lcnalloc.h" |
59 | #include "object_id.h" |
60 | #include "logging.h" |
61 | #include "misc.h" |
62 | |
63 | /* |
64 | * Endianness considerations |
65 | * |
66 | * According to RFC 4122, GUIDs should be printed with the most |
67 | * significant byte first, and the six fields be compared individually |
68 | * for ordering. RFC 4122 does not define the internal representation. |
69 | * |
70 | * Here we always copy disk images with no endianness change, |
71 | * and, for indexing, GUIDs are compared as if they were a sequence |
72 | * of four unsigned 32 bit integers. |
73 | * |
74 | * --------------------- begin from RFC 4122 ---------------------- |
75 | * Consider each field of the UUID to be an unsigned integer as shown |
76 | * in the table in section Section 4.1.2. Then, to compare a pair of |
77 | * UUIDs, arithmetically compare the corresponding fields from each |
78 | * UUID in order of significance and according to their data type. |
79 | * Two UUIDs are equal if and only if all the corresponding fields |
80 | * are equal. |
81 | * |
82 | * UUIDs, as defined in this document, can also be ordered |
83 | * lexicographically. For a pair of UUIDs, the first one follows the |
84 | * second if the most significant field in which the UUIDs differ is |
85 | * greater for the first UUID. The second precedes the first if the |
86 | * most significant field in which the UUIDs differ is greater for |
87 | * the second UUID. |
88 | * |
89 | * The fields are encoded as 16 octets, with the sizes and order of the |
90 | * fields defined above, and with each field encoded with the Most |
91 | * Significant Byte first (known as network byte order). Note that the |
92 | * field names, particularly for multiplexed fields, follow historical |
93 | * practice. |
94 | * |
95 | * 0 1 2 3 |
96 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
97 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
98 | * | time_low | |
99 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
100 | * | time_mid | time_hi_and_version | |
101 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
102 | * |clk_seq_hi_res | clk_seq_low | node (0-1) | |
103 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
104 | * | node (2-5) | |
105 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
106 | * |
107 | * ---------------------- end from RFC 4122 ----------------------- |
108 | */ |
109 | |
110 | typedef struct { |
111 | union { |
112 | /* alignment may be needed to evaluate collations */ |
113 | u32 alignment; |
114 | GUID guid; |
115 | } object_id; |
116 | } OBJECT_ID_INDEX_KEY; |
117 | |
118 | typedef struct { |
119 | le64 file_id; |
120 | GUID birth_volume_id; |
121 | GUID birth_object_id; |
122 | GUID domain_id; |
123 | } OBJECT_ID_INDEX_DATA; // known as OBJ_ID_INDEX_DATA |
124 | |
125 | struct OBJECT_ID_INDEX { /* index entry in $Extend/$ObjId */ |
126 | INDEX_ENTRY_HEADER header; |
127 | OBJECT_ID_INDEX_KEY key; |
128 | OBJECT_ID_INDEX_DATA data; |
129 | } ; |
130 | |
131 | static ntfschar objid_index_name[] = { const_cpu_to_le16('$'), |
132 | const_cpu_to_le16('O') }; |
133 | #ifdef HAVE_SETXATTR /* extended attributes interface required */ |
134 | |
135 | /* |
136 | * Set the index for a new object id |
137 | * |
138 | * Returns 0 if success |
139 | * -1 if failure, explained by errno |
140 | */ |
141 | |
142 | static int set_object_id_index(ntfs_inode *ni, ntfs_index_context *xo, |
143 | const OBJECT_ID_ATTR *object_id) |
144 | { |
145 | struct OBJECT_ID_INDEX indx; |
146 | u64 file_id_cpu; |
147 | le64 file_id; |
148 | le16 seqn; |
149 | |
150 | seqn = ni->mrec->sequence_number; |
151 | file_id_cpu = MK_MREF(ni->mft_no,le16_to_cpu(seqn)); |
152 | file_id = cpu_to_le64(file_id_cpu); |
153 | indx.header.data_offset = const_cpu_to_le16( |
154 | sizeof(INDEX_ENTRY_HEADER) |
155 | + sizeof(OBJECT_ID_INDEX_KEY)); |
156 | indx.header.data_length = const_cpu_to_le16( |
157 | sizeof(OBJECT_ID_INDEX_DATA)); |
158 | indx.header.reservedV = const_cpu_to_le32(0); |
159 | indx.header.length = const_cpu_to_le16( |
160 | sizeof(struct OBJECT_ID_INDEX)); |
161 | indx.header.key_length = const_cpu_to_le16( |
162 | sizeof(OBJECT_ID_INDEX_KEY)); |
163 | indx.header.flags = const_cpu_to_le16(0); |
164 | indx.header.reserved = const_cpu_to_le16(0); |
165 | |
166 | memcpy(&indx.key.object_id,object_id,sizeof(GUID)); |
167 | |
168 | indx.data.file_id = file_id; |
169 | memcpy(&indx.data.birth_volume_id, |
170 | &object_id->birth_volume_id,sizeof(GUID)); |
171 | memcpy(&indx.data.birth_object_id, |
172 | &object_id->birth_object_id,sizeof(GUID)); |
173 | memcpy(&indx.data.domain_id, |
174 | &object_id->domain_id,sizeof(GUID)); |
175 | ntfs_index_ctx_reinit(xo); |
176 | return (ntfs_ie_add(xo,(INDEX_ENTRY*)&indx)); |
177 | } |
178 | |
179 | #endif /* HAVE_SETXATTR */ |
180 | |
181 | /* |
182 | * Open the $Extend/$ObjId file and its index |
183 | * |
184 | * Return the index context if opened |
185 | * or NULL if an error occurred (errno tells why) |
186 | * |
187 | * The index has to be freed and inode closed when not needed any more. |
188 | */ |
189 | |
190 | static ntfs_index_context *open_object_id_index(ntfs_volume *vol) |
191 | { |
192 | u64 inum; |
193 | ntfs_inode *ni; |
194 | ntfs_inode *dir_ni; |
195 | ntfs_index_context *xo; |
196 | |
197 | /* do not use path_name_to inode - could reopen root */ |
198 | dir_ni = ntfs_inode_open(vol, FILE_Extend); |
199 | ni = (ntfs_inode*)NULL; |
200 | if (dir_ni) { |
201 | inum = ntfs_inode_lookup_by_mbsname(dir_ni,"$ObjId"); |
202 | if (inum != (u64)-1) |
203 | ni = ntfs_inode_open(vol, inum); |
204 | ntfs_inode_close(dir_ni); |
205 | } |
206 | if (ni) { |
207 | xo = ntfs_index_ctx_get(ni, objid_index_name, 2); |
208 | if (!xo) { |
209 | ntfs_inode_close(ni); |
210 | } |
211 | } else |
212 | xo = (ntfs_index_context*)NULL; |
213 | return (xo); |
214 | } |
215 | |
216 | #ifdef HAVE_SETXATTR /* extended attributes interface required */ |
217 | |
218 | /* |
219 | * Merge object_id data stored in the index into |
220 | * a full object_id struct. |
221 | * |
222 | * returns 0 if merging successful |
223 | * -1 if no data could be merged. This is generally not an error |
224 | */ |
225 | |
226 | static int merge_index_data(ntfs_inode *ni, |
227 | const OBJECT_ID_ATTR *objectid_attr, |
228 | OBJECT_ID_ATTR *full_objectid) |
229 | { |
230 | OBJECT_ID_INDEX_KEY key; |
231 | struct OBJECT_ID_INDEX *entry; |
232 | ntfs_index_context *xo; |
233 | ntfs_inode *xoni; |
234 | int res; |
235 | |
236 | res = -1; |
237 | xo = open_object_id_index(ni->vol); |
238 | if (xo) { |
239 | memcpy(&key.object_id,objectid_attr,sizeof(GUID)); |
240 | if (!ntfs_index_lookup(&key, |
241 | sizeof(OBJECT_ID_INDEX_KEY), xo)) { |
242 | entry = (struct OBJECT_ID_INDEX*)xo->entry; |
243 | /* make sure inode numbers match */ |
244 | if (entry |
245 | && (MREF(le64_to_cpu(entry->data.file_id)) |
246 | == ni->mft_no)) { |
247 | memcpy(&full_objectid->birth_volume_id, |
248 | &entry->data.birth_volume_id, |
249 | sizeof(GUID)); |
250 | memcpy(&full_objectid->birth_object_id, |
251 | &entry->data.birth_object_id, |
252 | sizeof(GUID)); |
253 | memcpy(&full_objectid->domain_id, |
254 | &entry->data.domain_id, |
255 | sizeof(GUID)); |
256 | res = 0; |
257 | } |
258 | } |
259 | xoni = xo->ni; |
260 | ntfs_index_ctx_put(xo); |
261 | ntfs_inode_close(xoni); |
262 | } |
263 | return (res); |
264 | } |
265 | |
266 | #endif /* HAVE_SETXATTR */ |
267 | |
268 | /* |
269 | * Remove an object id index entry if attribute present |
270 | * |
271 | * Returns the size of existing object id |
272 | * (the existing object_d is returned) |
273 | * -1 if failure, explained by errno |
274 | */ |
275 | |
276 | static int remove_object_id_index(ntfs_attr *na, ntfs_index_context *xo, |
277 | OBJECT_ID_ATTR *old_attr) |
278 | { |
279 | OBJECT_ID_INDEX_KEY key; |
280 | struct OBJECT_ID_INDEX *entry; |
281 | s64 size; |
282 | int ret; |
283 | |
284 | ret = na->data_size; |
285 | if (ret) { |
286 | /* read the existing object id attribute */ |
287 | size = ntfs_attr_pread(na, 0, sizeof(GUID), old_attr); |
288 | if (size >= (s64)sizeof(GUID)) { |
289 | memcpy(&key.object_id, |
290 | &old_attr->object_id,sizeof(GUID)); |
291 | if (!ntfs_index_lookup(&key, |
292 | sizeof(OBJECT_ID_INDEX_KEY), xo)) { |
293 | entry = (struct OBJECT_ID_INDEX*)xo->entry; |
294 | memcpy(&old_attr->birth_volume_id, |
295 | &entry->data.birth_volume_id, |
296 | sizeof(GUID)); |
297 | memcpy(&old_attr->birth_object_id, |
298 | &entry->data.birth_object_id, |
299 | sizeof(GUID)); |
300 | memcpy(&old_attr->domain_id, |
301 | &entry->data.domain_id, |
302 | sizeof(GUID)); |
303 | if (ntfs_index_rm(xo)) |
304 | ret = -1; |
305 | } |
306 | } else { |
307 | ret = -1; |
308 | errno = ENODATA; |
309 | } |
310 | } |
311 | return (ret); |
312 | } |
313 | |
314 | #ifdef HAVE_SETXATTR /* extended attributes interface required */ |
315 | |
316 | /* |
317 | * Update the object id and index |
318 | * |
319 | * The object_id attribute should have been created and the |
320 | * non-duplication of the GUID should have been checked before. |
321 | * |
322 | * Returns 0 if success |
323 | * -1 if failure, explained by errno |
324 | * If could not remove the existing index, nothing is done, |
325 | * If could not write the new data, no index entry is inserted |
326 | * If failed to insert the index, data is removed |
327 | */ |
328 | |
329 | static int update_object_id(ntfs_inode *ni, ntfs_index_context *xo, |
330 | const OBJECT_ID_ATTR *value, size_t size) |
331 | { |
332 | OBJECT_ID_ATTR old_attr; |
333 | ntfs_attr *na; |
334 | int oldsize; |
335 | int written; |
336 | int res; |
337 | |
338 | res = 0; |
339 | |
340 | na = ntfs_attr_open(ni, AT_OBJECT_ID, AT_UNNAMED, 0); |
341 | if (na) { |
342 | |
343 | /* remove the existing index entry */ |
344 | oldsize = remove_object_id_index(na,xo,&old_attr); |
345 | if (oldsize < 0) |
346 | res = -1; |
347 | else { |
348 | /* resize attribute */ |
349 | res = ntfs_attr_truncate(na, (s64)sizeof(GUID)); |
350 | /* write the object_id in attribute */ |
351 | if (!res && value) { |
352 | written = (int)ntfs_attr_pwrite(na, |
353 | (s64)0, (s64)sizeof(GUID), |
354 | &value->object_id); |
355 | if (written != (s64)sizeof(GUID)) { |
356 | ntfs_log_error("Failed to update " |
357 | "object id\n"); |
358 | errno = EIO; |
359 | res = -1; |
360 | } |
361 | } |
362 | /* write index part if provided */ |
363 | if (!res |
364 | && ((size < sizeof(OBJECT_ID_ATTR)) |
365 | || set_object_id_index(ni,xo,value))) { |
366 | /* |
367 | * If cannot index, try to remove the object |
368 | * id and log the error. There will be an |
369 | * inconsistency if removal fails. |
370 | */ |
371 | ntfs_attr_rm(na); |
372 | ntfs_log_error("Failed to index object id." |
373 | " Possible corruption.\n"); |
374 | } |
375 | } |
376 | ntfs_attr_close(na); |
377 | NInoSetDirty(ni); |
378 | } else |
379 | res = -1; |
380 | return (res); |
381 | } |
382 | |
383 | /* |
384 | * Add a (dummy) object id to an inode if it does not exist |
385 | * |
386 | * returns 0 if attribute was inserted (or already present) |
387 | * -1 if adding failed (explained by errno) |
388 | */ |
389 | |
390 | static int add_object_id(ntfs_inode *ni, int flags) |
391 | { |
392 | int res; |
393 | u8 dummy; |
394 | |
395 | res = -1; /* default return */ |
396 | if (!ntfs_attr_exist(ni,AT_OBJECT_ID, AT_UNNAMED,0)) { |
397 | if (!(flags & XATTR_REPLACE)) { |
398 | /* |
399 | * no object id attribute : add one, |
400 | * apparently, this does not feed the new value in |
401 | * Note : NTFS version must be >= 3 |
402 | */ |
403 | if (ni->vol->major_ver >= 3) { |
404 | res = ntfs_attr_add(ni, AT_OBJECT_ID, |
405 | AT_UNNAMED, 0, &dummy, (s64)0); |
406 | NInoSetDirty(ni); |
407 | } else |
408 | errno = EOPNOTSUPP; |
409 | } else |
410 | errno = ENODATA; |
411 | } else { |
412 | if (flags & XATTR_CREATE) |
413 | errno = EEXIST; |
414 | else |
415 | res = 0; |
416 | } |
417 | return (res); |
418 | } |
419 | |
420 | #endif /* HAVE_SETXATTR */ |
421 | |
422 | /* |
423 | * Delete an object_id index entry |
424 | * |
425 | * Returns 0 if success |
426 | * -1 if failure, explained by errno |
427 | */ |
428 | |
429 | int ntfs_delete_object_id_index(ntfs_inode *ni) |
430 | { |
431 | ntfs_index_context *xo; |
432 | ntfs_inode *xoni; |
433 | ntfs_attr *na; |
434 | OBJECT_ID_ATTR old_attr; |
435 | int res; |
436 | |
437 | res = 0; |
438 | na = ntfs_attr_open(ni, AT_OBJECT_ID, AT_UNNAMED, 0); |
439 | if (na) { |
440 | /* |
441 | * read the existing object id |
442 | * and un-index it |
443 | */ |
444 | xo = open_object_id_index(ni->vol); |
445 | if (xo) { |
446 | if (remove_object_id_index(na,xo,&old_attr) < 0) |
447 | res = -1; |
448 | xoni = xo->ni; |
449 | ntfs_index_entry_mark_dirty(xo); |
450 | NInoSetDirty(xoni); |
451 | ntfs_index_ctx_put(xo); |
452 | ntfs_inode_close(xoni); |
453 | } |
454 | ntfs_attr_close(na); |
455 | } |
456 | return (res); |
457 | } |
458 | |
459 | #ifdef HAVE_SETXATTR /* extended attributes interface required */ |
460 | |
461 | /* |
462 | * Get the ntfs object id into an extended attribute |
463 | * |
464 | * If present, the object_id from the attribute and the GUIDs |
465 | * from the index are returned (formatted as OBJECT_ID_ATTR) |
466 | * |
467 | * Returns the global size (can be 0, 16 or 64) |
468 | * and the buffer is updated if it is long enough |
469 | */ |
470 | |
471 | int ntfs_get_ntfs_object_id(ntfs_inode *ni, char *value, size_t size) |
472 | { |
473 | OBJECT_ID_ATTR full_objectid; |
474 | OBJECT_ID_ATTR *objectid_attr; |
475 | s64 attr_size; |
476 | int full_size; |
477 | |
478 | full_size = 0; /* default to no data and some error to be defined */ |
479 | if (ni) { |
480 | objectid_attr = (OBJECT_ID_ATTR*)ntfs_attr_readall(ni, |
481 | AT_OBJECT_ID,(ntfschar*)NULL, 0, &attr_size); |
482 | if (objectid_attr) { |
483 | /* restrict to only GUID present in attr */ |
484 | if (attr_size == sizeof(GUID)) { |
485 | memcpy(&full_objectid.object_id, |
486 | objectid_attr,sizeof(GUID)); |
487 | full_size = sizeof(GUID); |
488 | /* get data from index, if any */ |
489 | if (!merge_index_data(ni, objectid_attr, |
490 | &full_objectid)) { |
491 | full_size = sizeof(OBJECT_ID_ATTR); |
492 | } |
493 | if (full_size <= (s64)size) { |
494 | if (value) |
495 | memcpy(value,&full_objectid, |
496 | full_size); |
497 | else |
498 | errno = EINVAL; |
499 | } |
500 | } else { |
501 | /* unexpected size, better return unsupported */ |
502 | errno = EOPNOTSUPP; |
503 | full_size = 0; |
504 | } |
505 | free(objectid_attr); |
506 | } else |
507 | errno = ENODATA; |
508 | } |
509 | return (full_size ? (int)full_size : -errno); |
510 | } |
511 | |
512 | /* |
513 | * Set the object id from an extended attribute |
514 | * |
515 | * If the size is 64, the attribute and index are set. |
516 | * else if the size is not less than 16 only the attribute is set. |
517 | * The object id index is set accordingly. |
518 | * |
519 | * Returns 0, or -1 if there is a problem |
520 | */ |
521 | |
522 | int ntfs_set_ntfs_object_id(ntfs_inode *ni, |
523 | const char *value, size_t size, int flags) |
524 | { |
525 | OBJECT_ID_INDEX_KEY key; |
526 | ntfs_inode *xoni; |
527 | ntfs_index_context *xo; |
528 | int res; |
529 | |
530 | res = 0; |
531 | if (ni && value && (size >= sizeof(GUID))) { |
532 | xo = open_object_id_index(ni->vol); |
533 | if (xo) { |
534 | /* make sure the GUID was not used somewhere */ |
535 | memcpy(&key.object_id, value, sizeof(GUID)); |
536 | if (ntfs_index_lookup(&key, |
537 | sizeof(OBJECT_ID_INDEX_KEY), xo)) { |
538 | ntfs_index_ctx_reinit(xo); |
539 | res = add_object_id(ni, flags); |
540 | if (!res) { |
541 | /* update value and index */ |
542 | res = update_object_id(ni,xo, |
543 | (const OBJECT_ID_ATTR*)value, |
544 | size); |
545 | } |
546 | } else { |
547 | /* GUID is present elsewhere */ |
548 | res = -1; |
549 | errno = EEXIST; |
550 | } |
551 | xoni = xo->ni; |
552 | ntfs_index_entry_mark_dirty(xo); |
553 | NInoSetDirty(xoni); |
554 | ntfs_index_ctx_put(xo); |
555 | ntfs_inode_close(xoni); |
556 | } else { |
557 | res = -1; |
558 | } |
559 | } else { |
560 | errno = EINVAL; |
561 | res = -1; |
562 | } |
563 | return (res ? -1 : 0); |
564 | } |
565 | |
566 | /* |
567 | * Remove the object id |
568 | * |
569 | * Returns 0, or -1 if there is a problem |
570 | */ |
571 | |
572 | int ntfs_remove_ntfs_object_id(ntfs_inode *ni) |
573 | { |
574 | int res; |
575 | int olderrno; |
576 | ntfs_attr *na; |
577 | ntfs_inode *xoni; |
578 | ntfs_index_context *xo; |
579 | int oldsize; |
580 | OBJECT_ID_ATTR old_attr; |
581 | |
582 | res = 0; |
583 | if (ni) { |
584 | /* |
585 | * open and delete the object id |
586 | */ |
587 | na = ntfs_attr_open(ni, AT_OBJECT_ID, |
588 | AT_UNNAMED,0); |
589 | if (na) { |
590 | /* first remove index (old object id needed) */ |
591 | xo = open_object_id_index(ni->vol); |
592 | if (xo) { |
593 | oldsize = remove_object_id_index(na,xo, |
594 | &old_attr); |
595 | if (oldsize < 0) { |
596 | res = -1; |
597 | } else { |
598 | /* now remove attribute */ |
599 | res = ntfs_attr_rm(na); |
600 | if (res |
601 | && (oldsize > (int)sizeof(GUID))) { |
602 | /* |
603 | * If we could not remove the |
604 | * attribute, try to restore the |
605 | * index and log the error. There |
606 | * will be an inconsistency if |
607 | * the reindexing fails. |
608 | */ |
609 | set_object_id_index(ni, xo, |
610 | &old_attr); |
611 | ntfs_log_error( |
612 | "Failed to remove object id." |
613 | " Possible corruption.\n"); |
614 | } |
615 | } |
616 | |
617 | xoni = xo->ni; |
618 | ntfs_index_entry_mark_dirty(xo); |
619 | NInoSetDirty(xoni); |
620 | ntfs_index_ctx_put(xo); |
621 | ntfs_inode_close(xoni); |
622 | } |
623 | olderrno = errno; |
624 | ntfs_attr_close(na); |
625 | /* avoid errno pollution */ |
626 | if (errno == ENOENT) |
627 | errno = olderrno; |
628 | } else { |
629 | errno = ENODATA; |
630 | res = -1; |
631 | } |
632 | NInoSetDirty(ni); |
633 | } else { |
634 | errno = EINVAL; |
635 | res = -1; |
636 | } |
637 | return (res ? -1 : 0); |
638 | } |
639 | |
640 | #endif /* HAVE_SETXATTR */ |
641 |