hammer2 - update documentation, cleanup
authorMatthew Dillon <dillon@apollo.backplane.com>
Sun, 5 Apr 2015 02:22:39 +0000 (19:22 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Sun, 5 Apr 2015 02:22:39 +0000 (19:22 -0700)
* Update the DESIGN document.

* Cleanup API for hammer2_inode_lock*() and hammer2_inode_unlock*().

* Start work on the synchronization thread framework.

17 files changed:
sbin/hammer2/cmd_pfs.c
sbin/hammer2/main.c
sbin/hammer2/subs.c
sys/vfs/hammer2/DESIGN
sys/vfs/hammer2/hammer2.h
sys/vfs/hammer2/hammer2_bulkscan.c
sys/vfs/hammer2/hammer2_chain.c
sys/vfs/hammer2/hammer2_cluster.c
sys/vfs/hammer2/hammer2_disk.h
sys/vfs/hammer2/hammer2_freemap.c
sys/vfs/hammer2/hammer2_inode.c
sys/vfs/hammer2/hammer2_iocom.c
sys/vfs/hammer2/hammer2_ioctl.c
sys/vfs/hammer2/hammer2_subr.c
sys/vfs/hammer2/hammer2_syncthr.c
sys/vfs/hammer2/hammer2_vfsops.c
sys/vfs/hammer2/hammer2_vnops.c

index b8e5809..cab7962 100644 (file)
@@ -67,9 +67,6 @@ cmd_pfs_list(const char *sel_path)
                case HAMMER2_PFSTYPE_CACHE:
                        printf("CACHE       ");
                        break;
-               case HAMMER2_PFSTYPE_COPY:
-                       printf("COPY        ");
-                       break;
                case HAMMER2_PFSTYPE_SLAVE:
                        printf("SLAVE       ");
                        break;
@@ -87,6 +84,9 @@ cmd_pfs_list(const char *sel_path)
                        case HAMMER2_PFSSUBTYPE_SNAPSHOT:
                                printf("SNAPSHOT    ");
                                break;
+                       case HAMMER2_PFSSUBTYPE_AUTOSNAP:
+                               printf("AUTOSNAP    ");
+                               break;
                        default:
                                printf("MASTER(sub?)");
                                break;
index 5be8bc6..e69fc26 100644 (file)
@@ -87,8 +87,8 @@ main(int ac, char **av)
                         */
                        if (strcasecmp(optarg, "CACHE") == 0) {
                                pfs_type = HAMMER2_PFSTYPE_CACHE;
-                       } else if (strcasecmp(optarg, "COPY") == 0) {
-                               pfs_type = HAMMER2_PFSTYPE_COPY;
+                       } else if (strcasecmp(optarg, "DUMMY") == 0) {
+                               pfs_type = HAMMER2_PFSTYPE_DUMMY;
                        } else if (strcasecmp(optarg, "SLAVE") == 0) {
                                pfs_type = HAMMER2_PFSTYPE_SLAVE;
                        } else if (strcasecmp(optarg, "SOFT_SLAVE") == 0) {
index 2ebbf7e..4927967 100644 (file)
@@ -203,10 +203,12 @@ hammer2_pfstype_to_str(uint8_t type)
        switch(type) {
        case HAMMER2_PFSTYPE_NONE:
                return("NONE");
+       case HAMMER2_PFSTYPE_SUPROOT:
+               return("SUPROOT");
+       case HAMMER2_PFSTYPE_DUMMY:
+               return("DUMMY");
        case HAMMER2_PFSTYPE_CACHE:
                return("CACHE");
-       case HAMMER2_PFSTYPE_COPY:
-               return("COPY");
        case HAMMER2_PFSTYPE_SLAVE:
                return("SLAVE");
        case HAMMER2_PFSTYPE_SOFT_SLAVE:
index 5d9aab5..f3a5f02 100644 (file)
@@ -2,31 +2,46 @@
                            HAMMER2 DESIGN DOCUMENT
 
                                Matthew Dillon
-                                08-Feb-2012
-                                14-May-2013
                             dillon@backplane.com
 
-* These features have been speced in the media structures.
+                              03-Apr-2015 (v3)
+                              14-May-2013 (v2)
+                              08-Feb-2012 (v1)
+
+                       Current Status as of document date
+
+* Filesystem Core      - operational
+  - bulkfree           - operational
+  - Compression                - operational
+  - Snapshots          - operational
+  - Deduper            - specced
+  - Subhierarchy quotas - specced
+  - Logical Encryption - not specced yet
+  - Copies             - not specced yet
+  - fsync bypass       - not specced yet
+
+* Clustering core
+  - Network msg core   - operational
+  - Network blk device - operational
+  - Error handling     - under development
+  - Quorum Protocol    - under development
+  - Synchronization    - under development
+  - Transaction replay - not specced yet
+  - Cache coherency    - not specced yet
 
-* Implementation work has begun.
-
-* Filesytem core is now operational, cluster messaging links are primitive
-  but work (and are fully encrypted).  Work continues on the block allocator
-  and work has not yet begun on copies, block-encryption, block-compression,
-  mirroring, or quorum/cluster ops.
-
-* Obviously a fully functional filesystem is not yet ready but once the
-  freemap and the backend garbage collector is implemented the HAMMER2
-  filesystem will be usable.  Missing many features, but usable.
+                                   Feature List
 
-* Design of all media elements is complete.
+* Multiple roots, with many features.  This is implemented via the super-root
+  concept.  When mounting a HAMMER2 filesystem you specify a device path and
+  a directory name in the super-root.  (HAMMER1 had only one root).
 
-                                   Feature List
+* All cluster types and multiple PFSs (belonging to the same or different
+  clusters) can be mixed on one physical filesystem.
 
-* Multiple roots (allowing snapshots to be mounted).  This is implemented
-  via the super-root concept.  When mounting a HAMMER2 filesystem you specify
-  a device path and a directory name in the super-root.  (HAMMER1 had only
-  one root).
+  This allows independent cluster components to be configured within a
+  single formatted H2 filesystem.  Each component is a super-root entry,
+  a cluster identifier, and a unique identifier.  The network protocl
+  integrates the component into the cluster when it is created
 
 * Roots are really no different from snapshots (HAMMER1 distinguished between
   its root mount and its PFS's.  HAMMER2 does not).
   automatic fine-grained snapshots.  H2 snapshots are cheap enough that you
   can create fine-grained snapshots if you desire.
 
-* HAMMER2 flushes formalized a synchronization point for the flush, wait
-  for all running modifying operations to complete to memory (not to disk)
-  while temporarily stalling new modifying operation initiations.  The
-  flush is then able to proceed concurrent with unstalling and allowing
-  new modifying operations to run.
+* HAMMER2 formalizes a synchronization point for the flush, does a pre-flush
+  that does not update the volume root, then waits for all running modifying
+  operations to complete to memory (not to disk) while temporarily stalling
+  new modifying operation initiations.  The final flush is then executed.
+
+  At the moment we do not allow concurrent modifying operations during the
+  final flush phase.  Ultimately I would like to, but doing so can be complex.
 
-* The flush is fully meta-data-synchronized in HAMMER2.  In HAMMER1 it was
-  possible for flushes to bisect inode creation vs directory entry creation
-  and to create problems with directory renames.  HAMMER2 has no issues with
-  any of these.  Dealing with data synchronization is another matter but
-  it should be possible to address explcit write()'s properly.  mmap()'d
-  R+W data... not so easy.
+* HAMMER2 flushes and synchronization points do not bisect VOPs (system calls).
+  (HAMMER1 flushes could wind up bisecting VOPs).  This means the H2 flushes
+  leave the filesystem in a far more consistent state than H1 flushes did.
 
 * Directory sub-hierarchy-based quotas for space and inode usage tracking.
   Any directory can be used.
   is completely asynchronous and dirty buffers can be retired by the OS
   directly to backing store with no further interactions with the filesystem.
 
-* Incremental queueless mirroring / mirroring-streams.  Because HAMMER2 is
-  block-oriented and copy-on-write each blockref tracks both direct
-  modifications to the referenced data via (modify_tid) and indirect
-  modifications to the referenced data or any sub-tree via (mirror_tid).
-  This makes it possible to do an incremental scan of meta-data that covers
-  only changes made since the mirror_tid recorded in a prior-run.
-
-  This feature is also intended to be used to locate recently allocated
-  blocks and thus be able to fixup the freemap after a crash.
-
-  HAMMER2 mirroring works a bit differently than HAMMER1 mirroring in
-  that HAMMER2 does not keep track of 'deleted' records.  Instead any
-  recursion by the mirroring code which finds that (modify_tid) has
-  been updated must also send the direct block table or indirect block
-  table state it winds up recursing through so the target can check
-  similar key ranges and locate elements to be deleted.  This can be
-  avoided if the mirroring stream is mostly caught up in that very recent
-  deletions will be cached in memory and can be queried, allowing shorter
-  record deletions to be passed in the stream instead.
-
-* Will support multiple compression algorithms configured on subdirectory
-  tree basis and on a file basis.  Up to 64K block compression will be used.
-  Only compression ratios near powers of 2 that are at least 2:1 (e.g. 2:1,
-  4:1, 8:1, etc) will work in this scheme because physical block allocations
-  in HAMMER2 are always power-of-2.
+* Background synchronization and mirroring occurs at the logical level.
+  When a failure occurs or a normal validation scan comes up with
+  discrepancies, the synchronization thread will use the quorum to figure
+  out which information is not correct and update accordingly.
 
-  Compression algorithm #0 will mean no compression and no zero-checking.
-  Compression algorithm #1 will mean zero-checking but no other compression.
-  Real compression will be supported starting with algorithm 2.
+* Support for multiple compression algorithms configured on subdirectory
+  tree basis and on a file basis.  Block compression up to 64KB will be used.
+  Only compression ratios at powers of 2 that are at least 2:1 (e.g. 2:1,
+  4:1, 8:1, etc) will work in this scheme because physical block allocations
+  in HAMMER2 are always power-of-2.  Modest compression can be achieved with
+  low overhead, is turned on by default, and is compatible with deduplication.
+
+* Encryption.  Whole-disk encryption is supported by another layer, but I
+  intend to give H2 an encryption feature at the logical layer which works
+  approximately as follows:
+
+  - Encryption controlled by the client on an inode/sub-tree basis.
+  - Server has no visibility to decrypted data.
+  - Encrypt filenames in directory entries.  Since the filename[] array
+    is 256 bytes wide, client can add random bytes after the normal
+    terminator to make it virtually impossible for an attacker to figure
+    out the filename.
+  - Encrypt file size and most inode contents.
+  - Encrypt file data (holes are not encrypted).
+  - Encryption occurs after compression, with random filler.
+  - Check codes calculated after encryption & compression (not before).
+
+  - Blockrefs are not encrypted.
+  - Directory and File Topology is not encrypted.
+  - Encryption is not sub-topology validation.  Client would have to keep
+    track of that itself.  Server or other clients can still e.g. remove
+    files, rename, etc.
+
+  In particular, note that even though the file size field can be encrypted,
+  the server does have visibility on the block topology and thus has a pretty
+  good idea how big the file is.  However, a client could add junk blocks
+  at the end of a file to make this less apparent, at the cost of space.
+
+  If a client really wants a fully validated H2-encrypted space the easiest
+  solution is to format a filesystem within an encrypted file by treating it
+  as a block device, but I digress.
 
 * Zero detection on write (writing all-zeros), which requires the data
-  buffer to be scanned, will be supported as compression algorithm #1.
-  This allows the writing of 0's to create holes and will be the default
-  compression algorithm for HAMMER2.
-
-* Copies support for redundancy.  Each copy has its own blockref.  The
-  blockrefs representing the copies must exist within the same blockset
-  (set of 8 blockrefs), though I may relax this requirement in the
-  implementation.
-
-  The design is such that the filesystem should be able to function at
-  full speed even if disks are pulled or inserted, as long as at least one
-  good copy is present.  A background task will be needed to resynchronize
-  missing copies (or remove excessive copies in the case where the copies
-  value is reduced on a live filesystem).
-
-  Copies are specified using the same copyinfo[] array that is used to
-  specify cluster interconnections for PFS's.
-
-* Clusterable with MESI cache coherency and dynamic granularity.
-  The media format for HAMMER1 was less condusive to logical clustering
-  than I had hoped so I was never able to get that aspect of my personal goals
-  working with HAMMER1.  HAMMER2 effectively solves the issues that cropped
-  up with HAMMER1 (mainly that HAMMER1's B-Tree did not reflect the logical
-  file/directory hierarchy, making cache coherency very difficult).
-
-* Hardlinks will be supported.  All other standard features will be supported
-  too of course.  Hardlinks in this sort of filesystem require significant
-  work.
+  buffer to be scanned, is fully supported.  This allows the writing of 0's
+  to create holes.
+
+* Copies support for redundancy within a single physical filesystem.
+  Up to 256 physical disks and/or partitions can be ganged to form a
+  single physical filesystem.  If you use a disk or RAID aggregation 
+  layer then the actual number of physical disks that can be associated
+  with a single H2 filesystem is unbounded.
+
+  H2 handles this by interleaving the 64-bit address space 256-ways on
+  level-1 (2GB) boundaries.  Thus, a single physical disk from H2's point
+  of view can be sized up to 2^56 which is 64 Petabytes, and the total
+  filesystem size can be up to 16 Exabytes.
+
+  Copies support is implemented by having multiple blockref entries for
+  the same key, each with a different copyid.  The copyid represents which
+  of the 256 slots is used.  Meta-data is also subject to the copies
+  mechanism.  However, for both meta-data and data, each copy should be
+  identical so the check fields in the blockref for all copies should wind
+  up being the same, and any valid copy can be used by the block-level
+  hammer2_chain code to access the filesystem.  File accesses will attempt
+  to use the same copy.  If an I/O read error occurs, a different copy will
+  be chosen.  Modifying operations must update all copies and/or create
+  new copies as needed.  If a write error occurs on a copy and other copies
+  are available, the errored target will be taken offline.
+
+  It is possible to configure H2 to write out fewer copies on-write and then
+  use a background scan to beef-up the number of copies to improve real-time
+  throughput.
+
+* MESI Cache coherency for multi-master/multi-client clustering operations.
+  The servers hosting the MASTERs are also responsible for keeping track of
+  the cache state.
+
+* Hardlinks and softlinks are supported.  Hardlinks are somewhat complex to
+  deal with and there is still an edge case.  I am trying to avoid storing
+  the hardlinks at the root level because that messes up my concept for
+  sub-tree quotas and is unnecessarily burdensome in terms of SMP collisions
+  under heavy loads.
 
 * The media blockref structure is now large enough to support up to a 192-bit
   check value, which would typically be a cryptographic hash of some sort.
 * Fully verified deduplication will be supported and automatic (and
   necessary in many respects).
 
-* Non-verified de-duplication will be supported as a configurable option on
-  a file or subdirectory tree.  Non-verified deduplication would use the
-  largest available check code (192 bits) and not bother to verify data
-  matches during the dedup pass, which is necessary on extremely large
-  filesystems with a great deal of deduplicable data (as otherwise a large
-  chunk of the media would have to be read to implement the dedup).
+* Unverified de-duplication will be supported as a configurable option on a
+  file or subdirectory tree.  Unverified deduplication must use the largest
+  available check code (192 bits).  It will not verify that data content with
+  the same check code is actually identical during the dedup pass, resulting
+  in approximately 100x to 1000x the deduplication performance but at the cost
+  of potentially corrupting some data.
 
-  This feature is intended only for those files where occassional corruption
-  is ok, such as in a large data store of farmed web content.
+  The Unverified dedup feature is intended only for those files where
+  occassional corruption is ok, such as in a web-crawler data store or
+  other situations where the data content is not critically important
+  or can be externally recovered if it becomes corrupt.
 
                                GENERAL DESIGN
 
@@ -149,51 +188,83 @@ the filesystem.
 The copy-on-write nature of the filesystem implies that any modification
 whatsoever will have to eventually synchronize new disk blocks all the way
 to the super-root of the filesystem and the volume header itself.  This forms
-the basis for crash recovery.  All disk writes are to new blocks except for
-the volume header, thus allowing all writes to run concurrently except for
-the volume header update at the end.
+the basis for crash recovery and also ensures that recovery occurs on a
+completed high-level transaction boundary.  All disk writes are to new blocks
+except for the volume header (which cycles through 4 copies), thus allowing
+all writes to run asynchronously and concurrently prior to and during a flush,
+and then just doing a final synchronization and volume header update at the
+end.
 
 Clearly this method requires intermediate modifications to the chain to be
 cached so multiple modifications can be aggregated prior to being
-synchronized.  One advantage, however, is that the cache can be flushed at
-any time WITHOUT having to allocate yet another new block when further
-modifications are made as long as the volume header has not yet been flushed.
-This means that buffer cache overhead is very well bounded and can handle
-filesystem operations of any complexity even on boxes with very small amounts
-of physical memory.
-
-I intend to implement a shortcut to make fsync()'s run fast, and that is to
-allow deep updates to blockrefs to shortcut to auxillary space in the
-volume header to satisfy the fsync requirement.  The related blockref is
-then recorded when the filesystem is mounted after a crash and the update
-chain is reconstituted when a matching blockref is encountered again during
-normal operation of the filesystem.
-
-Basically this means that no real work needs to be done at mount-time
-even after a crash.
+synchronized.  One advantage, however, is that the normal buffer cache can
+be used and intermediate elements can be retired to disk by H2 or the OS
+at any time.  This means that HAMMER2 has very resource overhead from the
+point of view of the operating system.  Unlike HAMMER1 which had to lock
+dirty buffers in memory for long periods of time, HAMMER2 has no such
+requirement.
+
+Buffer cache overhead is very well bounded and can handle filesystem
+operations of any complexity, even on boxes with very small amounts
+of physical memory.  Buffer cache overhead is significantly lower with H2
+than with H1 (and orders of magnitude lower than ZFS).
+
+At some point I intend to implement a shortcut to make fsync()'s run fast,
+and that is to allow deep updates to blockrefs to shortcut to auxillary
+space in the volume header to satisfy the fsync requirement.  The related
+blockref is then recorded when the filesystem is mounted after a crash and
+the update chain is reconstituted when a matching blockref is encountered
+again during normal operation of the filesystem.
+
+                           DIRECTORIES AND INODES
 
 Directories are hashed, and another major design element is that directory
-entries ARE INODES.  They are one and the same.  In addition to directory
-entries being inodes the data for very small files (512 bytes or smaller)
-can be directly embedded in the inode (overloaded onto the same space that
-the direct blockref array uses).  This should result in very high
-performance.
+entries ARE INODES.  They are one and the same, with a special placemarker
+for hardlinks.  Inodes are 1KB.
+
+Half of the inode structure (512 bytes) is used to hold top-level blockrefs
+to the radix block tree representing the file contents.  Files which are
+less than or equal to 512 bytes in size will simply store the file contents
+in this area instead of a blockref array.  So files <= 512 bytes take only
+1KB of space inclusive of the inode.
 
 Inode numbers are not spatially referenced, which complicates NFS servers
 but doesn't complicate anything else.  The inode number is stored in the
-inode itself, an absolutely necessary feature in order to support the
-hugely flexible snapshots that we want to have in HAMMER2.
+inode itself, an absolute necessity required to properly support HAMMER2s
+hugely flexible snapshots.
+
+                                   RECOVERY
+
+H2 allows freemap flushes to lag behind topology flushes.  The freemap flush
+tracks a separate transaction id in the volume header.
+
+On mount, HAMMER2 will first locate the highest-sequenced check-code-validated
+volume header from the 4 copies available (if the filesystem is big enough,
+e.g. > ~10GB or so, there will be 4 copies of the volume header).
+
+HAMMER2 will then run an incremental scan of the topology for transaction ids
+between the last freemap flush and the current topology in order to update
+the freemap.  Because this scan is incremental the worst-case time to run
+the scan is about the time it takes to run one flush.
+
+The filesystem is then ready for use.
 
                            DISK I/O OPTIMIZATIONS
 
-The freemap implements a 1KB allocation resolution.  The minimum I/O size
-is 16KB.  HAMMER2 typically implements 16KB and 64KB physical I/O sizes
-and will cluster larger I/O's.
+The freemap implements a 1KB allocation resolution.  Each 2MB segment managed
+by the freemap is zoned and has a tendancy to collect inodes, small data,
+indirect blocks, and larger data blocks into separate segments.  The idea is
+to greatly improve I/O performance (particularly by laying inodes down next
+to each other which has a huge effect on directory scans).
+
+The current implementation of HAMMER2 implements a fixed block size of 64KB
+in order to allow aliasing of hammer2_dio's in its IO subsystem.  This way
+we don't have to worry about matching the buffer cache / DIO cache to the
+variable block size of underlying elements.
 
-Each 2MB segment managed by the freemap handles just one particular
-physical I/O size.  Typically this means that inodes, small data, and
-initial (small) indirect blocks get clustered together.  Also large 64KB
-file-data and indirect blocks get clustered together.
+HAMMER2 also allows OS support for ganging buffers together into even
+larger blocks for I/O, OS-supported read-ahead, and other performance
+features typically provided by the OS at the block-level.
 
                                  HARDLINKS
 
@@ -204,153 +275,153 @@ an index of inode numbers for any basic HAMMER2 feature if we can help it.
 Hardlinks are handled by placing the inode for a multiply-hardlinked file
 in the closest common parent directory.  If "a/x" and "a/y" are hardlinked
 the inode for the hardlinked file will be placed in directory "a", e.g.
-"a/3239944", but it will be invisible and will be in an out-of-band namespace.
-The directory entries "a/x" and "a/y" will be given the same inode number
-but in fact just be placemarks that cause HAMMER2 to recurse upwards through
-the directory tree to find the invisible inode number.
+"a/<inode_number>".  The actual file inode will be an invisible out-of-band
+entry in the directory.  The directory entries "a/x" and "a/y" will be given
+the same inode number but in fact they are only placemarks that cause
+HAMMER2 to recurse upwards through the directory tree to find the invisible
+real inode.
 
 Because directories are hashed and a different namespace (hash key range)
 is used for hardlinked inodes, standard directory scans are able to trivially
 skip this invisible namespace and inode-specific lookups can restrict their
-lookup to within this space.
-
-The nature of snapshotting makes handling link-count 2->1 and 1->2 cases
-trivial.  Basically the inode media structure is copied as needed to break-up
-or re-form the standard directory entry/inode.  There are no backpointers in
-HAMMER2 and no reference counts on the blocks (see FREEMAP NOTES below), so
-it is an utterly trivial operation.
+lookup to within this space.  No linear scans are needed.
 
                                FREEMAP NOTES
 
+The freemap is stored in the reserved blocks situated in the ~4MB reserved
+area at the baes of every ~2GB level-1 zone.  The current implementation
+reserves 8 copies of every freemap block and cycles through them in order
+to make the freemap operate in a copy-on-write fashion.
+
+    - Freemap is copy-on-write.
+    - Freemap operations are transactional, same as everything else.
+    - All backup volume headers are consistent on-mount.
+
+The Freemap is organized using the same radix blockmap algorithm used for
+files and directories, but with fixed radix values.  For a maximally-sized
+filesystem the Freemap will wind up being a 5-level-deep radix blockmap,
+but the top-level is embedded in the volume header so insofar as performance
+goes it is really just a 4-level blockmap.
+
+The freemap radix allocation mechanism is also the same, meaning that it is
+bottom-up and will not allocate unnecessary intermediate levels for smaller
+filesystems.  A 16GB filesystem uses a 2-level blockmap (volume header + one
+level), a 16TB filesystem uses a 3-level blockmap (volume header + two levels),
+and so forth.
+
+The Freemap has bitmap granularity down to 16KB and a linear iterator that
+can linearly allocate space down to 1KB.  Due to fragmentation it is possible
+for the linear allocator to become marginalized, but it is relatively easy
+to for a reallocation of small blocks every once in a while (like once a year
+if you care at all) and once the old data cycles out of the snapshots, or you
+also rewrite the snapshots (which you can do), the freemap should wind up
+relatively optimal again.  Generally speaking I believe that algorithms can
+be developed to make this a non-problem without requiring any media structure
+changes.
+
 In order to implement fast snapshots (and writable snapshots for that
-matter), HAMMER2 does NOT ref-count allocations.  The freemap which
-is still under design just won't do that.  All the freemap does is
-keep track of 100% free blocks.
-
-This not only trivializes all the snapshot features it also trivializes
-hardlink handling and solves the problem of keeping the freemap sychronized
-in the event of a crash.  Now all we have to do after a crash is make
-sure blocks allocated before the freemap was flushed are properly
-marked as allocated in the allocmap.  This is a trivial exercise using the
-same algorithm the mirror streaming code uses (which is very similar to
-HAMMER1)... an incremental meta-data scan that covers only the blocks that
-might have been allocated between the last allocation map sync and now.
-
-Thus the freemap does not have to be synchronized during a fsync().
-
-The complexity is in figuring out what can be freed... that is, when one
-can mark blocks in the freemap as being free.  HAMMER2 implements this as
-a background task which essentially must scan available meta-data to
-determine which blocks are not being referenced.
+matter), HAMMER2 does NOT ref-count allocations.  All the freemap does is
+keep track of 100% free blocks plus some extra bits for staging the bulkfree
+scan.  The lack of ref-counting makes it possible to:
+
+    - Completely trivialize HAMMER2s snapshot operations.
+    - Allows any volume header backup to be used trivially.
+    - Allows whole sub-trees to be destroyed without having to scan them.
+    - Simplifies normal crash recovery operations.
+    - Simplifies catastrophic recovery operations.
+
+Normal crash recovery is simply a matter of doing an incremental scan
+of the topology between the last flushed freemap TID and the last flushed
+topology TID.  This usually takes only a few seconds and allows:
+
+    - Freemap flushes to be be deferred for any number of topology flush
+      cycles.
+    - Does not have to be flushed for fsync, reducing fsync overhead.
+
+                               FREEMAP - BULKFREE
+
+Blocks are freed via a bulkfree scan, which is a two-stage meta-data scan.
+Blocks are first marked as being possibly free and then finalized in the
+second scan.  Live filesystem operations are allowed to run during these
+scans and any freemap block that is allocated or adjusted after the first
+scan will simply be re-marked as allocated and the second scan will not
+transition it to being free.
+
+The cost of not doing ref-count tracking is that HAMMER2 must perform two
+bulkfree scans of the meta-data to determine which blocks can actually be
+freed.  This can be complicated by the volume header backups and snapshots
+which cause the same meta-data topology to be scanned over and over again,
+but mitigated somewhat by keeping a cache of higher-level nodes to detect
+when we would scan a sub-topology that we have already scanned.  Due to the
+copy-on-write nature of the filesystem, such detection is easy to implement.
 
 Part of the ongoing design work is finding ways to reduce the scope of this
 meta-data scan so the entire filesystem's meta-data does not need to be
 scanned (though in tests with HAMMER1, even full meta-data scans have
-turned out to be fairly low cost).  In other words, its an area that we
-can continue to improve on as the filesystem matures.  Not only that, but
-we can completely change the freemap algorithms without creating
-incompatibilities (at worse simply having to require that a R+W mount do
-a full meta-data scan when upgrading or downgrading the freemap algorithm).
+turned out to be fairly low cost).  In other words, its an area where
+improvements can be made without any media format changes.
+
+Another advantage of operating the freemap like this is that some future
+version of HAMMER2 might decide to completely change how the freemap works
+and would be able to make the change with relatively low downtime.
 
                                  CLUSTERING
 
 Clustering, as always, is the most difficult bit but we have some advantages
 with HAMMER2 that we did not have with HAMMER1.  First, HAMMER2's media
-structures generally follow the kernel's filesystem hiearchy.  Second,
+structures generally follow the kernel's filesystem hiearchy which allows
+cluster operations to use topology cache and lock state.  Second,
 HAMMER2's writable snapshots make it possible to implement several forms
 of multi-master clustering.
 
 The mount device path you specify serves to bootstrap your entry into
-the cluster.  This can be local media or directly specify a network
-cluster connection (or several).  When a local media mount is used the
-volume header is scanned for local copies and the best volume header is
-selected from all available copies.  Multiple devices may be specified for
-redundancy.
-
-The volume header on local media also contains cluster connection
-specifications keyed by super-root pfsid.  Network connections are
-maintained to all targets.  ALL ELEMENTS ARE TREATED ACCORDING TO TYPE
-NO MATTER WHICH ONE YOU MOUNT FROM.
-
-The actual networked cluster may be far larger than the elements you list
-in the hammer2_copy_data[] array, but your machine will only make direct
-connections as specified by the array.
-
-In the simplest case you simply network a few machines together as ring 0
-masters and each client connects directly to all the masters (and/or are
-the masters themselves).  Thus any quorum operation is straight-forward.
-These master nodes are labeled 'ring 0'.
-
-If you have too many clients to reasonably connect directly you set up
-sub-clusters as satellites.  This is called 'ring 1'.  Ring 1 may contain
-several sub-clusters.  A client then connects to all the nodes in a
-particular sub-cluster (typically 3).  The quorum protocol runs as per
-normal except that once the operation is resolved against the sub-cluster
-an aggregation must be resolved against the master nodes (ring 0).  The
-sub-cluster does this for the client... all the client sees is the normal
-quorum operation against the sub-cluster.
-
-Since each node in the sub-cluster connects to all master nodes we get
-a multiplication.  If we set a reasonable upper limit of, say, 256
-connections at each master node then ring 1 may contain 85 sub-clusters x 3
-nodes in each sub-cluster.
-
-In the most complex case when one wishes to support potentially millions
-of clients then further fan-out is required into ring 2, ring 3, and
-so forth.  However, each sub-cluster in ring 2 must only connect to
-1 sub-cluster in ring 1 (otherwise the cache state will become mightily
-confused).  Using reasonable metrics this will allow ring 2 to contain
-85 * 85 = 7225 sub-clusters.  At this point you could have 1000 clients
-connect to each sub-cluster and support 7.2 million clients, but if that
-isn't enough going to another ring will support 61M clients, and so forth.
-
-Each ring imposes additional latencies for cache operations but the key
-to making this work efficiently is that the satellite clusters can negotiate
-coarse-grained cache coherency locks with the next lower ring and then
-fan-out finer-grained locks to the next higher ring.  Since caching can
-occur anywhere (including on the connecting client), it is the cache
-coherency lock that ultimately dictates efficiency and allows a client
-(or satellite) to access large amoutns of data from local storage.
-
-Modifying operations, particularly commits, also have higher latencies
-when multiple rings are in use.  In this situation it is possible to
-short-cut localized operations by having competing clients connect to
-to sub-clusters which are near each other topologically... having the
-competing clients connect to the same sub-cluster would be the most optimal.
-
-In addition, sub-clusters (typically in ring 1) can act in SOFT_MASTER mode
-which allows the sub-cluster to acknowledge a full commit within its own
-quorum only, and then resolve asynchronously to the masters in ring 0.
-
-The nodes in these intermediate rings can be pure proxies with only memory
-caches, use local media for persistent cache, or use local media to
-completely slave the filesystem.
-
-    ADMIN      - Media does not participate, administrative proxy only
-    CLIENT     - Media does not participate, client only
-    CACHE      - Media only acts as a persistent cache
-    COPY       - Media only acts as a local copy
-    SLAVE      - Media is a RO slave that can be mounted RW
-
-    SOFT_SLAVE - This is a SLAVE which can become writable when
-                 the quorum is not available, but is not guaranteed
-                 to be able to be merged back when the quorum becomes
-                 available again.  Elements which cannot be merged
-                 back remain localized and writable until manual
-                 or scripted intervention recombines them.
-
-    SOFT_MASTER        - Similar to the above but can form a sub-cluster
-                 and run the quorum protocol within the sub-cluster
-                 to serve machines that connect to the sub-cluster
-                 when the master cluster is not available.
-
-                 The SOFT_MASTER nodes in a sub-cluster must be
-                 fully interconnected with each other.
-
-    MASTER     - This is a MASTER node in the quorum protocol.
-
-                 The MASTER nodes in a cluster must be fully
-                 interconnected with each other.
+the cluster.  This is typically local media.  It can even be a ram-disk
+that only contains placemarkers that help HAMMER2 connect to a fully
+networked cluster.
+
+With HAMMER2 you mount a directory entry under the super-root.  This entry
+will contain a cluster identifier that helps HAMMER2 identify and integrate
+with the nodes making up the cluster.  HAMMER2 will automatically integrate
+*all* entries under the super-root when you mount one of them.  You have to
+mount at least one for HAMMER2 to integrate the block device in the larger
+cluster.  This mount will typically be a SOFT_MASTER, DUMMY, SLAVE, or CACHE
+mount that simply serves to cause hammer to integrate the rest of the
+represented cluster.  ALL CLUSTER ELEMENTS ARE TREATED ACCORDING TO TYPE
+NO MATTER WHICH ONE YOU MOUNT.
+
+For cluster servers every HAMMER2-formatted partition has a "LOCAL" MASTER
+which can be mounted in order to make the rest of the elements under the
+super-root available to the network.  (In a prior specification I emplaced
+the cluster connections in the volume header's configuration space but I no
+longer do that).
+
+Connecting to the wider networked cluster involves setting up the /etc/hammer2
+directory with appropriate IP addresses and keys.  The user-mode hammer2
+service daemon maintains the connections and performs graph operations
+via libdmsg.
+
+Node types within the cluster:
+
+    DUMMY      - Used as a local placeholder (typically in ramdisk)
+    CACHE      - Used as a local placeholder and cache (typically on a SSD)
+    SLAVE      - A SLAVE in the cluster, can source data on quorum agreement.
+    MASTER     - A MASTER in the cluster, can source and sink data on quorum
+                 agreement.
+    SOFT_SLAVE - A SLAVE in the cluster, can source data locally without
+                 quorum agreement (must be directly mounted).
+    SOFT_MASTER        - A local MASTER but *not* a MASTER in the cluster.  Can source
+                 and sink data locally without quorum agreement, intended to
+                 be synchronized with the real MASTERs when connectivity
+                 allows.  Operations are not coherent with the real MASTERS
+                 even when they are available.
+
+    NOTE: SNAPSHOT, AUTOSNAP, etc represent sub-types, typically under a
+         SLAVE.  A SNAPSHOT or AUTOSNAP is a SLAVE sub-type that is no longer
+         synchronized against current masters.
+
+    NOTE: Any SLAVE or other copy can be turned into its own writable MASTER
+         by giving it a unique cluster id, taking it out of the cluster that
+         originally spawned it.
 
 There are four major protocols:
 
@@ -428,3 +499,161 @@ to determine cache state at any given level, and (mirror_tid) is used to
 determine whether any recursively underlying state is desynchronized.
 The inode structure also has two additional transaction ids used to optimize
 path lookups, stat, and directory lookup/scan operations.
+
+                      MASTER & SLAVE SYNCHRONIZATION
+
+With HAMMER2 I really want to be hard-nosed about the consistency of the
+filesystem, including the consistency of SLAVEs (snapshots, etc).  In order
+to guarantee consistency we take advantage of the copy-on-write nature of
+the filesystem by forking consistent nodes and using the forked copy as the
+source for synchronization.
+
+Similarly, the target for synchronization is not updated on the fly but instead
+is also forked and the forked copy is updated.  When synchronization is
+complete, forked sources can be thrown away and forked copies can replace
+the original synchronization target.
+
+This may seem complex, but 'forking a copy' is actually a virtually free
+operation.  The top-level inode (under the super-root), on-media, is simply
+copied to a new inode and poof, we have an unchanging snapshot to work with.
+
+       - Making a snapshot is fast... almost instantanious.
+
+       - Snapshots are used for various purposes, including synchronization
+         of out-of-date nodes.
+
+       - A snapshot can be converted into a MASTER or some other PFS type.
+
+       - A snapshot can be forked off from its parent cluster entirely and
+         turned into its own writable filesystem, either as a single MASTER
+         or this can be done across the cluster by forking a quorum+ of
+         existing MASTERs and transfering them all to a new cluster id.
+
+More complex is reintegrating the target once the synchronization is complete.
+For SLAVEs we just delete the old SLAVE and rename the copy to the same name.
+However, if the SLAVE is mounted and not optioned as a static mount (that is
+the mounter wants to see updates as they are synchronized), a reconciliation
+must occur on the live mount to clean up the vnode, inode, and chain caches
+and shift any remaining vnodes over to the updated copy.
+
+       - A mounted SLAVE can track updates made to the SLAVE but the
+         actual mechanism is that the SLAVE PFS is replaced with an
+         updated copy, typically every 30-60 seconds.
+
+Reintegrating a MASTER which has fallen out of the quorum due to being out
+of date is also somewhat more complex.  The same updating mechanic is used,
+we actually have to throw the 'old' MASTER away once the new one has been
+updated.  However if the cluster is undergoing heavy modifications the
+updated MASTER will be out of date almost the instant its source is
+snapshotted.  Reintegrating a MASTER thus requires a somewhat more complex
+interaction.
+
+       - If a MASTER is really out of date we can run one or more
+         synchronization passes concurrent with modifying operations.
+         The quorum can remain live.
+
+       - A final synchronization pass is required with quorum operations
+         blocked to reintegrate the now up-to-date MASTER into the cluster.
+
+
+                               QUORUM OPERATIONS
+
+Quorum operations can be broken down into HARD BLOCK operations and NETWORK
+operations.  If your MASTERs are all local mounts, then failures and
+sequencing is easy to deal with.
+
+Quorum operations on a networked cluster are more complex.  The problems:
+
+    - Masters cannot rely on clients to moderate quorum transactions.
+      Apart from the reliance being unsafe, the client could also
+      lose contact with one or more masters during the transaction and
+      leave one or more masters out-of-sync without the master(s) knowing
+      they are out of sync.
+
+    - When many clients are present, we do not want a flakey network
+      link from one to cause one or more masters to go out of
+      synchronization and potentially stall the whole works.
+
+    - Normal hammer2 mounts allow a virtually unlimited number of modifying
+      transactions between actual flushes.  The media flush rolls everything
+      up into a single transaction id per flush.  Detection of 'missing'
+      transactions in a concurrent multi-client setup when one or more client
+      temporarily loses connectivity is thus difficult.
+
+    - Clients have a limited amount of time to reconnect to a cluster after
+      a network disconnect before their MESI cache states are lost.
+
+    - Clients may proceed with several transactions before knowing for sure
+      that earlier transactions were completely successful.  Performance is
+      important, we won't be waiting for a full quorum-verified synchronous
+      flush to media before allowing a system call to return.
+
+    - Masters can decide that a client's MESI cache states were lost (i.e.
+      that the transaction was too slow) as well.
+
+The solutions (for modifying transactions):
+
+    - Masters handle quorum confirmation amongst themselves and do not rely
+      on the client for that purpose.
+
+    - A client can connect to one or more masters regardless of the size of
+      the quorum and can submit modifying operations to a single master if
+      desired.  The master will take care of the rest.
+
+      A client must still validate the quorum (and obtain MESI cache states)
+      when doing read-only operations in order to present the correct data
+      to the user process for the VOP.
+
+    - Masters will run a 2-phase commit amongst themselves, often concurrent
+      with other non-conflicting transactions, and will serialize operations
+      and/or enforce synchronization points for 2-phase completion on
+      serialized transactions from the same client or when cache state
+      ownership is shifted from one client to another.
+
+    - Clients will usually allow operations to run asynchronously and return
+      from system calls more or less ASAP once they own the necessary cache
+      coherency locks.  The client can select the validation mode to wait for
+      with mount options:
+
+      (1) Fully async          (mount -o async)
+      (2) Wait for phase-1 ack (mount)
+      (3) Wait for phase-2 ack (mount -o sync)         (fsync - wait p2ack)
+      (4) Wait for flush       (mount -o sync)         (fsync - wait flush)
+
+      Modifying system calls cannot be told to wait for a full media
+      flush, as full media flushes are prohibitively expensive.  You
+      still have to fsync().
+
+      The fsync wait mode for network links can be selected, either to
+      return after the phase-2 ack or to return after the media flush.
+      The default is to wait for the phase-2 ack, which at least guarantees
+      that a network failure after that point will not disrupt operations
+      issued before the fsync.
+
+    - Clients must adjust the chain state for modifying operations prior to
+      releasing chain locks / returning from the system call, even if the
+      masters have not finished the transaction.  A late failure by the
+      cluster will result in desynchronized state which requires erroring
+      out the whole filesystem or resynchronizing somehow.
+
+    - Clients can opt to keep a record of transactions through the phase-2
+      ack or the actual media flush on the masters.
+
+      However, replaying/revalidating the log cannot necessarily guarantee
+      success.  If the masters lose synchronization due to network issues
+      between masters (or if the client was mounted fully-async), or if enough
+      masters crash simultaniously such that a quorum fails to flush even
+      after the phase-2 ack, then it is possible that by the time a client
+      is able to replay/revalidate, some other client has squeeded in and
+      committed something that would conflict.
+
+      If the client crashes it works similarly to a crash with a local storage
+      mount... many dirty buffers might be lost.  And the same happens in
+      the cluster case.
+
+                               TRANSACTION LOG
+
+Keeping a short-term transaction log, much less being able to properly replay
+it, is fraught with difficulty and I've made it a separate development task.
+    
+
index 57fc804..48c8bce 100644 (file)
@@ -351,17 +351,22 @@ RB_PROTOTYPE(hammer2_chain_tree, hammer2_chain, rbnode, hammer2_chain_cmp);
 /*
  * Special notes on flags:
  *
- * INITIAL - This flag allows a chain to be created and for storage to
- *          be allocated without having to immediately instantiate the
- *          related buffer.  The data is assumed to be all-zeros.  It
- *          is primarily used for indirect blocks.
+ * INITIAL     - This flag allows a chain to be created and for storage to
+ *               be allocated without having to immediately instantiate the
+ *               related buffer.  The data is assumed to be all-zeros.  It
+ *               is primarily used for indirect blocks.
  *
- * MODIFIED- The chain's media data has been modified.
- * UPDATE  - Chain might not be modified but parent blocktable needs update
+ * MODIFIED    - The chain's media data has been modified.
  *
- * BMAPPED - Indicates that the chain is present in the parent blockmap.
- * BMAPUPD - Indicates that the chain is present but needs to be updated
- *          in the parent blockmap.
+ * UPDATE      - Chain might not be modified but parent blocktable needs update
+ *
+ * FICTITIOUS  - Faked chain as a placeholder for an error condition.  This
+ *               chain is unsuitable for I/O.
+ *
+ * BMAPPED     - Indicates that the chain is present in the parent blockmap.
+ *
+ * BMAPUPD     - Indicates that the chain is present but needs to be updated
+ *               in the parent blockmap.
  */
 #define HAMMER2_CHAIN_MODIFIED         0x00000001      /* dirty chain data */
 #define HAMMER2_CHAIN_ALLOCATED                0x00000002      /* kmalloc'd chain */
@@ -373,7 +378,7 @@ RB_PROTOTYPE(hammer2_chain_tree, hammer2_chain, rbnode, hammer2_chain_cmp);
 #define HAMMER2_CHAIN_DEFERRED         0x00000080      /* flush depth defer */
 #define HAMMER2_CHAIN_IOFLUSH          0x00000100      /* bawrite on put */
 #define HAMMER2_CHAIN_ONFLUSH          0x00000200      /* on a flush list */
-#define HAMMER2_CHAIN_UNUSED00000400   0x00000400
+#define HAMMER2_CHAIN_FICTITIOUS       0x00000400      /* unsuitable for I/O */
 #define HAMMER2_CHAIN_VOLUMESYNC       0x00000800      /* needs volume sync */
 #define HAMMER2_CHAIN_UNUSED00001000   0x00001000
 #define HAMMER2_CHAIN_UNUSED00002000   0x00002000
@@ -397,6 +402,11 @@ RB_PROTOTYPE(hammer2_chain_tree, hammer2_chain, rbnode, hammer2_chain_cmp);
  * I/O otherwise.  If set for a cluster it generally means that the cluster
  * code could not find a valid copy to present.
  *
+ * IO          - An I/O error occurred
+ * CHECK       - I/O succeeded but did not match the check code
+ * INCOMPLETE  - A cluster is not complete enough to use, or
+ *               a chain cannot be loaded because its parent has an error.
+ *
  * NOTE: API allows callers to check zero/non-zero to determine if an error
  *      condition exists.
  *
@@ -545,8 +555,12 @@ typedef struct hammer2_cluster     hammer2_cluster_t;
 /*
  * WRHARD      - Hard mounts can write fully synchronized
  * RDHARD      - Hard mounts can read fully synchronized
+ * UNHARD      - Unsynchronized masters present
+ * NOHARD      - No masters visible
  * WRSOFT      - Soft mounts can write to at least the SOFT_MASTER
  * RDSOFT      - Soft mounts can read from at least a SOFT_SLAVE
+ * UNSOFT      - Unsynchronized slaves present
+ * NOSOFT      - No slaves visible
  * RDSLAVE     - slaves are accessible (possibly unsynchronized or remote).
  * MSYNCED     - All masters are fully synchronized
  * SSYNCED     - All known local slaves are fully synchronized to masters
@@ -578,10 +592,14 @@ typedef struct hammer2_cluster    hammer2_cluster_t;
 #define HAMMER2_CLUSTER_LOCKED 0x00000004      /* cluster lks not recursive */
 #define HAMMER2_CLUSTER_WRHARD 0x00000100      /* hard-mount can write */
 #define HAMMER2_CLUSTER_RDHARD 0x00000200      /* hard-mount can read */
-#define HAMMER2_CLUSTER_WRSOFT 0x00000400      /* soft-mount can write */
-#define HAMMER2_CLUSTER_RDSOFT 0x00000800      /* soft-mount can read */
-#define HAMMER2_CLUSTER_MSYNCED        0x00001000      /* all masters synchronized */
-#define HAMMER2_CLUSTER_SSYNCED        0x00002000      /* known slaves synchronized */
+#define HAMMER2_CLUSTER_UNHARD 0x00000400      /* unsynchronized masters */
+#define HAMMER2_CLUSTER_NOHARD 0x00000800      /* no masters visible */
+#define HAMMER2_CLUSTER_WRSOFT 0x00001000      /* soft-mount can write */
+#define HAMMER2_CLUSTER_RDSOFT 0x00002000      /* soft-mount can read */
+#define HAMMER2_CLUSTER_UNSOFT 0x00004000      /* unsynchronized slaves */
+#define HAMMER2_CLUSTER_NOSOFT 0x00008000      /* no slaves visible */
+#define HAMMER2_CLUSTER_MSYNCED        0x00010000      /* all masters synchronized */
+#define HAMMER2_CLUSTER_SSYNCED        0x00020000      /* known slaves synchronized */
 
 #define HAMMER2_CLUSTER_ANYDATA        ( HAMMER2_CLUSTER_RDHARD |      \
                                  HAMMER2_CLUSTER_RDSOFT |      \
@@ -906,6 +924,7 @@ struct hammer2_pfs {
        struct bio_queue_head   wthread_bioq;   /* logical buffer bioq */
        hammer2_mtx_t           wthread_mtx;    /* interlock */
        int                     wthread_destroy;/* termination sequencing */
+       uint32_t                status_flags;   /* cached cluster flags */
 };
 
 typedef struct hammer2_pfs hammer2_pfs_t;
@@ -1029,11 +1048,10 @@ extern int write_thread_wakeup;
 #define hammer2_icrc32c(buf, size, crc)        iscsi_crc32_ext((buf), (size), (crc))
 
 int hammer2_signal_check(time_t *timep);
-hammer2_cluster_t *hammer2_inode_lock_ex(hammer2_inode_t *ip);
-hammer2_cluster_t *hammer2_inode_lock_nex(hammer2_inode_t *ip, int how);
-hammer2_cluster_t *hammer2_inode_lock_sh(hammer2_inode_t *ip);
-void hammer2_inode_unlock_ex(hammer2_inode_t *ip, hammer2_cluster_t *chain);
-void hammer2_inode_unlock_sh(hammer2_inode_t *ip, hammer2_cluster_t *chain);
+const char *hammer2_error_str(int error);
+
+hammer2_cluster_t *hammer2_inode_lock(hammer2_inode_t *ip, int how);
+void hammer2_inode_unlock(hammer2_inode_t *ip, hammer2_cluster_t *cluster);
 hammer2_mtx_state_t hammer2_inode_lock_temp_release(hammer2_inode_t *ip);
 void hammer2_inode_lock_temp_restore(hammer2_inode_t *ip,
                        hammer2_mtx_state_t ostate);
@@ -1070,8 +1088,6 @@ void hammer2_adjreadcounter(hammer2_blockref_t *bref, size_t bytes);
  */
 struct vnode *hammer2_igetv(hammer2_inode_t *ip, hammer2_cluster_t *cparent,
                        int *errorp);
-void hammer2_inode_lock_nlinks(hammer2_inode_t *ip);
-void hammer2_inode_unlock_nlinks(hammer2_inode_t *ip);
 hammer2_inode_t *hammer2_inode_lookup(hammer2_pfs_t *pmp,
                        hammer2_tid_t inum);
 hammer2_inode_t *hammer2_inode_get(hammer2_pfs_t *pmp,
index f4eeb60..38630d9 100644 (file)
@@ -510,6 +510,15 @@ h2_bulkfree_sync(hammer2_bulkfree_info_t *cbinfo)
                        }
                        goto next;
                }
+               if (live_chain->error) {
+                       kprintf("hammer2_bulkfree: error %s looking up "
+                               "live leaf for allocated data near %016jx\n",
+                               hammer2_error_str(live_chain->error),
+                               (intmax_t)data_off);
+                       hammer2_chain_unlock(live_chain);
+                       live_chain = NULL;
+                       goto next;
+               }
 
                bmapindex = (data_off & HAMMER2_FREEMAP_LEVEL1_MASK) >>
                            HAMMER2_FREEMAP_LEVEL0_RADIX;
index c8307d0..4684f74 100644 (file)
@@ -508,9 +508,7 @@ hammer2_chain_drop_data(hammer2_chain_t *chain, int lastdrop)
  * Ref and lock a chain element, acquiring its data with I/O if necessary,
  * and specify how you would like the data to be resolved.
  *
- * Returns 0 on success or an error code if the data could not be acquired.
- * The chain element is locked on return regardless of whether an error
- * occurred or not.
+ * If an I/O or other fatal error occurs, chain->error will be set to non-zero.
  *
  * The lock is allowed to recurse, multiple locking ops will aggregate
  * the requested resolve types.  Once data is assigned it will not be
@@ -995,6 +993,7 @@ hammer2_chain_modify(hammer2_trans_t *trans, hammer2_chain_t *chain, int flags)
 
        hmp = chain->hmp;
        obref = chain->bref;
+       KKASSERT((chain->flags & HAMMER2_CHAIN_FICTITIOUS) == 0);
 
        /*
         * Data is not optional for freemap chains (we must always be sure
@@ -1487,6 +1486,12 @@ hammer2_chain_getparent(hammer2_chain_t **parentp, int how)
  * NULL is returned if no match was found, but (*parentp) will still
  * potentially be adjusted.
  *
+ * If a fatal error occurs (typically an I/O error), a dummy chain is
+ * returned with chain->error and error-identifying information set.  This
+ * chain will assert if you try to do anything fancy with it.
+ *
+ * XXX Depending on where the error occurs we should allow continued iteration.
+ *
  * On return (*key_nextp) will point to an iterative value for key_beg.
  * (If NULL is returned (*key_nextp) is set to key_end).
  *
@@ -1767,6 +1772,12 @@ done:
  * If chain is NULL we assume the parent was exhausted and continue the
  * iteration at the next parent.
  *
+ * If a fatal error occurs (typically an I/O error), a dummy chain is
+ * returned with chain->error and error-identifying information set.  This
+ * chain will assert if you try to do anything fancy with it.
+ *
+ * XXX Depending on where the error occurs we should allow continued iteration.
+ *
  * parent must be locked on entry and remains locked throughout.  chain's
  * lock status must match flags.  Chain is always at least referenced.
  *
@@ -1802,6 +1813,9 @@ hammer2_chain_next(hammer2_chain_t **parentp, hammer2_chain_t *chain,
                        hammer2_chain_unlock(chain);
 
                /*
+                * chain invalid past this point, but we can still do a
+                * pointer comparison w/parent.
+                *
                 * Any scan where the lookup returned degenerate data embedded
                 * in the inode has an invalid index and must terminate.
                 */
@@ -1898,6 +1912,7 @@ hammer2_chain_scan(hammer2_chain_t *parent, hammer2_chain_t *chain,
        }
 
 again:
+       KKASSERT(parent->error == 0);   /* XXX case not handled yet */
        if (--maxloops == 0)
                panic("hammer2_chain_scan: maxloops");
        /*
@@ -2048,6 +2063,8 @@ done:
  * (*parentp) must be exclusive locked and may be replaced on return
  * depending on how much work the function had to do.
  *
+ * (*parentp) must not be errored or this function will assert.
+ *
  * (*chainp) usually starts out NULL and returns the newly created chain,
  * but if the caller desires the caller may allocate a disconnected chain
  * and pass it in instead.
@@ -2085,6 +2102,7 @@ hammer2_chain_create(hammer2_trans_t *trans, hammer2_chain_t **parentp,
         */
        parent = *parentp;
        KKASSERT(hammer2_mtx_owned(&parent->core.lock));
+       KKASSERT(parent->error == 0);
        hmp = parent->hmp;
        chain = *chainp;
 
@@ -2356,6 +2374,8 @@ done:
  * Note that hammer2_cluster_duplicate() *ONLY* uses the key and keybits fields
  * from a passed-in bref and uses the old chain's bref for everything else.
  *
+ * Neither (parent) or (chain) can be errored.
+ *
  * If (parent) is non-NULL then the new duplicated chain is inserted under
  * the parent.
  *
@@ -2386,6 +2406,7 @@ hammer2_chain_rename(hammer2_trans_t *trans, hammer2_blockref_t *bref,
         */
        hmp = chain->hmp;
        KKASSERT(chain->parent == NULL);
+       KKASSERT(chain->error == 0);
 
        /*
         * Now create a duplicate of the chain structure, associating
@@ -2412,6 +2433,7 @@ hammer2_chain_rename(hammer2_trans_t *trans, hammer2_blockref_t *bref,
        if (parentp && (parent = *parentp) != NULL) {
                KKASSERT(hammer2_mtx_owned(&parent->core.lock));
                KKASSERT(parent->refs > 0);
+               KKASSERT(parent->error == 0);
 
                hammer2_chain_create(trans, parentp, &chain, chain->pmp,
                                     bref->key, bref->keybits, bref->type,
@@ -2426,6 +2448,8 @@ hammer2_chain_rename(hammer2_trans_t *trans, hammer2_blockref_t *bref,
  *
  * The chain is removed from the live view (the RBTREE) as well as the parent's
  * blockmap.  Both chain and its parent must be locked.
+ *
+ * parent may not be errored.  chain can be errored.
  */
 static void
 _hammer2_chain_delete_helper(hammer2_trans_t *trans,
@@ -2434,7 +2458,8 @@ _hammer2_chain_delete_helper(hammer2_trans_t *trans,
 {
        hammer2_dev_t *hmp;
 
-       KKASSERT((chain->flags & HAMMER2_CHAIN_DELETED) == 0);
+       KKASSERT((chain->flags & (HAMMER2_CHAIN_DELETED |
+                                 HAMMER2_CHAIN_FICTITIOUS)) == 0);
        hmp = chain->hmp;
 
        if (chain->flags & HAMMER2_CHAIN_BMAPPED) {
@@ -2447,6 +2472,7 @@ _hammer2_chain_delete_helper(hammer2_trans_t *trans,
                int count;
 
                KKASSERT(parent != NULL);
+               KKASSERT(parent->error == 0);
                KKASSERT((parent->flags & HAMMER2_CHAIN_INITIAL) == 0);
                hammer2_chain_modify(trans, parent,
                                     HAMMER2_MODIFY_OPTDATA);
index 53cdad1..3d61e47 100644 (file)
 #include "hammer2.h"
 
 /*
- * Returns TRUE if any chain in the cluster needs to be resized.
+ * Returns non-zero if any chain in the cluster needs to be resized.
+ * Errored elements are not used in the calculation.
  */
 int
 hammer2_cluster_need_resize(hammer2_cluster_t *cluster, int bytes)
@@ -124,38 +125,67 @@ hammer2_cluster_need_resize(hammer2_cluster_t *cluster, int bytes)
        hammer2_chain_t *chain;
        int i;
 
+       KKASSERT(cluster->flags & HAMMER2_CLUSTER_LOCKED);
        for (i = 0; i < cluster->nchains; ++i) {
                chain = cluster->array[i].chain;
-               if (chain && chain->bytes != bytes)
+               if (chain == NULL)
+                       continue;
+               if (chain->error)
+                       continue;
+               if (chain->bytes != bytes)
                        return 1;
        }
        return 0;
 }
 
+/*
+ * Returns the bref type of the cluster's foucs.
+ *
+ * If the cluster is errored, returns HAMMER2_BREF_TYPE_EMPTY (0).
+ * The cluster must be locked.
+ */
 uint8_t
 hammer2_cluster_type(hammer2_cluster_t *cluster)
 {
-       return(cluster->focus->bref.type);
+       KKASSERT(cluster->flags & HAMMER2_CLUSTER_LOCKED);
+       if (cluster->error == 0)
+               return(cluster->focus->bref.type);
+       return 0;
 }
 
+/*
+ * Returns non-zero if the cluster's focus is flagged as being modified.
+ *
+ * If the cluster is errored, returns 0.
+ */
 int
 hammer2_cluster_modified(hammer2_cluster_t *cluster)
 {
-       return((cluster->focus->flags & HAMMER2_CHAIN_MODIFIED) != 0);
+       KKASSERT(cluster->flags & HAMMER2_CLUSTER_LOCKED);
+       if (cluster->error == 0)
+               return((cluster->focus->flags & HAMMER2_CHAIN_MODIFIED) != 0);
+       return 0;
 }
 
 /*
- * Return a bref representative of the cluster.  Any data offset is removed
- * (since it would only be applicable to a particular chain in the cluster).
+ * Returns the bref of the cluster's focus, sans any data-offset information
+ * (since offset information is per-node and wouldn't be useful).
+ *
+ * Callers use this function to access mirror_tid, key, and keybits.
  *
- * However, the radix portion of data_off is used for many purposes and will
- * be retained.
+ * If the cluster is errored, returns an empty bref.
+ * The cluster must be locked.
  */
 void
 hammer2_cluster_bref(hammer2_cluster_t *cluster, hammer2_blockref_t *bref)
 {
-       *bref = cluster->focus->bref;
-       bref->data_off &= HAMMER2_OFF_MASK_RADIX;
+       KKASSERT(cluster->flags & HAMMER2_CLUSTER_LOCKED);
+       if (cluster->error == 0) {
+               *bref = cluster->focus->bref;
+               bref->data_off = 0;
+       } else {
+               bzero(bref, sizeof(*bref));
+       }
 }
 
 /*
@@ -163,6 +193,9 @@ hammer2_cluster_bref(hammer2_cluster_t *cluster, hammer2_blockref_t *bref)
  * as having been unlinked.  Allows the vnode reclaim to avoid loading
  * the inode data from disk e.g. when unmount or recycling old, clean
  * vnodes.
+ *
+ * The cluster does not need to be locked.
+ * The focus cannot be used since the cluster might not be locked.
  */
 int
 hammer2_cluster_isunlinked(hammer2_cluster_t *cluster)
@@ -180,6 +213,10 @@ hammer2_cluster_isunlinked(hammer2_cluster_t *cluster)
        return (flags & HAMMER2_CHAIN_UNLINKED);
 }
 
+/*
+ * Set a bitmask of flags in all chains related to a cluster.
+ * The cluster should probably be locked.
+ */
 void
 hammer2_cluster_set_chainflags(hammer2_cluster_t *cluster, uint32_t flags)
 {
@@ -193,6 +230,10 @@ hammer2_cluster_set_chainflags(hammer2_cluster_t *cluster, uint32_t flags)
        }
 }
 
+/*
+ * Set a bitmask of flags in all chains related to a cluster.
+ * The cluster should probably be locked.
+ */
 void
 hammer2_cluster_clr_chainflags(hammer2_cluster_t *cluster, uint32_t flags)
 {
@@ -206,6 +247,15 @@ hammer2_cluster_clr_chainflags(hammer2_cluster_t *cluster, uint32_t flags)
        }
 }
 
+/*
+ * Flag the cluster for flushing recursively up to the root.  Despite the
+ * work it does, this is relatively benign.  It just makes sure that the
+ * flusher has top-down visibility to this cluster.
+ *
+ * Errored chains are not flagged for flushing.
+ *
+ * The cluster should probably be locked.
+ */
 void
 hammer2_cluster_setflush(hammer2_trans_t *trans, hammer2_cluster_t *cluster)
 {
@@ -214,11 +264,20 @@ hammer2_cluster_setflush(hammer2_trans_t *trans, hammer2_cluster_t *cluster)
 
        for (i = 0; i < cluster->nchains; ++i) {
                chain = cluster->array[i].chain;
-               if (chain)
-                       hammer2_chain_setflush(trans, chain);
+               if (chain == NULL)
+                       continue;
+               if (chain->error)
+                       continue;
+               hammer2_chain_setflush(trans, chain);
        }
 }
 
+/*
+ * Set the check mode for the cluster.
+ * Errored elements of the cluster are ignored.
+ *
+ * The cluster must be locked.
+ */
 void
 hammer2_cluster_setmethod_check(hammer2_trans_t *trans,
                                hammer2_cluster_t *cluster,
@@ -227,24 +286,26 @@ hammer2_cluster_setmethod_check(hammer2_trans_t *trans,
        hammer2_chain_t *chain;
        int i;
 
+       KKASSERT(cluster->flags & HAMMER2_CLUSTER_LOCKED);
        for (i = 0; i < cluster->nchains; ++i) {
                chain = cluster->array[i].chain;
-               if (chain) {
-                       KKASSERT(chain->flags & HAMMER2_CHAIN_MODIFIED);
-                       chain->bref.methods &= ~HAMMER2_ENC_CHECK(-1);
-                       chain->bref.methods |= HAMMER2_ENC_CHECK(check_algo);
-               }
+               if (chain == NULL)
+                       continue;
+               if (chain->error)
+                       continue;
+               KKASSERT(chain->flags & HAMMER2_CHAIN_MODIFIED);
+               chain->bref.methods &= ~HAMMER2_ENC_CHECK(-1);
+               chain->bref.methods |= HAMMER2_ENC_CHECK(check_algo);
        }
 }
 
 /*
- * Create a cluster with one ref from the specified chain.  The chain
- * is not further referenced.  The caller typically supplies a locked
- * chain and transfers ownership to the cluster.
+ * Create a degenerate cluster with one ref from a single locked chain.
+ * The returned cluster will be focused on the chain and inherit its
+ * error state.
  *
- * The returned cluster will be focused on the chain (strictly speaking,
- * the focus should be NULL if the chain is not locked but we do not check
- * for this condition).
+ * The chain's lock and reference are transfered to the new cluster, so
+ * the caller should not try to unlock the chain separately.
  *
  * We fake the flags.
  */
@@ -259,6 +320,7 @@ hammer2_cluster_from_chain(hammer2_chain_t *chain)
        cluster->focus = chain;
        cluster->pmp = chain->pmp;
        cluster->refs = 1;
+       cluster->error = chain->error;
        cluster->flags = HAMMER2_CLUSTER_LOCKED |
                         HAMMER2_CLUSTER_WRHARD |
                         HAMMER2_CLUSTER_RDHARD |
@@ -269,7 +331,7 @@ hammer2_cluster_from_chain(hammer2_chain_t *chain)
 }
 
 /*
- * Add a reference to a cluster.
+ * Add a reference to a cluster and its underlying chains.
  *
  * We must also ref the underlying chains in order to allow ref/unlock
  * sequences to later re-lock.
@@ -415,6 +477,26 @@ hammer2_cluster_resolve(hammer2_cluster_t *cluster)
                chain = cluster->array[i].chain;
                if (chain == NULL)
                        continue;
+               if (chain->error) {
+                       if (cluster->focus == NULL || cluster->focus == chain) {
+                               /* error will be overridden by valid focus */
+                               cluster->error = chain->error;
+                       }
+
+                       /*
+                        * Must count total masters and slaves whether the
+                        * chain is errored or not.
+                        */
+                       switch (cluster->pmp->pfs_types[i]) {
+                       case HAMMER2_PFSTYPE_MASTER:
+                               ++ttlmasters;
+                               break;
+                       case HAMMER2_PFSTYPE_SLAVE:
+                               ++ttlslaves;
+                               break;
+                       }
+                       continue;
+               }
 
                switch (cluster->pmp->pfs_types[i]) {
                case HAMMER2_PFSTYPE_MASTER:
@@ -459,6 +541,13 @@ hammer2_cluster_resolve(hammer2_cluster_t *cluster)
                chain = cluster->array[i].chain;
                if (chain == NULL)
                        continue;
+               if (chain->error) {
+                       if (cluster->focus == NULL || cluster->focus == chain) {
+                               /* error will be overridden by valid focus */
+                               cluster->error = chain->error;
+                       }
+                       continue;
+               }
 
                switch (cluster->pmp->pfs_types[i]) {
                case HAMMER2_PFSTYPE_MASTER:
@@ -480,6 +569,8 @@ hammer2_cluster_resolve(hammer2_cluster_t *cluster)
                                        cluster->focus = chain;
                                        cluster->error = chain->error;
                                }
+                       } else if (chain->error == 0) {
+                               nflags |= HAMMER2_CLUSTER_UNHARD;
                        }
                        break;
                case HAMMER2_PFSTYPE_SLAVE:
@@ -500,6 +591,8 @@ hammer2_cluster_resolve(hammer2_cluster_t *cluster)
                                        cluster->focus = chain;
                                        cluster->error = chain->error;
                                }
+                       } else if (chain->error == 0) {
+                               nflags |= HAMMER2_CLUSTER_UNSOFT;
                        }
                        break;
                case HAMMER2_PFSTYPE_SOFT_MASTER:
@@ -529,6 +622,11 @@ hammer2_cluster_resolve(hammer2_cluster_t *cluster)
                }
        }
 
+       if (ttlslaves == 0)
+               nflags |= HAMMER2_CLUSTER_NOHARD;
+       if (ttlmasters == 0)
+               nflags |= HAMMER2_CLUSTER_NOSOFT;
+
        /*
         * Set SSYNCED or MSYNCED for slaves and masters respectively if
         * all available nodes (even if 0 are available) are fully
@@ -683,13 +781,14 @@ hammer2_cluster_modify(hammer2_trans_t *trans, hammer2_cluster_t *cluster,
        resolve_again = 0;
        for (i = 0; i < cluster->nchains; ++i) {
                chain = cluster->array[i].chain;
-               if (chain) {
-                       hammer2_chain_modify(trans, chain, flags);
-                       if (cluster->focus == chain &&
-                           chain->error) {
-                               cluster->error = chain->error;
-                               resolve_again = 1;
-                       }
+               if (chain == NULL)
+                       continue;
+               if (chain->error)
+                       continue;
+               hammer2_chain_modify(trans, chain, flags);
+               if (cluster->focus == chain && chain->error) {
+                       cluster->error = chain->error;
+                       resolve_again = 1;
                }
        }
        if (resolve_again)
@@ -723,6 +822,8 @@ hammer2_cluster_modsync(hammer2_cluster_t *cluster)
                scan = cluster->array[i].chain;
                if (scan == NULL || scan == focus)
                        continue;
+               if (scan->error)
+                       continue;
                KKASSERT(scan->flags & HAMMER2_CHAIN_MODIFIED);
                KKASSERT(focus->bytes == scan->bytes &&
                         focus->bref.type == scan->bref.type);
@@ -838,6 +939,12 @@ hammer2_cluster_lookup(hammer2_cluster_t *cparent, hammer2_key_t *key_nextp,
                cluster->array[i].chain = chain;
                if (chain == NULL) {
                        ++null_count;
+               } else if (chain->error) {
+                       /*
+                        * Leave errored chain in cluster, but it cannot be
+                        * the cluster's focus.
+                        */
+                       ;
                } else {
                        int ddflag = (chain->bref.type ==
                                      HAMMER2_BREF_TYPE_INODE);
@@ -932,6 +1039,12 @@ hammer2_cluster_next(hammer2_cluster_t *cparent, hammer2_cluster_t *cluster,
                cluster->array[i].chain = chain;
                if (chain == NULL) {
                        ++null_count;
+               } else if (chain->error) {
+                       /*
+                        * Leave errored chain in cluster, but it cannot be
+                        * the cluster's focus.
+                        */
+                       ;
                } else {
                        int ddflag = (chain->bref.type ==
                                      HAMMER2_BREF_TYPE_INODE);
@@ -1023,10 +1136,9 @@ hammer2_cluster_create(hammer2_trans_t *trans, hammer2_cluster_t *cparent,
 /*
  * Rename a cluster to a new parent.
  *
- * WARNING! Unlike hammer2_chain_rename(), only the key and keybits fields
- *         are used from a passed-in non-NULL bref pointer.  All other fields
- *         are extracted from the original chain for each chain in the
- *         iteration.
+ * WARNING! Any passed-in bref is probaly from hammer2_cluster_bref(),
+ *         So the data_off field is not relevant.  Only the key and
+ *         keybits are used.
  */
 void
 hammer2_cluster_rename(hammer2_trans_t *trans, hammer2_blockref_t *bref,
@@ -1197,7 +1309,7 @@ hammer2_cluster_snapshot(hammer2_trans_t *trans, hammer2_cluster_t *ocluster,
                        if (nchain)
                                hammer2_flush(trans, nchain);
                }
-               hammer2_inode_unlock_ex(nip, ncluster);
+               hammer2_inode_unlock(nip, ncluster);
        }
        return (error);
 }
index ec31fe2..0edd230 100644 (file)
@@ -595,6 +595,8 @@ typedef struct hammer2_blockref hammer2_blockref_t;
 
 /*
  * On-media and off-media blockref types.
+ *
+ * types >= 128 are pseudo values that should never be present on-media.
  */
 #define HAMMER2_BREF_TYPE_EMPTY                0
 #define HAMMER2_BREF_TYPE_INODE                1
@@ -878,7 +880,8 @@ struct hammer2_inode_data {
         *
         * (not yet implemented)
         */
-       hammer2_off_t   reservedE0[4];  /* 00E0/E8/F0/F8 */
+       uint64_t        decrypt_check;  /* 00E0 decryption validator */
+       hammer2_off_t   reservedE0[3];  /* 00E8/F0/F8 */
 
        unsigned char   filename[HAMMER2_INODE_MAXNAME];
                                        /* 0100-01FF (256 char, unterminated) */
@@ -933,7 +936,7 @@ typedef struct hammer2_inode_data hammer2_inode_data_t;
  */
 #define HAMMER2_PFSTYPE_NONE           0x00
 #define HAMMER2_PFSTYPE_CACHE          0x01
-#define HAMMER2_PFSTYPE_COPY           0x02
+#define HAMMER2_PFSTYPE_UNUSED02       0x02
 #define HAMMER2_PFSTYPE_SLAVE          0x03
 #define HAMMER2_PFSTYPE_SOFT_SLAVE     0x04
 #define HAMMER2_PFSTYPE_SOFT_MASTER    0x05
@@ -945,7 +948,7 @@ typedef struct hammer2_inode_data hammer2_inode_data_t;
 
 #define HAMMER2_PFSTRAN_NONE           0x00    /* no transition in progress */
 #define HAMMER2_PFSTRAN_CACHE          0x10
-#define HAMMER2_PFSTRAN_COPY           0x20
+#define HAMMER2_PFSTRAN_UNMUSED20      0x20
 #define HAMMER2_PFSTRAN_SLAVE          0x30
 #define HAMMER2_PFSTRAN_SOFT_SLAVE     0x40
 #define HAMMER2_PFSTRAN_SOFT_MASTER    0x50
@@ -959,7 +962,8 @@ typedef struct hammer2_inode_data hammer2_inode_data_t;
 #define HAMMER2_PFS_ENC_TRANSITION(n)  (((n) & 0x0F) << 4)
 
 #define HAMMER2_PFSSUBTYPE_NONE                0
-#define HAMMER2_PFSSUBTYPE_SNAPSHOT    1
+#define HAMMER2_PFSSUBTYPE_SNAPSHOT    1       /* manual/managed snapshot */
+#define HAMMER2_PFSSUBTYPE_AUTOSNAP    2       /* automatic snapshot */
 
 /*
  * PFS mode of operation is a bitmask.  This is typically not stored
index 69f3c41..e89de3f 100644 (file)
@@ -358,6 +358,14 @@ hammer2_freemap_try_alloc(hammer2_trans_t *trans, hammer2_chain_t **parentp,
 
                        hammer2_freemap_init(trans, hmp, key, chain);
                }
+       } else if (chain->error) {
+               /*
+                * Error during lookup.
+                */
+               kprintf("hammer2_freemap_try_alloc: %016jx: error %s\n",
+                       (intmax_t)bref->data_off,
+                       hammer2_error_str(chain->error));
+               error = EIO;
        } else if ((chain->bref.check.freemap.bigmask & (1 << radix)) == 0) {
                /*
                 * Already flagged as not having enough space
@@ -859,6 +867,14 @@ hammer2_freemap_adjust(hammer2_trans_t *trans, hammer2_dev_t *hmp,
                        (intmax_t)bref->data_off);
                goto done;
        }
+       if (chain->error) {
+               kprintf("hammer2_freemap_adjust: %016jx: error %s\n",
+                       (intmax_t)bref->data_off,
+                       hammer2_error_str(chain->error));
+               hammer2_chain_unlock(chain);
+               chain = NULL;
+               goto done;
+       }
 
        /*
         * Create any missing leaf(s) if we are doing a recovery (marking
index df577ac..434e6e3 100644 (file)
@@ -64,16 +64,24 @@ hammer2_inode_cmp(hammer2_inode_t *ip1, hammer2_inode_t *ip2)
 /*
  * HAMMER2 inode locks
  *
- * HAMMER2 offers shared locks and exclusive locks on inodes.
+ * HAMMER2 offers shared and exclusive locks on inodes.  Pass a mask of
+ * flags for options:
  *
- * The standard exclusive inode lock always resolves the inode meta-data,
- * but there is a bypass version used by the vnode reclamation code that
- * avoids the I/O.
+ *     - pass HAMMER2_RESOLVE_SHARED if a shared lock is desired.  The
+ *       inode locking function will automatically set the RDONLY flag.
+ *
+ *     - pass HAMMER2_RESOLVE_ALWAYS if you need the inode's meta-data.
+ *       Most front-end inode locks do.
+ *
+ *     - pass HAMMER2_RESOLVE_NEVER if you do not want to require that
+ *       the inode data be resolved.  This is used by the syncthr because
+ *       it can run on an unresolved/out-of-sync cluster, and also by the
+ *       vnode reclamation code to avoid unnecessary I/O (particularly when
+ *       disposing of hundreds of thousands of cached vnodes).
  *
  * The inode locking function locks the inode itself, resolves any stale
  * chains in the inode's cluster, and allocates a fresh copy of the
- * cluster with 1 ref and all the underlying chains locked.  Duplication
- * races are handled by this function.
+ * cluster with 1 ref and all the underlying chains locked.
  *
  * ip->cluster will be stable while the inode is locked.
  *
@@ -90,23 +98,27 @@ hammer2_inode_cmp(hammer2_inode_t *ip1, hammer2_inode_t *ip2)
  *      will feel free to reduce the chain set in the cluster as an
  *      optimization.  It will still be validated against the quorum if
  *      appropriate, but the optimization might be able to reduce data
- *      accesses to one node.
+ *      accesses to one node.  This flag is automatically set if the inode
+ *      is locked with HAMMER2_RESOLVE_SHARED.
  */
 hammer2_cluster_t *
-hammer2_inode_lock_ex(hammer2_inode_t *ip)
-{
-       return hammer2_inode_lock_nex(ip, HAMMER2_RESOLVE_ALWAYS);
-}
-
-hammer2_cluster_t *
-hammer2_inode_lock_nex(hammer2_inode_t *ip, int how)
+hammer2_inode_lock(hammer2_inode_t *ip, int how)
 {
        hammer2_cluster_t *cluster;
 
        KKASSERT((how & HAMMER2_RESOLVE_NOREF) == 0);
 
        hammer2_inode_ref(ip);
-       hammer2_mtx_ex(&ip->lock);
+
+       /* 
+        * Inode structure mutex
+        */
+       if (how & HAMMER2_RESOLVE_SHARED) {
+               how |= HAMMER2_RESOLVE_RDONLY;
+               hammer2_mtx_sh(&ip->lock);
+       } else {
+               hammer2_mtx_ex(&ip->lock);
+       }
 
        /*
         * Create a copy of ip->cluster and lock it.  Note that the copy
@@ -121,96 +133,30 @@ hammer2_inode_lock_nex(hammer2_inode_t *ip, int how)
         */
        cluster = hammer2_cluster_copy(&ip->cluster);
        hammer2_cluster_lock(cluster, how | HAMMER2_RESOLVE_NOREF);
-       ip->cluster.focus = cluster->focus;
 
        /*
-        * Returned cluster must resolve hardlink pointers
+        * cluster->focus will be set if resolving RESOLVE_ALWAYS, but
+        * only update the cached focus in the inode structure when taking
+        * out an exclusive lock.
         */
-       if ((how & HAMMER2_RESOLVE_MASK) == HAMMER2_RESOLVE_ALWAYS) {
-               const hammer2_inode_data_t *ripdata;
-               ripdata = &hammer2_cluster_rdata(cluster)->ipdata;
-               KKASSERT(ripdata->type != HAMMER2_OBJTYPE_HARDLINK);
-#if 0
-               if (ripdata->type == HAMMER2_OBJTYPE_HARDLINK &&
-                   (cluster->focus->flags & HAMMER2_CHAIN_DELETED) == 0) {
-                       error = hammer2_hardlink_find(ip->pip, NULL, &cluster);
-                       KKASSERT(error == 0);
-               }
-#endif
-       }
-       return (cluster);
-}
-
-void
-hammer2_inode_unlock_ex(hammer2_inode_t *ip, hammer2_cluster_t *cluster)
-{
-       if (cluster)
-               hammer2_cluster_unlock(cluster);
-       hammer2_mtx_unlock(&ip->lock);
-       hammer2_inode_drop(ip);
-}
-
-/*
- * Standard shared inode lock always resolves the inode meta-data.
- *
- * This type of inode lock may be used only when the overall operation is
- * non-modifying.  It will also optimize cluster accesses for non-modifying
- * operations.
- *
- * NOTE: We don't combine the inode/chain lock because putting away an
- *       inode would otherwise confuse multiple lock holders of the inode.
- *
- *      Shared locks are especially sensitive to having too many shared
- *      lock counts (from the same thread) on certain paths which might
- *      need to upgrade them.  Only one count of a shared lock can be
- *      upgraded.
- */
-hammer2_cluster_t *
-hammer2_inode_lock_sh(hammer2_inode_t *ip)
-{
-       const hammer2_inode_data_t *ripdata;
-       hammer2_cluster_t *cluster;
-
-       hammer2_inode_ref(ip);
-       hammer2_mtx_sh(&ip->lock);
+       if ((how & HAMMER2_RESOLVE_SHARED) == 0)
+               ip->cluster.focus = cluster->focus;
 
        /*
-        * Create a copy of ip->cluster and lock it.  Note that the copy
-        * will have a ref on the cluster AND its chains and we don't want
-        * a second ref to either when we lock it.
-        *
-        * The copy will not have a focus until it is locked.
-        *
-        * Chains available in the cluster may be reduced once a quorum is
-        * acquired, and can be reduced further as an optimization due to
-        * RDONLY being set.
+        * Returned cluster must resolve hardlink pointers.
         */
-       cluster = hammer2_cluster_copy(&ip->cluster);
-       hammer2_cluster_lock(cluster, HAMMER2_RESOLVE_ALWAYS |
-                                     HAMMER2_RESOLVE_SHARED |
-                                     HAMMER2_RESOLVE_NOREF |
-                                     HAMMER2_RESOLVE_RDONLY);
-       /* do not update ip->cluster.focus on a shared inode lock! */
-       /*ip->cluster.focus = cluster->focus;*/
+       if ((how & HAMMER2_RESOLVE_MASK) == HAMMER2_RESOLVE_ALWAYS &&
+           cluster->error == 0) {
+               const hammer2_inode_data_t *ripdata;
 
-       /*
-        * Returned cluster must resolve hardlink pointers
-        */
-       ripdata = &hammer2_cluster_rdata(cluster)->ipdata;
-       KKASSERT(ripdata->type != HAMMER2_OBJTYPE_HARDLINK);
-#if 0
-       if (ripdata->type == HAMMER2_OBJTYPE_HARDLINK &&
-           (cluster->focus->flags & HAMMER2_CHAIN_DELETED) == 0) {
-               error = hammer2_hardlink_find(ip->pip, NULL, &cluster);
-               KKASSERT(error == 0);
+               ripdata = &hammer2_cluster_rdata(cluster)->ipdata;
+               KKASSERT(ripdata->type != HAMMER2_OBJTYPE_HARDLINK);
        }
-#endif
-
        return (cluster);
 }
 
 void
-hammer2_inode_unlock_sh(hammer2_inode_t *ip, hammer2_cluster_t *cluster)
+hammer2_inode_unlock(hammer2_inode_t *ip, hammer2_cluster_t *cluster)
 {
        if (cluster)
                hammer2_cluster_unlock(cluster);
@@ -613,7 +559,7 @@ again:
 
        /*
         * ref and lock on nip gives it state compatible to after a
-        * hammer2_inode_lock_ex() call.
+        * hammer2_inode_lock() call.
         */
        nip->refs = 1;
        hammer2_mtx_init(&nip->lock, "h2inode");
@@ -688,7 +634,7 @@ hammer2_inode_create(hammer2_trans_t *trans, hammer2_inode_t *dip,
         * NOTE: hidden inodes do not have iterators.
         */
 retry:
-       cparent = hammer2_inode_lock_ex(dip);
+       cparent = hammer2_inode_lock(dip, HAMMER2_RESOLVE_ALWAYS);
        dipdata = &hammer2_cluster_rdata(cparent)->ipdata;
        dip_uid = dipdata->uid;
        dip_gid = dipdata->gid;
@@ -729,12 +675,12 @@ retry:
         */
        if (error == EAGAIN) {
                hammer2_cluster_ref(cparent);
-               hammer2_inode_unlock_ex(dip, cparent);
+               hammer2_inode_unlock(dip, cparent);
                hammer2_cluster_wait(cparent);
                hammer2_cluster_drop(cparent);
                goto retry;
        }
-       hammer2_inode_unlock_ex(dip, cparent);
+       hammer2_inode_unlock(dip, cparent);
        cparent = NULL;
 
        if (error) {
@@ -1257,7 +1203,7 @@ again:
        /*
         * Search for the filename in the directory
         */
-       cparent = hammer2_inode_lock_ex(dip);
+       cparent = hammer2_inode_lock(dip, HAMMER2_RESOLVE_ALWAYS);
        cluster = hammer2_cluster_lookup(cparent, &key_next,
                                     lhc, lhc + HAMMER2_DIRHASH_LOMASK, 0);
        while (cluster) {
@@ -1273,7 +1219,7 @@ again:
                                               lhc + HAMMER2_DIRHASH_LOMASK,
                                               0);
        }
-       hammer2_inode_unlock_ex(dip, NULL);     /* retain cparent */
+       hammer2_inode_unlock(dip, NULL);        /* retain cparent */
 
        /*
         * Not found or wrong type (isdir < 0 disables the type check).
@@ -1503,7 +1449,7 @@ hammer2_inode_install_hidden(hammer2_pfs_t *pmp)
         * and data chains inherit from their respective file inode *_algo
         * fields.
         */
-       cparent = hammer2_inode_lock_ex(pmp->iroot);
+       cparent = hammer2_inode_lock(pmp->iroot, HAMMER2_RESOLVE_ALWAYS);
        ripdata = &hammer2_cluster_rdata(cparent)->ipdata;
        dip_check_algo = ripdata->check_algo;
        dip_comp_algo = ripdata->comp_algo;
@@ -1538,8 +1484,8 @@ hammer2_inode_install_hidden(hammer2_pfs_t *pmp)
                                                    0, HAMMER2_TID_MAX, 0);
                }
 
-               hammer2_inode_unlock_ex(pmp->ihidden, cluster);
-               hammer2_inode_unlock_ex(pmp->iroot, cparent);
+               hammer2_inode_unlock(pmp->ihidden, cluster);
+               hammer2_inode_unlock(pmp->iroot, cparent);
                hammer2_trans_done(&trans);
                kprintf("hammer2: PFS loaded hidden dir, "
                        "removed %d dead entries\n", count);
@@ -1554,7 +1500,7 @@ hammer2_inode_install_hidden(hammer2_pfs_t *pmp)
                                       HAMMER2_BREF_TYPE_INODE,
                                       HAMMER2_INODE_BYTES,
                                       0);
-       hammer2_inode_unlock_ex(pmp->iroot, cparent);
+       hammer2_inode_unlock(pmp->iroot, cparent);
 
        hammer2_cluster_modify(&trans, cluster, 0);
        wipdata = &hammer2_cluster_wdata(cluster)->ipdata;
@@ -1568,7 +1514,7 @@ hammer2_inode_install_hidden(hammer2_pfs_t *pmp)
 
        pmp->ihidden = hammer2_inode_get(pmp, pmp->iroot, cluster);
        hammer2_inode_ref(pmp->ihidden);
-       hammer2_inode_unlock_ex(pmp->ihidden, cluster);
+       hammer2_inode_unlock(pmp->ihidden, cluster);
        hammer2_trans_done(&trans);
 }
 
@@ -1596,11 +1542,11 @@ hammer2_inode_move_to_hidden(hammer2_trans_t *trans,
        KKASSERT(pmp->ihidden != NULL);
 
        hammer2_cluster_delete(trans, *cparentp, *clusterp, 0);
-       dcluster = hammer2_inode_lock_ex(pmp->ihidden);
+       dcluster = hammer2_inode_lock(pmp->ihidden, HAMMER2_RESOLVE_ALWAYS);
        error = hammer2_inode_connect(trans, clusterp, 0,
                                      pmp->ihidden, dcluster,
                                      NULL, 0, inum);
-       hammer2_inode_unlock_ex(pmp->ihidden, dcluster);
+       hammer2_inode_unlock(pmp->ihidden, dcluster);
        KKASSERT(error == 0);
 }
 
@@ -1838,7 +1784,7 @@ hammer2_hardlink_find(hammer2_inode_t *dip,
        cparent = NULL;
 
        while ((ip = pip) != NULL) {
-               cparent = hammer2_inode_lock_ex(ip);
+               cparent = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS);
                hammer2_inode_drop(ip);                 /* loop */
                KKASSERT(hammer2_cluster_type(cparent) ==
                         HAMMER2_BREF_TYPE_INODE);
@@ -1851,7 +1797,7 @@ hammer2_hardlink_find(hammer2_inode_t *dip,
                pip = ip->pip;          /* safe, ip held locked */
                if (pip)
                        hammer2_inode_ref(pip);         /* loop */
-               hammer2_inode_unlock_ex(ip, NULL);
+               hammer2_inode_unlock(ip, NULL);
        }
 
        /*
@@ -1864,16 +1810,16 @@ hammer2_hardlink_find(hammer2_inode_t *dip,
        if (rcluster) {
                if (cparentp) {
                        *cparentp = cparent;
-                       hammer2_inode_unlock_ex(ip, NULL);
+                       hammer2_inode_unlock(ip, NULL);
                } else {
-                       hammer2_inode_unlock_ex(ip, cparent);
+                       hammer2_inode_unlock(ip, cparent);
                }
                return (0);
        } else {
                if (cparentp)
                        *cparentp = NULL;
                if (ip)
-                       hammer2_inode_unlock_ex(ip, cparent);
+                       hammer2_inode_unlock(ip, cparent);
                return (EIO);
        }
 }
index 17dc0b0..ca4e170 100644 (file)
@@ -298,7 +298,7 @@ hammer2_update_spans(hammer2_dev_t *hmp, kdmsg_state_t *state)
         * up later on.
         */
        spmp = hmp->spmp;
-       cparent = hammer2_inode_lock_ex(spmp->iroot);
+       cparent = hammer2_inode_lock(spmp->iroot, HAMMER2_RESOLVE_ALWAYS);
        cluster = hammer2_cluster_lookup(cparent, &key_next,
                                         HAMMER2_KEY_MIN,
                                         HAMMER2_KEY_MAX,
@@ -332,7 +332,7 @@ hammer2_update_spans(hammer2_dev_t *hmp, kdmsg_state_t *state)
                                               HAMMER2_KEY_MAX,
                                               0);
        }
-       hammer2_inode_unlock_ex(spmp->iroot, cparent);
+       hammer2_inode_unlock(spmp->iroot, cparent);
 }
 
 static
index fcc4103..5478350 100644 (file)
@@ -380,8 +380,8 @@ hammer2_ioctl_pfs_get(hammer2_inode_t *ip, void *data)
        error = 0;
        hmp = ip->pmp->iroot->cluster.focus->hmp; /* XXX */
        pfs = data;
-       cparent = hammer2_inode_lock_ex(hmp->spmp->iroot);
-       rcluster = hammer2_inode_lock_ex(ip->pmp->iroot);
+       cparent = hammer2_inode_lock(hmp->spmp->iroot, HAMMER2_RESOLVE_ALWAYS);
+       rcluster = hammer2_inode_lock(ip->pmp->iroot, HAMMER2_RESOLVE_ALWAYS);
 
        /*
         * Search for the first key or specific key.  Remember that keys
@@ -403,7 +403,7 @@ hammer2_ioctl_pfs_get(hammer2_inode_t *ip, void *data)
                                                 pfs->name_key, pfs->name_key,
                                                 0);
        }
-       hammer2_inode_unlock_ex(ip->pmp->iroot, rcluster);
+       hammer2_inode_unlock(ip->pmp->iroot, rcluster);
 
        while (cluster &&
               hammer2_cluster_type(cluster) != HAMMER2_BREF_TYPE_INODE) {
@@ -448,7 +448,7 @@ hammer2_ioctl_pfs_get(hammer2_inode_t *ip, void *data)
                pfs->name_next = (hammer2_key_t)-1;
                error = ENOENT;
        }
-       hammer2_inode_unlock_ex(hmp->spmp->iroot, cparent);
+       hammer2_inode_unlock(hmp->spmp->iroot, cparent);
 
        return (error);
 }
@@ -472,7 +472,8 @@ hammer2_ioctl_pfs_lookup(hammer2_inode_t *ip, void *data)
        error = 0;
        hmp = ip->pmp->iroot->cluster.focus->hmp; /* XXX */
        pfs = data;
-       cparent = hammer2_inode_lock_sh(hmp->spmp->iroot);
+       cparent = hammer2_inode_lock(hmp->spmp->iroot, HAMMER2_RESOLVE_ALWAYS |
+                                                      HAMMER2_RESOLVE_SHARED);
 
        pfs->name[sizeof(pfs->name) - 1] = 0;
        len = strlen(pfs->name);
@@ -512,7 +513,7 @@ hammer2_ioctl_pfs_lookup(hammer2_inode_t *ip, void *data)
        } else {
                error = ENOENT;
        }
-       hammer2_inode_unlock_sh(hmp->spmp->iroot, cparent);
+       hammer2_inode_unlock(hmp->spmp->iroot, cparent);
 
        return (error);
 }
@@ -570,7 +571,7 @@ hammer2_ioctl_pfs_create(hammer2_inode_t *ip, void *data)
                hammer2_pfsalloc(ncluster, nipdata, bref.mirror_tid);
                /* XXX rescan */
 #endif
-               hammer2_inode_unlock_ex(nip, ncluster);
+               hammer2_inode_unlock(nip, ncluster);
        }
        hammer2_trans_done(&trans);
 
@@ -615,9 +616,9 @@ hammer2_ioctl_pfs_snapshot(hammer2_inode_t *ip, void *data)
 
        hammer2_trans_init(&trans, ip->pmp,
                           HAMMER2_TRANS_ISFLUSH | HAMMER2_TRANS_NEWINODE);
-       cparent = hammer2_inode_lock_ex(ip);
+       cparent = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS);
        error = hammer2_cluster_snapshot(&trans, cparent, pfs);
-       hammer2_inode_unlock_ex(ip, cparent);
+       hammer2_inode_unlock(ip, cparent);
        hammer2_trans_done(&trans);
 
        return (error);
@@ -632,16 +633,23 @@ hammer2_ioctl_inode_get(hammer2_inode_t *ip, void *data)
        const hammer2_inode_data_t *ripdata;
        hammer2_ioc_inode_t *ino;
        hammer2_cluster_t *cparent;
+       int error;
 
        ino = data;
 
-       cparent = hammer2_inode_lock_sh(ip);
-       ripdata = &hammer2_cluster_rdata(cparent)->ipdata;
-       ino->ip_data = *ripdata;
-       ino->kdata = ip;
-       hammer2_inode_unlock_sh(ip, cparent);
+       cparent = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS |
+                                        HAMMER2_RESOLVE_SHARED);
+       if (cparent->error) {
+               error = EIO;
+       } else {
+               ripdata = &hammer2_cluster_rdata(cparent)->ipdata;
+               ino->ip_data = *ripdata;
+               ino->kdata = ip;
+               error = 0;
+       }
+       hammer2_inode_unlock(ip, cparent);
 
-       return (0);
+       return error;
 }
 
 /*
@@ -660,7 +668,7 @@ hammer2_ioctl_inode_set(hammer2_inode_t *ip, void *data)
        int dosync = 0;
 
        hammer2_trans_init(&trans, ip->pmp, 0);
-       cparent = hammer2_inode_lock_ex(ip);
+       cparent = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS);
        ripdata = &hammer2_cluster_rdata(cparent)->ipdata;
 
        if (ino->ip_data.check_algo != ripdata->check_algo) {
@@ -689,7 +697,7 @@ hammer2_ioctl_inode_set(hammer2_inode_t *ip, void *data)
        if (dosync)
                hammer2_cluster_modsync(cparent);
        hammer2_trans_done(&trans);
-       hammer2_inode_unlock_ex(ip, cparent);
+       hammer2_inode_unlock(ip, cparent);
 
        return (error);
 }
index c7563be..f7a48b9 100644 (file)
@@ -435,3 +435,28 @@ hammer2_signal_check(time_t *timep)
        }
        return error;
 }
+
+const char *
+hammer2_error_str(int error)
+{
+       const char *str;
+
+       switch(error) {
+       case HAMMER2_ERROR_NONE:
+               str = "0";
+               break;
+       case HAMMER2_ERROR_IO:
+               str = "I/O";
+               break;
+       case HAMMER2_ERROR_CHECK:
+               str = "check/crc";
+               break;
+       case HAMMER2_ERROR_INCOMPLETE:
+               str = "incomplete-node";
+               break;
+       default:
+               str = "unknown";
+               break;
+       }
+       return (str);
+}
index d7032b2..e37b0a8 100644 (file)
  */
 #include "hammer2.h"
 
+static void hammer2_sync_slaves(hammer2_syncthr_t *thr, hammer2_pfs_t *pmp,
+                       hammer2_cluster_t **cparentp);
+static void hammer2_update_pfs_status(hammer2_pfs_t *pmp,
+                       hammer2_cluster_t *cparent);
+
 /*
  * Initialize the suspplied syncthr structure, starting the specified
  * thread.
@@ -123,6 +128,11 @@ void
 hammer2_syncthr_primary(void *arg)
 {
        hammer2_syncthr_t *thr = arg;
+       hammer2_cluster_t *cparent;
+       hammer2_pfs_t *pmp;
+       hammer2_trans_t trans;
+
+       pmp = thr->pmp;
 
        lockmgr(&thr->lk, LK_EXCLUSIVE);
        while ((thr->flags & HAMMER2_SYNCTHR_STOP) == 0) {
@@ -142,15 +152,86 @@ hammer2_syncthr_primary(void *arg)
                        continue;
                }
 
-               /* reset state on REMASTER request */
+               /*
+                * Reset state on REMASTER request
+                */
                if (thr->flags & HAMMER2_SYNCTHR_REMASTER) {
                        atomic_clear_int(&thr->flags, HAMMER2_SYNCTHR_REMASTER);
                        /* reset state */
                }
-               lksleep(&thr->flags, &thr->lk, 0, "h2idle", 0);
+
+               /*
+                * Synchronization scan.
+                */
+               hammer2_trans_init(&trans, pmp, 0);
+               cparent = hammer2_inode_lock(pmp->iroot, HAMMER2_RESOLVE_NEVER);
+               hammer2_update_pfs_status(pmp, cparent);
+               hammer2_sync_slaves(thr, pmp, &cparent);
+               hammer2_inode_unlock(pmp->iroot, cparent);
+               hammer2_trans_done(&trans);
+
+               /*
+                * Wait for event, or 5-second poll.
+                */
+               lksleep(&thr->flags, &thr->lk, 0, "h2idle", hz * 5);
        }
        thr->td = NULL;
        wakeup(thr);
        lockmgr(&thr->lk, LK_RELEASE);
        /* thr structure can go invalid after this point */
 }
+
+/*
+ * Given a locked cluster created from pmp->iroot, update the PFS's
+ * reporting status.
+ */
+static
+void
+hammer2_update_pfs_status(hammer2_pfs_t *pmp, hammer2_cluster_t *cparent)
+{
+       uint32_t flags;
+
+       flags = cparent->flags & HAMMER2_CLUSTER_ZFLAGS;
+       if (pmp->status_flags == flags)
+               return;
+       pmp->status_flags = flags;
+
+       kprintf("pfs %p", pmp);
+       if (flags & HAMMER2_CLUSTER_MSYNCED)
+               kprintf(" masters-all-good");
+       if (flags & HAMMER2_CLUSTER_SSYNCED)
+               kprintf(" slaves-all-good");
+
+       if (flags & HAMMER2_CLUSTER_WRHARD)
+               kprintf(" quorum/rw");
+       else if (flags & HAMMER2_CLUSTER_RDHARD)
+               kprintf(" quorum/ro");
+
+       if (flags & HAMMER2_CLUSTER_UNHARD)
+               kprintf(" out-of-sync-masters");
+       else if (flags & HAMMER2_CLUSTER_NOHARD)
+               kprintf(" no-masters-visible");
+
+       if (flags & HAMMER2_CLUSTER_WRSOFT)
+               kprintf(" soft/rw");
+       else if (flags & HAMMER2_CLUSTER_RDSOFT)
+               kprintf(" soft/ro");
+
+       if (flags & HAMMER2_CLUSTER_UNSOFT)
+               kprintf(" out-of-sync-slaves");
+       else if (flags & HAMMER2_CLUSTER_NOSOFT)
+               kprintf(" no-slaves-visible");
+       kprintf("\n");
+}
+
+/*
+ *
+ */
+static
+void
+hammer2_sync_slaves(hammer2_syncthr_t *thr, hammer2_pfs_t *pmp,
+                   hammer2_cluster_t **cparentp)
+{
+
+
+}
index 52c2b26..4e45220 100644 (file)
@@ -375,7 +375,7 @@ hammer2_pfsalloc(hammer2_cluster_t *cluster,
        if (pmp->iroot == NULL) {
                pmp->iroot = hammer2_inode_get(pmp, NULL, NULL);
                hammer2_inode_ref(pmp->iroot);
-               hammer2_inode_unlock_ex(pmp->iroot, NULL);
+               hammer2_inode_unlock(pmp->iroot, NULL);
        }
 
        /*
@@ -870,6 +870,16 @@ hammer2_vfs_mount(struct mount *mp, char *path, caddr_t data,
                        hammer2_vfs_unmount(mp, MNT_FORCE);
                        return EINVAL;
                }
+               if (schain->error) {
+                       kprintf("hammer2_mount: error %s reading super-root\n",
+                               hammer2_error_str(schain->error));
+                       hammer2_chain_unlock(schain);
+                       schain = NULL;
+                       hammer2_unmount_helper(mp, NULL, hmp);
+                       lockmgr(&hammer2_mntlk, LK_RELEASE);
+                       hammer2_vfs_unmount(mp, MNT_FORCE);
+                       return EINVAL;
+               }
 
                /*
                 * Sanity-check schain's pmp and finish initialization.
@@ -894,7 +904,7 @@ hammer2_vfs_mount(struct mount *mp, char *path, caddr_t data,
                spmp->spmp_hmp = hmp;
                spmp->pfs_types[0] = ripdata->pfs_type;
                hammer2_inode_ref(spmp->iroot);
-               hammer2_inode_unlock_ex(spmp->iroot, cluster);
+               hammer2_inode_unlock(spmp->iroot, cluster);
                schain = NULL;
                /* leave spmp->iroot with one ref */
 
@@ -927,7 +937,7 @@ hammer2_vfs_mount(struct mount *mp, char *path, caddr_t data,
         * cluster->pmp will incorrectly point to spmp and must be fixed
         * up later on.
         */
-       cparent = hammer2_inode_lock_ex(spmp->iroot);
+       cparent = hammer2_inode_lock(spmp->iroot, HAMMER2_RESOLVE_ALWAYS);
        lhc = hammer2_dirhash(label, strlen(label));
        cluster = hammer2_cluster_lookup(cparent, &key_next,
                                      lhc, lhc + HAMMER2_DIRHASH_LOMASK,
@@ -942,7 +952,7 @@ hammer2_vfs_mount(struct mount *mp, char *path, caddr_t data,
                                            key_next,
                                            lhc + HAMMER2_DIRHASH_LOMASK, 0);
        }
-       hammer2_inode_unlock_ex(spmp->iroot, cparent);
+       hammer2_inode_unlock(spmp->iroot, cparent);
 
        /*
         * PFS could not be found?
@@ -1071,7 +1081,7 @@ hammer2_update_pmps(hammer2_dev_t *hmp)
         * up later on.
         */
        spmp = hmp->spmp;
-       cparent = hammer2_inode_lock_ex(spmp->iroot);
+       cparent = hammer2_inode_lock(spmp->iroot, HAMMER2_RESOLVE_ALWAYS);
        cluster = hammer2_cluster_lookup(cparent, &key_next,
                                         HAMMER2_KEY_MIN,
                                         HAMMER2_KEY_MAX,
@@ -1090,7 +1100,7 @@ hammer2_update_pmps(hammer2_dev_t *hmp)
                                               HAMMER2_KEY_MAX,
                                               0);
        }
-       hammer2_inode_unlock_ex(spmp->iroot, cparent);
+       hammer2_inode_unlock(spmp->iroot, cparent);
 }
 
 /*
@@ -1163,7 +1173,8 @@ hammer2_write_thread(void *arg)
                         * NOTE: hammer2_write_file_core() may indirectly
                         *       modify and modsync the inode.
                         */
-                       cparent = hammer2_inode_lock_ex(ip);
+                       cparent = hammer2_inode_lock(ip,
+                                                    HAMMER2_RESOLVE_ALWAYS);
                        if (ip->flags & (HAMMER2_INODE_RESIZED |
                                         HAMMER2_INODE_MTIME)) {
                                hammer2_inode_fsync(&trans, ip, cparent);
@@ -1177,7 +1188,7 @@ hammer2_write_thread(void *arg)
                                                lbase, IO_ASYNC,
                                                pblksize, &error);
                        /* ripdata can be invalid after call */
-                       hammer2_inode_unlock_ex(ip, cparent);
+                       hammer2_inode_unlock(ip, cparent);
                        if (error) {
                                kprintf("hammer2: error in buffer write\n");
                                bp->b_flags |= B_ERROR;
@@ -2077,9 +2088,11 @@ hammer2_vfs_root(struct mount *mp, struct vnode **vpp)
                *vpp = NULL;
                error = EINVAL;
        } else {
-               cparent = hammer2_inode_lock_sh(pmp->iroot);
+               cparent = hammer2_inode_lock(pmp->iroot,
+                                               HAMMER2_RESOLVE_ALWAYS |
+                                               HAMMER2_RESOLVE_SHARED);
                vp = hammer2_igetv(pmp->iroot, cparent, &error);
-               hammer2_inode_unlock_sh(pmp->iroot, cparent);
+               hammer2_inode_unlock(pmp->iroot, cparent);
                *vpp = vp;
                if (vp == NULL)
                        kprintf("vnodefail\n");
index 991c7fc..33c0e66 100644 (file)
@@ -206,8 +206,8 @@ hammer2_vop_inactive(struct vop_inactive_args *ap)
         * the strategy code.  Simply mark the inode modified so it gets
         * picked up by our normal flush.
         */
-       cluster = hammer2_inode_lock_nex(ip, HAMMER2_RESOLVE_NEVER |
-                                            HAMMER2_RESOLVE_RDONLY);
+       cluster = hammer2_inode_lock(ip, HAMMER2_RESOLVE_NEVER |
+                                        HAMMER2_RESOLVE_RDONLY);
        KKASSERT(cluster);
 
        /*
@@ -221,11 +221,11 @@ hammer2_vop_inactive(struct vop_inactive_args *ap)
                int nblksize;
 
                nblksize = hammer2_calc_logical(ip, 0, &lbase, NULL);
-               hammer2_inode_unlock_ex(ip, cluster);
+               hammer2_inode_unlock(ip, cluster);
                nvtruncbuf(vp, 0, nblksize, 0, 0);
                vrecycle(vp);
        } else {
-               hammer2_inode_unlock_ex(ip, cluster);
+               hammer2_inode_unlock(ip, cluster);
        }
        LOCKSTOP;
        return (0);
@@ -256,8 +256,8 @@ hammer2_vop_reclaim(struct vop_reclaim_args *ap)
         * Inode must be locked for reclaim.
         */
        pmp = ip->pmp;
-       cluster = hammer2_inode_lock_nex(ip, HAMMER2_RESOLVE_NEVER |
-                                            HAMMER2_RESOLVE_RDONLY);
+       cluster = hammer2_inode_lock(ip, HAMMER2_RESOLVE_NEVER |
+                                        HAMMER2_RESOLVE_RDONLY);
 
        /*
         * The final close of a deleted file or directory marks it for
@@ -292,10 +292,10 @@ hammer2_vop_reclaim(struct vop_reclaim_args *ap)
                hammer2_spin_ex(&pmp->list_spin);
                TAILQ_INSERT_TAIL(&pmp->unlinkq, ipul, entry);
                hammer2_spin_unex(&pmp->list_spin);
-               hammer2_inode_unlock_ex(ip, cluster);   /* unlock */
+               hammer2_inode_unlock(ip, cluster);      /* unlock */
                /* retain ref from vp for ipul */
        } else {
-               hammer2_inode_unlock_ex(ip, cluster);   /* unlock */
+               hammer2_inode_unlock(ip, cluster);      /* unlock */
                hammer2_inode_drop(ip);                 /* vp ref */
        }
        /* cluster no longer referenced */
@@ -340,7 +340,7 @@ hammer2_vop_fsync(struct vop_fsync_args *ap)
         * which call this function will eventually call chain_flush
         * on the volume root as a catch-all, which is far more optimal.
         */
-       cluster = hammer2_inode_lock_ex(ip);
+       cluster = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS);
        atomic_clear_int(&ip->flags, HAMMER2_INODE_MODIFIED);
        vclrisdirty(vp);
        if (ip->flags & (HAMMER2_INODE_RESIZED|HAMMER2_INODE_MTIME))
@@ -354,7 +354,7 @@ hammer2_vop_fsync(struct vop_fsync_args *ap)
                hammer2_flush(&trans, cluster);
        }
 #endif
-       hammer2_inode_unlock_ex(ip, cluster);
+       hammer2_inode_unlock(ip, cluster);
        hammer2_trans_done(&trans);
 
        LOCKSTOP;
@@ -373,12 +373,13 @@ hammer2_vop_access(struct vop_access_args *ap)
        int error;
 
        LOCKSTART;
-       cluster = hammer2_inode_lock_sh(ip);
+       cluster = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS |
+                                        HAMMER2_RESOLVE_SHARED);
        ripdata = &hammer2_cluster_rdata(cluster)->ipdata;
        uid = hammer2_to_unix_xid(&ripdata->uid);
        gid = hammer2_to_unix_xid(&ripdata->gid);
        error = vop_helper_access(ap, uid, gid, ripdata->mode, ripdata->uflags);
-       hammer2_inode_unlock_sh(ip, cluster);
+       hammer2_inode_unlock(ip, cluster);
 
        LOCKSTOP;
        return (error);
@@ -402,7 +403,8 @@ hammer2_vop_getattr(struct vop_getattr_args *ap)
        ip = VTOI(vp);
        pmp = ip->pmp;
 
-       cluster = hammer2_inode_lock_sh(ip);
+       cluster = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS |
+                                        HAMMER2_RESOLVE_SHARED);
        ripdata = &hammer2_cluster_rdata(cluster)->ipdata;
        KKASSERT(hammer2_cluster_type(cluster) == HAMMER2_BREF_TYPE_INODE);
 
@@ -429,7 +431,7 @@ hammer2_vop_getattr(struct vop_getattr_args *ap)
        vap->va_vaflags = VA_UID_UUID_VALID | VA_GID_UUID_VALID |
                          VA_FSID_UUID_VALID;
 
-       hammer2_inode_unlock_sh(ip, cluster);
+       hammer2_inode_unlock(ip, cluster);
 
        LOCKSTOP;
        return (0);
@@ -466,7 +468,7 @@ hammer2_vop_setattr(struct vop_setattr_args *ap)
 
        hammer2_pfs_memory_wait(ip->pmp);
        hammer2_trans_init(&trans, ip->pmp, 0);
-       cluster = hammer2_inode_lock_ex(ip);
+       cluster = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS);
        ripdata = &hammer2_cluster_rdata(cluster)->ipdata;
        error = 0;
 
@@ -536,13 +538,14 @@ hammer2_vop_setattr(struct vop_setattr_args *ap)
                case VREG:
                        if (vap->va_size == ip->size)
                                break;
-                       hammer2_inode_unlock_ex(ip, cluster);
+                       hammer2_inode_unlock(ip, cluster);
                        if (vap->va_size < ip->size) {
                                hammer2_truncate_file(ip, vap->va_size);
                        } else {
                                hammer2_extend_file(ip, vap->va_size);
                        }
-                       cluster = hammer2_inode_lock_ex(ip);
+                       cluster = hammer2_inode_lock(ip,
+                                                    HAMMER2_RESOLVE_ALWAYS);
                        /* RELOAD */
                        ripdata = &hammer2_cluster_rdata(cluster)->ipdata;
                        domtime = 1;
@@ -612,7 +615,7 @@ done:
        }
        if (dosync)
                hammer2_cluster_modsync(cluster);
-       hammer2_inode_unlock_ex(ip, cluster);
+       hammer2_inode_unlock(ip, cluster);
        hammer2_trans_done(&trans);
        hammer2_knote(ip->vp, kflags);
 
@@ -662,7 +665,9 @@ hammer2_vop_readdir(struct vop_readdir_args *ap)
        }
        cookie_index = 0;
 
-       cparent = hammer2_inode_lock_sh(ip);
+       cparent = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS |
+                                        HAMMER2_RESOLVE_SHARED);
+
        ripdata = &hammer2_cluster_rdata(cparent)->ipdata;
 
        /*
@@ -700,18 +705,23 @@ hammer2_vop_readdir(struct vop_readdir_args *ap)
                while (ip->pip != NULL && ip != ip->pmp->iroot) {
                        xip = ip->pip;
                        hammer2_inode_ref(xip);
-                       hammer2_inode_unlock_sh(ip, cparent);
-                       xcluster = hammer2_inode_lock_sh(xip);
-                       cparent = hammer2_inode_lock_sh(ip);
+                       hammer2_inode_unlock(ip, cparent);
+                       xcluster = hammer2_inode_lock(xip,
+                                                     HAMMER2_RESOLVE_ALWAYS |
+                                                     HAMMER2_RESOLVE_SHARED);
+
+                       cparent = hammer2_inode_lock(ip,
+                                                     HAMMER2_RESOLVE_ALWAYS |
+                                                     HAMMER2_RESOLVE_SHARED);
                        hammer2_inode_drop(xip);
                        ripdata = &hammer2_cluster_rdata(cparent)->ipdata;
                        if (xip == ip->pip) {
                                inum = hammer2_cluster_rdata(xcluster)->
                                        ipdata.inum & HAMMER2_DIRHASH_USERMSK;
-                               hammer2_inode_unlock_sh(xip, xcluster);
+                               hammer2_inode_unlock(xip, xcluster);
                                break;
                        }
-                       hammer2_inode_unlock_sh(xip, xcluster);
+                       hammer2_inode_unlock(xip, xcluster);
                }
                r = vop_write_dirent(&error, uio, inum, DT_DIR, 2, "..");
                if (r)
@@ -790,7 +800,7 @@ hammer2_vop_readdir(struct vop_readdir_args *ap)
        if (cluster)
                hammer2_cluster_unlock(cluster);
 done:
-       hammer2_inode_unlock_sh(ip, cparent);
+       hammer2_inode_unlock(ip, cparent);
        if (ap->a_eofflag)
                *ap->a_eofflag = (cluster == NULL);
        if (hammer2_debug & 0x0020)
@@ -1256,7 +1266,9 @@ hammer2_vop_nresolve(struct vop_nresolve_args *ap)
        /*
         * Note: In DragonFly the kernel handles '.' and '..'.
         */
-       cparent = hammer2_inode_lock_sh(dip);
+       cparent = hammer2_inode_lock(dip, HAMMER2_RESOLVE_ALWAYS |
+                                         HAMMER2_RESOLVE_SHARED);
+
        cluster = hammer2_cluster_lookup(cparent, &key_next,
                                         lhc, lhc + HAMMER2_DIRHASH_LOMASK,
                                         HAMMER2_LOOKUP_SHARED);
@@ -1273,7 +1285,7 @@ hammer2_vop_nresolve(struct vop_nresolve_args *ap)
                                               lhc + HAMMER2_DIRHASH_LOMASK,
                                               HAMMER2_LOOKUP_SHARED);
        }
-       hammer2_inode_unlock_sh(dip, cparent);
+       hammer2_inode_unlock(dip, cparent);
 
        /*
         * Resolve hardlink entries before acquiring the inode.
@@ -1303,9 +1315,10 @@ hammer2_vop_nresolve(struct vop_nresolve_args *ap)
                if (ripdata->type == HAMMER2_OBJTYPE_HARDLINK) {
                        kprintf("nresolve: fixup hardlink\n");
                        hammer2_inode_ref(ip);
-                       hammer2_inode_unlock_ex(ip, NULL);
+                       hammer2_inode_unlock(ip, NULL);
                        hammer2_cluster_unlock(cluster);
-                       cluster = hammer2_inode_lock_ex(ip);
+                       cluster = hammer2_inode_lock(ip,
+                                                    HAMMER2_RESOLVE_ALWAYS);
                        ripdata = &hammer2_cluster_rdata(cluster)->ipdata;
                        hammer2_inode_drop(ip);
                        kprintf("nresolve: fixup to type %02x\n",
@@ -1346,7 +1359,7 @@ hammer2_vop_nresolve(struct vop_nresolve_args *ap)
         *       might already exist, but always allocates a new one.
         *
         * WARNING: inode structure is locked exclusively via inode_get
-        *          but chain was locked shared.  inode_unlock_ex()
+        *          but chain was locked shared.  inode_unlock()
         *          will handle it properly.
         */
        if (cluster) {
@@ -1357,7 +1370,7 @@ hammer2_vop_nresolve(struct vop_nresolve_args *ap)
                } else if (error == ENOENT) {
                        cache_setvp(ap->a_nch, NULL);
                }
-               hammer2_inode_unlock_ex(ip, cluster);
+               hammer2_inode_unlock(ip, cluster);
 
                /*
                 * The vp should not be released until after we've disposed
@@ -1394,9 +1407,9 @@ hammer2_vop_nlookupdotdot(struct vop_nlookupdotdot_args *ap)
                LOCKSTOP;
                return ENOENT;
        }
-       cparent = hammer2_inode_lock_ex(ip);
+       cparent = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS);
        *ap->a_vpp = hammer2_igetv(ip, cparent, &error);
-       hammer2_inode_unlock_ex(ip, cparent);
+       hammer2_inode_unlock(ip, cparent);
 
        LOCKSTOP;
        return error;
@@ -1437,7 +1450,7 @@ hammer2_vop_nmkdir(struct vop_nmkdir_args *ap)
                *ap->a_vpp = NULL;
        } else {
                *ap->a_vpp = hammer2_igetv(nip, cluster, &error);
-               hammer2_inode_unlock_ex(nip, cluster);
+               hammer2_inode_unlock(nip, cluster);
        }
        hammer2_trans_done(&trans);
 
@@ -1489,10 +1502,11 @@ hammer2_vop_advlock(struct vop_advlock_args *ap)
        hammer2_cluster_t *cparent;
        hammer2_off_t size;
 
-       cparent = hammer2_inode_lock_sh(ip);
+       cparent = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS |
+                                        HAMMER2_RESOLVE_SHARED);
        ripdata = &hammer2_cluster_rdata(cparent)->ipdata;
        size = ripdata->size;
-       hammer2_inode_unlock_sh(ip, cparent);
+       hammer2_inode_unlock(ip, cparent);
        return (lf_advlock(ap, &ip->advlock, size));
 }
 
@@ -1559,10 +1573,10 @@ hammer2_vop_nlink(struct vop_nlink_args *ap)
         */
        fdip = ip->pip;
        cdip = hammer2_inode_common_parent(fdip, tdip);
-       cdcluster = hammer2_inode_lock_ex(cdip);
-       fdcluster = hammer2_inode_lock_ex(fdip);
-       tdcluster = hammer2_inode_lock_ex(tdip);
-       cluster = hammer2_inode_lock_ex(ip);
+       cdcluster = hammer2_inode_lock(cdip, HAMMER2_RESOLVE_ALWAYS);
+       fdcluster = hammer2_inode_lock(fdip, HAMMER2_RESOLVE_ALWAYS);
+       tdcluster = hammer2_inode_lock(tdip, HAMMER2_RESOLVE_ALWAYS);
+       cluster = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS);
        error = hammer2_hardlink_consolidate(&trans, ip, &cluster,
                                             cdip, cdcluster, 1);
        if (error)
@@ -1582,10 +1596,10 @@ hammer2_vop_nlink(struct vop_nlink_args *ap)
                cache_setvp(ap->a_nch, ap->a_vp);
        }
 done:
-       hammer2_inode_unlock_ex(ip, cluster);
-       hammer2_inode_unlock_ex(tdip, tdcluster);
-       hammer2_inode_unlock_ex(fdip, fdcluster);
-       hammer2_inode_unlock_ex(cdip, cdcluster);
+       hammer2_inode_unlock(ip, cluster);
+       hammer2_inode_unlock(tdip, tdcluster);
+       hammer2_inode_unlock(fdip, fdcluster);
+       hammer2_inode_unlock(cdip, cdcluster);
        hammer2_inode_drop(cdip);
        hammer2_trans_done(&trans);
 
@@ -1634,7 +1648,7 @@ hammer2_vop_ncreate(struct vop_ncreate_args *ap)
                *ap->a_vpp = NULL;
        } else {
                *ap->a_vpp = hammer2_igetv(nip, ncluster, &error);
-               hammer2_inode_unlock_ex(nip, ncluster);
+               hammer2_inode_unlock(nip, ncluster);
        }
        hammer2_trans_done(&trans);
 
@@ -1684,7 +1698,7 @@ hammer2_vop_nmknod(struct vop_nmknod_args *ap)
                *ap->a_vpp = NULL;
        } else {
                *ap->a_vpp = hammer2_igetv(nip, ncluster, &error);
-               hammer2_inode_unlock_ex(nip, ncluster);
+               hammer2_inode_unlock(nip, ncluster);
        }
        hammer2_trans_done(&trans);
 
@@ -1756,10 +1770,10 @@ hammer2_vop_nsymlink(struct vop_nsymlink_args *ap)
                        nipdata->size = bytes;
                        nip->size = bytes;
                        hammer2_cluster_modsync(ncparent);
-                       hammer2_inode_unlock_ex(nip, ncparent);
+                       hammer2_inode_unlock(nip, ncparent);
                        /* nipdata = NULL; not needed */
                } else {
-                       hammer2_inode_unlock_ex(nip, ncparent);
+                       hammer2_inode_unlock(nip, ncparent);
                        /* nipdata = NULL; not needed */
                        bzero(&auio, sizeof(auio));
                        bzero(&aiov, sizeof(aiov));
@@ -1776,7 +1790,7 @@ hammer2_vop_nsymlink(struct vop_nsymlink_args *ap)
                        error = 0;
                }
        } else {
-               hammer2_inode_unlock_ex(nip, ncparent);
+               hammer2_inode_unlock(nip, ncparent);
        }
        hammer2_trans_done(&trans);
 
@@ -1938,9 +1952,9 @@ hammer2_vop_nrename(struct vop_nrename_args *ap)
         *          other pointers.
         */
        cdip = hammer2_inode_common_parent(ip->pip, tdip);
-       cdcluster = hammer2_inode_lock_ex(cdip);
-       fdcluster = hammer2_inode_lock_ex(fdip);
-       tdcluster = hammer2_inode_lock_ex(tdip);
+       cdcluster = hammer2_inode_lock(cdip, HAMMER2_RESOLVE_ALWAYS);
+       fdcluster = hammer2_inode_lock(fdip, HAMMER2_RESOLVE_ALWAYS);
+       tdcluster = hammer2_inode_lock(tdip, HAMMER2_RESOLVE_ALWAYS);
 
        /*
         * Keep a tight grip on the inode so the temporary unlinking from
@@ -1976,7 +1990,7 @@ hammer2_vop_nrename(struct vop_nrename_args *ap)
         *           we do use one later remember that it must be reloaded
         *           on any modification to the inode, including connects.
         */
-       cluster = hammer2_inode_lock_ex(ip);
+       cluster = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS);
        error = hammer2_hardlink_consolidate(&trans, ip, &cluster,
                                             cdip, cdcluster, 0);
        if (error)
@@ -2029,10 +2043,10 @@ hammer2_vop_nrename(struct vop_nrename_args *ap)
                hammer2_inode_repoint(ip, (hlink ? ip->pip : tdip), cluster);
        }
 done:
-       hammer2_inode_unlock_ex(ip, cluster);
-       hammer2_inode_unlock_ex(tdip, tdcluster);
-       hammer2_inode_unlock_ex(fdip, fdcluster);
-       hammer2_inode_unlock_ex(cdip, cdcluster);
+       hammer2_inode_unlock(ip, cluster);
+       hammer2_inode_unlock(tdip, tdcluster);
+       hammer2_inode_unlock(fdip, fdcluster);
+       hammer2_inode_unlock(cdip, cdcluster);
        hammer2_inode_drop(ip);
        hammer2_inode_drop(cdip);
        hammer2_run_unlinkq(&trans, fdip->pmp);
@@ -2125,12 +2139,13 @@ hammer2_strategy_read(struct vop_strategy_args *ap)
        /*
         * Lookup the file offset.
         */
-       cparent = hammer2_inode_lock_sh(ip);
+       cparent = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS |
+                                        HAMMER2_RESOLVE_SHARED);
        cluster = hammer2_cluster_lookup(cparent, &key_dummy,
                                       lbase, lbase,
                                       HAMMER2_LOOKUP_NODATA |
                                       HAMMER2_LOOKUP_SHARED);
-       hammer2_inode_unlock_sh(ip, cparent);
+       hammer2_inode_unlock(ip, cparent);
 
        /*
         * Data is zero-fill if no cluster could be found
@@ -2417,7 +2432,7 @@ hammer2_run_unlinkq(hammer2_trans_t *trans, hammer2_pfs_t *pmp)
                ip = ipul->ip;
                kfree(ipul, pmp->minode);
 
-               cluster = hammer2_inode_lock_ex(ip);
+               cluster = hammer2_inode_lock(ip, HAMMER2_RESOLVE_ALWAYS);
                ripdata = &hammer2_cluster_rdata(cluster)->ipdata;
                if (hammer2_debug & 0x400) {
                        kprintf("hammer2: unlink on reclaim: %s refs=%d\n",
@@ -2429,7 +2444,7 @@ hammer2_run_unlinkq(hammer2_trans_t *trans, hammer2_pfs_t *pmp)
                hammer2_cluster_delete(trans, cparent, cluster,
                                       HAMMER2_DELETE_PERMANENT);
                hammer2_cluster_unlock(cparent);
-               hammer2_inode_unlock_ex(ip, cluster);   /* inode lock */
+               hammer2_inode_unlock(ip, cluster);      /* inode lock */
                hammer2_inode_drop(ip);                 /* ipul ref */
 
                hammer2_spin_ex(&pmp->list_spin);