Skip to content

Commit

Permalink
interp: add custom handler for os.Stat/os.Lstat operations
Browse files Browse the repository at this point in the history
The interpreter has interfaces for all OS interactions except stat-ing files.
Add a new handler, noting that we need both Stat and Lstat.

See #630.
  • Loading branch information
ihar-orca authored May 9, 2022
1 parent bdb1e1d commit af386ca
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 30 deletions.
14 changes: 14 additions & 0 deletions interp/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ type Runner struct {
// glob expansion. It must be non-nil.
readDirHandler ReadDirHandlerFunc

// statHandler is a function responsible for getting file stat. It must be non-nil.
statHandler StatHandlerFunc

stdin io.Reader
stdout io.Writer
stderr io.Writer
Expand Down Expand Up @@ -174,6 +177,7 @@ func New(opts ...RunnerOption) (*Runner, error) {
execHandler: DefaultExecHandler(2 * time.Second),
openHandler: DefaultOpenHandler(),
readDirHandler: DefaultReadDirHandler(),
statHandler: DefaultStatHandler(),
}
r.dirStack = r.dirBootstrap[:0]
for _, opt := range opts {
Expand Down Expand Up @@ -336,6 +340,14 @@ func ReadDirHandler(f ReadDirHandlerFunc) RunnerOption {
}
}

// StatHandler sets the stat handler. See StatHandlerFunc for more info.
func StatHandler(f StatHandlerFunc) RunnerOption {
return func(r *Runner) error {
r.statHandler = f
return nil
}
}

// StdIO configures an interpreter's standard input, standard output, and
// standard error. If out or err are nil, they default to a writer that discards
// the output.
Expand Down Expand Up @@ -437,6 +449,7 @@ func (r *Runner) Reset() {
execHandler: r.execHandler,
openHandler: r.openHandler,
readDirHandler: r.readDirHandler,
statHandler: r.statHandler,

// These can be set by functions like Dir or Params, but
// builtins can overwrite them; reset the fields to whatever the
Expand Down Expand Up @@ -590,6 +603,7 @@ func (r *Runner) Subshell() *Runner {
execHandler: r.execHandler,
openHandler: r.openHandler,
readDirHandler: r.readDirHandler,
statHandler: r.statHandler,
stdin: r.stdin,
stdout: r.stdout,
stderr: r.stderr,
Expand Down
12 changes: 6 additions & 6 deletions interp/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
r.errf("usage: cd [dir]\n")
return 2
}
return r.changeDir(path)
return r.changeDir(ctx, path)
case "wait":
if len(args) > 0 {
panic("wait with args not handled yet")
Expand Down Expand Up @@ -493,13 +493,13 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
return 1
}
newtop := swap()
if code := r.changeDir(newtop); code != 0 {
if code := r.changeDir(ctx, newtop); code != 0 {
return code
}
r.builtinCode(ctx, syntax.Pos{}, "dirs", nil)
case 1:
if change {
if code := r.changeDir(args[0]); code != 0 {
if code := r.changeDir(ctx, args[0]); code != 0 {
return code
}
r.dirStack = append(r.dirStack, r.Dir)
Expand Down Expand Up @@ -528,7 +528,7 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
r.dirStack = r.dirStack[:len(r.dirStack)-1]
if change {
newtop := r.dirStack[len(r.dirStack)-1]
if code := r.changeDir(newtop); code != 0 {
if code := r.changeDir(ctx, newtop); code != 0 {
return code
}
} else {
Expand Down Expand Up @@ -852,12 +852,12 @@ func (r *Runner) readLine(raw bool) ([]byte, error) {
}
}

func (r *Runner) changeDir(path string) int {
func (r *Runner) changeDir(ctx context.Context, path string) int {
if path == "" {
path = "."
}
path = r.absPath(path)
info, err := r.stat(path)
info, err := r.stat(ctx, path)
if err != nil || !info.IsDir() {
return 1
}
Expand Down
15 changes: 15 additions & 0 deletions interp/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,18 @@ func DefaultReadDirHandler() ReadDirHandlerFunc {
return ioutil.ReadDir(path)
}
}

// StatHandlerFunc is a handler which gets the file stat. the first argument provides directory to use as
// basedir if name is relative path
type StatHandlerFunc func(ctx context.Context, name string, followSymlinks bool) (os.FileInfo, error)

// DefaultStatHandler returns a StatHandlerFunc used by default. It uses os.Stat()
func DefaultStatHandler() StatHandlerFunc {
return func(ctx context.Context, path string, followSymlinks bool) (os.FileInfo, error) {
if !followSymlinks {
return os.Lstat(path)
} else {
return os.Stat(path)
}
}
}
10 changes: 8 additions & 2 deletions interp/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,12 @@ func (r *Runner) open(ctx context.Context, path string, flags int, mode os.FileM
return f, err
}

func (r *Runner) stat(name string) (os.FileInfo, error) {
return os.Stat(r.absPath(name))
func (r *Runner) stat(ctx context.Context, name string) (os.FileInfo, error) {
path := absPath(r.Dir, name)
return r.statHandler(ctx, path, true)
}

func (r *Runner) lstat(ctx context.Context, name string) (os.FileInfo, error) {
path := absPath(r.Dir, name)
return r.statHandler(ctx, path, false)
}
44 changes: 22 additions & 22 deletions interp/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (r *Runner) bashTest(ctx context.Context, expr syntax.TestExpr, classic boo
}
return ""
}
if r.binTest(x.Op, r.bashTest(ctx, x.X, classic), r.bashTest(ctx, x.Y, classic)) {
if r.binTest(ctx, x.Op, r.bashTest(ctx, x.X, classic), r.bashTest(ctx, x.Y, classic)) {
return "1"
}
return ""
Expand All @@ -59,7 +59,7 @@ func (r *Runner) bashTest(ctx context.Context, expr syntax.TestExpr, classic boo
return ""
}

func (r *Runner) binTest(op syntax.BinTestOperator, x, y string) bool {
func (r *Runner) binTest(ctx context.Context, op syntax.BinTestOperator, x, y string) bool {
switch op {
case syntax.TsReMatch:
re, err := regexp.Compile(y)
Expand All @@ -69,22 +69,22 @@ func (r *Runner) binTest(op syntax.BinTestOperator, x, y string) bool {
}
return re.MatchString(x)
case syntax.TsNewer:
info1, err1 := r.stat(x)
info2, err2 := r.stat(y)
info1, err1 := r.stat(ctx, x)
info2, err2 := r.stat(ctx, y)
if err1 != nil || err2 != nil {
return false
}
return info1.ModTime().After(info2.ModTime())
case syntax.TsOlder:
info1, err1 := r.stat(x)
info2, err2 := r.stat(y)
info1, err1 := r.stat(ctx, x)
info2, err2 := r.stat(ctx, y)
if err1 != nil || err2 != nil {
return false
}
return info1.ModTime().Before(info2.ModTime())
case syntax.TsDevIno:
info1, err1 := r.stat(x)
info2, err2 := r.stat(y)
info1, err1 := r.stat(ctx, x)
info2, err2 := r.stat(ctx, y)
if err1 != nil || err2 != nil {
return false
}
Expand Down Expand Up @@ -112,40 +112,40 @@ func (r *Runner) binTest(op syntax.BinTestOperator, x, y string) bool {
}
}

func (r *Runner) statMode(name string, mode os.FileMode) bool {
info, err := r.stat(name)
func (r *Runner) statMode(ctx context.Context, name string, mode os.FileMode) bool {
info, err := r.stat(ctx, name)
return err == nil && info.Mode()&mode != 0
}

func (r *Runner) unTest(ctx context.Context, op syntax.UnTestOperator, x string) bool {
switch op {
case syntax.TsExists:
_, err := r.stat(x)
_, err := r.stat(ctx, x)
return err == nil
case syntax.TsRegFile:
info, err := r.stat(x)
info, err := r.stat(ctx, x)
return err == nil && info.Mode().IsRegular()
case syntax.TsDirect:
return r.statMode(x, os.ModeDir)
return r.statMode(ctx, x, os.ModeDir)
case syntax.TsCharSp:
return r.statMode(x, os.ModeCharDevice)
return r.statMode(ctx, x, os.ModeCharDevice)
case syntax.TsBlckSp:
info, err := r.stat(x)
info, err := r.stat(ctx, x)
return err == nil && info.Mode()&os.ModeDevice != 0 &&
info.Mode()&os.ModeCharDevice == 0
case syntax.TsNmPipe:
return r.statMode(x, os.ModeNamedPipe)
return r.statMode(ctx, x, os.ModeNamedPipe)
case syntax.TsSocket:
return r.statMode(x, os.ModeSocket)
return r.statMode(ctx, x, os.ModeSocket)
case syntax.TsSmbLink:
info, err := os.Lstat(r.absPath(x))
info, err := r.lstat(ctx, x)
return err == nil && info.Mode()&os.ModeSymlink != 0
case syntax.TsSticky:
return r.statMode(x, os.ModeSticky)
return r.statMode(ctx, x, os.ModeSticky)
case syntax.TsUIDSet:
return r.statMode(x, os.ModeSetuid)
return r.statMode(ctx, x, os.ModeSetuid)
case syntax.TsGIDSet:
return r.statMode(x, os.ModeSetgid)
return r.statMode(ctx, x, os.ModeSetgid)
// case syntax.TsGrpOwn:
// case syntax.TsUsrOwn:
// case syntax.TsModif:
Expand All @@ -165,7 +165,7 @@ func (r *Runner) unTest(ctx context.Context, op syntax.UnTestOperator, x string)
_, err := exec.LookPath(r.absPath(x))
return err == nil
case syntax.TsNoEmpty:
info, err := r.stat(x)
info, err := r.stat(ctx, x)
return err == nil && info.Size() > 0
case syntax.TsFdTerm:
fd := atoi(x)
Expand Down

0 comments on commit af386ca

Please sign in to comment.