Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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=true")
}

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
11 changes: 6 additions & 5 deletions go/kbfs/libfuse/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,13 +381,14 @@ func (f *FS) Root() (fs.Node, error) {
return f.root, nil
}

// quotaUsageStaleTolerance is the lifespan of stale usage data that libfuse
// accepts in the Statfs handler. In other words, this causes libkbfs to issue
// a fresh RPC call if cached usage data is older than 10s.
const quotaUsageStaleTolerance = 10 * time.Second

// 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
15 changes: 14 additions & 1 deletion go/kbfs/libfuse/mounter_non_osx.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

package libfuse

import "bazil.org/fuse"
import (
"context"

"bazil.org/fuse"
"github.com/keybase/client/go/logger"
)

func getPlatformSpecificMountOptions(dir string, platformParams PlatformParams) ([]fuse.MountOption, error) {
options := []fuse.MountOption{}
Expand All @@ -27,3 +32,11 @@ func translatePlatformSpecificError(err error, platformParams PlatformParams) er
func (m *mounter) reinstallMountDirIfPossible() {
// no-op
}

var noop = func() {}

func wrapCtxWithShorterTimeoutForUnmount(
_ logger.Logger, ctx context.Context, _ int) (
newCtx context.Context, maybeUnmounting bool, cancel context.CancelFunc) {
return ctx, false, noop
}
39 changes: 39 additions & 0 deletions go/kbfs/libfuse/mounter_osx.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
package libfuse

import (
"context"
"errors"
"time"

"bazil.org/fuse"
"github.com/keybase/client/go/install/libnativeinstaller"
"github.com/keybase/client/go/logger"
)

var kbfusePath = fuse.OSXFUSEPaths{
Expand Down Expand Up @@ -84,3 +87,39 @@ func (m *mounter) reinstallMountDirIfPossible() {
err = libnativeinstaller.InstallMountDir(m.runMode, m.log)
m.log.Debug("InstallMountDir: err=%v", err)
}

// quotaUsageStaleTolerance is the lifespan of stale usage data that libfuse
// accepts in the Statfs handler. In other words, this causes libkbfs to issue
// 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
// 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
}
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(_ int) (path string, err error) {
return "", notImplementedErr
}
4 changes: 3 additions & 1 deletion go/mounter/mounter_non_osx.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

package mounter

import "fmt"
import (
"fmt"
)

// IsMounted returns true if directory is mounted (by kbfuse)
func IsMounted(dir string, log Log) (bool, error) {
Expand Down