Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions go/kbfs/libfuse/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,18 +192,27 @@ func (f *File) sync(ctx context.Context) error {

// Fsync implements the fs.NodeFsyncer interface for File.
func (f *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) (err error) {
ctx, maybeUnmounting, cancel := wrapCtxWithShorterTimeoutForUnmount(f.folder.fs.log, ctx, int(req.Pid))
defer cancel()
if maybeUnmounting {
f.folder.fs.log.CInfof(ctx, "Fsync: maybeUnmounting=%v", maybeUnmounting)
Comment thread
songgao marked this conversation as resolved.
Outdated
}

ctx = f.folder.fs.config.MaybeStartTrace(
ctx, "File.Fsync", f.node.GetBasename().String())
defer func() { f.folder.fs.config.MaybeFinishTrace(ctx, err) }()

f.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "File Fsync")
defer func() { err = f.folder.processError(ctx, libkbfs.WriteMode, err) }()

// This fits in situation 1 as described in libkbfs/delayed_cancellation.go
err = libcontext.EnableDelayedCancellationWithGracePeriod(
ctx, f.folder.fs.config.DelayedCancellationGracePeriod())
if err != nil {
return err
if !maybeUnmounting {
// This fits in situation 1 as described in
// libkbfs/delayed_cancellation.go
err = libcontext.EnableDelayedCancellationWithGracePeriod(
ctx, f.folder.fs.config.DelayedCancellationGracePeriod())
if err != nil {
return err
}
}

return f.sync(ctx)
Expand Down
36 changes: 36 additions & 0 deletions go/kbfs/libfuse/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,8 +386,44 @@ func (f *FS) Root() (fs.Node, error) {
// a fresh RPC call if cached usage data is older than 10s.
const quotaUsageStaleTolerance = 10 * time.Second

const unmountCallTolerance = time.Second

var unmountingExecPaths = map[string]bool{
"/usr/sbin/diskutil": true,
"/usr/libexec/lsd": true,
"/sbin/umount": true,
}

var noop = func() {}

// wrapCtxWithShorterTimeoutForUnmount wraps ctx witha a timeout of
Comment thread
songgao marked this conversation as resolved.
Outdated
// unmountCallTolerance if pid is /usr/sbin/diskutil, /usr/libexec/lsd, or
// /sbin/umount. This is useful for calls that usually happen during unmounting
// such as Statfs and Fsync. If we block on those calls, `diskutil umount force
// <mnt>` is blocked as well. So make them timeout after 2s to make unmounting
// work.
func wrapCtxWithShorterTimeoutForUnmount(log logger.Logger,
ctx context.Context, pid int) (newCtx context.Context, maybeUnmounting bool, cancel context.CancelFunc) {
p, err := pidPath(pid)
if err != nil {
return ctx, false, noop
}
if unmountingExecPaths[p] {
log.CDebugf(ctx, "wrapping context with timeout for %s", p)
newCtx, cancel = context.WithTimeout(ctx, unmountCallTolerance)
return newCtx, true, cancel
}
return ctx, false, noop
}

// Statfs implements the fs.FSStatfser interface for FS.
func (f *FS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) error {
ctx, maybeUnmounting, cancel := wrapCtxWithShorterTimeoutForUnmount(f.log, ctx, int(req.Pid))
defer cancel()
if maybeUnmounting {
f.log.CInfof(ctx, "Statfs: maybeUnmounting=true")
}

*resp = fuse.StatfsResponse{
Bsize: fuseBlockSize,
Namelen: ^uint32(0),
Expand Down
41 changes: 41 additions & 0 deletions go/kbfs/libfuse/pidpath_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2021 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.
//
// +build darwin

package libfuse

// #include <libproc.h>
// #include <stdlib.h>
// #include <errno.h>
import "C"

import (
"errors"
"strconv"
"unsafe"
)

// pidPath returns the exec path for process pid. Adapted from
// https://ops.tips/blog/macos-pid-absolute-path-and-procfs-exploration/
func pidPath(pid int) (path string, err error) {
const bufSize = C.PROC_PIDPATHINFO_MAXSIZE
buf := C.CString(string(make([]byte, bufSize)))
defer C.free(unsafe.Pointer(buf))

ret, err := C.proc_pidpath(C.int(pid), unsafe.Pointer(buf), bufSize)
if err != nil {
return "", err
}
if ret < 0 {
return "", errors.New(
"error calling proc_pidpath. exit code: " + strconv.Itoa(int(ret)))
}
if ret == 0 {
return "", errors.New("proc_pidpath returned empty buffer")
}

path = C.GoString(buf)
return
}
15 changes: 15 additions & 0 deletions go/kbfs/libfuse/pidpath_others.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2021 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.
//
// +build !darwin

package libfuse

import "github.com/pkg/errors"

var notImplementedErr = errors.New("unimplemented")

func pidPath(pid int) (path string, err error) {
Comment thread
songgao marked this conversation as resolved.
Outdated
return "", notImplementedErr
}