| 1 | #!/usr/bin/env python2
 | 
| 2 | """
 | 
| 3 | builtin_process.py - Builtins that deal with processes or modify process state.
 | 
| 4 | 
 | 
| 5 | This is sort of the opposite of builtin_pure.py.
 | 
| 6 | """
 | 
| 7 | from __future__ import print_function
 | 
| 8 | 
 | 
| 9 | import resource
 | 
| 10 | from resource import (RLIM_INFINITY, RLIMIT_CORE, RLIMIT_CPU, RLIMIT_DATA,
 | 
| 11 |                       RLIMIT_FSIZE, RLIMIT_NOFILE, RLIMIT_STACK, RLIMIT_AS)
 | 
| 12 | from signal import SIGCONT
 | 
| 13 | 
 | 
| 14 | from _devbuild.gen import arg_types
 | 
| 15 | from _devbuild.gen.syntax_asdl import loc
 | 
| 16 | from _devbuild.gen.runtime_asdl import (cmd_value, job_state_e, wait_status,
 | 
| 17 |                                         wait_status_e)
 | 
| 18 | from core import dev
 | 
| 19 | from core import error
 | 
| 20 | from core.error import e_usage, e_die_status
 | 
| 21 | from core import process  # W1_OK, W1_ECHILD
 | 
| 22 | from core import pyos
 | 
| 23 | from core import pyutil
 | 
| 24 | from core import vm
 | 
| 25 | from frontend import flag_util
 | 
| 26 | from frontend import typed_args
 | 
| 27 | from mycpp import mops
 | 
| 28 | from mycpp import mylib
 | 
| 29 | from mycpp.mylib import log, tagswitch, print_stderr
 | 
| 30 | 
 | 
| 31 | import posix_ as posix
 | 
| 32 | 
 | 
| 33 | from typing import TYPE_CHECKING, List, Tuple, Optional, cast
 | 
| 34 | if TYPE_CHECKING:
 | 
| 35 |     from core.process import Waiter, ExternalProgram, FdState
 | 
| 36 |     from core.state import Mem, SearchPath
 | 
| 37 |     from core.ui import ErrorFormatter
 | 
| 38 | 
 | 
| 39 | 
 | 
| 40 | class Jobs(vm._Builtin):
 | 
| 41 |     """List jobs."""
 | 
| 42 | 
 | 
| 43 |     def __init__(self, job_list):
 | 
| 44 |         # type: (process.JobList) -> None
 | 
| 45 |         self.job_list = job_list
 | 
| 46 | 
 | 
| 47 |     def Run(self, cmd_val):
 | 
| 48 |         # type: (cmd_value.Argv) -> int
 | 
| 49 | 
 | 
| 50 |         attrs, arg_r = flag_util.ParseCmdVal('jobs', cmd_val)
 | 
| 51 |         arg = arg_types.jobs(attrs.attrs)
 | 
| 52 | 
 | 
| 53 |         if arg.l:
 | 
| 54 |             style = process.STYLE_LONG
 | 
| 55 |         elif arg.p:
 | 
| 56 |             style = process.STYLE_PID_ONLY
 | 
| 57 |         else:
 | 
| 58 |             style = process.STYLE_DEFAULT
 | 
| 59 | 
 | 
| 60 |         self.job_list.DisplayJobs(style)
 | 
| 61 | 
 | 
| 62 |         if arg.debug:
 | 
| 63 |             self.job_list.DebugPrint()
 | 
| 64 | 
 | 
| 65 |         return 0
 | 
| 66 | 
 | 
| 67 | 
 | 
| 68 | class Fg(vm._Builtin):
 | 
| 69 |     """Put a job in the foreground."""
 | 
| 70 | 
 | 
| 71 |     def __init__(self, job_control, job_list, waiter):
 | 
| 72 |         # type: (process.JobControl, process.JobList, Waiter) -> None
 | 
| 73 |         self.job_control = job_control
 | 
| 74 |         self.job_list = job_list
 | 
| 75 |         self.waiter = waiter
 | 
| 76 | 
 | 
| 77 |     def Run(self, cmd_val):
 | 
| 78 |         # type: (cmd_value.Argv) -> int
 | 
| 79 | 
 | 
| 80 |         job_spec = ''  # get current job by default
 | 
| 81 |         if len(cmd_val.argv) > 1:
 | 
| 82 |             job_spec = cmd_val.argv[1]
 | 
| 83 | 
 | 
| 84 |         job = self.job_list.GetJobWithSpec(job_spec)
 | 
| 85 |         if job is None:
 | 
| 86 |             log('No job to put in the foreground')
 | 
| 87 |             return 1
 | 
| 88 | 
 | 
| 89 |         pgid = job.ProcessGroupId()
 | 
| 90 |         assert pgid != process.INVALID_PGID, \
 | 
| 91 |             'Processes put in the background should have a PGID'
 | 
| 92 | 
 | 
| 93 |         # TODO: Print job ID rather than the PID
 | 
| 94 |         log('Continue PID %d', pgid)
 | 
| 95 |         # Put the job's process group back into the foreground. GiveTerminal() must
 | 
| 96 |         # be called before sending SIGCONT or else the process might immediately get
 | 
| 97 |         # suspsended again if it tries to read/write on the terminal.
 | 
| 98 |         self.job_control.MaybeGiveTerminal(pgid)
 | 
| 99 |         job.SetForeground()
 | 
| 100 |         # needed for Wait() loop to work
 | 
| 101 |         job.state = job_state_e.Running
 | 
| 102 |         posix.killpg(pgid, SIGCONT)
 | 
| 103 | 
 | 
| 104 |         status = -1
 | 
| 105 |         wait_st = job.JobWait(self.waiter)
 | 
| 106 |         UP_wait_st = wait_st
 | 
| 107 |         with tagswitch(wait_st) as case:
 | 
| 108 |             if case(wait_status_e.Proc):
 | 
| 109 |                 wait_st = cast(wait_status.Proc, UP_wait_st)
 | 
| 110 |                 status = wait_st.code
 | 
| 111 | 
 | 
| 112 |             elif case(wait_status_e.Pipeline):
 | 
| 113 |                 wait_st = cast(wait_status.Pipeline, UP_wait_st)
 | 
| 114 |                 # TODO: handle PIPESTATUS?  Is this right?
 | 
| 115 |                 status = wait_st.codes[-1]
 | 
| 116 | 
 | 
| 117 |             elif case(wait_status_e.Cancelled):
 | 
| 118 |                 wait_st = cast(wait_status.Cancelled, UP_wait_st)
 | 
| 119 |                 status = 128 + wait_st.sig_num
 | 
| 120 | 
 | 
| 121 |             else:
 | 
| 122 |                 raise AssertionError()
 | 
| 123 | 
 | 
| 124 |         return status
 | 
| 125 | 
 | 
| 126 | 
 | 
| 127 | class Bg(vm._Builtin):
 | 
| 128 |     """Put a job in the background."""
 | 
| 129 | 
 | 
| 130 |     def __init__(self, job_list):
 | 
| 131 |         # type: (process.JobList) -> None
 | 
| 132 |         self.job_list = job_list
 | 
| 133 | 
 | 
| 134 |     def Run(self, cmd_val):
 | 
| 135 |         # type: (cmd_value.Argv) -> int
 | 
| 136 | 
 | 
| 137 |         # How does this differ from 'fg'?  It doesn't wait and it sets controlling
 | 
| 138 |         # terminal?
 | 
| 139 | 
 | 
| 140 |         raise error.Usage("isn't implemented", loc.Missing)
 | 
| 141 | 
 | 
| 142 | 
 | 
| 143 | class Fork(vm._Builtin):
 | 
| 144 | 
 | 
| 145 |     def __init__(self, shell_ex):
 | 
| 146 |         # type: (vm._Executor) -> None
 | 
| 147 |         self.shell_ex = shell_ex
 | 
| 148 | 
 | 
| 149 |     def Run(self, cmd_val):
 | 
| 150 |         # type: (cmd_value.Argv) -> int
 | 
| 151 |         _, arg_r = flag_util.ParseCmdVal('fork',
 | 
| 152 |                                          cmd_val,
 | 
| 153 |                                          accept_typed_args=True)
 | 
| 154 | 
 | 
| 155 |         arg, location = arg_r.Peek2()
 | 
| 156 |         if arg is not None:
 | 
| 157 |             e_usage('got unexpected argument %r' % arg, location)
 | 
| 158 | 
 | 
| 159 |         cmd = typed_args.OptionalBlock(cmd_val)
 | 
| 160 |         if cmd is None:
 | 
| 161 |             e_usage('expected a block', loc.Missing)
 | 
| 162 | 
 | 
| 163 |         return self.shell_ex.RunBackgroundJob(cmd)
 | 
| 164 | 
 | 
| 165 | 
 | 
| 166 | class ForkWait(vm._Builtin):
 | 
| 167 | 
 | 
| 168 |     def __init__(self, shell_ex):
 | 
| 169 |         # type: (vm._Executor) -> None
 | 
| 170 |         self.shell_ex = shell_ex
 | 
| 171 | 
 | 
| 172 |     def Run(self, cmd_val):
 | 
| 173 |         # type: (cmd_value.Argv) -> int
 | 
| 174 |         _, arg_r = flag_util.ParseCmdVal('forkwait',
 | 
| 175 |                                          cmd_val,
 | 
| 176 |                                          accept_typed_args=True)
 | 
| 177 |         arg, location = arg_r.Peek2()
 | 
| 178 |         if arg is not None:
 | 
| 179 |             e_usage('got unexpected argument %r' % arg, location)
 | 
| 180 | 
 | 
| 181 |         cmd = typed_args.OptionalBlock(cmd_val)
 | 
| 182 |         if cmd is None:
 | 
| 183 |             e_usage('expected a block', loc.Missing)
 | 
| 184 | 
 | 
| 185 |         return self.shell_ex.RunSubshell(cmd)
 | 
| 186 | 
 | 
| 187 | 
 | 
| 188 | class Exec(vm._Builtin):
 | 
| 189 | 
 | 
| 190 |     def __init__(self, mem, ext_prog, fd_state, search_path, errfmt):
 | 
| 191 |         # type: (Mem, ExternalProgram, FdState, SearchPath, ErrorFormatter) -> None
 | 
| 192 |         self.mem = mem
 | 
| 193 |         self.ext_prog = ext_prog
 | 
| 194 |         self.fd_state = fd_state
 | 
| 195 |         self.search_path = search_path
 | 
| 196 |         self.errfmt = errfmt
 | 
| 197 | 
 | 
| 198 |     def Run(self, cmd_val):
 | 
| 199 |         # type: (cmd_value.Argv) -> int
 | 
| 200 |         _, arg_r = flag_util.ParseCmdVal('exec', cmd_val)
 | 
| 201 | 
 | 
| 202 |         # Apply redirects in this shell.  # NOTE: Redirects were processed earlier.
 | 
| 203 |         if arg_r.AtEnd():
 | 
| 204 |             self.fd_state.MakePermanent()
 | 
| 205 |             return 0
 | 
| 206 | 
 | 
| 207 |         environ = self.mem.GetExported()
 | 
| 208 |         i = arg_r.i
 | 
| 209 |         cmd = cmd_val.argv[i]
 | 
| 210 |         argv0_path = self.search_path.CachedLookup(cmd)
 | 
| 211 |         if argv0_path is None:
 | 
| 212 |             e_die_status(127, 'exec: %r not found' % cmd, cmd_val.arg_locs[1])
 | 
| 213 | 
 | 
| 214 |         # shift off 'exec', and remove typed args because they don't apply
 | 
| 215 |         c2 = cmd_value.Argv(cmd_val.argv[i:], cmd_val.arg_locs[i:], None, None,
 | 
| 216 |                             None, None)
 | 
| 217 | 
 | 
| 218 |         self.ext_prog.Exec(argv0_path, c2, environ)  # NEVER RETURNS
 | 
| 219 |         # makes mypy and C++ compiler happy
 | 
| 220 |         raise AssertionError('unreachable')
 | 
| 221 | 
 | 
| 222 | 
 | 
| 223 | class Wait(vm._Builtin):
 | 
| 224 |     """
 | 
| 225 |     wait: wait [-n] [id ...]
 | 
| 226 |         Wait for job completion and return exit status.
 | 
| 227 | 
 | 
| 228 |         Waits for each process identified by an ID, which may be a process ID or a
 | 
| 229 |         job specification, and reports its termination status.  If ID is not
 | 
| 230 |         given, waits for all currently active child processes, and the return
 | 
| 231 |         status is zero.  If ID is a a job specification, waits for all processes
 | 
| 232 |         in that job's pipeline.
 | 
| 233 | 
 | 
| 234 |         If the -n option is supplied, waits for the next job to terminate and
 | 
| 235 |         returns its exit status.
 | 
| 236 | 
 | 
| 237 |         Exit Status:
 | 
| 238 |         Returns the status of the last ID; fails if ID is invalid or an invalid
 | 
| 239 |         option is given.
 | 
| 240 |     """
 | 
| 241 | 
 | 
| 242 |     def __init__(self, waiter, job_list, mem, tracer, errfmt):
 | 
| 243 |         # type: (Waiter, process.JobList, Mem, dev.Tracer, ErrorFormatter) -> None
 | 
| 244 |         self.waiter = waiter
 | 
| 245 |         self.job_list = job_list
 | 
| 246 |         self.mem = mem
 | 
| 247 |         self.tracer = tracer
 | 
| 248 |         self.errfmt = errfmt
 | 
| 249 | 
 | 
| 250 |     def Run(self, cmd_val):
 | 
| 251 |         # type: (cmd_value.Argv) -> int
 | 
| 252 |         with dev.ctx_Tracer(self.tracer, 'wait', cmd_val.argv):
 | 
| 253 |             return self._Run(cmd_val)
 | 
| 254 | 
 | 
| 255 |     def _Run(self, cmd_val):
 | 
| 256 |         # type: (cmd_value.Argv) -> int
 | 
| 257 |         attrs, arg_r = flag_util.ParseCmdVal('wait', cmd_val)
 | 
| 258 |         arg = arg_types.wait(attrs.attrs)
 | 
| 259 | 
 | 
| 260 |         job_ids, arg_locs = arg_r.Rest2()
 | 
| 261 | 
 | 
| 262 |         if arg.n:
 | 
| 263 |             # Loop until there is one fewer process running, there's nothing to wait
 | 
| 264 |             # for, or there's a signal
 | 
| 265 |             n = self.job_list.NumRunning()
 | 
| 266 |             if n == 0:
 | 
| 267 |                 status = 127
 | 
| 268 |             else:
 | 
| 269 |                 target = n - 1
 | 
| 270 |                 status = 0
 | 
| 271 |                 while self.job_list.NumRunning() > target:
 | 
| 272 |                     result = self.waiter.WaitForOne()
 | 
| 273 |                     if result == process.W1_OK:
 | 
| 274 |                         status = self.waiter.last_status
 | 
| 275 |                     elif result == process.W1_ECHILD:
 | 
| 276 |                         # nothing to wait for, or interrupted
 | 
| 277 |                         status = 127
 | 
| 278 |                         break
 | 
| 279 |                     elif result >= 0:  # signal
 | 
| 280 |                         status = 128 + result
 | 
| 281 |                         break
 | 
| 282 | 
 | 
| 283 |             return status
 | 
| 284 | 
 | 
| 285 |         if len(job_ids) == 0:
 | 
| 286 |             #log('*** wait')
 | 
| 287 | 
 | 
| 288 |             # BUG: If there is a STOPPED process, this will hang forever, because we
 | 
| 289 |             # don't get ECHILD.  Not sure it matters since you can now Ctrl-C it.
 | 
| 290 |             # But how to fix this?
 | 
| 291 | 
 | 
| 292 |             status = 0
 | 
| 293 |             while self.job_list.NumRunning() != 0:
 | 
| 294 |                 result = self.waiter.WaitForOne()
 | 
| 295 |                 if result == process.W1_ECHILD:
 | 
| 296 |                     # nothing to wait for, or interrupted.  status is 0
 | 
| 297 |                     break
 | 
| 298 |                 elif result >= 0:  # signal
 | 
| 299 |                     status = 128 + result
 | 
| 300 |                     break
 | 
| 301 | 
 | 
| 302 |             return status
 | 
| 303 | 
 | 
| 304 |         # Get list of jobs.  Then we need to check if they are ALL stopped.
 | 
| 305 |         # Returns the exit code of the last one on the COMMAND LINE, not the exit
 | 
| 306 |         # code of last one to FINISH.
 | 
| 307 |         jobs = []  # type: List[process.Job]
 | 
| 308 |         for i, job_id in enumerate(job_ids):
 | 
| 309 |             location = arg_locs[i]
 | 
| 310 | 
 | 
| 311 |             job = None  # type: Optional[process.Job]
 | 
| 312 |             if job_id == '' or job_id.startswith('%'):
 | 
| 313 |                 job = self.job_list.GetJobWithSpec(job_id)
 | 
| 314 | 
 | 
| 315 |             if job is None:
 | 
| 316 |                 # Does it look like a PID?
 | 
| 317 |                 try:
 | 
| 318 |                     pid = int(job_id)
 | 
| 319 |                 except ValueError:
 | 
| 320 |                     raise error.Usage(
 | 
| 321 |                         'expected PID or jobspec, got %r' % job_id, location)
 | 
| 322 | 
 | 
| 323 |                 job = self.job_list.ProcessFromPid(pid)
 | 
| 324 | 
 | 
| 325 |             if job is None:
 | 
| 326 |                 self.errfmt.Print_("%s isn't a child of this shell" % job_id,
 | 
| 327 |                                    blame_loc=location)
 | 
| 328 |                 return 127
 | 
| 329 | 
 | 
| 330 |             jobs.append(job)
 | 
| 331 | 
 | 
| 332 |         status = 1  # error
 | 
| 333 |         for job in jobs:
 | 
| 334 |             wait_st = job.JobWait(self.waiter)
 | 
| 335 |             UP_wait_st = wait_st
 | 
| 336 |             with tagswitch(wait_st) as case:
 | 
| 337 |                 if case(wait_status_e.Proc):
 | 
| 338 |                     wait_st = cast(wait_status.Proc, UP_wait_st)
 | 
| 339 |                     status = wait_st.code
 | 
| 340 | 
 | 
| 341 |                 elif case(wait_status_e.Pipeline):
 | 
| 342 |                     wait_st = cast(wait_status.Pipeline, UP_wait_st)
 | 
| 343 |                     # TODO: handle PIPESTATUS?  Is this right?
 | 
| 344 |                     status = wait_st.codes[-1]
 | 
| 345 | 
 | 
| 346 |                 elif case(wait_status_e.Cancelled):
 | 
| 347 |                     wait_st = cast(wait_status.Cancelled, UP_wait_st)
 | 
| 348 |                     status = 128 + wait_st.sig_num
 | 
| 349 | 
 | 
| 350 |                 else:
 | 
| 351 |                     raise AssertionError()
 | 
| 352 | 
 | 
| 353 |         return status
 | 
| 354 | 
 | 
| 355 | 
 | 
| 356 | class Umask(vm._Builtin):
 | 
| 357 | 
 | 
| 358 |     def __init__(self):
 | 
| 359 |         # type: () -> None
 | 
| 360 |         """Dummy constructor for mycpp."""
 | 
| 361 |         pass
 | 
| 362 | 
 | 
| 363 |     def Run(self, cmd_val):
 | 
| 364 |         # type: (cmd_value.Argv) -> int
 | 
| 365 | 
 | 
| 366 |         argv = cmd_val.argv[1:]
 | 
| 367 |         if len(argv) == 0:
 | 
| 368 |             # umask() has a dumb API: you can't get it without modifying it first!
 | 
| 369 |             # NOTE: dash disables interrupts around the two umask() calls, but that
 | 
| 370 |             # shouldn't be a concern for us.  Signal handlers won't call umask().
 | 
| 371 |             mask = posix.umask(0)
 | 
| 372 |             posix.umask(mask)  #
 | 
| 373 |             print('0%03o' % mask)  # octal format
 | 
| 374 |             return 0
 | 
| 375 | 
 | 
| 376 |         if len(argv) == 1:
 | 
| 377 |             a = argv[0]
 | 
| 378 |             try:
 | 
| 379 |                 new_mask = int(a, 8)
 | 
| 380 |             except ValueError:
 | 
| 381 |                 # NOTE: This also happens when we have '8' or '9' in the input.
 | 
| 382 |                 print_stderr(
 | 
| 383 |                     "osh warning: umask with symbolic input isn't implemented")
 | 
| 384 |                 return 1
 | 
| 385 | 
 | 
| 386 |             posix.umask(new_mask)
 | 
| 387 |             return 0
 | 
| 388 | 
 | 
| 389 |         e_usage('umask: unexpected arguments', loc.Missing)
 | 
| 390 | 
 | 
| 391 | 
 | 
| 392 | def _LimitString(lim, factor):
 | 
| 393 |     # type: (mops.BigInt, int) -> str
 | 
| 394 |     if mops.Equal(lim, mops.FromC(RLIM_INFINITY)):
 | 
| 395 |         return 'unlimited'
 | 
| 396 |     else:
 | 
| 397 |         i = mops.Div(lim, mops.IntWiden(factor))
 | 
| 398 |         return mops.ToStr(i)
 | 
| 399 | 
 | 
| 400 | 
 | 
| 401 | class Ulimit(vm._Builtin):
 | 
| 402 | 
 | 
| 403 |     def __init__(self):
 | 
| 404 |         # type: () -> None
 | 
| 405 |         """Dummy constructor for mycpp."""
 | 
| 406 | 
 | 
| 407 |         self._table = None  # type: List[Tuple[str, int, int, str]]
 | 
| 408 | 
 | 
| 409 |     def _Table(self):
 | 
| 410 |         # type: () -> List[Tuple[str, int, int, str]]
 | 
| 411 | 
 | 
| 412 |         # POSIX 2018
 | 
| 413 |         #
 | 
| 414 |         # https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html
 | 
| 415 |         if self._table is None:
 | 
| 416 |             # This table matches _ULIMIT_RESOURCES in frontend/flag_def.py
 | 
| 417 | 
 | 
| 418 |             # flag, RLIMIT_X, factor, description
 | 
| 419 |             self._table = [
 | 
| 420 |                 # Following POSIX and most shells except bash, -f is in
 | 
| 421 |                 # blocks of 512 bytes
 | 
| 422 |                 ('-c', RLIMIT_CORE, 512, 'core dump size'),
 | 
| 423 |                 ('-d', RLIMIT_DATA, 1024, 'data segment size'),
 | 
| 424 |                 ('-f', RLIMIT_FSIZE, 512, 'file size'),
 | 
| 425 |                 ('-n', RLIMIT_NOFILE, 1, 'file descriptors'),
 | 
| 426 |                 ('-s', RLIMIT_STACK, 1024, 'stack size'),
 | 
| 427 |                 ('-t', RLIMIT_CPU, 1, 'CPU seconds'),
 | 
| 428 |                 ('-v', RLIMIT_AS, 1024, 'address space size'),
 | 
| 429 |             ]
 | 
| 430 | 
 | 
| 431 |         return self._table
 | 
| 432 | 
 | 
| 433 |     def _FindFactor(self, what):
 | 
| 434 |         # type: (int) -> int
 | 
| 435 |         for _, w, factor, _ in self._Table():
 | 
| 436 |             if w == what:
 | 
| 437 |                 return factor
 | 
| 438 |         raise AssertionError()
 | 
| 439 | 
 | 
| 440 |     def Run(self, cmd_val):
 | 
| 441 |         # type: (cmd_value.Argv) -> int
 | 
| 442 | 
 | 
| 443 |         attrs, arg_r = flag_util.ParseCmdVal('ulimit', cmd_val)
 | 
| 444 |         arg = arg_types.ulimit(attrs.attrs)
 | 
| 445 | 
 | 
| 446 |         what = 0
 | 
| 447 |         num_what_flags = 0
 | 
| 448 | 
 | 
| 449 |         if arg.c:
 | 
| 450 |             what = RLIMIT_CORE
 | 
| 451 |             num_what_flags += 1
 | 
| 452 | 
 | 
| 453 |         if arg.d:
 | 
| 454 |             what = RLIMIT_DATA
 | 
| 455 |             num_what_flags += 1
 | 
| 456 | 
 | 
| 457 |         if arg.f:
 | 
| 458 |             what = RLIMIT_FSIZE
 | 
| 459 |             num_what_flags += 1
 | 
| 460 | 
 | 
| 461 |         if arg.n:
 | 
| 462 |             what = RLIMIT_NOFILE
 | 
| 463 |             num_what_flags += 1
 | 
| 464 | 
 | 
| 465 |         if arg.s:
 | 
| 466 |             what = RLIMIT_STACK
 | 
| 467 |             num_what_flags += 1
 | 
| 468 | 
 | 
| 469 |         if arg.t:
 | 
| 470 |             what = RLIMIT_CPU
 | 
| 471 |             num_what_flags += 1
 | 
| 472 | 
 | 
| 473 |         if arg.v:
 | 
| 474 |             what = RLIMIT_AS
 | 
| 475 |             num_what_flags += 1
 | 
| 476 | 
 | 
| 477 |         if num_what_flags > 1:
 | 
| 478 |             raise error.Usage(
 | 
| 479 |                 'can only handle one resource at a time; got too many flags',
 | 
| 480 |                 cmd_val.arg_locs[0])
 | 
| 481 | 
 | 
| 482 |         # Print all
 | 
| 483 |         show_all = arg.a or arg.all
 | 
| 484 |         if show_all:
 | 
| 485 |             if num_what_flags > 0:
 | 
| 486 |                 raise error.Usage("doesn't accept resource flags with -a",
 | 
| 487 |                                   cmd_val.arg_locs[0])
 | 
| 488 | 
 | 
| 489 |             extra, extra_loc = arg_r.Peek2()
 | 
| 490 |             if extra is not None:
 | 
| 491 |                 raise error.Usage('got extra arg with -a', extra_loc)
 | 
| 492 | 
 | 
| 493 |             # Worst case 20 == len(str(2**64))
 | 
| 494 |             fmt = '%5s %15s %15s %7s  %s'
 | 
| 495 |             print(fmt % ('FLAG', 'SOFT', 'HARD', 'FACTOR', 'DESC'))
 | 
| 496 |             for flag, what, factor, desc in self._Table():
 | 
| 497 |                 soft, hard = pyos.GetRLimit(what)
 | 
| 498 | 
 | 
| 499 |                 soft2 = _LimitString(soft, factor)
 | 
| 500 |                 hard2 = _LimitString(hard, factor)
 | 
| 501 |                 print(fmt % (flag, soft2, hard2, str(factor), desc))
 | 
| 502 | 
 | 
| 503 |             return 0
 | 
| 504 | 
 | 
| 505 |         if num_what_flags == 0:
 | 
| 506 |             what = RLIMIT_FSIZE  # -f is the default
 | 
| 507 | 
 | 
| 508 |         s, s_loc = arg_r.Peek2()
 | 
| 509 | 
 | 
| 510 |         if s is None:
 | 
| 511 |             factor = self._FindFactor(what)
 | 
| 512 |             soft, hard = pyos.GetRLimit(what)
 | 
| 513 |             if arg.H:
 | 
| 514 |                 print(_LimitString(hard, factor))
 | 
| 515 |             else:
 | 
| 516 |                 print(_LimitString(soft, factor))
 | 
| 517 |             return 0
 | 
| 518 | 
 | 
| 519 |         # Set the given resource
 | 
| 520 |         if s == 'unlimited':
 | 
| 521 |             # In C, RLIM_INFINITY is rlim_t
 | 
| 522 |             limit = mops.FromC(RLIM_INFINITY)
 | 
| 523 |         else:
 | 
| 524 |             try:
 | 
| 525 |                 big_int = mops.FromStr(s)
 | 
| 526 |             except ValueError as e:
 | 
| 527 |                 raise error.Usage(
 | 
| 528 |                     "expected a number or 'unlimited', got %r" % s, s_loc)
 | 
| 529 | 
 | 
| 530 |             if mops.Greater(mops.IntWiden(0), big_int):
 | 
| 531 |                 raise error.Usage(
 | 
| 532 |                     "doesn't accept negative numbers, got %r" % s, s_loc)
 | 
| 533 | 
 | 
| 534 |             factor = self._FindFactor(what)
 | 
| 535 | 
 | 
| 536 |             fac = mops.IntWiden(factor)
 | 
| 537 |             limit = mops.Mul(big_int, fac)
 | 
| 538 | 
 | 
| 539 |             # Overflow check like bash does
 | 
| 540 |             # TODO: This should be replaced with a different overflow check
 | 
| 541 |             # when we have arbitrary precision integers
 | 
| 542 |             if not mops.Equal(mops.Div(limit, fac), big_int):
 | 
| 543 |                 #log('div %s', mops.ToStr(mops.Div(limit, fac)))
 | 
| 544 |                 raise error.Usage(
 | 
| 545 |                     'detected integer overflow: %s' % mops.ToStr(big_int),
 | 
| 546 |                     s_loc)
 | 
| 547 | 
 | 
| 548 |         arg_r.Next()
 | 
| 549 |         extra2, extra_loc2 = arg_r.Peek2()
 | 
| 550 |         if extra2 is not None:
 | 
| 551 |             raise error.Usage('got extra arg', extra_loc2)
 | 
| 552 | 
 | 
| 553 |         # Now set the resource
 | 
| 554 |         soft, hard = pyos.GetRLimit(what)
 | 
| 555 | 
 | 
| 556 |         # For error message
 | 
| 557 |         old_soft = soft
 | 
| 558 |         old_hard = hard
 | 
| 559 | 
 | 
| 560 |         # Bash behavior: manipulate both, unless a flag is parsed.  This
 | 
| 561 |         # differs from zsh!
 | 
| 562 |         if not arg.S and not arg.H:
 | 
| 563 |             soft = limit
 | 
| 564 |             hard = limit
 | 
| 565 |         if arg.S:
 | 
| 566 |             soft = limit
 | 
| 567 |         if arg.H:
 | 
| 568 |             hard = limit
 | 
| 569 | 
 | 
| 570 |         if mylib.PYTHON:
 | 
| 571 |             try:
 | 
| 572 |                 pyos.SetRLimit(what, soft, hard)
 | 
| 573 |             except OverflowError:  # only happens in CPython
 | 
| 574 |                 raise error.Usage('detected overflow', s_loc)
 | 
| 575 |             except (ValueError, resource.error) as e:
 | 
| 576 |                 # Annoying: Python binding changes IOError -> ValueError
 | 
| 577 | 
 | 
| 578 |                 print_stderr('ulimit error: %s' % e)
 | 
| 579 | 
 | 
| 580 |                 # Extra info we could expose in C++ too
 | 
| 581 |                 print_stderr('soft=%s hard=%s -> soft=%s hard=%s' % (
 | 
| 582 |                     _LimitString(old_soft, factor),
 | 
| 583 |                     _LimitString(old_hard, factor),
 | 
| 584 |                     _LimitString(soft, factor),
 | 
| 585 |                     _LimitString(hard, factor),
 | 
| 586 |                 ))
 | 
| 587 |                 return 1
 | 
| 588 |         else:
 | 
| 589 |             try:
 | 
| 590 |                 pyos.SetRLimit(what, soft, hard)
 | 
| 591 |             except (IOError, OSError) as e:
 | 
| 592 |                 print_stderr('ulimit error: %s' % pyutil.strerror(e))
 | 
| 593 |                 return 1
 | 
| 594 | 
 | 
| 595 |         return 0
 | 
| 596 | 
 | 
| 597 | 
 | 
| 598 | # vim: sw=4
 |