1// Copyright (c) 2017, Daniel MartΓ <mvdan@mvdan.cc>
2// See LICENSE for licensing information
3
4package interp
5
6import (
7 "context"
8 "fmt"
9 "os"
10 "regexp"
11
12 "golang.org/x/term"
13
14 "mvdan.cc/sh/v3/expand"
15 "mvdan.cc/sh/v3/syntax"
16)
17
18// non-empty string is true, empty string is false
19func (r *Runner) bashTest(ctx context.Context, expr syntax.TestExpr, classic bool) string {
20 switch x := expr.(type) {
21 case *syntax.Word:
22 if classic {
23 // In the classic "test" mode, we already expanded and
24 // split the list of words, so don't redo that work.
25 return r.document(x)
26 }
27 return r.literal(x)
28 case *syntax.ParenTest:
29 return r.bashTest(ctx, x.X, classic)
30 case *syntax.BinaryTest:
31 switch x.Op {
32 case syntax.TsMatchShort, syntax.TsMatch, syntax.TsNoMatch:
33 str := r.literal(x.X.(*syntax.Word))
34 yw := x.Y.(*syntax.Word)
35 if classic { // test, [
36 lit := r.literal(yw)
37 if (str == lit) == (x.Op != syntax.TsNoMatch) {
38 return "1"
39 }
40 } else { // [[
41 pattern := r.pattern(yw)
42 if match(pattern, str) == (x.Op != syntax.TsNoMatch) {
43 return "1"
44 }
45 }
46 return ""
47 }
48 if r.binTest(ctx, x.Op, r.bashTest(ctx, x.X, classic), r.bashTest(ctx, x.Y, classic)) {
49 return "1"
50 }
51 return ""
52 case *syntax.UnaryTest:
53 if r.unTest(ctx, x.Op, r.bashTest(ctx, x.X, classic)) {
54 return "1"
55 }
56 return ""
57 }
58 return ""
59}
60
61func (r *Runner) binTest(ctx context.Context, op syntax.BinTestOperator, x, y string) bool {
62 switch op {
63 case syntax.TsReMatch:
64 re, err := regexp.Compile(y)
65 if err != nil {
66 r.exit = 2
67 return false
68 }
69 return re.MatchString(x)
70 case syntax.TsNewer:
71 info1, err1 := r.stat(ctx, x)
72 info2, err2 := r.stat(ctx, y)
73 if err1 != nil || err2 != nil {
74 return false
75 }
76 return info1.ModTime().After(info2.ModTime())
77 case syntax.TsOlder:
78 info1, err1 := r.stat(ctx, x)
79 info2, err2 := r.stat(ctx, y)
80 if err1 != nil || err2 != nil {
81 return false
82 }
83 return info1.ModTime().Before(info2.ModTime())
84 case syntax.TsDevIno:
85 info1, err1 := r.stat(ctx, x)
86 info2, err2 := r.stat(ctx, y)
87 if err1 != nil || err2 != nil {
88 return false
89 }
90 return os.SameFile(info1, info2)
91 case syntax.TsEql:
92 return atoi(x) == atoi(y)
93 case syntax.TsNeq:
94 return atoi(x) != atoi(y)
95 case syntax.TsLeq:
96 return atoi(x) <= atoi(y)
97 case syntax.TsGeq:
98 return atoi(x) >= atoi(y)
99 case syntax.TsLss:
100 return atoi(x) < atoi(y)
101 case syntax.TsGtr:
102 return atoi(x) > atoi(y)
103 case syntax.AndTest:
104 return x != "" && y != ""
105 case syntax.OrTest:
106 return x != "" || y != ""
107 case syntax.TsBefore:
108 return x < y
109 default: // syntax.TsAfter
110 return x > y
111 }
112}
113
114func (r *Runner) statMode(ctx context.Context, name string, mode os.FileMode) bool {
115 info, err := r.stat(ctx, name)
116 return err == nil && info.Mode()&mode != 0
117}
118
119// These are copied from x/sys/unix as we can't import it here.
120const (
121 access_R_OK = 0x4
122 access_W_OK = 0x2
123 access_X_OK = 0x1
124)
125
126func (r *Runner) unTest(ctx context.Context, op syntax.UnTestOperator, x string) bool {
127 switch op {
128 case syntax.TsExists:
129 _, err := r.stat(ctx, x)
130 return err == nil
131 case syntax.TsRegFile:
132 info, err := r.stat(ctx, x)
133 return err == nil && info.Mode().IsRegular()
134 case syntax.TsDirect:
135 return r.statMode(ctx, x, os.ModeDir)
136 case syntax.TsCharSp:
137 return r.statMode(ctx, x, os.ModeCharDevice)
138 case syntax.TsBlckSp:
139 info, err := r.stat(ctx, x)
140 return err == nil && info.Mode()&os.ModeDevice != 0 &&
141 info.Mode()&os.ModeCharDevice == 0
142 case syntax.TsNmPipe:
143 return r.statMode(ctx, x, os.ModeNamedPipe)
144 case syntax.TsSocket:
145 return r.statMode(ctx, x, os.ModeSocket)
146 case syntax.TsSmbLink:
147 info, err := r.lstat(ctx, x)
148 return err == nil && info.Mode()&os.ModeSymlink != 0
149 case syntax.TsSticky:
150 return r.statMode(ctx, x, os.ModeSticky)
151 case syntax.TsUIDSet:
152 return r.statMode(ctx, x, os.ModeSetuid)
153 case syntax.TsGIDSet:
154 return r.statMode(ctx, x, os.ModeSetgid)
155 // case syntax.TsGrpOwn:
156 // case syntax.TsUsrOwn:
157 // case syntax.TsModif:
158 case syntax.TsRead:
159 return r.access(ctx, r.absPath(x), access_R_OK) == nil
160 case syntax.TsWrite:
161 return r.access(ctx, r.absPath(x), access_W_OK) == nil
162 case syntax.TsExec:
163 return r.access(ctx, r.absPath(x), access_X_OK) == nil
164 case syntax.TsNoEmpty:
165 info, err := r.stat(ctx, x)
166 return err == nil && info.Size() > 0
167 case syntax.TsFdTerm:
168 fd := atoi(x)
169 var f any
170 switch fd {
171 case 0:
172 f = r.stdin
173 case 1:
174 f = r.stdout
175 case 2:
176 f = r.stderr
177 }
178 if f, ok := f.(interface{ Fd() uintptr }); ok {
179 // Support [os.File.Fd] methods such as the one on [*os.File].
180 return term.IsTerminal(int(f.Fd()))
181 }
182 // TODO: allow term.IsTerminal here too if running in the
183 // "single process" mode.
184 return false
185 case syntax.TsEmpStr:
186 return x == ""
187 case syntax.TsNempStr:
188 return x != ""
189 case syntax.TsOptSet:
190 if _, opt := r.optByName(x, false); opt != nil {
191 return *opt
192 }
193 return false
194 case syntax.TsVarSet:
195 return r.lookupVar(x).IsSet()
196 case syntax.TsRefVar:
197 return r.lookupVar(x).Kind == expand.NameRef
198 case syntax.TsNot:
199 return x == ""
200 case syntax.TsUsrOwn, syntax.TsGrpOwn:
201 return r.unTestOwnOrGrp(ctx, op, x)
202 default:
203 panic(fmt.Sprintf("unhandled unary test op: %v", op))
204 }
205}