-
Notifications
You must be signed in to change notification settings - Fork 226
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds program to allow syscalls to be canceled
- Loading branch information
Showing
12 changed files
with
272 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"time" | ||
|
||
"github.com/netapp/trident/internal/syswrap/unix" | ||
) | ||
|
||
var syscalls = map[string]func([]string) (interface{}, error){ | ||
"statfs": unix.Statfs, | ||
"exists": unix.Exists, | ||
} | ||
|
||
type result struct { | ||
output interface{} | ||
err error | ||
} | ||
|
||
func main() { | ||
timeout, syscall, args, err := parseArgs(os.Args) | ||
if err != nil { | ||
exit(err) | ||
} | ||
|
||
select { | ||
case <-time.After(timeout): | ||
exit(fmt.Errorf("timed out waiting for %s", syscall)) | ||
case res := <-func() chan result { | ||
r := make(chan result) | ||
go func() { | ||
s, ok := syscalls[syscall] | ||
if !ok { | ||
r <- result{ | ||
err: fmt.Errorf("unknown syscall: %s", syscall), | ||
} | ||
} | ||
i, e := s(args) | ||
r <- result{ | ||
output: i, | ||
err: e, | ||
} | ||
}() | ||
return r | ||
}(): | ||
if res.err != nil { | ||
exit(res.err) | ||
} | ||
_ = json.NewEncoder(os.Stdout).Encode(res.output) | ||
} | ||
} | ||
|
||
func exit(err error) { | ||
_, _ = fmt.Fprintf(os.Stderr, "error: %v", err) | ||
os.Exit(1) | ||
} | ||
|
||
func parseArgs(args []string) (timeout time.Duration, syscall string, syscallArgs []string, err error) { | ||
if len(args) < 3 { | ||
err = fmt.Errorf("expected at least 3 arguments") | ||
return | ||
} | ||
timeout, err = time.ParseDuration(args[1]) | ||
if err != nil { | ||
return | ||
} | ||
|
||
syscall = args[2] | ||
|
||
if len(args) > 3 { | ||
syscallArgs = args[3:] | ||
} | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# syswrap | ||
|
||
`syswrap` is a small program to wrap system calls with timeouts. In Go, syscalls cannot be cleaned up if they are | ||
blocked, for example if `lstat` is called on an inaccessible NFS mount, but because Go can create additional goroutines | ||
the calling application will not block. This can cause resource exhaustion if syscalls are not bounded. | ||
|
||
Linux will clean up syscalls if the owning process ends, so `syswrap` exists to allow Trident to create a new process | ||
that owns a potentially blocking syscall. | ||
|
||
The Trident-accessible interface is in this package. | ||
|
||
## Usage | ||
|
||
`syswrap <timeout> <syscall> <syscall args...>` | ||
|
||
`<timeout>` is in Go format, i.e. `30s` | ||
|
||
`<syscall>` is the name of the call, see `cmd/syswrap/main.go` | ||
|
||
`<syscall args...>` are string representations of any arguments required by the call. | ||
|
||
## Adding Syscalls | ||
|
||
See `syswrap.Exists` for a cross-platform example, and `syswrap.Statfs` for a Linux-only example. | ||
|
||
There are 3 steps to adding a new syscall: | ||
|
||
1. Add func to `internal/syswrap` package. This func calls the syswrap binary, and it should also call the syscall | ||
itself if the syswrap binary is not found. | ||
2. Add func to `internal/syswrap/unix` package. The func must have the signature | ||
`func(args []string) (output interface{}, err error)`, and parse its args then call the syscall. Any fields in output | ||
that need to be used by Trident must be exported. | ||
3. Add func name to `cmd/syswrap/main.syscalls` map. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
//go:build windows || darwin | ||
|
||
package syswrap | ||
|
||
import ( | ||
"context" | ||
"os" | ||
"time" | ||
) | ||
|
||
func Exists(_ context.Context, path string, _ time.Duration) (bool, error) { | ||
_, err := os.Stat(path) | ||
return err == nil, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// Package syswrap wraps syscalls that need to be canceled | ||
package syswrap | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"os" | ||
"time" | ||
|
||
"golang.org/x/sys/unix" | ||
|
||
"github.com/netapp/trident/utils/exec" | ||
) | ||
|
||
const syswrapBin = "/syswrap" | ||
|
||
func Statfs(ctx context.Context, path string, timeout time.Duration) (unix.Statfs_t, error) { | ||
buf, err := exec.NewCommand().Execute(ctx, syswrapBin, timeout.String(), "statfs", path) | ||
if err != nil { | ||
// If syswrap is unavailable fall back to blocking call. This may hang if NFS backend is unreachable | ||
var pe *os.PathError | ||
ok := errors.As(err, &pe) | ||
if !ok { | ||
return unix.Statfs_t{}, err | ||
} | ||
|
||
var fsStat unix.Statfs_t | ||
err = unix.Statfs(path, &fsStat) | ||
return fsStat, err | ||
} | ||
|
||
var b unix.Statfs_t | ||
err = json.Unmarshal(buf, &b) | ||
return b, err | ||
} | ||
|
||
func Exists(ctx context.Context, path string, timeout time.Duration) (bool, error) { | ||
buf, err := exec.NewCommand().Execute(ctx, syswrapBin, timeout.String(), "exists", path) | ||
if err != nil { | ||
// If syswrap is unavailable fall back to blocking call. This may hang if NFS backend is unreachable | ||
var pe *os.PathError | ||
ok := errors.As(err, &pe) | ||
if !ok { | ||
return false, err | ||
} | ||
|
||
_, err = os.Stat(path) | ||
return err == nil, err | ||
} | ||
|
||
var b bool | ||
err = json.Unmarshal(buf, &b) | ||
return b, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package syswrap | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
|
||
"github.com/netapp/trident/utils/exec" | ||
) | ||
|
||
// errors.Is should work to detect PathError from Execute, but it currently does not. If this test starts to fail | ||
// then the errors.As calls in this package should be converted to errors.Is. | ||
func TestSyswrapUnavailableRequiresAs(t *testing.T) { | ||
_, err := os.Stat(syswrapBin) | ||
assert.Error(t, err) | ||
wd, err := os.Getwd() | ||
assert.NoError(t, err) | ||
_, err = exec.NewCommand().Execute(context.Background(), syswrapBin, "1s", "statfs", wd) | ||
assert.Error(t, err) | ||
assert.False(t, errors.Is(err, &os.PathError{})) | ||
|
||
var pe *os.PathError | ||
assert.True(t, errors.As(err, &pe)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Package unix parses string arguments and calls the system call | ||
package unix | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"golang.org/x/sys/unix" | ||
) | ||
|
||
func Statfs(args []string) (interface{}, error) { | ||
if len(args) != 1 { | ||
return nil, fmt.Errorf("expected 1 argument") | ||
} | ||
var buf unix.Statfs_t | ||
err := unix.Statfs(args[0], &buf) | ||
return &buf, err | ||
} | ||
|
||
func Exists(args []string) (interface{}, error) { | ||
if len(args) != 1 { | ||
return nil, fmt.Errorf("expected 1 argument") | ||
} | ||
_, err := os.Stat(args[0]) | ||
exists := err == nil | ||
return &exists, err | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.