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