-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjurt-root-command
More file actions
executable file
·359 lines (326 loc) · 12.9 KB
/
Copy pathjurt-root-command
File metadata and controls
executable file
·359 lines (326 loc) · 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
#!/usr/bin/python
import sys
import os
import shlex
import subprocess
from jurtlib import util
from jurtlib.command import JurtCommand, CliError
from jurtlib.template import template_expand
MOUNT_TYPES = {
"proc": ("proc", "jurt-mounted-proc-for-you", "/proc"),
"sys": ("sysfs", "jurt-mounted-sys-for-you", "/sys"),
"pts": ("devpts", "jurt-kindly-mounted-pts-for-you", "/dev/pts")}
class RootCommand(JurtCommand):
descr = "Runs a command privilleged user"
usage = "%prog -t TYPE [options]"
def init_parser(self, parser):
super(RootCommand, self).init_parser(parser)
parser.add_option("-t", "--type", default=None,
help="Type of command to be run")
parser.add_option("--pm", default=None,
help="Package manager type used")
parser.add_option("--target", default=None,
help="Target name used (if applicable)")
parser.add_option("--dry-run", default=False, action="store_true",
help="Don't execute anything")
parser.add_option("--root", type="string", default=None,
help="execute inside chroot")
parser.add_option("--arch", type="string", default=None,
help="set the arch used by --root")
parser.add_option("--run-as", type="string", default=None,
metavar="USER", help="Become USER (after chroot)")
parser.add_option("-u", "--uid", type="string", default=None,
help="set UID used")
parser.add_option("-g", "--gid", type="string", default=None,
help="set GID used")
parser.add_option("-m", "--mode", type="string", default=None,
help="Destination file mode")
parser.add_option("--timeout", type="int", default=None,
help="Command execution timeout")
parser.add_option("--ignore-errors", default=False,
action="store_true",
help="Command execution timeout")
def config_files(self, config):
return [config.conf.system_file]
def run(self):
if not self.opts.type:
raise CliError, "--pm is mandatory"
mname = "cmd_" + self.opts.type
try:
getattr(self, mname)()
except AttributeError:
raise CliError, "invalid operation type: %s" % self.opts.type
def _requires_target(f):
def w(self):
if self.opts.target is None:
raise CliError, "--target is mandatory for this --type"
try:
target = self.jurt.targets[self.opts.target]
except KeyError:
raise CliError, "invalid target: %s" % (self.opts.target)
self.target = target
return f(self)
return w
def _requires_root(f):
def w(self):
if not self.opts.root:
raise CliError, "--root is required for this command"
return f(self)
return w
def _exec(self, args, exit=True, error=True, interactive=False):
allcmd = []
if self.opts.timeout is not None:
allcmd.extend(("timeout", str(self.opts.timeout)))
if self.opts.root:
if self.opts.arch:
sysarch = self.target.packagemanager.system_arch()
if self.opts.arch != sysarch:
allcmd.extend(self.target.rootmanager.setarch_command(sysarch,
self.opts.arch))
self.target.rootmanager.check_valid_subdir(self.opts.root)
allcmd.extend(shlex.split(self.config.root.chroot_command))
allcmd.append(self.opts.root)
if self.opts.run_as:
allcmd.extend(shlex.split(self.config.root.su_command))
allcmd.append(self.opts.run_as)
allcmd.append("-c")
allcmd.append(subprocess.list2cmdline(args)) # blarg
else:
allcmd.extend(args)
cmdline = subprocess.list2cmdline(allcmd)
if not interactive:
sys.stderr.write(">>>>>> running: %s\n" % (cmdline))
sys.stderr.flush()
if not self.opts.dry_run:
p = subprocess.Popen(args=allcmd, shell=False)
p.wait()
if not self.opts.ignore_errors:
if p.returncode != 0:
msg = ("command failed with %d (output above "
"^^^^^^^^^^)\n" % p.returncode)
# in case of timeout (err 124), only return the error
# code to the caller
if exit and not (self.opts.timeout
and p.returncode == 124):
raise CliError, msg
else:
if error:
sys.stderr.write(msg + "\n")
sys.exit(p.returncode)
@_requires_target
def cmd_runpm(self):
if self.opts.pm is None:
raise CliError, "the option --pm is mandatory"
self.target.packagemanager.validate_cmd_args(self.opts.pm, self.args)
self._exec(self.target.packagemanager.cmd_args(self.opts.pm, self.args))
def _install_cmd(self, dir=False):
cmd = [self.config.root.install_command]
if dir:
cmd.append("-d")
if self.opts.uid:
cmd.extend(("-o", self.opts.uid))
if self.opts.gid:
cmd.extend(("-g", self.opts.gid))
if self.opts.mode:
cmd.extend(("-m", self.opts.mode))
return cmd
@_requires_target
def cmd_adduser(self):
if not self.args:
raise CliError, "you must provide an username"
cmd = shlex.split(self.config.root.adduser_command)
cmd.extend(("-u", self.opts.uid))
cmd.append(self.args[0])
if not self.opts.dry_run:
self._exec(cmd)
@_requires_target
def cmd_copy(self):
if len(self.args) < 2:
raise CliError, "copy requires two operands"
source = self.args[0]
dest = self.args[1]
self.target.rootmanager.check_valid_subdir(dest)
cmd = self._install_cmd()
cmd.append(source)
cmd.append(dest)
if not self.opts.dry_run:
self._exec(cmd)
@_requires_target
def cmd_copyout(self):
if len(self.args) < 2:
raise CliError, "copy requires two operands"
source = self.args[0]
dest = self.args[1]
self.target.rootmanager.check_valid_subdir(source)
self.target.rootmanager.check_valid_outdir(dest)
cmd = self._install_cmd()
cmd.append(source)
cmd.append(dest)
if not self.opts.dry_run:
self._exec(cmd)
@_requires_target
def cmd_cheapcopy(self):
if len(self.args) < 2:
raise CliError, "copy requires two operands"
source = self.args[0]
dest = self.args[1]
self.target.rootmanager.check_valid_subdir(dest)
copyopts = "-af"
if util.same_partition(source, dest):
copyopts += "l"
cmd = ["cp", copyopts, source, dest]
if not self.opts.dry_run:
self._exec(cmd)
@_requires_target
def cmd_mkdir(self):
for arg in self.args:
self.target.rootmanager.check_valid_subdir(arg)
cmd = self._install_cmd(dir=True)
cmd.append(arg)
if not self.opts.dry_run:
self._exec(cmd)
def _check_build_user(self, username):
# checks whether the user being used inside the chroot is the one
# set in configuration, if not, then it must be some user that is
# member of the jurt group
import grp
sysbuilder = self.target.builder.build_user_info()[0]
if username != sysbuilder:
groupname = self.config.root.jurt_group
try:
group = grp.getgrnam(groupname)
except KeyError:
raise CliError, ("the group %s does not exist, cannot check "
"--run-as" % (groupname))
if username not in group.gr_mem:
raise CliError, ("the user %s is not a member of the group "
"%s" % (username, groupname))
@_requires_target
@_requires_root
def cmd_runcmd(self):
if not self.opts.run_as:
raise CliError, "--run-as is required for run"
self._check_build_user(self.opts.run_as)
if not self.opts.dry_run:
self._exec(self.args)
def _mount_info(self):
if not self.args:
raise CliError, "you must provide a mount type"
typename = self.args[0]
try:
mountinfo = MOUNT_TYPES[typename]
except KeyError:
raise CliError, "invalid mount type: %s" % (typename)
return mountinfo
@_requires_target
@_requires_root
def cmd_mount(self):
fsname, devpath, mountpoint = self._mount_info()
args = ["mount", "-t", fsname, devpath, mountpoint]
if not self.opts.dry_run:
self._exec(args)
@_requires_target
@_requires_root
def cmd_umount(self):
_, _, mountpoint = self._mount_info()
args = ["umount", mountpoint]
if not self.opts.dry_run:
self._exec(args)
def _tmp_cachepath(self, cachepath):
import tempfile
base = os.path.dirname(cachepath)
prefix = os.path.basename(cachepath) + "."
return tempfile.mktemp(dir=base, prefix=prefix)
def _comp_decomp(self, comp=False):
if not self.args:
raise CliError, "a root path is mandatory"
root = self.args[0]
file = self.args[1]
self.target.rootmanager.check_valid_subdir(root)
try:
if comp:
fun = self.target.rootmanager.root_compress_command
else:
fun = self.target.rootmanager.root_decompress_command
except AttributeError:
raise CliError, "this target doesn't support using compressed root"
if comp:
tmpname = self._tmp_cachepath(file)
else:
tmpname = file
args = fun(root, tmpname)
if not self.opts.dry_run:
self._exec(args, exit=False)
if comp:
os.rename(tmpname, file)
@_requires_target
def cmd_rootcompress(self):
self._comp_decomp(True)
@_requires_target
def cmd_rootdecompress(self):
self._comp_decomp(False)
@_requires_target
@_requires_root
def cmd_postcommand(self):
args = shlex.split(self.config.root.su_for_post_command)
args.append(self.target.rootmanager.post_command())
if not self.opts.dry_run:
self._exec(args)
@_requires_target
@_requires_root
def cmd_interactiveshell(self):
if not self.args:
raise CliError, "username is mandatory in args"
if not self.target.rootmanager.allows_interactive_shell():
raise CliError, "interactive configuration not allowed "\
"for this target"
args = shlex.split(self.config.root.sudo_interactive_shell_command)
args.extend(("-u", self.args[0]))
rawcmd = self.config.root.interactive_shell_command
env = {"target": self.target.name, "root": self.opts.root}
cmdline = template_expand(rawcmd, env)
args.extend(shlex.split(cmdline))
if not self.opts.dry_run:
self._exec(args, error=False, interactive=True)
@_requires_target
@_requires_root
def cmd_interactiveprepare(self):
if not self.args:
raise CliError, "username is mandatory in args"
user = self.args[0]
allowedcmds = self.target.packagemanager.allowed_pm_commands()
cmdsline = ",".join(allowedcmds)
rawline = self.config.root.sudo_pm_allow_format
env = {"user": user, "commands": cmdsline}
sudoline = template_expand(rawline, env)
sudoerspath = os.path.abspath(self.opts.root+ "/" +
self.config.root.sudoers)
f = open(sudoerspath, "a")
f.write(sudoline + "\n")
f.close()
def cmd_test(self):
if os.geteuid() != 0:
sys.stderr.write("error: i am not root\n")
sys.exit(1)
@_requires_target
def cmd_btrfssnapshot(self):
if len(self.args) != 2:
raise CliError, "unexpected number of args"
from_ = self.args[0]
to = self.args[1]
self.target.rootmanager.check_valid_subdir(from_)
self.target.rootmanager.check_valid_subdir(to)
args = self.target.rootmanager.snapsvcmd[:]
args.append(from_)
args.append(to)
self._exec(args)
@_requires_target
def cmd_btrfscreate(self):
if len(self.args) != 1:
raise CliError, "unexpected number of args"
dest = self.args[0]
self.target.rootmanager.check_valid_subdir(dest)
args = self.target.rootmanager.newsvcmd[:]
args.append(dest)
self._exec(args)
RootCommand().main()