-
Notifications
You must be signed in to change notification settings - Fork 0
/
hammer2_inode.c
1035 lines (959 loc) · 28.4 KB
/
hammer2_inode.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2011-2012 The DragonFly Project. All rights reserved.
*
* This code is derived from software contributed to The DragonFly Project
* by Matthew Dillon <dillon@dragonflybsd.org>
* by Venkatesh Srinivas <vsrinivas@dragonflybsd.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name of The DragonFly Project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific, prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "dfly_wrap.h"
#include "hammer2.h"
#include "dfly/vm/vm_extern.h"
#include "dfly/sys/buf.h"
#include "dfly/sys/buf2.h"
#define SRF_SYSIDUSED 0x0001 /* sysid was used for access */
#define SRF_ALLOCATED 0x0002 /* sysref_alloc used to allocate */
#define SRF_PUTAWAY 0x0004 /* in objcache */
typedef long long intmax_t;
RB_HEAD(sysref_rb_tree, sysref);
RB_PROTOTYPE2(sysref_rb_tree, sysref, rbnode, rb_sysref_compare, sysid_t);
/*
* Adding a ref to an inode is only legal if the inode already has at least
* one ref.
*/
void
hammer2_inode_ref(hammer2_inode_t *ip)
{
hammer2_chain_ref(ip->hmp, &ip->chain);
}
/*
* Drop an inode reference, freeing the inode when the last reference goes
* away.
*/
void
hammer2_inode_drop(hammer2_inode_t *ip)
{
hammer2_chain_drop(ip->hmp, &ip->chain);
}
/*
* Get the vnode associated with the given inode, allocating the vnode if
* necessary. The vnode will be returned exclusively locked.
*
* The caller must lock the inode (shared or exclusive).
*
* Great care must be taken to avoid deadlocks and vnode acquisition/reclaim
* races.
*/
struct vnode *
hammer2_igetv(hammer2_inode_t *ip, int *errorp)
{
struct vnode *vp;
hammer2_pfsmount_t *pmp;
ccms_state_t ostate;
pmp = ip->pmp;
KKASSERT(pmp != NULL);
*errorp = 0;
for (;;) {
/*
* Attempt to reuse an existing vnode assignment. It is
* possible to race a reclaim so the vget() may fail. The
* inode must be unlocked during the vget() to avoid a
* deadlock against a reclaim.
*/
vp = ip->vp;
if (vp) {
/*
* Inode must be unlocked during the vget() to avoid
* possible deadlocks, vnode is held to prevent
* destruction during the vget(). The vget() can
* still fail if we lost a reclaim race on the vnode.
*/
/* XXX vhold_interlocked(vp);
*/
ccms_thread_unlock(&ip->chain.cst);
/* XXX
if (vget(vp, LK_EXCLUSIVE)) {
vdrop(vp);
ccms_thread_lock(&ip->chain.cst,
CCMS_STATE_EXCLUSIVE);
continue;
}
*/
ccms_thread_lock(&ip->chain.cst, CCMS_STATE_EXCLUSIVE);
/* XX vdrop(vp);
*/
/* vp still locked and ref from vget */
*errorp = 0;
break;
}
/*
* No vnode exists, allocate a new vnode. Beware of
* allocation races. This function will return an
* exclusively locked and referenced vnode.
*/
/* XXX
*errorp = getnewvnode(VT_HAMMER2, pmp->mp, &vp, 0, 0);
if (*errorp) {
vp = NULL;
break;
}
*/
/*
* Lock the inode and check for an allocation race.
*/
ostate = ccms_thread_lock_upgrade(&ip->chain.cst);
if (ip->vp != NULL) {
vp->v_type = VBAD;
/* XXX vx_put(vp); */
ccms_thread_lock_restore(&ip->chain.cst, ostate);
continue;
}
switch (ip->ip_data.type) {
case HAMMER2_OBJTYPE_DIRECTORY:
vp->v_type = VDIR;
break;
case HAMMER2_OBJTYPE_REGFILE:
vp->v_type = VREG;
/* XVFS vinitvmio(vp, ip->ip_data.size,
HAMMER2_LBUFSIZE,
(int)ip->ip_data.size & HAMMER2_LBUFMASK);
*/
break;
case HAMMER2_OBJTYPE_SOFTLINK:
/*
* XXX for now we are using the generic file_read
* and file_write code so we need a buffer cache
* association.
*/
vp->v_type = VLNK;
/* XVFS vinitvmio(vp, ip->ip_data.size,
HAMMER2_LBUFSIZE,
(int)ip->ip_data.size & HAMMER2_LBUFMASK);
*/
break;
/* XXX FIFO */
default:
panic("hammer2: unhandled objtype %d",
ip->ip_data.type);
break;
}
if (ip == pmp->iroot)
/* vsetflags(vp, VROOT); */
vp->v_data = ip;
ip->vp = vp;
hammer2_chain_ref(ip->hmp, &ip->chain); /* vp association */
ccms_thread_lock_restore(&ip->chain.cst, ostate);
break;
}
/*
* Return non-NULL vp and *errorp == 0, or NULL vp and *errorp != 0.
*/
if (hammer2_debug & 0x0002) {
kprintf("igetv vp %p refs %d aux %d\n",
vp, vp->v_sysref.refcnt, vp->v_auxrefs);
}
return (vp);
}
/*
* This helper function may be used by VFSs to implement UNIX initial
* ownership semantics when creating new objects inside directories.
*/
uid_t
vop_helper_create_uid(struct mount *mp, mode_t dmode, uid_t duid,
struct ucred *cred, mode_t *modep)
{
#ifdef SUIDDIR
if ((mp->mnt_flag & MNT_SUIDDIR) && (dmode & S_ISUID) &&
duid != cred->cr_uid && duid) {
*modep &= ~07111;
return(duid);
}
#endif
/* return(cred->cr_uid); */
return 0;
}
/*
* Create a new inode in the specified directory using the vattr to
* figure out the type of inode.
*
* If no error occurs the new inode with its chain locked is returned in
* *nipp, otherwise an error is returned and *nipp is set to NULL.
*
* If vap and/or cred are NULL the related fields are not set and the
* inode type defaults to a directory. This is used when creating PFSs
* under the super-root, so the inode number is set to 1 in this case.
*/
int
hammer2_inode_create(hammer2_inode_t *dip,
struct vattr *vap, struct ucred *cred,
const uint8_t *name, size_t name_len,
hammer2_inode_t **nipp)
{
hammer2_mount_t *hmp = dip->hmp;
hammer2_chain_t *chain;
hammer2_chain_t *parent;
hammer2_inode_t *nip;
hammer2_key_t lhc;
int error;
uid_t xuid;
lhc = hammer2_dirhash(name, name_len);
/*
* Locate the inode or indirect block to create the new
* entry in. At the same time check for key collisions
* and iterate until we don't get one.
*/
parent = &dip->chain;
hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS);
error = 0;
while (error == 0) {
chain = hammer2_chain_lookup(hmp, &parent, lhc, lhc, 0);
if (chain == NULL)
break;
if ((lhc & HAMMER2_DIRHASH_VISIBLE) == 0)
error = ENOSPC;
if ((lhc & HAMMER2_DIRHASH_LOMASK) == HAMMER2_DIRHASH_LOMASK)
error = ENOSPC;
hammer2_chain_unlock(hmp, chain);
chain = NULL;
++lhc;
}
if (error == 0) {
chain = hammer2_chain_create(hmp, parent, NULL, lhc, 0,
HAMMER2_BREF_TYPE_INODE,
HAMMER2_INODE_BYTES);
if (chain == NULL)
error = EIO;
}
hammer2_chain_unlock(hmp, parent);
/*
* Handle the error case
*/
if (error) {
KKASSERT(chain == NULL);
*nipp = NULL;
return (error);
}
/*
* Set up the new inode
*/
nip = chain->u.ip;
*nipp = nip;
hammer2_voldata_lock(hmp);
if (vap) {
nip->ip_data.type = hammer2_get_obj_type(vap->va_type);
nip->ip_data.inum = hmp->voldata.alloc_tid++;
/* XXX modify/lock */
} else {
nip->ip_data.type = HAMMER2_OBJTYPE_DIRECTORY;
nip->ip_data.inum = 1;
}
hammer2_voldata_unlock(hmp);
nip->ip_data.version = HAMMER2_INODE_VERSION_ONE;
hammer2_update_time(&nip->ip_data.ctime);
nip->ip_data.mtime = nip->ip_data.ctime;
if (vap)
nip->ip_data.mode = vap->va_mode;
nip->ip_data.nlinks = 1;
if (vap) {
if (dip) {
xuid = hammer2_to_unix_xid(&dip->ip_data.uid);
xuid = vop_helper_create_uid(dip->pmp->mp,
dip->ip_data.mode,
xuid,
cred,
&vap->va_mode);
} else {
xuid = 0;
}
/* XXX
if (vap->va_vaflags & VA_UID_UUID_VALID)
nip->ip_data.uid = vap->va_uid_uuid;
else if (vap->va_uid != (uid_t)VNOVAL)
hammer2_guid_to_uuid(&nip->ip_data.uid, vap->va_uid);
else
*/
hammer2_guid_to_uuid(&nip->ip_data.uid, xuid);
/* XXX if (vap->va_vaflags & VA_GID_UUID_VALID)
nip->ip_data.gid = vap->va_gid_uuid;
else if (vap->va_gid != (gid_t)VNOVAL)
hammer2_guid_to_uuid(&nip->ip_data.gid, vap->va_gid);
else */ if (dip)
nip->ip_data.gid = dip->ip_data.gid;
}
/*
* Regular files and softlinks allow a small amount of data to be
* directly embedded in the inode. This flag will be cleared if
* the size is extended past the embedded limit.
*/
if (nip->ip_data.type == HAMMER2_OBJTYPE_REGFILE ||
nip->ip_data.type == HAMMER2_OBJTYPE_SOFTLINK) {
nip->ip_data.op_flags |= HAMMER2_OPFLAG_DIRECTDATA;
}
KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
bcopy(name, nip->ip_data.filename, name_len);
nip->ip_data.name_key = lhc;
nip->ip_data.name_len = name_len;
return (0);
}
/*
* Duplicate the specified existing inode in the specified target directory.
* If name is NULL the inode is duplicated as a hidden directory entry.
*
* Returns the new inode. The old inode is left alone.
*
* XXX name needs to be NULL for now.
*/
int
hammer2_inode_duplicate(hammer2_inode_t *dip, hammer2_inode_t *oip,
hammer2_inode_t **nipp,
const uint8_t *name, size_t name_len)
{
hammer2_mount_t *hmp = dip->hmp;
hammer2_inode_t *nip;
hammer2_chain_t *parent;
hammer2_chain_t *chain;
hammer2_key_t lhc;
int error;
if (name) {
lhc = hammer2_dirhash(name, name_len);
} else {
lhc = oip->ip_data.inum;
KKASSERT((lhc & HAMMER2_DIRHASH_VISIBLE) == 0);
}
/*
* Locate the inode or indirect block to create the new
* entry in. At the same time check for key collisions
* and iterate until we don't get one.
*/
nip = NULL;
parent = &dip->chain;
hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS);
error = 0;
while (error == 0) {
chain = hammer2_chain_lookup(hmp, &parent, lhc, lhc, 0);
if (chain == NULL)
break;
/* XXX bcmp name if not NULL */
if ((lhc & HAMMER2_DIRHASH_LOMASK) == HAMMER2_DIRHASH_LOMASK)
error = ENOSPC;
if ((lhc & HAMMER2_DIRHASH_VISIBLE) == 0) /* shouldn't happen */
error = ENOSPC;
hammer2_chain_unlock(hmp, chain);
chain = NULL;
++lhc;
}
/*
* Create entry in common parent directory.
*/
if (error == 0) {
chain = hammer2_chain_create(hmp, parent, NULL, lhc, 0,
HAMMER2_BREF_TYPE_INODE /* n/a */,
HAMMER2_INODE_BYTES); /* n/a */
if (chain == NULL)
error = EIO;
}
hammer2_chain_unlock(hmp, parent);
/*
* Handle the error case
*/
if (error) {
KKASSERT(chain == NULL);
return (error);
}
/*
* XXX This is currently a horrible hack. Well, if we wanted to
* duplicate a file, i.e. as in a snapshot, we definitely
* would have to flush it first.
*
* For hardlink target generation we can theoretically move any
* active chain structures without flushing, but that gets really
* iffy for code which follows chain->parent and ip->pip links.
*
* XXX only works with files. Duplicating a directory hierarchy
* requires a flush but doesn't deal with races post-flush.
* Well, it would work I guess, but you might catch some files
* mid-operation.
*
* We cannot leave oip with any in-memory chains because (for a
* hardlink), oip will become a OBJTYPE_HARDLINK which is just a
* pointer to the real hardlink's inum and can't have any sub-chains.
* XXX might be 0-ref chains left.
*/
hammer2_inode_lock_ex(oip);
hammer2_chain_flush(hmp, &oip->chain, 0);
hammer2_inode_unlock_ex(oip);
/*KKASSERT(RB_EMPTY(&oip->chain.rbhead));*/
nip = chain->u.ip;
hammer2_chain_modify(hmp, chain, 0);
nip->ip_data = oip->ip_data; /* sync media data after flush */
if (name) {
/*
* Directory entries are inodes so if the name has changed
* we have to update the inode.
*/
KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
bcopy(name, nip->ip_data.filename, name_len);
nip->ip_data.name_key = lhc;
nip->ip_data.name_len = name_len;
} else {
/*
* Directory entries are inodes but this is a hidden hardlink
* target. The name isn't used but to ease debugging give it
* a name after its inode number.
*/
ksnprintf(nip->ip_data.filename, sizeof(nip->ip_data.filename),
"0x%016jx", (intmax_t)nip->ip_data.inum);
nip->ip_data.name_len = strlen(nip->ip_data.filename);
nip->ip_data.name_key = lhc;
}
*nipp = nip;
return (0);
}
/*
* Connect inode (oip) to the specified directory using the specified name.
* (oip) must be locked.
*
* If (oip) is not currently connected we simply connect it up.
*
* If (oip) is already connected we create a OBJTYPE_HARDLINK entry which
* points to (oip)'s inode number. (oip) is expected to be the terminus of
* the hardlink sitting as a hidden file in a common parent directory
* in this situation (thus the lock order is correct).
*/
int
hammer2_inode_connect(hammer2_inode_t *dip, hammer2_inode_t *oip,
const uint8_t *name, size_t name_len)
{
hammer2_mount_t *hmp = dip->hmp;
hammer2_chain_t *chain;
hammer2_chain_t *parent;
hammer2_inode_t *nip;
hammer2_key_t lhc;
int error;
int hlink;
lhc = hammer2_dirhash(name, name_len);
hlink = (oip->chain.parent != NULL);
/*
* In fake mode flush oip so we can just snapshot it downbelow.
*/
if (hlink && hammer2_hardlink_enable < 0)
hammer2_chain_flush(hmp, &oip->chain, 0);
/*
* Locate the inode or indirect block to create the new
* entry in. At the same time check for key collisions
* and iterate until we don't get one.
*/
parent = &dip->chain;
hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS);
error = 0;
while (error == 0) {
chain = hammer2_chain_lookup(hmp, &parent, lhc, lhc, 0);
if (chain == NULL)
break;
if ((lhc & HAMMER2_DIRHASH_LOMASK) == HAMMER2_DIRHASH_LOMASK)
error = ENOSPC;
hammer2_chain_unlock(hmp, chain);
chain = NULL;
++lhc;
}
/*
* Passing a non-NULL chain to hammer2_chain_create() reconnects the
* existing chain instead of creating a new one. The chain's bref
* will be properly updated.
*/
if (error == 0) {
if (hlink) {
chain = hammer2_chain_create(hmp, parent,
NULL, lhc, 0,
HAMMER2_BREF_TYPE_INODE,
HAMMER2_INODE_BYTES);
} else {
chain = hammer2_chain_create(hmp, parent,
&oip->chain, lhc, 0,
HAMMER2_BREF_TYPE_INODE,
HAMMER2_INODE_BYTES);
if (chain)
KKASSERT(chain == &oip->chain);
}
if (chain == NULL)
error = EIO;
}
hammer2_chain_unlock(hmp, parent);
/*
* Handle the error case
*/
if (error) {
KKASSERT(chain == NULL);
return (error);
}
/*
* Directory entries are inodes so if the name has changed we have
* to update the inode.
*
* When creating an OBJTYPE_HARDLINK entry remember to unlock the
* chain, the caller will access the hardlink via the actual hardlink
* target file and not the hardlink pointer entry.
*/
if (hlink && hammer2_hardlink_enable >= 0) {
/*
* Create the HARDLINK pointer. oip represents the hardlink
* target in this situation.
*/
nip = chain->u.ip;
hammer2_chain_modify(hmp, chain, 0);
KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
bcopy(name, nip->ip_data.filename, name_len);
nip->ip_data.name_key = lhc;
nip->ip_data.name_len = name_len;
nip->ip_data.target_type = oip->ip_data.type;
nip->ip_data.type = HAMMER2_OBJTYPE_HARDLINK;
nip->ip_data.inum = oip->ip_data.inum;
nip->ip_data.nlinks = 1;
kprintf("created hardlink %*.*s\n",
(int)name_len, (int)name_len, name);
hammer2_chain_unlock(hmp, chain);
} else if (hlink && hammer2_hardlink_enable < 0) {
/*
* Create a snapshot (hardlink fake mode for debugging).
*/
nip = chain->u.ip;
nip->ip_data = oip->ip_data;
hammer2_chain_modify(hmp, chain, 0);
KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
bcopy(name, nip->ip_data.filename, name_len);
nip->ip_data.name_key = lhc;
nip->ip_data.name_len = name_len;
kprintf("created fake hardlink %*.*s\n",
(int)name_len, (int)name_len, name);
hammer2_chain_unlock(hmp, chain);
} else {
/*
* Normally disconnected inode (e.g. during a rename) that
* was reconnected. We must fixup the name stored in
* oip.
*
* We are using oip as chain, already locked by caller,
* do not unlock it.
*/
hammer2_chain_modify(hmp, chain, 0);
if (oip->ip_data.name_len != name_len ||
bcmp(oip->ip_data.filename, name, name_len) != 0) {
KKASSERT(name_len < HAMMER2_INODE_MAXNAME);
bcopy(name, oip->ip_data.filename, name_len);
oip->ip_data.name_key = lhc;
oip->ip_data.name_len = name_len;
}
oip->ip_data.nlinks = 1;
}
return (0);
}
/*
* Unlink the file from the specified directory inode. The directory inode
* does not need to be locked.
*
* isdir determines whether a directory/non-directory check should be made.
* No check is made if isdir is set to -1.
*/
int
hammer2_unlink_file(hammer2_inode_t *dip,
const uint8_t *name, size_t name_len,
int isdir, hammer2_inode_t *retain_ip)
{
hammer2_mount_t *hmp;
hammer2_chain_t *parent;
hammer2_chain_t *chain;
hammer2_chain_t *dparent;
hammer2_chain_t *dchain;
hammer2_key_t lhc;
hammer2_inode_t *ip;
hammer2_inode_t *oip;
int error;
uint8_t type;
error = 0;
oip = NULL;
hmp = dip->hmp;
lhc = hammer2_dirhash(name, name_len);
/*
* Search for the filename in the directory
*/
parent = &dip->chain;
hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS);
chain = hammer2_chain_lookup(hmp, &parent,
lhc, lhc + HAMMER2_DIRHASH_LOMASK,
0);
while (chain) {
if (chain->bref.type == HAMMER2_BREF_TYPE_INODE &&
chain->u.ip &&
name_len == chain->data->ipdata.name_len &&
bcmp(name, chain->data->ipdata.filename, name_len) == 0) {
break;
}
chain = hammer2_chain_next(hmp, &parent, chain,
lhc, lhc + HAMMER2_DIRHASH_LOMASK,
0);
}
/*
* Not found or wrong type (isdir < 0 disables the type check).
*/
if (chain == NULL) {
hammer2_chain_unlock(hmp, parent);
return ENOENT;
}
if ((type = chain->data->ipdata.type) == HAMMER2_OBJTYPE_HARDLINK)
type = chain->data->ipdata.target_type;
if (type == HAMMER2_OBJTYPE_DIRECTORY && isdir == 0) {
error = ENOTDIR;
goto done;
}
if (type != HAMMER2_OBJTYPE_DIRECTORY && isdir == 1) {
error = EISDIR;
goto done;
}
/*
* Hardlink must be resolved. We can't hold parent locked while we
* do this or we could deadlock.
*/
if (chain->data->ipdata.type == HAMMER2_OBJTYPE_HARDLINK) {
hammer2_chain_unlock(hmp, parent);
parent = NULL;
error = hammer2_hardlink_find(dip, &chain, &oip);
}
/*
* If this is a directory the directory must be empty. However, if
* isdir < 0 we are doing a rename and the directory does not have
* to be empty.
*
* NOTE: We check the full key range here which covers both visible
* and invisible entries. Theoretically there should be no
* invisible (hardlink target) entries if there are no visible
* entries.
*/
if (type == HAMMER2_OBJTYPE_DIRECTORY && isdir >= 0) {
dparent = chain;
hammer2_chain_lock(hmp, dparent, HAMMER2_RESOLVE_ALWAYS);
dchain = hammer2_chain_lookup(hmp, &dparent,
0, (hammer2_key_t)-1,
HAMMER2_LOOKUP_NODATA);
if (dchain) {
hammer2_chain_unlock(hmp, dchain);
hammer2_chain_unlock(hmp, dparent);
error = ENOTEMPTY;
goto done;
}
hammer2_chain_unlock(hmp, dparent);
dparent = NULL;
/* dchain NULL */
}
/*
* Ok, we can now unlink the chain. We always decrement nlinks even
* if the entry can be deleted in case someone has the file open and
* does an fstat().
*
* The chain itself will no longer be in the on-media topology but
* can still be flushed to the media (e.g. if an open descriptor
* remains). When the last vnode/ip ref goes away the chain will
* be marked unmodified, avoiding any further (now unnecesary) I/O.
*/
if (oip) {
/*
* If this was a hardlink we first delete the hardlink
* pointer entry.
*/
parent = oip->chain.parent;
hammer2_chain_lock(hmp, parent, HAMMER2_RESOLVE_ALWAYS);
hammer2_chain_lock(hmp, &oip->chain, HAMMER2_RESOLVE_ALWAYS);
hammer2_chain_delete(hmp, parent, &oip->chain,
(retain_ip == oip));
hammer2_chain_unlock(hmp, &oip->chain);
hammer2_chain_unlock(hmp, parent);
parent = NULL;
/*
* Then decrement nlinks on hardlink target.
*/
ip = chain->u.ip;
if (ip->ip_data.nlinks == 1) {
dparent = chain->parent;
hammer2_chain_ref(hmp, chain);
hammer2_chain_unlock(hmp, chain);
hammer2_chain_lock(hmp, dparent,
HAMMER2_RESOLVE_ALWAYS);
hammer2_chain_lock(hmp, chain, HAMMER2_RESOLVE_ALWAYS);
hammer2_chain_drop(hmp, chain);
hammer2_chain_modify(hmp, chain, 0);
--ip->ip_data.nlinks;
hammer2_chain_delete(hmp, dparent, chain, 0);
hammer2_chain_unlock(hmp, dparent);
} else {
hammer2_chain_modify(hmp, chain, 0);
--ip->ip_data.nlinks;
}
} else {
/*
* Otherwise this was not a hardlink and we can just
* remove the entry and decrement nlinks.
*/
ip = chain->u.ip;
hammer2_chain_modify(hmp, chain, 0);
--ip->ip_data.nlinks;
hammer2_chain_delete(hmp, parent, chain,
(retain_ip == ip));
}
error = 0;
done:
if (chain)
hammer2_chain_unlock(hmp, chain);
if (parent)
hammer2_chain_unlock(hmp, parent);
if (oip)
hammer2_chain_drop(oip->hmp, &oip->chain);
return error;
}
/*
* Calculate the allocation size for the file fragment straddling EOF
*/
int
hammer2_inode_calc_alloc(hammer2_key_t filesize)
{
int frag = (int)filesize & HAMMER2_PBUFMASK;
int radix;
if (frag == 0)
return(0);
for (radix = HAMMER2_MINALLOCRADIX; frag > (1 << radix); ++radix)
;
return (radix);
}
void
hammer2_inode_lock_nlinks(hammer2_inode_t *ip)
{
hammer2_chain_ref(ip->hmp, &ip->chain);
}
void
hammer2_inode_unlock_nlinks(hammer2_inode_t *ip)
{
hammer2_chain_drop(ip->hmp, &ip->chain);
}
/*
* Consolidate for hard link creation. This moves the specified terminal
* hardlink inode to a directory common to its current directory and tdip
* if necessary, replacing *ipp with the new inode chain element and
* modifying the original inode chain element to OBJTYPE_HARDLINK.
*
* If the original inode chain element was a prior incarnation of a hidden
* inode it can simply be deleted instead of converted.
*
* (*ipp)'s nlinks field is locked on entry and the new (*ipp)'s nlinks
* field will be locked on return (with the original's unlocked).
*
* The link count is bumped if requested.
*/
int
hammer2_hardlink_consolidate(hammer2_inode_t **ipp, hammer2_inode_t *tdip)
{
hammer2_mount_t *hmp;
hammer2_inode_t *oip = *ipp;
hammer2_inode_t *nip = NULL;
hammer2_inode_t *fdip;
hammer2_chain_t *parent;
int error;
hmp = tdip->hmp;
if (hammer2_hardlink_enable < 0)
return (0);
if (hammer2_hardlink_enable == 0)
/*XXX return (ENOTSUP);
*/
/*
* Find the common parent directory
*/
fdip = oip->pip;
while (fdip->depth > tdip->depth) {
fdip = fdip->pip;
KKASSERT(fdip != NULL);
}
while (tdip->depth > fdip->depth) {
tdip = tdip->pip;
KKASSERT(tdip != NULL);
}
while (fdip != tdip) {
fdip = fdip->pip;
tdip = tdip->pip;
KKASSERT(fdip != NULL);
KKASSERT(tdip != NULL);
}
/*
* Nothing to do (except bump the link count) if the hardlink has
* already been consolidated in the correct place.
*/
if (oip->pip == fdip &&
(oip->ip_data.name_key & HAMMER2_DIRHASH_VISIBLE) == 0) {
kprintf("hardlink already consolidated correctly\n");
nip = oip;
hammer2_inode_lock_ex(nip);
hammer2_chain_modify(hmp, &nip->chain, 0);
++nip->ip_data.nlinks;
hammer2_inode_unlock_ex(nip);
return (0);
}
/*
* Create a hidden inode directory entry in the parent, copying
* (*oip)'s state. Then replace oip with OBJTYPE_HARDLINK.
*
* The duplication function will either flush or move any chains
* under oip to the new hardlink target inode, retiring all chains
* related to oip before returning. XXX vp->ip races.
*/
error = hammer2_inode_duplicate(fdip, oip, &nip, NULL, 0);
if (error == 0) {
/*
* Bump nlinks on duplicated hidden inode.
*/
kprintf("hardlink consolidation success in parent dir %s\n",
fdip->ip_data.filename);
hammer2_inode_lock_nlinks(nip);
hammer2_inode_unlock_nlinks(oip);
hammer2_chain_modify(hmp, &nip->chain, 0);
++nip->ip_data.nlinks;
hammer2_inode_unlock_ex(nip);
if (oip->ip_data.name_key & HAMMER2_DIRHASH_VISIBLE) {
/*
* Replace the old inode with an OBJTYPE_HARDLINK
* pointer.
*/
hammer2_inode_lock_ex(oip);
hammer2_chain_modify(hmp, &oip->chain, 0);
oip->ip_data.target_type = oip->ip_data.type;
oip->ip_data.type = HAMMER2_OBJTYPE_HARDLINK;
oip->ip_data.uflags = 0;
oip->ip_data.rmajor = 0;
oip->ip_data.rminor = 0;
oip->ip_data.ctime = 0;
oip->ip_data.mtime = 0;
oip->ip_data.atime = 0;
oip->ip_data.btime = 0;
bzero(&oip->ip_data.uid, sizeof(oip->ip_data.uid));
bzero(&oip->ip_data.gid, sizeof(oip->ip_data.gid));
oip->ip_data.op_flags = HAMMER2_OPFLAG_DIRECTDATA;
oip->ip_data.cap_flags = 0;
oip->ip_data.mode = 0;
oip->ip_data.size = 0;
oip->ip_data.nlinks = 1;
oip->ip_data.iparent = 0; /* XXX */
oip->ip_data.pfs_type = 0;
oip->ip_data.pfs_inum = 0;
bzero(&oip->ip_data.pfs_clid,
sizeof(oip->ip_data.pfs_clid));
bzero(&oip->ip_data.pfs_fsid,
sizeof(oip->ip_data.pfs_fsid));
oip->ip_data.data_quota = 0;
oip->ip_data.data_count = 0;
oip->ip_data.inode_quota = 0;
oip->ip_data.inode_count = 0;
oip->ip_data.attr_tid = 0;
oip->ip_data.dirent_tid = 0;
bzero(&oip->ip_data.u, sizeof(oip->ip_data.u));
/* XXX transaction ids */
hammer2_inode_unlock_ex(oip);
} else {
/*
* The old inode was a hardlink target, which we
* have now moved. We must delete it so the new
* hardlink target at a higher directory level
* becomes the only hardlink target for this inode.
*/
kprintf("DELETE INVISIBLE\n");
parent = oip->chain.parent;
hammer2_chain_lock(hmp, parent,
HAMMER2_RESOLVE_ALWAYS);
hammer2_chain_lock(hmp, &oip->chain,
HAMMER2_RESOLVE_ALWAYS);
hammer2_chain_delete(hmp, parent, &oip->chain, 0);
hammer2_chain_unlock(hmp, &oip->chain);
hammer2_chain_unlock(hmp, parent);
}
*ipp = nip;
} else {
KKASSERT(nip == NULL);
}
return (error);
}
/*
* If (*ipp) is non-NULL it points to the forward OBJTYPE_HARDLINK inode while
* (*chainp) points to the resolved (hidden hardlink target) inode. In this
* situation when nlinks is 1 we wish to deconsolidate the hardlink, moving
* it back to the directory that now represents the only remaining link.
*/
int
hammer2_hardlink_deconsolidate(hammer2_inode_t *dip, hammer2_chain_t **chainp,
hammer2_inode_t **ipp)
{
if (*ipp == NULL)
return (0);
/* XXX */
return (0);
}
/*
* When presented with a (*chainp) representing an inode of type
* OBJTYPE_HARDLINK this code will save the original inode (with a ref)
* in (*ipp), and then locate the hidden hardlink target in (dip) or
* any parent directory above (dip). The locked (*chainp) is replaced
* with a new locked (*chainp) representing the hardlink target.
*/
int
hammer2_hardlink_find(hammer2_inode_t *dip, hammer2_chain_t **chainp,
hammer2_inode_t **ipp)