mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Mix checklocks and atomic analyzers.
This change makes the checklocks analyzer considerable more powerful, adding: * The ability to traverse complex structures, e.g. to have multiple nested fields as part of the annotation. * The ability to resolve simple anonymous functions and closures, and perform lock analysis across these invocations. This does not apply to closures that are passed elsewhere, since it is not possible to know the context in which they might be invoked. * The ability to annotate return values in addition to receivers and other parameters, with the same complex structures noted above. * Ignoring locking semantics for "fresh" objects, i.e. objects that are allocated in the local frame (typically a new-style function). * Sanity checking of locking state across block transitions and returns, to ensure that no unexpected locks are held. Note that initially, most of these findings are excluded by a comprehensive nogo.yaml. The findings that are included are fundamental lock violations. The changes here should be relatively low risk, minor refactorings to either include necessary annotations to simplify the code structure (in general removing closures in favor of methods) so that the analyzer can be easily track the lock state. This change additional includes two changes to nogo itself: * Sanity checking of all types to ensure that the binary and ast-derived types have a consistent objectpath, to prevent the bug above from occurring silently (and causing much confusion). This also requires a trick in order to ensure that serialized facts are consumable downstream. This can be removed with https://go-review.googlesource.com/c/tools/+/331789 merged. * A minor refactoring to isolation the objdump settings in its own package. This was originally used to implement the sanity check above, but this information is now being passed another way. The minor refactor is preserved however, since it cleans up the code slightly and is minimal risk. PiperOrigin-RevId: 382613300
This commit is contained in:
committed by
gVisor bot
parent
570ca57180
commit
16b751b6c6
@@ -46,6 +46,8 @@ global:
|
||||
- "(field|method|struct|type) .* should be .*"
|
||||
# Generated proto code sometimes duplicates imports with aliases.
|
||||
- "duplicate import"
|
||||
# These will never be annotated.
|
||||
- "unexpected call to atomic function"
|
||||
internal:
|
||||
suppress:
|
||||
# We use ALL_CAPS for system definitions,
|
||||
@@ -55,6 +57,10 @@ global:
|
||||
# Same story for underscores.
|
||||
- "should not use ALL_CAPS in Go names"
|
||||
- "should not use underscores in Go names"
|
||||
# These need to be annotated.
|
||||
- "unexpected call to atomic function.*"
|
||||
- "return with unexpected locks held.*"
|
||||
- "incompatible return states.*"
|
||||
exclude:
|
||||
# Generated: exempt all.
|
||||
- pkg/shim/runtimeoptions/runtimeoptions_cri.go
|
||||
@@ -76,49 +82,7 @@ analyzers:
|
||||
checklocks:
|
||||
internal:
|
||||
exclude:
|
||||
- "^-$" # b/181776900: analyzer fails on buildkite
|
||||
- pkg/sentry/fs/dirent.go # unsupported usage.
|
||||
- pkg/sentry/fs/fsutil/inode_cached.go # unsupported usage.
|
||||
- pkg/sentry/fs/gofer/inode_state.go # unsupported usage.
|
||||
- pkg/sentry/fs/gofer/session.go # unsupported usage.
|
||||
- pkg/sentry/fs/ramfs/dir.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/fuse/connection.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/kernfs/filesystem.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/kernfs/inode_impl_util.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/fuse/dev_test.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/gofer/filesystem.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/gofer/gofer.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/gofer/regular_file.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/gofer/revalidate.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/gofer/special_file.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/gofer/symlink.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/overlay/copy_up.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/overlay/filesystem.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/tmpfs/filesystem.go # unsupported usage.
|
||||
- pkg/sentry/fsimpl/verity/filesystem.go # unsupported usage.
|
||||
- pkg/sentry/kernel/futex/futex.go # unsupported usage.
|
||||
- pkg/sentry/kernel/pipe/vfs.go # unsupported usage.
|
||||
- pkg/sentry/mm/syscalls.go # unsupported usage.
|
||||
- pkg/sentry/kernel/fd_table.go # unsupported usage.
|
||||
- pkg/sentry/kernel/ptrace.go # unsupported usage.
|
||||
- pkg/sentry/time/calibrated_clock_test.go # unsupported usage.
|
||||
- pkg/sentry/kernel/task_context.go # unsupported usage.
|
||||
- pkg/sentry/pgalloc/pgalloc.go # unsupported usage.
|
||||
- pkg/sentry/socket/unix/transport/connectioned.go # unsupported usage.
|
||||
- pkg/sentry/vfs/dentry.go # unsupported usage.
|
||||
- pkg/tcpip/network/internal/ip/generic_multicast_protocol_test.go # unsupported usage.
|
||||
- pkg/tcpip/stack/conntrack.go # unsupported usage.
|
||||
- pkg/tcpip/transport/packet/endpoint_state.go # unsupported usage.
|
||||
- pkg/tcpip/transport/raw/endpoint_state.go # unsupported usage.
|
||||
- pkg/tcpip/transport/icmp/endpoint.go # unsupported usage.
|
||||
- pkg/tcpip/transport/icmp/endpoint_state.go # unsupported usage.
|
||||
- pkg/tcpip/transport/tcp/accept.go # unsupported usage.
|
||||
- pkg/tcpip/transport/tcp/connect.go # unsupported usage.
|
||||
- pkg/tcpip/transport/tcp/dispatcher.go # unsupported usage (TryLock)
|
||||
- pkg/tcpip/transport/tcp/endpoint.go # unsupported usage.
|
||||
- pkg/tcpip/transport/tcp/endpoint_state.go # unsupported usage.
|
||||
- pkg/tcpip/transport/udp/endpoint.go # unsupported usage (defer unlock in anonymous function)
|
||||
- pkg/tcpip/transport/udp/endpoint_state.go # unsupported usage (missing nested mutex annotation support)
|
||||
- "^-$" # b/181776900: analyzer fails on buildkite.
|
||||
shadow: # Disable for now.
|
||||
generated:
|
||||
exclude: [".*"]
|
||||
|
||||
+47
-37
@@ -488,11 +488,11 @@ func (d *Dirent) walk(ctx context.Context, root *Dirent, name string, walkMayUnl
|
||||
// Slow path: load the InodeOperations into memory. Since this is a hot path and the lookup may be
|
||||
// expensive, if possible release the lock and re-acquire it.
|
||||
if walkMayUnlock {
|
||||
d.mu.Unlock()
|
||||
d.mu.Unlock() // +checklocksforce: results in an inconsistent block.
|
||||
}
|
||||
c, err := d.Inode.Lookup(ctx, name)
|
||||
if walkMayUnlock {
|
||||
d.mu.Lock()
|
||||
d.mu.Lock() // +checklocksforce: see above.
|
||||
}
|
||||
// No dice.
|
||||
if err != nil {
|
||||
@@ -594,21 +594,27 @@ func (d *Dirent) exists(ctx context.Context, root *Dirent, name string) bool {
|
||||
|
||||
// lockDirectory should be called for any operation that changes this `d`s
|
||||
// children (creating or removing them).
|
||||
func (d *Dirent) lockDirectory() func() {
|
||||
// +checklocksacquire:d.dirMu
|
||||
// +checklocksacquire:d.mu
|
||||
func (d *Dirent) lockDirectory() {
|
||||
renameMu.RLock()
|
||||
d.dirMu.Lock()
|
||||
d.mu.Lock()
|
||||
return func() {
|
||||
d.mu.Unlock()
|
||||
d.dirMu.Unlock()
|
||||
renameMu.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
// unlockDirectory is the reverse of lockDirectory.
|
||||
// +checklocksrelease:d.dirMu
|
||||
// +checklocksrelease:d.mu
|
||||
func (d *Dirent) unlockDirectory() {
|
||||
d.mu.Unlock()
|
||||
d.dirMu.Unlock()
|
||||
renameMu.RUnlock() // +checklocksforce: see lockDirectory.
|
||||
}
|
||||
|
||||
// Create creates a new regular file in this directory.
|
||||
func (d *Dirent) Create(ctx context.Context, root *Dirent, name string, flags FileFlags, perms FilePermissions) (*File, error) {
|
||||
unlock := d.lockDirectory()
|
||||
defer unlock()
|
||||
d.lockDirectory()
|
||||
defer d.unlockDirectory()
|
||||
|
||||
// Does something already exist?
|
||||
if d.exists(ctx, root, name) {
|
||||
@@ -670,8 +676,8 @@ func (d *Dirent) finishCreate(ctx context.Context, child *Dirent, name string) {
|
||||
// genericCreate executes create if name does not exist. Removes a negative Dirent at name if
|
||||
// create succeeds.
|
||||
func (d *Dirent) genericCreate(ctx context.Context, root *Dirent, name string, create func() error) error {
|
||||
unlock := d.lockDirectory()
|
||||
defer unlock()
|
||||
d.lockDirectory()
|
||||
defer d.unlockDirectory()
|
||||
|
||||
// Does something already exist?
|
||||
if d.exists(ctx, root, name) {
|
||||
@@ -1021,8 +1027,8 @@ func (d *Dirent) Remove(ctx context.Context, root *Dirent, name string, dirPath
|
||||
panic("Dirent.Remove: root must not be nil")
|
||||
}
|
||||
|
||||
unlock := d.lockDirectory()
|
||||
defer unlock()
|
||||
d.lockDirectory()
|
||||
defer d.unlockDirectory()
|
||||
|
||||
// Try to walk to the node.
|
||||
child, err := d.walk(ctx, root, name, false /* may unlock */)
|
||||
@@ -1082,8 +1088,8 @@ func (d *Dirent) RemoveDirectory(ctx context.Context, root *Dirent, name string)
|
||||
panic("Dirent.Remove: root must not be nil")
|
||||
}
|
||||
|
||||
unlock := d.lockDirectory()
|
||||
defer unlock()
|
||||
d.lockDirectory()
|
||||
defer d.unlockDirectory()
|
||||
|
||||
// Check for dots.
|
||||
if name == "." {
|
||||
@@ -1259,17 +1265,15 @@ func (d *Dirent) dropExtendedReference() {
|
||||
d.Inode.MountSource.fscache.Remove(d)
|
||||
}
|
||||
|
||||
// lockForRename takes locks on oldParent and newParent as required by Rename
|
||||
// and returns a function that will unlock the locks taken. The returned
|
||||
// function must be called even if a non-nil error is returned.
|
||||
func lockForRename(oldParent *Dirent, oldName string, newParent *Dirent, newName string) (func(), error) {
|
||||
// lockForRename takes locks on oldParent and newParent as required by Rename.
|
||||
// On return, unlockForRename must always be called, even with an error.
|
||||
// +checklocksacquire:oldParent.mu
|
||||
// +checklocksacquire:newParent.mu
|
||||
func lockForRename(oldParent *Dirent, oldName string, newParent *Dirent, newName string) error {
|
||||
renameMu.Lock()
|
||||
if oldParent == newParent {
|
||||
oldParent.mu.Lock()
|
||||
return func() {
|
||||
oldParent.mu.Unlock()
|
||||
renameMu.Unlock()
|
||||
}, nil
|
||||
return nil // +checklocksforce: only one lock exists.
|
||||
}
|
||||
|
||||
// Renaming between directories is a bit subtle:
|
||||
@@ -1297,11 +1301,7 @@ func lockForRename(oldParent *Dirent, oldName string, newParent *Dirent, newName
|
||||
// itself.
|
||||
err = unix.EINVAL
|
||||
}
|
||||
return func() {
|
||||
newParent.mu.Unlock()
|
||||
oldParent.mu.Unlock()
|
||||
renameMu.Unlock()
|
||||
}, err
|
||||
return err
|
||||
}
|
||||
child = p
|
||||
}
|
||||
@@ -1310,11 +1310,21 @@ func lockForRename(oldParent *Dirent, oldName string, newParent *Dirent, newName
|
||||
// have no relationship; in either case we can do this:
|
||||
newParent.mu.Lock()
|
||||
oldParent.mu.Lock()
|
||||
return func() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// unlockForRename is the opposite of lockForRename.
|
||||
// +checklocksrelease:oldParent.mu
|
||||
// +checklocksrelease:newParent.mu
|
||||
func unlockForRename(oldParent, newParent *Dirent) {
|
||||
if oldParent == newParent {
|
||||
oldParent.mu.Unlock()
|
||||
newParent.mu.Unlock()
|
||||
renameMu.Unlock()
|
||||
}, nil
|
||||
renameMu.Unlock() // +checklocksforce: only one lock exists.
|
||||
return
|
||||
}
|
||||
newParent.mu.Unlock()
|
||||
oldParent.mu.Unlock()
|
||||
renameMu.Unlock() // +checklocksforce: not tracked.
|
||||
}
|
||||
|
||||
func (d *Dirent) checkSticky(ctx context.Context, victim *Dirent) error {
|
||||
@@ -1353,8 +1363,8 @@ func (d *Dirent) MayDelete(ctx context.Context, root *Dirent, name string) error
|
||||
return err
|
||||
}
|
||||
|
||||
unlock := d.lockDirectory()
|
||||
defer unlock()
|
||||
d.lockDirectory()
|
||||
defer d.unlockDirectory()
|
||||
|
||||
victim, err := d.walk(ctx, root, name, true /* may unlock */)
|
||||
if err != nil {
|
||||
@@ -1392,8 +1402,8 @@ func Rename(ctx context.Context, root *Dirent, oldParent *Dirent, oldName string
|
||||
}
|
||||
|
||||
// Acquire global renameMu lock, and mu locks on oldParent/newParent.
|
||||
unlock, err := lockForRename(oldParent, oldName, newParent, newName)
|
||||
defer unlock()
|
||||
err := lockForRename(oldParent, oldName, newParent, newName)
|
||||
defer unlockForRename(oldParent, newParent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+18
-8
@@ -80,23 +80,33 @@ func AsyncBarrier() {
|
||||
// Async executes a function asynchronously.
|
||||
//
|
||||
// Async must not be called recursively.
|
||||
// +checklocksignore
|
||||
func Async(f func()) {
|
||||
workMu.RLock()
|
||||
go func() { // S/R-SAFE: AsyncBarrier must be called.
|
||||
defer workMu.RUnlock() // Ensure RUnlock in case of panic.
|
||||
f()
|
||||
}()
|
||||
go asyncWork(f) // S/R-SAFE: AsyncBarrier must be called.
|
||||
}
|
||||
|
||||
// +checklocksignore
|
||||
func asyncWork(f func()) {
|
||||
// Ensure RUnlock in case of panic.
|
||||
defer workMu.RUnlock()
|
||||
f()
|
||||
}
|
||||
|
||||
// AsyncWithContext is just like Async, except that it calls the asynchronous
|
||||
// function with the given context as argument. This function exists to avoid
|
||||
// needing to allocate an extra function on the heap in a hot path.
|
||||
// +checklocksignore
|
||||
func AsyncWithContext(ctx context.Context, f func(context.Context)) {
|
||||
workMu.RLock()
|
||||
go func() { // S/R-SAFE: AsyncBarrier must be called.
|
||||
defer workMu.RUnlock() // Ensure RUnlock in case of panic.
|
||||
f(ctx)
|
||||
}()
|
||||
go asyncWorkWithContext(ctx, f)
|
||||
}
|
||||
|
||||
// +checklocksignore
|
||||
func asyncWorkWithContext(ctx context.Context, f func(context.Context)) {
|
||||
// Ensure RUnlock in case of panic.
|
||||
defer workMu.RUnlock()
|
||||
f(ctx)
|
||||
}
|
||||
|
||||
// AsyncErrorBarrier waits for all outstanding asynchronous work to complete, or
|
||||
|
||||
@@ -112,13 +112,6 @@ func (i *inodeFileState) loadLoading(_ struct{}) {
|
||||
// +checklocks:i.loading
|
||||
func (i *inodeFileState) afterLoad() {
|
||||
load := func() (err error) {
|
||||
// See comment on i.loading().
|
||||
defer func() {
|
||||
if err == nil {
|
||||
i.loading.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
// Manually restore the p9.File.
|
||||
name, ok := i.s.inodeMappings[i.sattr.InodeID]
|
||||
if !ok {
|
||||
@@ -167,6 +160,9 @@ func (i *inodeFileState) afterLoad() {
|
||||
i.savedUAttr = nil
|
||||
}
|
||||
|
||||
// See comment on i.loading(). This only unlocks on the
|
||||
// non-error path.
|
||||
i.loading.Unlock() // +checklocksforce: per comment.
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+47
-33
@@ -47,7 +47,8 @@ func (i *inodeOperations) Lookup(ctx context.Context, dir *fs.Inode, name string
|
||||
return nil, linuxerr.ENAMETOOLONG
|
||||
}
|
||||
|
||||
cp := i.session().cachePolicy
|
||||
s := i.session()
|
||||
cp := s.cachePolicy
|
||||
if cp.cacheReaddir() {
|
||||
// Check to see if we have readdirCache that indicates the
|
||||
// child does not exist. Avoid holding readdirMu longer than
|
||||
@@ -78,7 +79,7 @@ func (i *inodeOperations) Lookup(ctx context.Context, dir *fs.Inode, name string
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if i.session().overrides != nil {
|
||||
if s.overrides != nil {
|
||||
// Check if file belongs to a internal named pipe. Note that it doesn't need
|
||||
// to check for sockets because it's done in newInodeOperations below.
|
||||
deviceKey := device.MultiDeviceKey{
|
||||
@@ -86,13 +87,13 @@ func (i *inodeOperations) Lookup(ctx context.Context, dir *fs.Inode, name string
|
||||
SecondaryDevice: i.session().connID,
|
||||
Inode: qids[0].Path,
|
||||
}
|
||||
unlock := i.session().overrides.lock()
|
||||
if pipeInode := i.session().overrides.getPipe(deviceKey); pipeInode != nil {
|
||||
unlock()
|
||||
s.overrides.lock()
|
||||
if pipeInode := s.overrides.getPipe(deviceKey); pipeInode != nil {
|
||||
s.overrides.unlock()
|
||||
pipeInode.IncRef()
|
||||
return fs.NewDirent(ctx, pipeInode, name), nil
|
||||
}
|
||||
unlock()
|
||||
s.overrides.unlock()
|
||||
}
|
||||
|
||||
// Construct the Inode operations.
|
||||
@@ -221,17 +222,20 @@ func (i *inodeOperations) CreateHardLink(ctx context.Context, inode *fs.Inode, t
|
||||
if err := i.fileState.file.link(ctx, &targetOpts.fileState.file, newName); err != nil {
|
||||
return err
|
||||
}
|
||||
if i.session().cachePolicy.cacheUAttrs(inode) {
|
||||
|
||||
s := i.session()
|
||||
if s.cachePolicy.cacheUAttrs(inode) {
|
||||
// Increase link count.
|
||||
targetOpts.cachingInodeOps.IncLinks(ctx)
|
||||
}
|
||||
|
||||
i.touchModificationAndStatusChangeTime(ctx, inode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateDirectory uses Create to create a directory named s under inodeOperations.
|
||||
func (i *inodeOperations) CreateDirectory(ctx context.Context, dir *fs.Inode, s string, perm fs.FilePermissions) error {
|
||||
if len(s) > maxFilenameLen {
|
||||
func (i *inodeOperations) CreateDirectory(ctx context.Context, dir *fs.Inode, name string, perm fs.FilePermissions) error {
|
||||
if len(name) > maxFilenameLen {
|
||||
return linuxerr.ENAMETOOLONG
|
||||
}
|
||||
|
||||
@@ -247,16 +251,18 @@ func (i *inodeOperations) CreateDirectory(ctx context.Context, dir *fs.Inode, s
|
||||
perm.SetGID = true
|
||||
}
|
||||
|
||||
if _, err := i.fileState.file.mkdir(ctx, s, p9.FileMode(perm.LinuxMode()), p9.UID(owner.UID), p9.GID(owner.GID)); err != nil {
|
||||
if _, err := i.fileState.file.mkdir(ctx, name, p9.FileMode(perm.LinuxMode()), p9.UID(owner.UID), p9.GID(owner.GID)); err != nil {
|
||||
return err
|
||||
}
|
||||
if i.session().cachePolicy.cacheUAttrs(dir) {
|
||||
|
||||
s := i.session()
|
||||
if s.cachePolicy.cacheUAttrs(dir) {
|
||||
// Increase link count.
|
||||
//
|
||||
// N.B. This will update the modification time.
|
||||
i.cachingInodeOps.IncLinks(ctx)
|
||||
}
|
||||
if i.session().cachePolicy.cacheReaddir() {
|
||||
if s.cachePolicy.cacheReaddir() {
|
||||
// Invalidate readdir cache.
|
||||
i.markDirectoryDirty()
|
||||
}
|
||||
@@ -269,13 +275,14 @@ func (i *inodeOperations) Bind(ctx context.Context, dir *fs.Inode, name string,
|
||||
return nil, linuxerr.ENAMETOOLONG
|
||||
}
|
||||
|
||||
if i.session().overrides == nil {
|
||||
s := i.session()
|
||||
if s.overrides == nil {
|
||||
return nil, syserror.EOPNOTSUPP
|
||||
}
|
||||
|
||||
// Stabilize the override map while creation is in progress.
|
||||
unlock := i.session().overrides.lock()
|
||||
defer unlock()
|
||||
s.overrides.lock()
|
||||
defer s.overrides.unlock()
|
||||
|
||||
sattr, iops, err := i.createEndpointFile(ctx, dir, name, perm, p9.ModeSocket)
|
||||
if err != nil {
|
||||
@@ -284,7 +291,7 @@ func (i *inodeOperations) Bind(ctx context.Context, dir *fs.Inode, name string,
|
||||
|
||||
// Construct the positive Dirent.
|
||||
childDir := fs.NewDirent(ctx, fs.NewInode(ctx, iops, dir.MountSource, sattr), name)
|
||||
i.session().overrides.addBoundEndpoint(iops.fileState.key, childDir, ep)
|
||||
s.overrides.addBoundEndpoint(iops.fileState.key, childDir, ep)
|
||||
return childDir, nil
|
||||
}
|
||||
|
||||
@@ -298,8 +305,9 @@ func (i *inodeOperations) CreateFifo(ctx context.Context, dir *fs.Inode, name st
|
||||
mode := p9.FileMode(perm.LinuxMode()) | p9.ModeNamedPipe
|
||||
|
||||
// N.B. FIFOs use major/minor numbers 0.
|
||||
s := i.session()
|
||||
if _, err := i.fileState.file.mknod(ctx, name, mode, 0, 0, p9.UID(owner.UID), p9.GID(owner.GID)); err != nil {
|
||||
if i.session().overrides == nil || !linuxerr.Equals(linuxerr.EPERM, err) {
|
||||
if s.overrides == nil || !linuxerr.Equals(linuxerr.EPERM, err) {
|
||||
return err
|
||||
}
|
||||
// If gofer doesn't support mknod, check if we can create an internal fifo.
|
||||
@@ -311,13 +319,14 @@ func (i *inodeOperations) CreateFifo(ctx context.Context, dir *fs.Inode, name st
|
||||
}
|
||||
|
||||
func (i *inodeOperations) createInternalFifo(ctx context.Context, dir *fs.Inode, name string, owner fs.FileOwner, perm fs.FilePermissions) error {
|
||||
if i.session().overrides == nil {
|
||||
s := i.session()
|
||||
if s.overrides == nil {
|
||||
return linuxerr.EPERM
|
||||
}
|
||||
|
||||
// Stabilize the override map while creation is in progress.
|
||||
unlock := i.session().overrides.lock()
|
||||
defer unlock()
|
||||
s.overrides.lock()
|
||||
defer s.overrides.unlock()
|
||||
|
||||
sattr, fileOps, err := i.createEndpointFile(ctx, dir, name, perm, p9.ModeNamedPipe)
|
||||
if err != nil {
|
||||
@@ -336,7 +345,7 @@ func (i *inodeOperations) createInternalFifo(ctx context.Context, dir *fs.Inode,
|
||||
|
||||
// Construct the positive Dirent.
|
||||
childDir := fs.NewDirent(ctx, fs.NewInode(ctx, iops, dir.MountSource, sattr), name)
|
||||
i.session().overrides.addPipe(fileOps.fileState.key, childDir, inode)
|
||||
s.overrides.addPipe(fileOps.fileState.key, childDir, inode)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -386,8 +395,9 @@ func (i *inodeOperations) Remove(ctx context.Context, dir *fs.Inode, name string
|
||||
return linuxerr.ENAMETOOLONG
|
||||
}
|
||||
|
||||
s := i.session()
|
||||
var key *device.MultiDeviceKey
|
||||
if i.session().overrides != nil {
|
||||
if s.overrides != nil {
|
||||
// Find out if file being deleted is a socket or pipe that needs to be
|
||||
// removed from endpoint map.
|
||||
if d, err := i.Lookup(ctx, dir, name); err == nil {
|
||||
@@ -402,8 +412,8 @@ func (i *inodeOperations) Remove(ctx context.Context, dir *fs.Inode, name string
|
||||
}
|
||||
|
||||
// Stabilize the override map while deletion is in progress.
|
||||
unlock := i.session().overrides.lock()
|
||||
defer unlock()
|
||||
s.overrides.lock()
|
||||
defer s.overrides.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -412,7 +422,7 @@ func (i *inodeOperations) Remove(ctx context.Context, dir *fs.Inode, name string
|
||||
return err
|
||||
}
|
||||
if key != nil {
|
||||
i.session().overrides.remove(ctx, *key)
|
||||
s.overrides.remove(ctx, *key)
|
||||
}
|
||||
i.touchModificationAndStatusChangeTime(ctx, dir)
|
||||
|
||||
@@ -429,11 +439,13 @@ func (i *inodeOperations) RemoveDirectory(ctx context.Context, dir *fs.Inode, na
|
||||
if err := i.fileState.file.unlinkAt(ctx, name, 0x200); err != nil {
|
||||
return err
|
||||
}
|
||||
if i.session().cachePolicy.cacheUAttrs(dir) {
|
||||
|
||||
s := i.session()
|
||||
if s.cachePolicy.cacheUAttrs(dir) {
|
||||
// Decrease link count and updates atime.
|
||||
i.cachingInodeOps.DecLinks(ctx)
|
||||
}
|
||||
if i.session().cachePolicy.cacheReaddir() {
|
||||
if s.cachePolicy.cacheReaddir() {
|
||||
// Invalidate readdir cache.
|
||||
i.markDirectoryDirty()
|
||||
}
|
||||
@@ -463,12 +475,13 @@ func (i *inodeOperations) Rename(ctx context.Context, inode *fs.Inode, oldParent
|
||||
}
|
||||
|
||||
// Is the renamed entity a directory? Fix link counts.
|
||||
s := i.session()
|
||||
if fs.IsDir(i.fileState.sattr) {
|
||||
// Update cached state.
|
||||
if i.session().cachePolicy.cacheUAttrs(oldParent) {
|
||||
if s.cachePolicy.cacheUAttrs(oldParent) {
|
||||
oldParentInodeOperations.cachingInodeOps.DecLinks(ctx)
|
||||
}
|
||||
if i.session().cachePolicy.cacheUAttrs(newParent) {
|
||||
if s.cachePolicy.cacheUAttrs(newParent) {
|
||||
// Only IncLinks if there is a new addition to
|
||||
// newParent. If this is replacement, then the total
|
||||
// count remains the same.
|
||||
@@ -477,7 +490,7 @@ func (i *inodeOperations) Rename(ctx context.Context, inode *fs.Inode, oldParent
|
||||
}
|
||||
}
|
||||
}
|
||||
if i.session().cachePolicy.cacheReaddir() {
|
||||
if s.cachePolicy.cacheReaddir() {
|
||||
// Mark old directory dirty.
|
||||
oldParentInodeOperations.markDirectoryDirty()
|
||||
if oldParent != newParent {
|
||||
@@ -487,17 +500,18 @@ func (i *inodeOperations) Rename(ctx context.Context, inode *fs.Inode, oldParent
|
||||
}
|
||||
|
||||
// Rename always updates ctime.
|
||||
if i.session().cachePolicy.cacheUAttrs(inode) {
|
||||
if s.cachePolicy.cacheUAttrs(inode) {
|
||||
i.cachingInodeOps.TouchStatusChangeTime(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *inodeOperations) touchModificationAndStatusChangeTime(ctx context.Context, inode *fs.Inode) {
|
||||
if i.session().cachePolicy.cacheUAttrs(inode) {
|
||||
s := i.session()
|
||||
if s.cachePolicy.cacheUAttrs(inode) {
|
||||
i.cachingInodeOps.TouchModificationAndStatusChangeTime(ctx)
|
||||
}
|
||||
if i.session().cachePolicy.cacheReaddir() {
|
||||
if s.cachePolicy.cacheReaddir() {
|
||||
// Invalidate readdir cache.
|
||||
i.markDirectoryDirty()
|
||||
}
|
||||
|
||||
@@ -98,9 +98,14 @@ func (e *overrideMaps) remove(ctx context.Context, key device.MultiDeviceKey) {
|
||||
// lock blocks other addition and removal operations from happening while
|
||||
// the backing file is being created or deleted. Returns a function that unlocks
|
||||
// the endpoint map.
|
||||
func (e *overrideMaps) lock() func() {
|
||||
// +checklocksacquire:e.mu
|
||||
func (e *overrideMaps) lock() {
|
||||
e.mu.Lock()
|
||||
return func() { e.mu.Unlock() }
|
||||
}
|
||||
|
||||
// +checklocksrelease:e.mu
|
||||
func (e *overrideMaps) unlock() {
|
||||
e.mu.Unlock()
|
||||
}
|
||||
|
||||
// getBoundEndpoint returns the bound endpoint mapped to the given key.
|
||||
@@ -366,8 +371,8 @@ func newOverrideMaps() *overrideMaps {
|
||||
|
||||
// fillKeyMap populates key and dirent maps upon restore from saved pathmap.
|
||||
func (s *session) fillKeyMap(ctx context.Context) error {
|
||||
unlock := s.overrides.lock()
|
||||
defer unlock()
|
||||
s.overrides.lock()
|
||||
defer s.overrides.unlock()
|
||||
|
||||
for ep, dirPath := range s.overrides.pathMap {
|
||||
_, file, err := s.attach.walk(ctx, splitAbsolutePath(dirPath))
|
||||
@@ -394,8 +399,8 @@ func (s *session) fillKeyMap(ctx context.Context) error {
|
||||
// fillPathMap populates paths for overrides from dirents in direntMap
|
||||
// before save.
|
||||
func (s *session) fillPathMap(ctx context.Context) error {
|
||||
unlock := s.overrides.lock()
|
||||
defer unlock()
|
||||
s.overrides.lock()
|
||||
defer s.overrides.unlock()
|
||||
|
||||
for _, endpoint := range s.overrides.keyMap {
|
||||
mountRoot := endpoint.dirent.MountRoot()
|
||||
|
||||
@@ -32,10 +32,11 @@ func (i *inodeOperations) BoundEndpoint(inode *fs.Inode, path string) transport.
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.session().overrides != nil {
|
||||
unlock := i.session().overrides.lock()
|
||||
defer unlock()
|
||||
ep := i.session().overrides.getBoundEndpoint(i.fileState.key)
|
||||
s := i.session()
|
||||
if s.overrides != nil {
|
||||
s.overrides.lock()
|
||||
defer s.overrides.unlock()
|
||||
ep := s.overrides.getBoundEndpoint(i.fileState.key)
|
||||
if ep != nil {
|
||||
return ep
|
||||
}
|
||||
|
||||
@@ -147,6 +147,7 @@ func putDentrySlice(ds *[]*dentry) {
|
||||
// but dentry slices are allocated lazily, and it's much easier to say "defer
|
||||
// fs.renameMuRUnlockAndCheckCaching(&ds)" than "defer func() {
|
||||
// fs.renameMuRUnlockAndCheckCaching(ds) }()" to work around this.
|
||||
// +checklocksrelease:fs.renameMu
|
||||
func (fs *filesystem) renameMuRUnlockAndCheckCaching(ctx context.Context, dsp **[]*dentry) {
|
||||
fs.renameMu.RUnlock()
|
||||
if *dsp == nil {
|
||||
@@ -159,6 +160,7 @@ func (fs *filesystem) renameMuRUnlockAndCheckCaching(ctx context.Context, dsp **
|
||||
putDentrySlice(*dsp)
|
||||
}
|
||||
|
||||
// +checklocksrelease:fs.renameMu
|
||||
func (fs *filesystem) renameMuUnlockAndCheckCaching(ctx context.Context, ds **[]*dentry) {
|
||||
if *ds == nil {
|
||||
fs.renameMu.Unlock()
|
||||
@@ -540,7 +542,7 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
|
||||
if child.syntheticChildren != 0 {
|
||||
// This is definitely not an empty directory, irrespective of
|
||||
// fs.opts.interop.
|
||||
vfsObj.AbortDeleteDentry(&child.vfsd)
|
||||
vfsObj.AbortDeleteDentry(&child.vfsd) // +checklocksforce: PrepareDeleteDentry called if child != nil.
|
||||
return linuxerr.ENOTEMPTY
|
||||
}
|
||||
// If InteropModeShared is in effect and the first call to
|
||||
@@ -550,12 +552,12 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
|
||||
// still exist) would be a waste of time.
|
||||
if child.cachedMetadataAuthoritative() {
|
||||
if !child.isDir() {
|
||||
vfsObj.AbortDeleteDentry(&child.vfsd)
|
||||
vfsObj.AbortDeleteDentry(&child.vfsd) // +checklocksforce: see above.
|
||||
return syserror.ENOTDIR
|
||||
}
|
||||
for _, grandchild := range child.children {
|
||||
if grandchild != nil {
|
||||
vfsObj.AbortDeleteDentry(&child.vfsd)
|
||||
vfsObj.AbortDeleteDentry(&child.vfsd) // +checklocksforce: see above.
|
||||
return linuxerr.ENOTEMPTY
|
||||
}
|
||||
}
|
||||
@@ -565,12 +567,12 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
|
||||
} else {
|
||||
// child must be a non-directory file.
|
||||
if child != nil && child.isDir() {
|
||||
vfsObj.AbortDeleteDentry(&child.vfsd)
|
||||
vfsObj.AbortDeleteDentry(&child.vfsd) // +checklocksforce: see above.
|
||||
return syserror.EISDIR
|
||||
}
|
||||
if rp.MustBeDir() {
|
||||
if child != nil {
|
||||
vfsObj.AbortDeleteDentry(&child.vfsd)
|
||||
vfsObj.AbortDeleteDentry(&child.vfsd) // +checklocksforce: see above.
|
||||
}
|
||||
return syserror.ENOTDIR
|
||||
}
|
||||
@@ -583,7 +585,7 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
|
||||
err = parent.file.unlinkAt(ctx, name, flags)
|
||||
if err != nil {
|
||||
if child != nil {
|
||||
vfsObj.AbortDeleteDentry(&child.vfsd)
|
||||
vfsObj.AbortDeleteDentry(&child.vfsd) // +checklocksforce: see above.
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -601,7 +603,7 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
|
||||
}
|
||||
|
||||
if child != nil {
|
||||
vfsObj.CommitDeleteDentry(ctx, &child.vfsd)
|
||||
vfsObj.CommitDeleteDentry(ctx, &child.vfsd) // +checklocksforce: see above.
|
||||
child.setDeleted()
|
||||
if child.isSynthetic() {
|
||||
parent.syntheticChildren--
|
||||
|
||||
@@ -947,10 +947,10 @@ func (d *dentry) cachedMetadataAuthoritative() bool {
|
||||
// updateFromP9Attrs is called to update d's metadata after an update from the
|
||||
// remote filesystem.
|
||||
// Precondition: d.metadataMu must be locked.
|
||||
// +checklocks:d.metadataMu
|
||||
func (d *dentry) updateFromP9AttrsLocked(mask p9.AttrMask, attr *p9.Attr) {
|
||||
if mask.Mode {
|
||||
if got, want := uint32(attr.Mode.FileType()), d.fileType(); got != want {
|
||||
d.metadataMu.Unlock()
|
||||
panic(fmt.Sprintf("gofer.dentry file type changed from %#o to %#o", want, got))
|
||||
}
|
||||
atomic.StoreUint32(&d.mode, uint32(attr.Mode))
|
||||
@@ -989,6 +989,7 @@ func (d *dentry) updateFromP9AttrsLocked(mask p9.AttrMask, attr *p9.Attr) {
|
||||
|
||||
// Preconditions: !d.isSynthetic().
|
||||
// Preconditions: d.metadataMu is locked.
|
||||
// +checklocks:d.metadataMu
|
||||
func (d *dentry) refreshSizeLocked(ctx context.Context) error {
|
||||
d.handleMu.RLock()
|
||||
|
||||
@@ -1020,6 +1021,7 @@ func (d *dentry) updateFromGetattr(ctx context.Context) error {
|
||||
// Preconditions:
|
||||
// * !d.isSynthetic().
|
||||
// * d.metadataMu is locked.
|
||||
// +checklocks:d.metadataMu
|
||||
func (d *dentry) updateFromGetattrLocked(ctx context.Context) error {
|
||||
// Use d.readFile or d.writeFile, which represent 9P FIDs that have been
|
||||
// opened, in preference to d.file, which represents a 9P fid that has not.
|
||||
@@ -1044,7 +1046,8 @@ func (d *dentry) updateFromGetattrLocked(ctx context.Context) error {
|
||||
|
||||
_, attrMask, attr, err := file.getAttr(ctx, dentryAttrMask())
|
||||
if handleMuRLocked {
|
||||
d.handleMu.RUnlock() // must be released before updateFromP9AttrsLocked()
|
||||
// handleMu must be released before updateFromP9AttrsLocked().
|
||||
d.handleMu.RUnlock() // +checklocksforce: complex case.
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1470,7 +1473,7 @@ func (d *dentry) checkCachingLocked(ctx context.Context, renameMuWriteLocked boo
|
||||
if d.isDeleted() {
|
||||
d.watches.HandleDeletion(ctx)
|
||||
}
|
||||
d.destroyLocked(ctx)
|
||||
d.destroyLocked(ctx) // +checklocksforce: renameMu must be acquired at this point.
|
||||
return
|
||||
}
|
||||
// If d still has inotify watches and it is not deleted or invalidated, it
|
||||
@@ -1498,7 +1501,7 @@ func (d *dentry) checkCachingLocked(ctx context.Context, renameMuWriteLocked boo
|
||||
delete(d.parent.children, d.name)
|
||||
d.parent.dirMu.Unlock()
|
||||
}
|
||||
d.destroyLocked(ctx)
|
||||
d.destroyLocked(ctx) // +checklocksforce: see above.
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1527,7 +1530,7 @@ func (d *dentry) checkCachingLocked(ctx context.Context, renameMuWriteLocked boo
|
||||
d.fs.renameMu.Lock()
|
||||
defer d.fs.renameMu.Unlock()
|
||||
}
|
||||
d.fs.evictCachedDentryLocked(ctx)
|
||||
d.fs.evictCachedDentryLocked(ctx) // +checklocksforce: see above.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1544,6 +1547,7 @@ func (d *dentry) removeFromCacheLocked() {
|
||||
|
||||
// Precondition: fs.renameMu must be locked for writing; it may be temporarily
|
||||
// unlocked.
|
||||
// +checklocks:fs.renameMu
|
||||
func (fs *filesystem) evictAllCachedDentriesLocked(ctx context.Context) {
|
||||
for fs.cachedDentriesLen != 0 {
|
||||
fs.evictCachedDentryLocked(ctx)
|
||||
@@ -1552,6 +1556,7 @@ func (fs *filesystem) evictAllCachedDentriesLocked(ctx context.Context) {
|
||||
|
||||
// Preconditions:
|
||||
// * fs.renameMu must be locked for writing; it may be temporarily unlocked.
|
||||
// +checklocks:fs.renameMu
|
||||
func (fs *filesystem) evictCachedDentryLocked(ctx context.Context) {
|
||||
fs.cacheMu.Lock()
|
||||
victim := fs.cachedDentries.Back()
|
||||
@@ -1588,7 +1593,7 @@ func (fs *filesystem) evictCachedDentryLocked(ctx context.Context) {
|
||||
// will try to acquire fs.renameMu (which we have already acquired). Hence,
|
||||
// fs.renameMu will synchronize the destroy attempts.
|
||||
victim.cachingMu.Unlock()
|
||||
victim.destroyLocked(ctx)
|
||||
victim.destroyLocked(ctx) // +checklocksforce: owned as precondition, victim.fs == fs.
|
||||
}
|
||||
|
||||
// destroyLocked destroys the dentry.
|
||||
@@ -1598,6 +1603,7 @@ func (fs *filesystem) evictCachedDentryLocked(ctx context.Context) {
|
||||
// * d.refs == 0.
|
||||
// * d.parent.children[d.name] != d, i.e. d is not reachable by path traversal
|
||||
// from its former parent dentry.
|
||||
// +checklocks:d.fs.renameMu
|
||||
func (d *dentry) destroyLocked(ctx context.Context) {
|
||||
switch atomic.LoadInt64(&d.refs) {
|
||||
case 0:
|
||||
|
||||
@@ -247,16 +247,16 @@ func (fs *filesystem) revalidateHelper(ctx context.Context, vfsObj *vfs.VirtualF
|
||||
if found && !d.isSynthetic() {
|
||||
// First dentry is where the search is starting, just update attributes
|
||||
// since it cannot be replaced.
|
||||
d.updateFromP9AttrsLocked(stats[i].Valid, &stats[i].Attr)
|
||||
d.updateFromP9AttrsLocked(stats[i].Valid, &stats[i].Attr) // +checklocksforce: acquired by lockAllMetadata.
|
||||
}
|
||||
d.metadataMu.Unlock()
|
||||
d.metadataMu.Unlock() // +checklocksforce: see above.
|
||||
continue
|
||||
}
|
||||
|
||||
// Note that synthetic dentries will always fails the comparison check
|
||||
// below.
|
||||
if !found || d.qidPath != stats[i].QID.Path {
|
||||
d.metadataMu.Unlock()
|
||||
d.metadataMu.Unlock() // +checklocksforce: see above.
|
||||
if !found && d.isSynthetic() {
|
||||
// We have a synthetic file, and no remote file has arisen to replace
|
||||
// it.
|
||||
@@ -298,7 +298,7 @@ func (fs *filesystem) revalidateHelper(ctx context.Context, vfsObj *vfs.VirtualF
|
||||
}
|
||||
|
||||
// The file at this path hasn't changed. Just update cached metadata.
|
||||
d.updateFromP9AttrsLocked(stats[i].Valid, &stats[i].Attr)
|
||||
d.updateFromP9AttrsLocked(stats[i].Valid, &stats[i].Attr) // +checklocksforce: see above.
|
||||
d.metadataMu.Unlock()
|
||||
}
|
||||
|
||||
@@ -354,6 +354,7 @@ func (r *revalidateState) add(name string, d *dentry) {
|
||||
r.dentries = append(r.dentries, d)
|
||||
}
|
||||
|
||||
// +checklocksignore
|
||||
func (r *revalidateState) lockAllMetadata() {
|
||||
for _, d := range r.dentries {
|
||||
d.metadataMu.Lock()
|
||||
@@ -372,6 +373,7 @@ func (r *revalidateState) popFront() *dentry {
|
||||
|
||||
// reset releases all metadata locks and resets all fields to allow this
|
||||
// instance to be reused.
|
||||
// +checklocksignore
|
||||
func (r *revalidateState) reset() {
|
||||
if r.locked {
|
||||
// Unlock any remaining dentries.
|
||||
|
||||
@@ -41,7 +41,7 @@ func (d *dentry) readlink(ctx context.Context, mnt *vfs.Mount) (string, error) {
|
||||
d.haveTarget = true
|
||||
d.target = target
|
||||
}
|
||||
d.dataMu.Unlock()
|
||||
d.dataMu.Unlock() // +checklocksforce: guaranteed locked from above.
|
||||
}
|
||||
return target, err
|
||||
}
|
||||
|
||||
@@ -752,7 +752,7 @@ func (fs *Filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
|
||||
fs.deferDecRef(replaced)
|
||||
replaceVFSD = replaced.VFSDentry()
|
||||
}
|
||||
virtfs.CommitRenameReplaceDentry(ctx, srcVFSD, replaceVFSD)
|
||||
virtfs.CommitRenameReplaceDentry(ctx, srcVFSD, replaceVFSD) // +checklocksforce: to may be nil, that's okay.
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -788,7 +788,7 @@ func (fs *Filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error
|
||||
defer mntns.DecRef(ctx)
|
||||
vfsd := d.VFSDentry()
|
||||
if err := virtfs.PrepareDeleteDentry(mntns, vfsd); err != nil {
|
||||
return err
|
||||
return err // +checklocksforce: vfsd is not locked.
|
||||
}
|
||||
|
||||
if err := parentDentry.inode.RmDir(ctx, d.name, d.inode); err != nil {
|
||||
|
||||
@@ -87,7 +87,7 @@ func putDentrySlice(ds *[]*dentry) {
|
||||
// fs.renameMuRUnlockAndCheckDrop(&ds)" than "defer func() {
|
||||
// fs.renameMuRUnlockAndCheckDrop(ds) }()" to work around this.
|
||||
//
|
||||
// +checklocks:fs.renameMu
|
||||
// +checklocksrelease:fs.renameMu
|
||||
func (fs *filesystem) renameMuRUnlockAndCheckDrop(ctx context.Context, dsp **[]*dentry) {
|
||||
fs.renameMu.RUnlock()
|
||||
if *dsp == nil {
|
||||
@@ -113,7 +113,7 @@ func (fs *filesystem) renameMuRUnlockAndCheckDrop(ctx context.Context, dsp **[]*
|
||||
putDentrySlice(*dsp)
|
||||
}
|
||||
|
||||
// +checklocks:fs.renameMu
|
||||
// +checklocksrelease:fs.renameMu
|
||||
func (fs *filesystem) renameMuUnlockAndCheckDrop(ctx context.Context, ds **[]*dentry) {
|
||||
if *ds == nil {
|
||||
fs.renameMu.Unlock()
|
||||
|
||||
@@ -75,6 +75,7 @@ func putDentrySlice(ds *[]*dentry) {
|
||||
// but dentry slices are allocated lazily, and it's much easier to say "defer
|
||||
// fs.renameMuRUnlockAndCheckDrop(&ds)" than "defer func() {
|
||||
// fs.renameMuRUnlockAndCheckDrop(ds) }()" to work around this.
|
||||
// +checklocksrelease:fs.renameMu
|
||||
func (fs *filesystem) renameMuRUnlockAndCheckDrop(ctx context.Context, ds **[]*dentry) {
|
||||
fs.renameMu.RUnlock()
|
||||
if *ds == nil {
|
||||
@@ -90,6 +91,7 @@ func (fs *filesystem) renameMuRUnlockAndCheckDrop(ctx context.Context, ds **[]*d
|
||||
putDentrySlice(*ds)
|
||||
}
|
||||
|
||||
// +checklocksrelease:fs.renameMu
|
||||
func (fs *filesystem) renameMuUnlockAndCheckDrop(ctx context.Context, ds **[]*dentry) {
|
||||
if *ds == nil {
|
||||
fs.renameMu.Unlock()
|
||||
|
||||
@@ -398,8 +398,8 @@ func (m *Manager) Fork() *Manager {
|
||||
}
|
||||
|
||||
// lockBucket returns a locked bucket for the given key.
|
||||
func (m *Manager) lockBucket(k *Key) *bucket {
|
||||
var b *bucket
|
||||
// +checklocksacquire:b.mu
|
||||
func (m *Manager) lockBucket(k *Key) (b *bucket) {
|
||||
if k.Kind == KindSharedMappable {
|
||||
b = m.sharedBucket
|
||||
} else {
|
||||
@@ -410,7 +410,9 @@ func (m *Manager) lockBucket(k *Key) *bucket {
|
||||
}
|
||||
|
||||
// lockBuckets returns locked buckets for the given keys.
|
||||
func (m *Manager) lockBuckets(k1, k2 *Key) (*bucket, *bucket) {
|
||||
// +checklocksacquire:b1.mu
|
||||
// +checklocksacquire:b2.mu
|
||||
func (m *Manager) lockBuckets(k1, k2 *Key) (b1 *bucket, b2 *bucket) {
|
||||
// Buckets must be consistently ordered to avoid circular lock
|
||||
// dependencies. We order buckets in m.privateBuckets by index (lowest
|
||||
// index first), and all buckets in m.privateBuckets precede
|
||||
@@ -420,8 +422,8 @@ func (m *Manager) lockBuckets(k1, k2 *Key) (*bucket, *bucket) {
|
||||
if k1.Kind != KindSharedMappable && k2.Kind != KindSharedMappable {
|
||||
i1 := bucketIndexForAddr(k1.addr())
|
||||
i2 := bucketIndexForAddr(k2.addr())
|
||||
b1 := &m.privateBuckets[i1]
|
||||
b2 := &m.privateBuckets[i2]
|
||||
b1 = &m.privateBuckets[i1]
|
||||
b2 = &m.privateBuckets[i2]
|
||||
switch {
|
||||
case i1 < i2:
|
||||
b1.mu.Lock()
|
||||
@@ -432,19 +434,30 @@ func (m *Manager) lockBuckets(k1, k2 *Key) (*bucket, *bucket) {
|
||||
default:
|
||||
b1.mu.Lock()
|
||||
}
|
||||
return b1, b2
|
||||
return b1, b2 // +checklocksforce
|
||||
}
|
||||
|
||||
// At least one of b1 or b2 should be m.sharedBucket.
|
||||
b1 := m.sharedBucket
|
||||
b2 := m.sharedBucket
|
||||
b1 = m.sharedBucket
|
||||
b2 = m.sharedBucket
|
||||
if k1.Kind != KindSharedMappable {
|
||||
b1 = m.lockBucket(k1)
|
||||
} else if k2.Kind != KindSharedMappable {
|
||||
b2 = m.lockBucket(k2)
|
||||
}
|
||||
m.sharedBucket.mu.Lock()
|
||||
return b1, b2
|
||||
return b1, b2 // +checklocksforce
|
||||
}
|
||||
|
||||
// unlockBuckets unlocks two buckets.
|
||||
// +checklocksrelease:b1.mu
|
||||
// +checklocksrelease:b2.mu
|
||||
func (m *Manager) unlockBuckets(b1, b2 *bucket) {
|
||||
b1.mu.Unlock()
|
||||
if b1 != b2 {
|
||||
b2.mu.Unlock()
|
||||
}
|
||||
return // +checklocksforce
|
||||
}
|
||||
|
||||
// Wake wakes up to n waiters matching the bitmask on the given addr.
|
||||
@@ -477,10 +490,7 @@ func (m *Manager) doRequeue(t Target, addr, naddr hostarch.Addr, private bool, c
|
||||
defer k2.release(t)
|
||||
|
||||
b1, b2 := m.lockBuckets(&k1, &k2)
|
||||
defer b1.mu.Unlock()
|
||||
if b2 != b1 {
|
||||
defer b2.mu.Unlock()
|
||||
}
|
||||
defer m.unlockBuckets(b1, b2)
|
||||
|
||||
if checkval {
|
||||
if err := check(t, addr, val); err != nil {
|
||||
@@ -527,10 +537,7 @@ func (m *Manager) WakeOp(t Target, addr1, addr2 hostarch.Addr, private bool, nwa
|
||||
defer k2.release(t)
|
||||
|
||||
b1, b2 := m.lockBuckets(&k1, &k2)
|
||||
defer b1.mu.Unlock()
|
||||
if b2 != b1 {
|
||||
defer b2.mu.Unlock()
|
||||
}
|
||||
defer m.unlockBuckets(b1, b2)
|
||||
|
||||
done := 0
|
||||
cond, err := atomicOp(t, addr2, op)
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
// concurrent calls cannot deadlock.
|
||||
//
|
||||
// Preconditions: x != y.
|
||||
// +checklocksacquire:x.mu
|
||||
// +checklocksacquire:y.mu
|
||||
func lockTwoPipes(x, y *Pipe) {
|
||||
// Lock the two pipes in order of increasing address.
|
||||
if uintptr(unsafe.Pointer(x)) < uintptr(unsafe.Pointer(y)) {
|
||||
|
||||
@@ -157,6 +157,7 @@ func (p *Pipe) Ioctl(ctx context.Context, io usermem.IO, args arch.SyscallArgume
|
||||
//
|
||||
// mu must be held by the caller. waitFor returns with mu held, but it will
|
||||
// drop mu before blocking for any reader/writers.
|
||||
// +checklocks:mu
|
||||
func waitFor(mu *sync.Mutex, wakeupChan *chan struct{}, sleeper amutex.Sleeper) bool {
|
||||
// Ideally this function would simply use a condition variable. However, the
|
||||
// wait needs to be interruptible via 'sleeper', so we must sychronize via a
|
||||
|
||||
@@ -652,6 +652,7 @@ func (t *Task) forgetTracerLocked() {
|
||||
// Preconditions:
|
||||
// * The signal mutex must be locked.
|
||||
// * The caller must be running on the task goroutine.
|
||||
// +checklocks:t.tg.signalHandlers.mu
|
||||
func (t *Task) ptraceSignalLocked(info *linux.SignalInfo) bool {
|
||||
if linux.Signal(info.Signo) == linux.SIGKILL {
|
||||
return false
|
||||
|
||||
@@ -121,8 +121,9 @@ func (pg *ProcessGroup) Originator() *ThreadGroup {
|
||||
|
||||
// IsOrphan returns true if this process group is an orphan.
|
||||
func (pg *ProcessGroup) IsOrphan() bool {
|
||||
pg.originator.TaskSet().mu.RLock()
|
||||
defer pg.originator.TaskSet().mu.RUnlock()
|
||||
ts := pg.originator.TaskSet()
|
||||
ts.mu.RLock()
|
||||
defer ts.mu.RUnlock()
|
||||
return pg.ancestors == 0
|
||||
}
|
||||
|
||||
|
||||
@@ -204,6 +204,7 @@ func (mm *MemoryManager) populateVMA(ctx context.Context, vseg vmaIterator, ar h
|
||||
// * vseg.Range().IsSupersetOf(ar).
|
||||
//
|
||||
// Postconditions: mm.mappingMu will be unlocked.
|
||||
// +checklocksrelease:mm.mappingMu
|
||||
func (mm *MemoryManager) populateVMAAndUnlock(ctx context.Context, vseg vmaIterator, ar hostarch.AddrRange, precommit bool) {
|
||||
// See populateVMA above for commentary.
|
||||
if !vseg.ValuePtr().effectivePerms.Any() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user