Read or write to the snapshot inode first locks the snap lock, which is equal to the vnode lock, then locks a buffer from the operational range. On the other hand, copy on write path starts with the buffer locked, and needs to lock the snap lock. If the same buffer is simultaneously read and written, it gets into LoR. Break the reversal by locking buffers of the snapshot inode in ffs VOPs with LK_NOWAIT, and temporary unlock the snap lock if possible deadlock is detected. This is similar to how ffs_update() handles the situation.
Also
ufs: be more persistent with finishing some operations when the vnode is doomed after relock. The mere fact that the vnode is doomed does not prevent us from doing UFS operations on it while it is still belongs to UFS, which is determined by non-NULL v_data. Not finishing some operations, e.g. not syncing the inode block only because the vnode started reclamation, is not correct. Add macro IS_UFS() which incapsulates the v_data != NULL, and use it instead of VN_IS_DOOMED() for places where the operation completion is important.
Also
buf_alloc(): lock the buffer with LK_NOWAIT The buffer must not be accessed by any other thread, it is freshly allocated. As such, LK_NOWAIT should be nop but also it prevents recording the order between the buffer lock and any other locks we might own in the call to getnewbuf(). In particular, if we own FFS snap lock, it should avoid triggering false positive warning.
Per-commit view is available at https://kib.kiev.ua/git/gitweb.cgi?p=deviant3.git;a=shortlog;h=refs/heads/ufs