]> git.saurik.com Git - apple/hfs.git/blame_incremental - livefiles_hfs_plugin/lf_hfs_link.c
hfs-522.100.5.tar.gz
[apple/hfs.git] / livefiles_hfs_plugin / lf_hfs_link.c
... / ...
CommitLineData
1/* Copyright © 2017-2018 Apple Inc. All rights reserved.
2 *
3 * lf_hfs_link.c
4 * livefiles_hfs
5 *
6 * Created by Or Haimovich on 17/05/2018.
7 */
8
9#include "lf_hfs_link.h"
10#include "lf_hfs_vfsutils.h"
11#include "lf_hfs_vnops.h"
12#include "lf_hfs_vfsops.h"
13#include "lf_hfs_logger.h"
14#include "lf_hfs_utils.h"
15#include "lf_hfs_btrees_internal.h"
16#include "lf_hfs_xattr.h"
17#include "lf_hfs_endian.h"
18#include "lf_hfs_format.h"
19#include "lf_hfs_defs.h"
20
21/*
22 * Private directories where hardlink inodes reside.
23 */
24const char *hfs_private_names[] = {
25 HFSPLUSMETADATAFOLDER, /* FILE HARDLINKS */
26 HFSPLUS_DIR_METADATA_FOLDER /* DIRECTORY HARDLINKS */
27};
28
29static int getfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t *firstlink);
30static int setfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t firstlink);
31/*
32 * Set the first link attribute for a given file id.
33 *
34 * The attributes b-tree must already be locked.
35 * If journaling is enabled, a transaction must already be started.
36 */
37static int
38setfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t firstlink)
39{
40 FCB * btfile;
41 BTreeIterator * iterator;
42 FSBufferDescriptor btdata;
43 u_int8_t attrdata[FIRST_LINK_XATTR_REC_SIZE];
44 HFSPlusAttrData *dataptr;
45 int result;
46 u_int16_t datasize;
47
48 if (hfsmp->hfs_attribute_cp == NULL) {
49 return (EPERM);
50 }
51 iterator = hfs_mallocz(sizeof(*iterator));
52 if (iterator == NULL)
53 return ENOMEM;
54
55 result = hfs_buildattrkey(fileid, FIRST_LINK_XATTR_NAME, (HFSPlusAttrKey *)&iterator->key);
56 if (result) {
57 goto out;
58 }
59 dataptr = (HFSPlusAttrData *)&attrdata[0];
60 dataptr->recordType = kHFSPlusAttrInlineData;
61 dataptr->reserved[0] = 0;
62 dataptr->reserved[1] = 0;
63
64 /*
65 * Since attrData is variable length, we calculate the size of
66 * attrData by subtracting the size of all other members of
67 * structure HFSPlusAttData from the size of attrdata.
68 */
69 (void)snprintf((char *)&dataptr->attrData[0], sizeof(dataptr) - (4 * sizeof(uint32_t)), "%lu", (unsigned long)firstlink);
70
71 dataptr->attrSize = (u_int32_t)( 1 + strlen((char *)&dataptr->attrData[0]));
72
73 /* Calculate size of record rounded up to multiple of 2 bytes. */
74 datasize = sizeof(HFSPlusAttrData) - 2 + dataptr->attrSize + ((dataptr->attrSize & 1) ? 1 : 0);
75
76 btdata.bufferAddress = dataptr;
77 btdata.itemSize = datasize;
78 btdata.itemCount = 1;
79
80 btfile = hfsmp->hfs_attribute_cp->c_datafork;
81
82 /* Insert the attribute. */
83 result = BTInsertRecord(btfile, iterator, &btdata, datasize);
84 if (result == btExists) {
85 result = BTReplaceRecord(btfile, iterator, &btdata, datasize);
86 }
87 (void) BTFlushPath(btfile);
88out:
89 hfs_free(iterator);
90
91 return MacToVFSError(result);
92}
93
94/*
95 * Get the first link attribute for a given file id.
96 *
97 * The attributes b-tree must already be locked.
98 */
99static int
100getfirstlink(struct hfsmount * hfsmp, cnid_t fileid, cnid_t *firstlink)
101{
102 FCB * btfile;
103 BTreeIterator * iterator;
104 FSBufferDescriptor btdata;
105 u_int8_t attrdata[FIRST_LINK_XATTR_REC_SIZE];
106 HFSPlusAttrData *dataptr;
107 int result = 0;
108
109 if (hfsmp->hfs_attribute_cp == NULL) {
110 return (EPERM);
111 }
112 iterator = hfs_mallocz(sizeof(*iterator));
113 if (iterator == NULL)
114 return ENOMEM;
115
116 result = hfs_buildattrkey(fileid, FIRST_LINK_XATTR_NAME, (HFSPlusAttrKey *)&iterator->key);
117 if (result)
118 goto out;
119
120 dataptr = (HFSPlusAttrData *)&attrdata[0];
121
122 btdata.bufferAddress = dataptr;
123 btdata.itemSize = sizeof(attrdata);
124 btdata.itemCount = 1;
125
126 btfile = hfsmp->hfs_attribute_cp->c_datafork;
127
128 result = BTSearchRecord(btfile, iterator, &btdata, NULL, NULL);
129 if (result)
130 goto out;
131
132 if (dataptr->attrSize < 3) {
133 result = ENOENT;
134 goto out;
135 }
136 *firstlink = (cnid_t) strtoul((char*)&dataptr->attrData[0], NULL, 10);
137out:
138 hfs_free(iterator);
139
140 return MacToVFSError(result);
141}
142
143/* Find the oldest / last hardlink in the link chain */
144int
145hfs_lookup_lastlink (struct hfsmount *hfsmp, cnid_t linkfileid, cnid_t *lastid, struct cat_desc *cdesc) {
146 int lockflags;
147 int error;
148
149 *lastid = 0;
150
151 lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
152
153 error = cat_lookup_lastlink(hfsmp, linkfileid, lastid, cdesc);
154
155 hfs_systemfile_unlock(hfsmp, lockflags);
156
157 /*
158 * cat_lookup_lastlink will zero out the lastid/cdesc arguments as needed
159 * upon error cases.
160 */
161 return error;
162}
163
164/*
165 * Release a specific origin for a directory or file hard link
166 *
167 * cnode must be lock on entry
168 */
169void
170hfs_relorigin(struct cnode *cp, cnid_t parentcnid)
171{
172 linkorigin_t *origin, *prev;
173 pthread_t thread = pthread_self();
174
175 TAILQ_FOREACH_SAFE(origin, &cp->c_originlist, lo_link, prev)
176 {
177 if (origin->lo_thread == thread) {
178 TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
179 hfs_free(origin);
180 break;
181 } else if (origin->lo_parentcnid == parentcnid) {
182 /*
183 * If the threads don't match, then we don't want to
184 * delete the entry because that might cause other threads
185 * to fall back and use whatever happens to be in
186 * c_parentcnid or the wrong link ID. By setting the
187 * values to zero here, it should serve as an indication
188 * that the path is no longer valid and that's better than
189 * using a random parent ID or link ID.
190 */
191 origin->lo_parentcnid = 0;
192 origin->lo_cnid = 0;
193 }
194 }
195}
196
197/*
198 * Remove a link to a hardlink file/dir.
199 *
200 * Note: dvp and vp cnodes are already locked.
201 */
202int
203hfs_unlink(struct hfsmount *hfsmp, struct vnode *dvp, struct vnode *vp, struct componentname *cnp, int skip_reserve)
204{
205 struct cnode *cp;
206 struct cnode *dcp;
207 struct cat_desc cndesc;
208 char inodename[32];
209 cnid_t prevlinkid;
210 cnid_t nextlinkid;
211 int lockflags = 0;
212 int started_tr;
213 int error;
214
215 cp = VTOC(vp);
216 dcp = VTOC(dvp);
217
218 dcp->c_flag |= C_DIR_MODIFICATION;
219
220 if ((error = hfs_start_transaction(hfsmp)) != 0) {
221 started_tr = 0;
222 goto out;
223 }
224 started_tr = 1;
225
226 /*
227 * Protect against a race with rename by using the component
228 * name passed in and parent id from dvp (instead of using
229 * the cp->c_desc which may have changed).
230 *
231 * Re-lookup the component name so we get the correct cnid
232 * for the name (as opposed to the c_cnid in the cnode which
233 * could have changed before the cnode was locked).
234 */
235 cndesc.cd_flags = vnode_isdir(vp) ? CD_ISDIR : 0;
236 cndesc.cd_encoding = cp->c_desc.cd_encoding;
237 cndesc.cd_nameptr = (const u_int8_t *)cnp->cn_nameptr;
238 cndesc.cd_namelen = cnp->cn_namelen;
239 cndesc.cd_parentcnid = dcp->c_fileid;
240 cndesc.cd_hint = dcp->c_childhint;
241
242 lockflags = SFL_CATALOG | SFL_ATTRIBUTE;
243 if (cndesc.cd_flags & CD_ISDIR) {
244 /* We'll be removing the alias resource allocation blocks. */
245 lockflags |= SFL_BITMAP;
246 }
247 lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
248
249 if ((error = cat_lookuplink(hfsmp, &cndesc, &cndesc.cd_cnid, &prevlinkid, &nextlinkid))) {
250 goto out;
251 }
252
253 /* Reserve some space in the catalog file. */
254 if (!skip_reserve && (error = cat_preflight(hfsmp, 2 * CAT_DELETE, NULL))) {
255 goto out;
256 }
257
258 /* Purge any cached origin entries for a directory or file hard link. */
259 hfs_relorigin(cp, dcp->c_fileid);
260 if (dcp->c_fileid != dcp->c_cnid) {
261 hfs_relorigin(cp, dcp->c_cnid);
262 }
263
264 /* Delete the link record. */
265 if ((error = cat_deletelink(hfsmp, &cndesc))) {
266 goto out;
267 }
268
269 /* Update the parent directory. */
270 if (dcp->c_entries > 0) {
271 dcp->c_entries--;
272 }
273 if (cndesc.cd_flags & CD_ISDIR) {
274 DEC_FOLDERCOUNT(hfsmp, dcp->c_attr);
275 }
276 dcp->c_dirchangecnt++;
277 hfs_incr_gencount(dcp);
278
279 struct timeval tv;
280 microtime(&tv);
281 dcp->c_touch_chgtime = dcp->c_touch_modtime = true;
282 dcp->c_flag |= C_MODIFIED;
283 hfs_update(dcp->c_vp, 0);
284
285 /*
286 * If this is the last link then we need to process the inode.
287 * Otherwise we need to fix up the link chain.
288 */
289 --cp->c_linkcount;
290 if (cp->c_linkcount < 1) {
291 char delname[32];
292 struct cat_desc to_desc;
293 struct cat_desc from_desc;
294
295 /*
296 * If a file inode or directory inode is being deleted, rename
297 * it to an open deleted file. This ensures that deletion
298 * of inode and its corresponding extended attributes does
299 * not overflow the journal. This inode will be deleted
300 * either in hfs_vnop_inactive() or in hfs_remove_orphans().
301 * Note: a rename failure here is not fatal.
302 */
303 bzero(&from_desc, sizeof(from_desc));
304 bzero(&to_desc, sizeof(to_desc));
305 if (vnode_isdir(vp)) {
306 if (cp->c_entries != 0) {
307 LFHFS_LOG(LEVEL_ERROR, "hfs_unlink: dir not empty (id %d, %d entries)", cp->c_fileid, cp->c_entries);
308 hfs_assert(0);
309 }
310 MAKE_DIRINODE_NAME(inodename, sizeof(inodename),
311 cp->c_attr.ca_linkref);
312 from_desc.cd_parentcnid = hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid;
313 from_desc.cd_flags = CD_ISDIR;
314 to_desc.cd_flags = CD_ISDIR;
315 } else {
316 MAKE_INODE_NAME(inodename, sizeof(inodename),
317 cp->c_attr.ca_linkref);
318 from_desc.cd_parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
319 from_desc.cd_flags = 0;
320 to_desc.cd_flags = 0;
321 }
322 from_desc.cd_nameptr = (const u_int8_t *)inodename;
323 from_desc.cd_namelen = strlen(inodename);
324 from_desc.cd_cnid = cp->c_fileid;
325
326 MAKE_DELETED_NAME(delname, sizeof(delname), cp->c_fileid);
327 to_desc.cd_nameptr = (const u_int8_t *)delname;
328 to_desc.cd_namelen = strlen(delname);
329 to_desc.cd_parentcnid = hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid;
330 to_desc.cd_cnid = cp->c_fileid;
331
332 error = cat_rename(hfsmp, &from_desc, &hfsmp->hfs_private_desc[FILE_HARDLINKS],
333 &to_desc, (struct cat_desc *)NULL);
334 if (error == 0) {
335 cp->c_flag |= C_DELETED;
336 cp->c_attr.ca_recflags &= ~kHFSHasLinkChainMask;
337 cp->c_attr.ca_firstlink = 0;
338 if (vnode_isdir(vp)) {
339 hfsmp->hfs_private_attr[DIR_HARDLINKS].ca_entries--;
340 DEC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[DIR_HARDLINKS]);
341
342 hfsmp->hfs_private_attr[FILE_HARDLINKS].ca_entries++;
343 INC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[FILE_HARDLINKS]);
344
345 (void)cat_update(hfsmp, &hfsmp->hfs_private_desc[DIR_HARDLINKS],
346 &hfsmp->hfs_private_attr[DIR_HARDLINKS], NULL, NULL);
347 (void)cat_update(hfsmp, &hfsmp->hfs_private_desc[FILE_HARDLINKS],
348 &hfsmp->hfs_private_attr[FILE_HARDLINKS], NULL, NULL);
349 }
350 } else {
351 error = 0; /* rename failure here is not fatal */
352 }
353 } else /* Still some links left */ {
354 cnid_t firstlink = 0;
355
356 /*
357 * Update the start of the link chain.
358 * Note: Directory hard links store the first link in an attribute.
359 */
360 if (IS_DIR(vp) &&
361 getfirstlink(hfsmp, cp->c_fileid, &firstlink) == 0 &&
362 firstlink == cndesc.cd_cnid) {
363 if (setfirstlink(hfsmp, cp->c_fileid, nextlinkid) == 0)
364 cp->c_attr.ca_recflags |= kHFSHasAttributesMask;
365 } else if (cp->c_attr.ca_firstlink == cndesc.cd_cnid) {
366 cp->c_attr.ca_firstlink = nextlinkid;
367 }
368 /* Update previous link. */
369 if (prevlinkid) {
370 (void) cat_update_siblinglinks(hfsmp, prevlinkid, HFS_IGNORABLE_LINK, nextlinkid);
371 }
372 /* Update next link. */
373 if (nextlinkid) {
374 (void) cat_update_siblinglinks(hfsmp, nextlinkid, prevlinkid, HFS_IGNORABLE_LINK);
375 }
376 }
377
378 /*
379 * The call to cat_releasedesc below will only release the name
380 * buffer; it does not zero out the rest of the fields in the
381 * 'cat_desc' data structure.
382 *
383 * As a result, since there are still other links at this point,
384 * we need to make the current cnode descriptor point to the raw
385 * inode. If a path-based system call comes along first, it will
386 * replace the descriptor with a valid link ID. If a userland
387 * process already has a file descriptor open, then they will
388 * bypass that lookup, though. Replacing the descriptor CNID with
389 * the raw inode will force it to generate a new full path.
390 */
391 cp->c_cnid = cp->c_fileid;
392
393 /* Push new link count to disk. */
394 cp->c_ctime = tv.tv_sec;
395 (void) cat_update(hfsmp, &cp->c_desc, &cp->c_attr, NULL, NULL);
396
397 /* All done with the system files. */
398 hfs_systemfile_unlock(hfsmp, lockflags);
399 lockflags = 0;
400
401 /* Update file system stats. */
402 hfs_volupdate(hfsmp, VOL_RMFILE, (dcp->c_cnid == kHFSRootFolderID));
403
404 /*
405 * All done with this cnode's descriptor...
406 *
407 * Note: all future catalog calls for this cnode may be
408 * by fileid only. This is OK for HFS (which doesn't have
409 * file thread records) since HFS doesn't support hard links.
410 */
411 cat_releasedesc(&cp->c_desc);
412
413out:
414 if (lockflags) {
415 hfs_systemfile_unlock(hfsmp, lockflags);
416 }
417 if (started_tr) {
418 hfs_end_transaction(hfsmp);
419 }
420
421 dcp->c_flag &= ~C_DIR_MODIFICATION;
422 //TBD - We have wakeup here but can't see anyone who's msleeping on c_flag...
423 //wakeup((caddr_t)&dcp->c_flag);
424
425 return (error);
426}
427
428/*
429 * Cache the origin of a directory or file hard link
430 *
431 * cnode must be lock on entry
432 */
433void
434hfs_savelinkorigin(cnode_t *cp, cnid_t parentcnid)
435{
436 linkorigin_t *origin = NULL, *next = NULL;
437 pthread_t thread = pthread_self();
438 int count = 0;
439 int maxorigins = (S_ISDIR(cp->c_mode)) ? MAX_CACHED_ORIGINS : MAX_CACHED_FILE_ORIGINS;
440 /*
441 * Look for an existing origin first. If not found, create/steal one.
442 */
443 TAILQ_FOREACH_SAFE(origin, &cp->c_originlist, lo_link, next) {
444 ++count;
445 if (origin->lo_thread == thread) {
446 TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
447 break;
448 }
449 }
450 if (origin == NULL) {
451 /* Recycle the last (i.e., the oldest) if we have too many. */
452 if (count > maxorigins) {
453 origin = TAILQ_LAST(&cp->c_originlist, hfs_originhead);
454 TAILQ_REMOVE(&cp->c_originlist, origin, lo_link);
455 } else {
456 origin = hfs_malloc(sizeof(linkorigin_t));
457 }
458 origin->lo_thread = thread;
459 }
460 origin->lo_cnid = cp->c_cnid;
461 origin->lo_parentcnid = parentcnid;
462 TAILQ_INSERT_HEAD(&cp->c_originlist, origin, lo_link);
463}
464
465/*
466 * Initialize the HFS+ private system directories.
467 *
468 * These directories are used to hold the inodes
469 * for file and directory hardlinks as well as
470 * open-unlinked files.
471 *
472 * If they don't yet exist they will get created.
473 *
474 * This call is assumed to be made during mount.
475 */
476void
477hfs_privatedir_init(struct hfsmount * hfsmp, enum privdirtype type)
478{
479 struct vnode * dvp = NULL;
480 struct cnode * dcp = NULL;
481 struct cat_desc *priv_descp;
482 struct cat_attr *priv_attrp;
483 struct timeval tv;
484 int lockflags;
485 int trans = 0;
486 int error;
487
488 priv_descp = &hfsmp->hfs_private_desc[type];
489 priv_attrp = &hfsmp->hfs_private_attr[type];
490
491 /* Check if directory already exists. */
492 if (priv_descp->cd_cnid != 0) {
493 return;
494 }
495
496 priv_descp->cd_parentcnid = kRootDirID;
497 priv_descp->cd_nameptr = (const u_int8_t *)hfs_private_names[type];
498 priv_descp->cd_namelen = strlen((const char *)priv_descp->cd_nameptr);
499 priv_descp->cd_flags = CD_ISDIR | CD_DECOMPOSED;
500
501 lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
502 error = cat_lookup(hfsmp, priv_descp, 0, NULL, priv_attrp, NULL, NULL);
503
504 hfs_systemfile_unlock(hfsmp, lockflags);
505
506 if (error == 0) {
507 if (type == FILE_HARDLINKS) {
508 hfsmp->hfs_metadata_createdate = (uint32_t) priv_attrp->ca_itime;
509 }
510 priv_descp->cd_cnid = priv_attrp->ca_fileid;
511 goto exit;
512 }
513
514 /* Directory is missing, if this is read-only then we're done. */
515 if (hfsmp->hfs_flags & HFS_READ_ONLY) {
516 goto exit;
517 }
518
519 /* Grab the root directory so we can update it later. */
520 if (hfs_vget(hfsmp, kRootDirID, &dvp, 0, 0) != 0) {
521 goto exit;
522 }
523
524 dcp = VTOC(dvp);
525
526 /* Setup the default attributes */
527 bzero(priv_attrp, sizeof(struct cat_attr));
528 priv_attrp->ca_flags = UF_IMMUTABLE | UF_HIDDEN;
529 priv_attrp->ca_mode = S_IFDIR;
530 if (type == DIR_HARDLINKS) {
531 priv_attrp->ca_mode |= S_ISVTX | S_IRUSR | S_IXUSR | S_IRGRP |
532 S_IXGRP | S_IROTH | S_IXOTH;
533 }
534 priv_attrp->ca_linkcount = 1;
535 priv_attrp->ca_itime = hfsmp->hfs_itime;
536 priv_attrp->ca_recflags = kHFSHasFolderCountMask;
537
538 //TBD - Probebly need to adjust for files app and not for finder....
539 struct FndrDirInfo * fndrinfo;
540 fndrinfo = (struct FndrDirInfo *)&priv_attrp->ca_finderinfo;
541 fndrinfo->frLocation.v = SWAP_BE16(16384);
542 fndrinfo->frLocation.h = SWAP_BE16(16384);
543 fndrinfo->frFlags = SWAP_BE16(kIsInvisible + kNameLocked);
544
545 if (hfs_start_transaction(hfsmp) != 0) {
546 goto exit;
547 }
548 trans = 1;
549
550 /* Need the catalog and EA b-trees for CNID acquisition */
551 lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK);
552
553 /* Make sure there's space in the Catalog file. */
554 if (cat_preflight(hfsmp, CAT_CREATE, NULL) != 0) {
555 hfs_systemfile_unlock(hfsmp, lockflags);
556 goto exit;
557 }
558
559 /* Get the CNID for use */
560 cnid_t new_id;
561 if ((error = cat_acquire_cnid(hfsmp, &new_id))) {
562 hfs_systemfile_unlock (hfsmp, lockflags);
563 goto exit;
564 }
565
566 /* Create the private directory on disk. */
567 error = cat_create(hfsmp, new_id, priv_descp, priv_attrp, NULL);
568 if (error == 0) {
569 priv_descp->cd_cnid = priv_attrp->ca_fileid;
570
571 /* Update the parent directory */
572 dcp->c_entries++;
573 INC_FOLDERCOUNT(hfsmp, dcp->c_attr);
574 dcp->c_dirchangecnt++;
575 hfs_incr_gencount(dcp);
576 microtime(&tv);
577 dcp->c_ctime = tv.tv_sec;
578 dcp->c_mtime = tv.tv_sec;
579 (void) cat_update(hfsmp, &dcp->c_desc, &dcp->c_attr, NULL, NULL);
580 }
581
582 hfs_systemfile_unlock(hfsmp, lockflags);
583
584 if (error) {
585 goto exit;
586 }
587 if (type == FILE_HARDLINKS) {
588 hfsmp->hfs_metadata_createdate = (uint32_t) priv_attrp->ca_itime;
589 }
590 hfs_volupdate(hfsmp, VOL_MKDIR, 1);
591exit:
592 if (trans) {
593 hfs_end_transaction(hfsmp);
594 }
595 if (dvp) {
596 hfs_unlock(dcp);
597 hfs_vnop_reclaim(dvp);
598 }
599
600 //Curently disable -need to understand how much we need this...
601// if ((error == 0) && (type == DIR_HARDLINKS)) {
602// hfs_xattr_init(hfsmp);
603// }
604}
605
606/*
607 * Release any cached origins for a directory or file hard link
608 *
609 * cnode must be lock on entry
610 */
611void
612hfs_relorigins(struct cnode *cp)
613{
614 linkorigin_t *origin, *prev;
615
616 TAILQ_FOREACH_SAFE(origin, &cp->c_originlist, lo_link, prev) {
617 hfs_free(origin);
618 }
619 TAILQ_INIT(&cp->c_originlist);
620}
621
622/*
623 * Obtain the current parent cnid of a directory or file hard link
624 *
625 * cnode must be lock on entry
626 */
627cnid_t
628hfs_currentparent(cnode_t *cp, bool have_lock)
629{
630 if (cp->c_flag & C_HARDLINK) {
631 if (!have_lock)
632 hfs_lock(cp, HFS_SHARED_LOCK, HFS_LOCK_ALWAYS);
633
634 linkorigin_t *origin;
635 pthread_t thread = pthread_self();
636
637 TAILQ_FOREACH(origin, &cp->c_originlist, lo_link) {
638 if (origin->lo_thread == thread) {
639 if (!have_lock)
640 hfs_unlock(cp);
641 return (origin->lo_parentcnid);
642 }
643 }
644
645 if (!have_lock)
646 hfs_unlock(cp);
647 }
648 return (cp->c_parentcnid);
649}
650
651/*
652 * Create a new catalog link record
653 *
654 * An indirect link is a reference to an inode (the real
655 * file or directory record).
656 *
657 * All the indirect links for a given inode are chained
658 * together in a doubly linked list.
659 *
660 * Pre-Leopard file hard links do not have kHFSHasLinkChainBit
661 * set and do not have first/prev/next link IDs i.e. the values
662 * are zero. If a new link is being added to an existing
663 * pre-Leopard file hard link chain, do not set kHFSHasLinkChainBit.
664 */
665static int
666createindirectlink(struct hfsmount *hfsmp, u_int32_t linknum, struct cat_desc *descp,
667 cnid_t nextcnid, cnid_t *linkcnid, int is_inode_linkchain_set)
668{
669 struct FndrFileInfo *fip;
670 struct cat_attr attr;
671
672 if (linknum == 0) {
673 LFHFS_LOG(LEVEL_ERROR, "createindirectlink: linknum is zero!\n");
674 return (EINVAL);
675 }
676
677 /* Setup the default attributes */
678 bzero(&attr, sizeof(attr));
679
680 /* Links are matched to inodes by link ID and to volumes by create date */
681 attr.ca_linkref = linknum;
682 attr.ca_itime = hfsmp->hfs_metadata_createdate;
683 attr.ca_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
684 attr.ca_recflags = kHFSHasLinkChainMask | kHFSThreadExistsMask;
685 attr.ca_flags = UF_IMMUTABLE;
686 fip = (struct FndrFileInfo *)&attr.ca_finderinfo;
687
688 if (descp->cd_flags & CD_ISDIR) {
689 fip->fdType = SWAP_BE32 (kHFSAliasType);
690 fip->fdCreator = SWAP_BE32 (kHFSAliasCreator);
691 fip->fdFlags = SWAP_BE16 (kIsAlias);
692 } else /* file */ {
693 fip->fdType = SWAP_BE32 (kHardLinkFileType);
694 fip->fdCreator = SWAP_BE32 (kHFSPlusCreator);
695 fip->fdFlags = SWAP_BE16 (kHasBeenInited);
696 /* If the file inode does not have kHFSHasLinkChainBit set
697 * and the next link chain ID is zero, assume that this
698 * is pre-Leopard file inode. Therefore clear the bit.
699 */
700 if ((is_inode_linkchain_set == 0) && (nextcnid == 0)) {
701 attr.ca_recflags &= ~kHFSHasLinkChainMask;
702 }
703 }
704 /* Create the indirect link directly in the catalog */
705 return cat_createlink(hfsmp, descp, &attr, nextcnid, linkcnid);
706}
707
708
709/*
710 * Make a link to the cnode cp in the directory dp
711 * using the name in cnp. src_vp is the vnode that
712 * corresponds to 'cp' which was part of the arguments to
713 * hfs_vnop_link.
714 *
715 * The cnodes cp and dcp must be locked.
716 */
717int
718hfs_makelink(struct hfsmount *hfsmp, struct vnode *src_vp, struct cnode *cp,struct cnode *dcp, struct componentname *cnp)
719{
720 u_int32_t indnodeno = 0;
721 char inodename[32];
722 struct cat_desc to_desc;
723 struct cat_desc link_desc;
724 int newlink = 0;
725 int retval = 0;
726 cnid_t linkcnid = 0;
727 cnid_t orig_firstlink = 0;
728 enum privdirtype type = S_ISDIR(cp->c_mode) ? DIR_HARDLINKS : FILE_HARDLINKS;
729
730 if (hfsmp->cur_link_id == 0) {
731 hfsmp->cur_link_id = ((random() & 0x3fffffff) + 100);
732 }
733
734 /* We don't allow link nodes in our private system directories. */
735 if (dcp->c_fileid == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid ||
736 dcp->c_fileid == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
737 return (EPERM);
738 }
739
740 cat_cookie_t cookie;
741 bzero(&cookie, sizeof(cat_cookie_t));
742 /* Reserve some space in the Catalog file. */
743 if ((retval = cat_preflight(hfsmp, (2 * CAT_CREATE)+ CAT_RENAME, &cookie))) {
744 return (retval);
745 }
746
747 int lockflags = SFL_CATALOG | SFL_ATTRIBUTE;
748 /* Directory hard links allocate space for a symlink. */
749 if (type == DIR_HARDLINKS) {
750 lockflags |= SFL_BITMAP;
751 }
752 lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
753
754 /* Save the current cnid value so we restore it if an error occurs. */
755 cnid_t orig_cnid = cp->c_desc.cd_cnid;
756
757 /*
758 * If this is a new hardlink then we need to create the inode
759 * and replace the original file/dir object with a link node.
760 */
761 if ((cp->c_linkcount == 2) && !(cp->c_flag & C_HARDLINK)) {
762 newlink = 1;
763 bzero(&to_desc, sizeof(to_desc));
764 to_desc.cd_parentcnid = hfsmp->hfs_private_desc[type].cd_cnid;
765 to_desc.cd_cnid = cp->c_fileid;
766 to_desc.cd_flags = (type == DIR_HARDLINKS) ? CD_ISDIR : 0;
767
768 do {
769 if (type == DIR_HARDLINKS) {
770 /* Directory hardlinks always use the cnid. */
771 indnodeno = cp->c_fileid;
772 MAKE_DIRINODE_NAME(inodename, sizeof(inodename),
773 indnodeno);
774 } else {
775 /* Get a unique indirect node number */
776 if (retval == 0) {
777 indnodeno = cp->c_fileid;
778 } else {
779 indnodeno = hfsmp->cur_link_id++;
780 }
781 MAKE_INODE_NAME(inodename, sizeof(inodename),
782 indnodeno);
783 }
784 /* Move original file/dir to data node directory */
785 to_desc.cd_nameptr = (const u_int8_t *)inodename;
786 to_desc.cd_namelen = strlen(inodename);
787
788 retval = cat_rename(hfsmp, &cp->c_desc, &hfsmp->hfs_private_desc[type],
789 &to_desc, NULL);
790
791 if (retval != 0 && retval != EEXIST) {
792 LFHFS_LOG(LEVEL_ERROR, "hfs_makelink: cat_rename to %s failed (%d) fileid=%d, vol=%s\n",
793 inodename, retval, cp->c_fileid, hfsmp->vcbVN);
794 }
795 } while ((retval == EEXIST) && (type == FILE_HARDLINKS));
796 if (retval)
797 goto out;
798
799 /*
800 * Replace original file/dir with a link record.
801 */
802
803 bzero(&link_desc, sizeof(link_desc));
804 link_desc.cd_nameptr = cp->c_desc.cd_nameptr;
805 link_desc.cd_namelen = cp->c_desc.cd_namelen;
806 link_desc.cd_parentcnid = cp->c_parentcnid;
807 link_desc.cd_flags = S_ISDIR(cp->c_mode) ? CD_ISDIR : 0;
808
809 retval = createindirectlink(hfsmp, indnodeno, &link_desc, 0, &linkcnid, true);
810 if (retval)
811 {
812 int err;
813
814 /* Restore the cnode's cnid. */
815 cp->c_desc.cd_cnid = orig_cnid;
816
817 /* Put the original file back. */
818 err = cat_rename(hfsmp, &to_desc, &dcp->c_desc, &cp->c_desc, NULL);
819 if (err) {
820 if (err != EIO && err != ENXIO)
821 LFHFS_LOG(LEVEL_ERROR, "hfs_makelink: error %d from cat_rename backout 1", err);
822 hfs_mark_inconsistent(hfsmp, HFS_ROLLBACK_FAILED);
823 }
824 if (retval != EIO && retval != ENXIO) {
825 LFHFS_LOG(LEVEL_ERROR, "hfs_makelink: createindirectlink (1) failed: %d\n", retval);
826 retval = EIO;
827 }
828 goto out;
829 }
830 cp->c_attr.ca_linkref = indnodeno;
831 cp->c_desc.cd_cnid = linkcnid;
832 /* Directory hard links store the first link in an attribute. */
833 if (type == DIR_HARDLINKS) {
834 if (setfirstlink(hfsmp, cp->c_fileid, linkcnid) == 0)
835 cp->c_attr.ca_recflags |= kHFSHasAttributesMask;
836 } else /* FILE_HARDLINKS */ {
837 cp->c_attr.ca_firstlink = linkcnid;
838 }
839 cp->c_attr.ca_recflags |= kHFSHasLinkChainMask;
840 } else {
841 indnodeno = cp->c_attr.ca_linkref;
842 }
843
844 /*
845 * Create a catalog entry for the new link (parentID + name).
846 */
847
848 bzero(&link_desc, sizeof(link_desc));
849 link_desc.cd_nameptr = (const u_int8_t *)cnp->cn_nameptr;
850 link_desc.cd_namelen = strlen(cnp->cn_nameptr);
851 link_desc.cd_parentcnid = dcp->c_fileid;
852 link_desc.cd_flags = S_ISDIR(cp->c_mode) ? CD_ISDIR : 0;
853
854 /* Directory hard links store the first link in an attribute. */
855 if (type == DIR_HARDLINKS) {
856 retval = getfirstlink(hfsmp, cp->c_fileid, &orig_firstlink);
857 } else /* FILE_HARDLINKS */ {
858 orig_firstlink = cp->c_attr.ca_firstlink;
859 }
860 if (retval == 0)
861 retval = createindirectlink(hfsmp, indnodeno, &link_desc, orig_firstlink, &linkcnid, (cp->c_attr.ca_recflags & kHFSHasLinkChainMask));
862
863 if (retval && newlink) {
864 int err;
865
866 /* Get rid of new link */
867 (void) cat_delete(hfsmp, &cp->c_desc, &cp->c_attr);
868
869 /* Restore the cnode's cnid. */
870 cp->c_desc.cd_cnid = orig_cnid;
871
872 /* Put the original file back. */
873 err = cat_rename(hfsmp, &to_desc, &dcp->c_desc, &cp->c_desc, NULL);
874 if (err) {
875 if (err != EIO && err != ENXIO)
876 LFHFS_LOG(LEVEL_ERROR, "hfs_makelink: error %d from cat_rename backout 2", err);
877 hfs_mark_inconsistent(hfsmp, HFS_ROLLBACK_FAILED);
878 }
879
880 cp->c_attr.ca_linkref = 0;
881
882 if (retval != EIO && retval != ENXIO) {
883 LFHFS_LOG(LEVEL_ERROR, "hfs_makelink: createindirectlink (2) failed: %d\n", retval);
884 retval = EIO;
885 }
886 goto out;
887 } else if (retval == 0) {
888
889 /* Update the original first link to point back to the new first link. */
890 if (cp->c_attr.ca_recflags & kHFSHasLinkChainMask) {
891 (void) cat_update_siblinglinks(hfsmp, orig_firstlink, linkcnid, HFS_IGNORABLE_LINK);
892
893 /* Update the inode's first link value. */
894 if (type == DIR_HARDLINKS) {
895 if (setfirstlink(hfsmp, cp->c_fileid, linkcnid) == 0)
896 cp->c_attr.ca_recflags |= kHFSHasAttributesMask;
897 } else {
898 cp->c_attr.ca_firstlink = linkcnid;
899 }
900 }
901 /*
902 * Finally, if this is a new hardlink then:
903 * - update the private system directory
904 * - mark the cnode as a hard link
905 */
906 if (newlink) {
907
908 hfsmp->hfs_private_attr[type].ca_entries++;
909 /* From application perspective, directory hard link is a
910 * normal directory. Therefore count the new directory
911 * hard link for folder count calculation.
912 */
913 if (type == DIR_HARDLINKS) {
914 INC_FOLDERCOUNT(hfsmp, hfsmp->hfs_private_attr[type]);
915 }
916 retval = cat_update(hfsmp, &hfsmp->hfs_private_desc[type], &hfsmp->hfs_private_attr[type], NULL, NULL);
917 if (retval) {
918 if (retval != EIO && retval != ENXIO) {
919 LFHFS_LOG(LEVEL_ERROR, "hfs_makelink: cat_update of privdir failed! (%d)\n", retval);
920 retval = EIO;
921 }
922 hfs_mark_inconsistent(hfsmp, HFS_OP_INCOMPLETE);
923 }
924 cp->c_flag |= C_HARDLINK;
925
926 vnode_t vp;
927 if ((vp = cp->c_vp) != NULL) {
928 if (vp != src_vp) {
929 cp->c_flag |= C_NEED_DVNODE_PUT;
930 }
931 }
932 if ((vp = cp->c_rsrc_vp) != NULL) {
933 if (vp != src_vp) {
934 cp->c_flag |= C_NEED_RVNODE_PUT;
935 }
936 }
937 cp->c_flag |= C_MODIFIED;
938 cp->c_touch_chgtime = TRUE;
939 }
940 }
941out:
942 hfs_systemfile_unlock(hfsmp, lockflags);
943
944 cat_postflight(hfsmp, &cookie);
945
946 if (retval == 0 && newlink) {
947 hfs_volupdate(hfsmp, VOL_MKFILE, 0);
948 }
949 return (retval);
950}