├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ └── R-CMD-check.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── NEWS ├── R ├── chroot.R ├── fork.R ├── process.R ├── rlimit.R ├── userinfo.R └── util.R ├── cleanup ├── configure ├── configure.win ├── man ├── chroot.Rd ├── config.Rd ├── eval_fork.Rd ├── process.Rd ├── rlimit.Rd └── userinfo.Rd ├── readme.md ├── src ├── Makevars.in ├── chroot.c ├── fork.c ├── process.c ├── register.c ├── rlimit.c ├── userinfo.c └── util.c ├── tests ├── testthat.R └── testthat │ ├── test-forking.R │ └── test-process.R └── unix.Rproj /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.travis\.yml$ 4 | ^appveyor\.yml$ 5 | ^.gitignore$ 6 | ^readme.md$ 7 | ^.*\.o$ 8 | ^src/Makevars$ 9 | ^readme.md$ 10 | ^\.github$ 11 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | pull_request: 6 | 7 | name: R-CMD-check.yaml 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-13, r: 'release'} 22 | - {os: macos-14, r: 'release'} 23 | - {os: windows-latest, r: '4.1'} 24 | - {os: windows-latest, r: '4.2'} 25 | - {os: windows-latest, r: 'release'} 26 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 27 | - {os: ubuntu-latest, r: 'release'} 28 | - {os: ubuntu-latest, r: 'oldrel-1'} 29 | 30 | env: 31 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 32 | R_KEEP_PKG_SOURCE: yes 33 | 34 | steps: 35 | - uses: actions/checkout@v4 36 | 37 | - uses: r-lib/actions/setup-pandoc@v2 38 | 39 | - uses: r-lib/actions/setup-r@v2 40 | with: 41 | r-version: ${{ matrix.config.r }} 42 | http-user-agent: ${{ matrix.config.http-user-agent }} 43 | use-public-rspm: true 44 | 45 | - uses: r-lib/actions/setup-r-dependencies@v2 46 | with: 47 | extra-packages: any::rcmdcheck 48 | needs: check 49 | 50 | - uses: r-lib/actions/check-r-package@v2 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | *.o 5 | src/*.so 6 | src/*.dll 7 | src/Makevars 8 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: unix 2 | Title: POSIX System Utilities 3 | Version: 1.5.9 4 | Authors@R: person("Jeroen", "Ooms", email = "jeroenooms@gmail.com", 5 | comment = c(ORCID = "0000-0002-4035-0289"), role = c("aut", "cre")) 6 | Description: Bindings to system utilities found in most Unix systems such as 7 | POSIX functions which are not part of the Standard C Library. 8 | License: MIT + file LICENSE 9 | URL: https://jeroen.r-universe.dev/unix 10 | BugReports: https://github.com/jeroen/unix/issues 11 | OS_type: unix 12 | SystemRequirements: POSIX.1-2001, AppArmor (optional) 13 | RoxygenNote: 7.3.1 14 | Roxygen: list(markdown = TRUE) 15 | Suggests: 16 | testthat 17 | Language: en-US 18 | Encoding: UTF-8 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2017 2 | COPYRIGHT HOLDER: Jeroen Ooms 3 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(aa_config) 4 | export(chroot) 5 | export(eval_fork) 6 | export(eval_safe) 7 | export(getegid) 8 | export(geteuid) 9 | export(getgid) 10 | export(getpgid) 11 | export(getpid) 12 | export(getppid) 13 | export(getpriority) 14 | export(getuid) 15 | export(group_info) 16 | export(kill) 17 | export(rlimit_all) 18 | export(rlimit_as) 19 | export(rlimit_core) 20 | export(rlimit_cpu) 21 | export(rlimit_data) 22 | export(rlimit_fsize) 23 | export(rlimit_memlock) 24 | export(rlimit_nofile) 25 | export(rlimit_nproc) 26 | export(rlimit_stack) 27 | export(setegid) 28 | export(seteuid) 29 | export(setgid) 30 | export(setpgid) 31 | export(setpriority) 32 | export(setuid) 33 | export(sys_config) 34 | export(user_info) 35 | importFrom(grDevices,graphics.off) 36 | importFrom(grDevices,pdf) 37 | importFrom(tools,SIGCHLD) 38 | importFrom(tools,SIGHUP) 39 | importFrom(tools,SIGINT) 40 | importFrom(tools,SIGKILL) 41 | importFrom(tools,SIGQUIT) 42 | importFrom(tools,SIGSTOP) 43 | importFrom(tools,SIGTERM) 44 | importFrom(tools,SIGUSR1) 45 | importFrom(tools,SIGUSR2) 46 | useDynLib(unix,R_aa_change_profile) 47 | useDynLib(unix,R_aa_getcon) 48 | useDynLib(unix,R_aa_is_enabled) 49 | useDynLib(unix,R_chroot) 50 | useDynLib(unix,R_eval_fork) 51 | useDynLib(unix,R_freeze) 52 | useDynLib(unix,R_getegid) 53 | useDynLib(unix,R_geteuid) 54 | useDynLib(unix,R_getgid) 55 | useDynLib(unix,R_getpgid) 56 | useDynLib(unix,R_getpid) 57 | useDynLib(unix,R_getppid) 58 | useDynLib(unix,R_getpriority) 59 | useDynLib(unix,R_getuid) 60 | useDynLib(unix,R_group_info) 61 | useDynLib(unix,R_have_apparmor) 62 | useDynLib(unix,R_kill) 63 | useDynLib(unix,R_rlimit_as) 64 | useDynLib(unix,R_rlimit_core) 65 | useDynLib(unix,R_rlimit_cpu) 66 | useDynLib(unix,R_rlimit_data) 67 | useDynLib(unix,R_rlimit_fsize) 68 | useDynLib(unix,R_rlimit_memlock) 69 | useDynLib(unix,R_rlimit_nofile) 70 | useDynLib(unix,R_rlimit_nproc) 71 | useDynLib(unix,R_rlimit_stack) 72 | useDynLib(unix,R_safe_build) 73 | useDynLib(unix,R_set_interactive) 74 | useDynLib(unix,R_set_rlimits) 75 | useDynLib(unix,R_set_tempdir) 76 | useDynLib(unix,R_setegid) 77 | useDynLib(unix,R_seteuid) 78 | useDynLib(unix,R_setgid) 79 | useDynLib(unix,R_setpgid) 80 | useDynLib(unix,R_setpriority) 81 | useDynLib(unix,R_setuid) 82 | useDynLib(unix,R_user_info) 83 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | 1.5.8 2 | - Condition another api call 3 | 4 | 1.5.7 5 | - Remove duplicate functions definitions for setuid and setgid() 6 | 7 | 1.5.6 8 | - Fix a printf warning for CRAN 9 | 10 | 1.5.5 11 | - Fix strict-prototypes warnings 12 | 13 | 1.5.4 14 | - Remove examples and test for rlimit_as() and rlimit_data() which give an 15 | error on MacOS 12 if the value is too low. 16 | 17 | 1.5.3 18 | - On linux, eval_fork() no longer tries to kill orphan forks 19 | 20 | 1.5.2 21 | - Add 'AppArmor' in SystemRequirements to help rstudio/r-system-requirements 22 | 23 | 1.5.1 24 | - CRAN fix: do not use CPP in configure 25 | 26 | 1.5 27 | - Fix complication on Solaris 28 | 29 | 1.4 30 | - eval_fork and some more tools have been moved from 'sys' into 31 | this package. 32 | 33 | 1.1 34 | - Added geteuid(), seteuid(), getegid(), setegid() 35 | - Add rlimit_all() wrapper to show all current limits 36 | 37 | 1.0 38 | - Initial CRAN release 39 | -------------------------------------------------------------------------------- /R/chroot.R: -------------------------------------------------------------------------------- 1 | #' Change Root Dir 2 | #' 3 | #' Changes the root directory of the calling process to that specified in path. 4 | #' This directory will be used for pathnames beginning with `/`. 5 | #' **Only a privileged process (i.e. sudo) may call `chroot()`**. 6 | #' 7 | #' This call changes an ingredient in the pathname resolution process 8 | #' and does nothing else. In particular, it is not intended to be used 9 | #' for any kind of security purpose, neither to fully sandbox a process 10 | #' nor to restrict filesystem system calls. 11 | #' 12 | #' @export 13 | #' @param path directory of the new root 14 | #' @useDynLib unix R_chroot 15 | #' @references [CHROOT(2)](https://man7.org/linux/man-pages/man2/chroot.2.html) 16 | chroot <- function(path = getwd()){ 17 | path <- normalizePath(path, mustWork = TRUE) 18 | .Call(R_chroot, path) 19 | } 20 | -------------------------------------------------------------------------------- /R/fork.R: -------------------------------------------------------------------------------- 1 | #' Safe Evaluation 2 | #' 3 | #' Evaluates an expression in a temporary fork and returns the value without any 4 | #' side effects on the main R session. For [eval_safe()] the expression is wrapped 5 | #' in additional R code to handle errors and graphics. 6 | #' 7 | #' Some programs such as `Java` are not fork-safe and cannot be called from within a 8 | #' forked process if they have already been loaded in the main process. On MacOS any 9 | #' software calling `CoreFoundation` functionality might crash within the fork. This 10 | #' includes `libcurl` which has been built on OSX against native SecureTransport rather 11 | #' than OpenSSL for https connections. The same limitations hold for e.g. `parallel::mcparallel()`. 12 | #' 13 | #' @export 14 | #' @rdname eval_fork 15 | #' @importFrom grDevices pdf graphics.off 16 | #' @param expr expression to evaluate 17 | #' @param tmp the value of [tempdir()] inside the forked process 18 | #' @param timeout maximum time in seconds to allow for call to return 19 | #' @param device graphics device to use in the fork, see [dev.new()] 20 | #' @param rlimits named vector/list with rlimit values, for example: `c(cpu = 60, fsize = 1e6)`. 21 | #' @param uid evaluate as given user (uid or name). See [unix::setuid()], only for root. 22 | #' @param gid evaluate as given group (gid or name). See [unix::setgid()] only for root. 23 | #' @param priority (integer) priority of the child process. High value is low priority. 24 | #' @param std_out if and where to direct child process `STDOUT`. Must be one of 25 | #' `TRUE`, `FALSE`, filename, connection object or callback function. See section 26 | #' on *Output Streams* below for details. 27 | #' @param std_err if and where to direct child process `STDERR`. Must be one of 28 | #' `TRUE`, `FALSE`, filename, connection object or callback function. See section 29 | #' on *Output Streams* below for details. 30 | #' Non root user may only raise this value (decrease priority) 31 | #' @param profile AppArmor profile, see `RAppArmor::aa_change_profile()`. 32 | #' Requires the `RAppArmor` package (Debian/Ubuntu only) 33 | #' @examples 34 | #' # works like regular eval: 35 | #' eval_safe(rnorm(5)) 36 | #' 37 | #' # Exceptions get propagated 38 | #' test <- function() { doesnotexit() } 39 | #' tryCatch(eval_safe(test()), error = function(e){ 40 | #' cat("oh no!", e$message, "\n") 41 | #' }) 42 | #' 43 | #' # Honor interrupt and timeout, even inside C evaluations 44 | #' try(eval_safe(svd(matrix(rnorm(1e8), 1e4)), timeout = 2)) 45 | #' 46 | #' # Capture output 47 | #' outcon <- rawConnection(raw(0), "r+") 48 | #' eval_safe(print(sessionInfo()), std_out = outcon) 49 | #' cat(rawToChar(rawConnectionValue(outcon))) 50 | #' close(outcon) 51 | eval_safe <- function(expr, tmp = tempfile("fork"), std_out = stdout(), std_err = stderr(), 52 | timeout = 0, priority = NULL, uid = NULL, gid = NULL, rlimits = NULL, 53 | profile = NULL, device = pdf){ 54 | orig_expr <- substitute(expr) 55 | out <- eval_fork(expr = tryCatch({ 56 | if(length(priority)) 57 | setpriority(priority) 58 | if(length(rlimits)) 59 | set_rlimits(rlimits) 60 | if(length(gid)) 61 | setgid(gid) 62 | if(length(uid)) 63 | setuid(uid) 64 | if(length(profile)) 65 | aa_change_profile(profile) 66 | if(length(device)) 67 | options(device = device) 68 | graphics.off() 69 | options(menu.graphics = FALSE) 70 | 71 | # Pre-serialize because C level serialization in unix::eval_fork() has a performance bug 72 | serialize(withVisible(eval(orig_expr, parent.frame())), NULL) 73 | }, error = function(e){ 74 | old_class <- attr(e, "class") 75 | structure(e, class = c(old_class, "eval_fork_error")) 76 | }, finally = substitute(graphics.off())), 77 | tmp = tmp, timeout = timeout, std_out = std_out, std_err = std_err) 78 | if(inherits(out, "eval_fork_error")) 79 | base::stop(out) 80 | res <- unserialize(out) 81 | if(res$visible) 82 | res$value 83 | else 84 | invisible(res$value) 85 | } 86 | 87 | 88 | #' @rdname eval_fork 89 | #' @export 90 | eval_fork <- function(expr, tmp = tempfile("fork"), std_out = stdout(), std_err = stderr(), timeout = 0) { 91 | # Convert TRUE or filepath into connection objects 92 | std_out <- if(isTRUE(std_out) || identical(std_out, "")){ 93 | stdout() 94 | } else if(is.character(std_out)){ 95 | file(normalizePath(std_out, mustWork = FALSE)) 96 | } else std_out 97 | 98 | std_err <- if(isTRUE(std_err) || identical(std_err, "")){ 99 | stderr() 100 | } else if(is.character(std_err)){ 101 | std_err <- file(normalizePath(std_err, mustWork = FALSE)) 102 | } else std_err 103 | 104 | # Define the callbacks 105 | outfun <- if(inherits(std_out, "connection")){ 106 | if(!isOpen(std_out)){ 107 | open(std_out, "wb") 108 | on.exit(close(std_out), add = TRUE) 109 | } 110 | if(identical(summary(std_out)$text, "text")){ 111 | function(x){ 112 | cat(rawToChar(x), file = std_out) 113 | flush(std_out) 114 | } 115 | } else { 116 | function(x){ 117 | writeBin(x, con = std_out) 118 | flush(std_out) 119 | } 120 | } 121 | } else if(is.function(std_out)){ 122 | if(!length(formals(std_out))) 123 | stop("Function std_out must take at least one argument") 124 | std_out 125 | } 126 | 127 | errfun <- if(inherits(std_err, "connection")){ 128 | if(!isOpen(std_err)){ 129 | open(std_err, "wb") 130 | on.exit(close(std_err), add = TRUE) 131 | } 132 | if(identical(summary(std_err)$text, "text")){ 133 | function(x){ 134 | cat(rawToChar(x), file = std_err) 135 | flush(std_err) 136 | } 137 | } else { 138 | function(x){ 139 | writeBin(x, con = std_err) 140 | flush(std_err) 141 | } 142 | } 143 | } else if(is.function(std_err)){ 144 | if(!length(formals(std_err))) 145 | stop("Function std_err must take at least one argument") 146 | std_err 147 | } 148 | 149 | clenv <- force(parent.frame()) 150 | clexpr <- substitute(expr) 151 | eval_fork_internal(expr = clexpr, envir = clenv, tmp = tmp, timeout = timeout, outfun = outfun, 152 | errfun = errfun) 153 | } 154 | 155 | #' @useDynLib unix R_eval_fork 156 | eval_fork_internal <- function(expr, envir, tmp, timeout, outfun, errfun){ 157 | if(!file.exists(tmp)) 158 | dir.create(tmp) 159 | if(length(timeout)){ 160 | stopifnot(is.numeric(timeout)) 161 | timeout <- as.double(timeout) 162 | } else { 163 | timeout <- as.numeric(0) 164 | } 165 | tmp <- normalizePath(tmp) 166 | .Call(R_eval_fork, expr, envir, tmp, timeout, outfun, errfun) 167 | } 168 | 169 | # Limits MUST be named 170 | parse_limits <- function(..., as = NA, core = NA, cpu = NA, data = NA, fsize = NA, 171 | memlock = NA, nofile = NA, nproc = NA, stack = NA){ 172 | unknown <- list(...) 173 | if(length(unknown)) 174 | stop("Unsupported rlimits: ", paste(names(unknown), collapse = ", ")) 175 | out <- as.numeric(c(as, core, cpu, data, fsize, memlock, nofile, nproc, stack)) 176 | structure(out, names = names(formals(sys.function()))[-1]) 177 | } 178 | -------------------------------------------------------------------------------- /R/process.R: -------------------------------------------------------------------------------- 1 | #' Process Info 2 | #' 3 | #' Get or set attributes of the current process. 4 | #' 5 | #' Acronyms stand for: 6 | #' 7 | #' - `pid` Process ID 8 | #' - `ppid` Parent-Process ID 9 | #' - `pgid` Process-Group ID 10 | #' - `uid` User ID 11 | #' - `euid` Effective User ID 12 | #' - `gid` Group ID 13 | #' - `egid` Effective Group ID 14 | #' - `prio` Priority level 15 | #' 16 | #' An unprivileged (non-root) process cannot change it's `uid` and only lower 17 | #' process priority (higher value). 18 | #' 19 | #' @export 20 | #' @rdname process 21 | #' @useDynLib unix R_getuid 22 | #' @references [GETUID(2)](https://man7.org/linux/man-pages/man2/getuid.2.html) 23 | #' [GETPID(2)](https://man7.org/linux/man-pages/man2/getpid.2.html) 24 | #' [GETPGID(2)](https://man7.org/linux/man-pages/man2/getpgid.2.html) 25 | #' [GETPRIORITY(2)](https://man7.org/linux/man-pages/man2/getpriority.2.html) 26 | #' @examples # Current User: 27 | #' getuid() 28 | getuid <- function(){ 29 | .Call(R_getuid) 30 | } 31 | 32 | #' @export 33 | #' @rdname process 34 | #' @useDynLib unix R_getgid 35 | #' @examples # Current UserGroup: 36 | #' getgid() 37 | getgid <- function(){ 38 | .Call(R_getgid) 39 | } 40 | 41 | #' @export 42 | #' @rdname process 43 | #' @useDynLib unix R_geteuid 44 | #' @examples # Current UserGroup: 45 | #' geteuid() 46 | geteuid <- function(){ 47 | .Call(R_geteuid) 48 | } 49 | 50 | #' @export 51 | #' @rdname process 52 | #' @useDynLib unix R_getegid 53 | #' @examples # Current UserGroup: 54 | #' getegid() 55 | getegid <- function(){ 56 | .Call(R_getegid) 57 | } 58 | 59 | #' @export 60 | #' @rdname process 61 | #' @useDynLib unix R_getpid 62 | #' @examples # Process ID 63 | #' getpid() 64 | getpid <- function(){ 65 | .Call(R_getpid) 66 | } 67 | 68 | #' @export 69 | #' @rdname process 70 | #' @useDynLib unix R_getppid 71 | #' @examples # parent PID: 72 | #' getppid() 73 | getppid <- function(){ 74 | .Call(R_getppid) 75 | } 76 | 77 | #' @export 78 | #' @rdname process 79 | #' @useDynLib unix R_getpgid 80 | #' @examples # Process group id: 81 | #' getpgid() 82 | #' 83 | #' # Detach process group 84 | #' setpgid(0) 85 | #' getpgid() 86 | getpgid <- function(){ 87 | .Call(R_getpgid) 88 | } 89 | 90 | #' @export 91 | #' @rdname process 92 | #' @useDynLib unix R_getpriority 93 | #' @examples # Process priority: 94 | #' getpriority() 95 | getpriority <- function(){ 96 | .Call(R_getpriority) 97 | } 98 | 99 | #' @export 100 | #' @rdname process 101 | #' @useDynLib unix R_setuid 102 | #' @param uid User ID from `/etc/passwd`. 103 | setuid <- function(uid){ 104 | stopifnot(is.numeric(uid)) 105 | .Call(R_setuid, as.integer(uid)) 106 | } 107 | 108 | #' @export 109 | #' @rdname process 110 | #' @useDynLib unix R_seteuid 111 | seteuid <- function(uid){ 112 | .Call(R_seteuid, uid) 113 | } 114 | 115 | #' @export 116 | #' @rdname process 117 | #' @useDynLib unix R_setgid 118 | #' @param gid Group ID from `/etc/group`. 119 | setgid <- function(gid){ 120 | stopifnot(is.numeric(gid)) 121 | .Call(R_setgid, as.integer(gid)) 122 | } 123 | 124 | #' @export 125 | #' @rdname process 126 | #' @useDynLib unix R_setegid 127 | setegid <- function(gid){ 128 | .Call(R_setegid, gid) 129 | } 130 | 131 | #' @export 132 | #' @rdname process 133 | #' @useDynLib unix R_setpgid 134 | #' @param pgid Process Group ID. Default `0` sets pgid to the current pid. 135 | setpgid <- function(pgid = 0){ 136 | .Call(R_setpgid, pgid) 137 | } 138 | 139 | #' @export 140 | #' @rdname process 141 | #' @useDynLib unix R_setpriority 142 | #' @param prio Priority level 143 | #' @examples # Decrease priority 144 | #' setpriority(getpriority() + 1) 145 | setpriority <- function(prio){ 146 | stopifnot(is.numeric(prio)) 147 | .Call(R_setpriority, as.integer(prio)) 148 | } 149 | 150 | #' @export 151 | #' @rdname process 152 | #' @importFrom tools SIGHUP SIGINT SIGQUIT SIGKILL SIGTERM SIGSTOP SIGCHLD SIGUSR1 SIGUSR2 153 | #' @useDynLib unix R_kill 154 | #' @param pid process ID (integer) 155 | #' @param signal a signal number (integer), defaults to [tools::SIGTERM]. 156 | kill <- function(pid, signal = SIGTERM){ 157 | stopifnot(is.numeric(pid), is.numeric(signal)) 158 | .Call(R_kill, as.integer(pid), as.integer(signal)) 159 | } 160 | 161 | -------------------------------------------------------------------------------- /R/rlimit.R: -------------------------------------------------------------------------------- 1 | #' Resource Limits 2 | #' 3 | #' Get and set process resource limits. Each function returns the current limits, and 4 | #' can optionally update the limit by passing argument values. The `rlimit_all()` 5 | #' function is a convenience wrapper which prints all current hard and soft limits. 6 | #' 7 | #' 8 | #' Each resource has an associated soft and hard limit. The soft limit is the value 9 | #' that the kernel enforces for the corresponding resource. The hard limit acts as a 10 | #' ceiling for the soft limit: an unprivileged process may set only its soft limit to 11 | #' a value in the range from 0 up to the hard limit, and (irreversibly) lower its hard 12 | #' limit. 13 | #' 14 | #' 15 | #' Definitons from the [Linux manual page](https://man7.org/linux/man-pages/man2/setrlimit.2.html) 16 | #' are as follows: 17 | #' 18 | #' - `RLIMIT_AS` : the maximum size of the process's virtual memory (address space) in bytes. 19 | #' - `RLIMIT_CORE` : the maximum size of a core file that the process may dump. 20 | #' - `RLIMIT_CPU` : a limit in seconds on the amount of CPU time (**not** elapsed time) that 21 | #' the process may consume. When the process reaches the soft limit, it is sent a `SIGXCPU` signal. 22 | #' - `RLIMIT_DATA` : the maximum size of the process's data segment (initialized data, uninitialized 23 | #' data, and heap). 24 | #' - `RLIMIT_FSIZE` : the maximum size of files that the process may create. Attempts to extend a 25 | #' file beyond this limit result in delivery of a SIGXFSZ signal. 26 | #' - `RLIMIT_MEMLOCK` : the maximum number of bytes of memory that may be locked into RAM. 27 | #' - `RLIMIT_NOFILE` : a value one greater than the maximum file descriptor number that can be opened 28 | #' by this process. 29 | #' - `RLIMIT_NPROC` : the maximum number of processes that can be created for the real user ID of the 30 | #' calling process. Upon encountering this limit, fork fails with the error EAGAIN. Not enforced for 31 | #' root user. 32 | #' - `RLIMIT_STACK` : the maximum size of the process stack, in bytes. 33 | #' 34 | #' Note that the support for enforcing limits very widely by system. In particular 35 | #' `RLIMIT_AS` has a different meaning depending on how memory allocation is managed 36 | #' by the operating system (and doesn't work at all on MacOS). 37 | #' 38 | #' 39 | #' @rdname rlimit 40 | #' @name rlimit 41 | #' @export 42 | #' @param cur set the current (soft) limit for this resource. See details. 43 | #' @param max set the max (hard) limit for this resource. See details. 44 | #' @references [GETRLIMIT(2)](https://man7.org/linux/man-pages/man2/setrlimit.2.html) 45 | #' @examples # Print all limits 46 | #' rlimit_all() 47 | #' 48 | #' # Get one limit 49 | #' rlimit_as() 50 | #' 51 | #' \dontrun{ 52 | #' # Set a soft limit 53 | #' lim <- rlimit_as(1e9) 54 | #' print(lim) 55 | #' 56 | #' # Reset the limit to max 57 | #' rlimit_as(cur = lim$max) 58 | #' 59 | #' # Set a hard limit (irreversible) 60 | #' rlimit_as(max = 1e10) 61 | #' } 62 | rlimit_all <- function(){ 63 | data <- rbind( 64 | as = rlimit_as(), 65 | core = rlimit_core(), 66 | cpu = rlimit_cpu(), 67 | data = rlimit_data(), 68 | fsize = rlimit_fsize(), 69 | memlock = suppressWarnings(rlimit_memlock()), 70 | nofile = rlimit_nofile(), 71 | nproc = suppressWarnings(rlimit_nproc()), 72 | stack = rlimit_stack() 73 | ) 74 | list( 75 | cur = unlist(data[,"cur"]), 76 | max = unlist(data[,"max"]) 77 | ) 78 | } 79 | 80 | #' @rdname rlimit 81 | #' @useDynLib unix R_rlimit_as 82 | #' @export 83 | rlimit_as <- function(cur = NULL, max = NULL){ 84 | if(length(cur)) stopifnot(is.numeric(cur)) 85 | if(length(max)) stopifnot(is.numeric(max)) 86 | out <- .Call(R_rlimit_as, as.numeric(cur), as.numeric(max)) 87 | structure(as.list(out), names = c("cur", "max")) 88 | } 89 | 90 | #' @rdname rlimit 91 | #' @useDynLib unix R_rlimit_core 92 | #' @export 93 | rlimit_core <- function(cur = NULL, max = NULL){ 94 | if(length(cur)) stopifnot(is.numeric(cur)) 95 | if(length(max)) stopifnot(is.numeric(max)) 96 | out <- .Call(R_rlimit_core, as.numeric(cur), as.numeric(max)) 97 | structure(as.list(out), names = c("cur", "max")) 98 | } 99 | 100 | #' @rdname rlimit 101 | #' @useDynLib unix R_rlimit_cpu 102 | #' @export 103 | rlimit_cpu <- function(cur = NULL, max = NULL){ 104 | if(length(cur)) stopifnot(is.numeric(cur)) 105 | if(length(max)) stopifnot(is.numeric(max)) 106 | out <- .Call(R_rlimit_cpu, as.numeric(cur), as.numeric(max)) 107 | structure(as.list(out), names = c("cur", "max")) 108 | } 109 | 110 | #' @rdname rlimit 111 | #' @useDynLib unix R_rlimit_data 112 | #' @export 113 | rlimit_data <- function(cur = NULL, max = NULL){ 114 | if(length(cur)) stopifnot(is.numeric(cur)) 115 | if(length(max)) stopifnot(is.numeric(max)) 116 | out <- .Call(R_rlimit_data, as.numeric(cur), as.numeric(max)) 117 | structure(as.list(out), names = c("cur", "max")) 118 | } 119 | 120 | #' @rdname rlimit 121 | #' @useDynLib unix R_rlimit_fsize 122 | #' @export 123 | rlimit_fsize <- function(cur = NULL, max = NULL){ 124 | if(length(cur)) stopifnot(is.numeric(cur)) 125 | if(length(max)) stopifnot(is.numeric(max)) 126 | out <- .Call(R_rlimit_fsize, as.numeric(cur), as.numeric(max)) 127 | structure(as.list(out), names = c("cur", "max")) 128 | } 129 | 130 | #' @rdname rlimit 131 | #' @useDynLib unix R_rlimit_memlock 132 | #' @export 133 | rlimit_memlock <- function(cur = NULL, max = NULL){ 134 | if(length(cur)) stopifnot(is.numeric(cur)) 135 | if(length(max)) stopifnot(is.numeric(max)) 136 | out <- .Call(R_rlimit_memlock, as.numeric(cur), as.numeric(max)) 137 | structure(as.list(out), names = c("cur", "max")) 138 | } 139 | 140 | #' @rdname rlimit 141 | #' @useDynLib unix R_rlimit_nofile 142 | #' @export 143 | rlimit_nofile <- function(cur = NULL, max = NULL){ 144 | if(length(cur)) stopifnot(is.numeric(cur)) 145 | if(length(max)) stopifnot(is.numeric(max)) 146 | out <- .Call(R_rlimit_nofile, as.numeric(cur), as.numeric(max)) 147 | structure(as.list(out), names = c("cur", "max")) 148 | } 149 | 150 | #' @rdname rlimit 151 | #' @useDynLib unix R_rlimit_nproc 152 | #' @export 153 | rlimit_nproc <- function(cur = NULL, max = NULL){ 154 | if(length(cur)) stopifnot(is.numeric(cur)) 155 | if(length(max)) stopifnot(is.numeric(max)) 156 | out <- .Call(R_rlimit_nproc, as.numeric(cur), as.numeric(max)) 157 | structure(as.list(out), names = c("cur", "max")) 158 | } 159 | 160 | #' @rdname rlimit 161 | #' @useDynLib unix R_rlimit_stack 162 | #' @export 163 | rlimit_stack <- function(cur = NULL, max = NULL){ 164 | if(length(cur)) stopifnot(is.numeric(cur)) 165 | if(length(max)) stopifnot(is.numeric(max)) 166 | out <- .Call(R_rlimit_stack, as.numeric(cur), as.numeric(max)) 167 | structure(as.list(out), names = c("cur", "max")) 168 | } 169 | -------------------------------------------------------------------------------- /R/userinfo.R: -------------------------------------------------------------------------------- 1 | #' User / Group Info 2 | #' 3 | #' Lookup a user or group info via user uid/name or group gid/name. 4 | #' 5 | #' @export 6 | #' @rdname userinfo 7 | #' @name userinfo 8 | #' @param uid user ID (integer) or name (string) 9 | #' @useDynLib unix R_user_info 10 | #' @references [GETPWNAM(3)](https://man7.org/linux/man-pages/man3/getpwnam.3.html) 11 | #' [GETGRNAM(3)](https://man7.org/linux/man-pages/man3/getgrnam.3.html) 12 | #' @examples # Get info current user 13 | #' user_info() 14 | #' group_info() 15 | user_info <- function(uid = getuid()){ 16 | if(is.numeric(uid)) 17 | uid <- as.integer(uid) 18 | stopifnot(length(uid) > 0, is.numeric(uid) || is.character(uid)) 19 | out <- .Call(R_user_info, uid) 20 | structure(out, names = c("name", "passwd", "uid", "gid", "gecos", "dir", "shell")) 21 | } 22 | 23 | #' @export 24 | #' @rdname userinfo 25 | #' @param gid group ID (integer) or name (string) 26 | #' @useDynLib unix R_group_info 27 | group_info <- function(gid = getgid()){ 28 | if(is.numeric(gid)) 29 | gid <- as.integer(gid) 30 | stopifnot(length(gid) > 0, is.integer(gid) || is.character(gid)) 31 | out <- .Call(R_group_info, gid) 32 | structure(out, names = c("name", "passwd", "gid", "members")) 33 | } 34 | -------------------------------------------------------------------------------- /R/util.R: -------------------------------------------------------------------------------- 1 | #' Package config 2 | #' 3 | #' Shows which features are enabled in the package configuration. 4 | #' 5 | #' @export 6 | #' @rdname config 7 | #' @examples sys_config() 8 | sys_config <- function(){ 9 | list( 10 | safe = safe_build(), 11 | apparmor = have_apparmor() 12 | ) 13 | } 14 | 15 | #' @rdname config 16 | #' @export 17 | aa_config <- function(){ 18 | status <- aa_getcon() 19 | list( 20 | compiled = have_apparmor(), 21 | enabled = aa_is_enabled(), 22 | con = status$con, 23 | mode = status$mode 24 | ) 25 | } 26 | 27 | #' @useDynLib unix R_freeze 28 | freeze <- function(interrupt = TRUE){ 29 | .Call(R_freeze, as.logical(interrupt)) 30 | } 31 | 32 | #' @useDynLib unix R_safe_build 33 | safe_build <- function(){ 34 | .Call(R_safe_build) 35 | } 36 | 37 | #' @useDynLib unix R_have_apparmor 38 | have_apparmor <- function(){ 39 | .Call(R_have_apparmor) 40 | } 41 | 42 | #' @useDynLib unix R_aa_is_enabled 43 | aa_is_enabled <- function(){ 44 | .Call(R_aa_is_enabled) 45 | } 46 | 47 | #' @useDynLib unix R_aa_getcon 48 | aa_getcon <- function(){ 49 | out <- .Call(R_aa_getcon) 50 | if(!length(out)) 51 | return(out) 52 | structure(out, names = c("con", "mode")) 53 | } 54 | 55 | #' @useDynLib unix R_set_tempdir 56 | set_tempdir <- function(path){ 57 | path <- normalizePath(path) 58 | if(!file.exists(path)) 59 | dir.create(path) 60 | .Call(R_set_tempdir, path) 61 | } 62 | 63 | #' @useDynLib unix R_set_interactive 64 | set_interactive <- function(set){ 65 | stopifnot(is.logical(set)) 66 | .Call(R_set_interactive, set) 67 | } 68 | 69 | #' @useDynLib unix R_aa_change_profile 70 | aa_change_profile <- function(profile){ 71 | stopifnot(is.character(profile)) 72 | .Call(R_aa_change_profile, profile) 73 | } 74 | 75 | #' @useDynLib unix R_set_rlimits 76 | set_rlimits <- function(rlimits){ 77 | rlimits <- do.call(parse_limits, as.list(rlimits)) 78 | .Call(R_set_rlimits, rlimits) 79 | } 80 | -------------------------------------------------------------------------------- /cleanup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rm -f src/Makevars 3 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # To build on Linux Systems without AppArmor use: 3 | # NO_APPARMOR=1 4 | 5 | # Library settings 6 | PKG_CONFIG_NAME="libapparmor" 7 | PKG_DEB_NAME="libapparmor-dev" 8 | PKG_TEST_HEADER="" 9 | PKG_LIBS="-lapparmor" 10 | 11 | # Linux Kernel without AppArmor 12 | UNAME=`uname` 13 | if [ "$NO_APPARMOR" ] || [ "$UNAME" != "Linux" ] || [ ! -d "/sys/module/apparmor" ]; then 14 | echo "Building without AppArmor ($UNAME)" 15 | sed -e "s|@cflags@||" -e "s|@libs@||" src/Makevars.in > src/Makevars 16 | exit 0 17 | else 18 | echo "Checking for AppArmor... ($UNAME)" 19 | fi 20 | 21 | #Try pkg-config 22 | pkg-config --exists libapparmor 2> /dev/null 23 | if [ $? -eq 0 ]; then 24 | echo "Found pkg-config cflags/libs!" 25 | PKG_CFLAGS=$(pkg-config --cflags libapparmor) 26 | PKG_LIBS=$(pkg-config --libs libapparmor) 27 | fi 28 | 29 | # For debugging 30 | echo "Using PKG_CFLAGS=$PKG_CFLAGS" 31 | echo "Using PKG_LIBS=$PKG_LIBS" 32 | 33 | # Find compiler 34 | CC=$(${R_HOME}/bin/R CMD config CC) 35 | CFLAGS=$(${R_HOME}/bin/R CMD config CFLAGS) 36 | CPPFLAGS=$(${R_HOME}/bin/R CMD config CPPFLAGS) 37 | 38 | # Test for header 39 | echo "#include $PKG_TEST_HEADER" | ${CC} -E ${CPPFLAGS} ${PKG_CFLAGS} ${CFLAGS} -xc - >/dev/null 2>&1 40 | 41 | # Customize the error 42 | if [ $? -ne 0 ]; then 43 | echo "Looks like AppArmor is not installed." 44 | echo "To install AppArmor, run: sudo apt-get install $PKG_DEB_NAME." 45 | if [ "$FORCE_APPARMOR" ]; then 46 | exit 1 47 | else 48 | echo "Disabling apparmor support" 49 | sed -e "s|@cflags@||" -e "s|@libs@||" src/Makevars.in > src/Makevars 50 | exit 0 51 | fi 52 | fi 53 | 54 | # Write to Makevars 55 | sed -e "s|@cflags@|-DHAVE_APPARMOR $PKG_CFLAGS|" -e "s|@libs@|$PKG_LIBS|" src/Makevars.in > src/Makevars 56 | exit 0 57 | -------------------------------------------------------------------------------- /configure.win: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "This package is UNIX only, which means it does not work on Windows." 3 | exit 1 4 | -------------------------------------------------------------------------------- /man/chroot.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/chroot.R 3 | \name{chroot} 4 | \alias{chroot} 5 | \title{Change Root Dir} 6 | \usage{ 7 | chroot(path = getwd()) 8 | } 9 | \arguments{ 10 | \item{path}{directory of the new root} 11 | } 12 | \description{ 13 | Changes the root directory of the calling process to that specified in path. 14 | This directory will be used for pathnames beginning with \code{/}. 15 | \strong{Only a privileged process (i.e. sudo) may call \code{chroot()}}. 16 | } 17 | \details{ 18 | This call changes an ingredient in the pathname resolution process 19 | and does nothing else. In particular, it is not intended to be used 20 | for any kind of security purpose, neither to fully sandbox a process 21 | nor to restrict filesystem system calls. 22 | } 23 | \references{ 24 | \href{https://man7.org/linux/man-pages/man2/chroot.2.html}{CHROOT(2)} 25 | } 26 | -------------------------------------------------------------------------------- /man/config.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/util.R 3 | \name{sys_config} 4 | \alias{sys_config} 5 | \alias{aa_config} 6 | \title{Package config} 7 | \usage{ 8 | sys_config() 9 | 10 | aa_config() 11 | } 12 | \description{ 13 | Shows which features are enabled in the package configuration. 14 | } 15 | \examples{ 16 | sys_config() 17 | } 18 | -------------------------------------------------------------------------------- /man/eval_fork.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/fork.R 3 | \name{eval_safe} 4 | \alias{eval_safe} 5 | \alias{eval_fork} 6 | \title{Safe Evaluation} 7 | \usage{ 8 | eval_safe( 9 | expr, 10 | tmp = tempfile("fork"), 11 | std_out = stdout(), 12 | std_err = stderr(), 13 | timeout = 0, 14 | priority = NULL, 15 | uid = NULL, 16 | gid = NULL, 17 | rlimits = NULL, 18 | profile = NULL, 19 | device = pdf 20 | ) 21 | 22 | eval_fork( 23 | expr, 24 | tmp = tempfile("fork"), 25 | std_out = stdout(), 26 | std_err = stderr(), 27 | timeout = 0 28 | ) 29 | } 30 | \arguments{ 31 | \item{expr}{expression to evaluate} 32 | 33 | \item{tmp}{the value of \code{\link[=tempdir]{tempdir()}} inside the forked process} 34 | 35 | \item{std_out}{if and where to direct child process \code{STDOUT}. Must be one of 36 | \code{TRUE}, \code{FALSE}, filename, connection object or callback function. See section 37 | on \emph{Output Streams} below for details.} 38 | 39 | \item{std_err}{if and where to direct child process \code{STDERR}. Must be one of 40 | \code{TRUE}, \code{FALSE}, filename, connection object or callback function. See section 41 | on \emph{Output Streams} below for details. 42 | Non root user may only raise this value (decrease priority)} 43 | 44 | \item{timeout}{maximum time in seconds to allow for call to return} 45 | 46 | \item{priority}{(integer) priority of the child process. High value is low priority.} 47 | 48 | \item{uid}{evaluate as given user (uid or name). See \code{\link[=setuid]{setuid()}}, only for root.} 49 | 50 | \item{gid}{evaluate as given group (gid or name). See \code{\link[=setgid]{setgid()}} only for root.} 51 | 52 | \item{rlimits}{named vector/list with rlimit values, for example: \code{c(cpu = 60, fsize = 1e6)}.} 53 | 54 | \item{profile}{AppArmor profile, see \code{RAppArmor::aa_change_profile()}. 55 | Requires the \code{RAppArmor} package (Debian/Ubuntu only)} 56 | 57 | \item{device}{graphics device to use in the fork, see \code{\link[=dev.new]{dev.new()}}} 58 | } 59 | \description{ 60 | Evaluates an expression in a temporary fork and returns the value without any 61 | side effects on the main R session. For \code{\link[=eval_safe]{eval_safe()}} the expression is wrapped 62 | in additional R code to handle errors and graphics. 63 | } 64 | \details{ 65 | Some programs such as \code{Java} are not fork-safe and cannot be called from within a 66 | forked process if they have already been loaded in the main process. On MacOS any 67 | software calling \code{CoreFoundation} functionality might crash within the fork. This 68 | includes \code{libcurl} which has been built on OSX against native SecureTransport rather 69 | than OpenSSL for https connections. The same limitations hold for e.g. \code{parallel::mcparallel()}. 70 | } 71 | \examples{ 72 | # works like regular eval: 73 | eval_safe(rnorm(5)) 74 | 75 | # Exceptions get propagated 76 | test <- function() { doesnotexit() } 77 | tryCatch(eval_safe(test()), error = function(e){ 78 | cat("oh no!", e$message, "\n") 79 | }) 80 | 81 | # Honor interrupt and timeout, even inside C evaluations 82 | try(eval_safe(svd(matrix(rnorm(1e8), 1e4)), timeout = 2)) 83 | 84 | # Capture output 85 | outcon <- rawConnection(raw(0), "r+") 86 | eval_safe(print(sessionInfo()), std_out = outcon) 87 | cat(rawToChar(rawConnectionValue(outcon))) 88 | close(outcon) 89 | } 90 | -------------------------------------------------------------------------------- /man/process.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/process.R 3 | \name{getuid} 4 | \alias{getuid} 5 | \alias{getgid} 6 | \alias{geteuid} 7 | \alias{getegid} 8 | \alias{getpid} 9 | \alias{getppid} 10 | \alias{getpgid} 11 | \alias{getpriority} 12 | \alias{setuid} 13 | \alias{seteuid} 14 | \alias{setgid} 15 | \alias{setegid} 16 | \alias{setpgid} 17 | \alias{setpriority} 18 | \alias{kill} 19 | \title{Process Info} 20 | \usage{ 21 | getuid() 22 | 23 | getgid() 24 | 25 | geteuid() 26 | 27 | getegid() 28 | 29 | getpid() 30 | 31 | getppid() 32 | 33 | getpgid() 34 | 35 | getpriority() 36 | 37 | setuid(uid) 38 | 39 | seteuid(uid) 40 | 41 | setgid(gid) 42 | 43 | setegid(gid) 44 | 45 | setpgid(pgid = 0) 46 | 47 | setpriority(prio) 48 | 49 | kill(pid, signal = SIGTERM) 50 | } 51 | \arguments{ 52 | \item{uid}{User ID from \verb{/etc/passwd}.} 53 | 54 | \item{gid}{Group ID from \verb{/etc/group}.} 55 | 56 | \item{pgid}{Process Group ID. Default \code{0} sets pgid to the current pid.} 57 | 58 | \item{prio}{Priority level} 59 | 60 | \item{pid}{process ID (integer)} 61 | 62 | \item{signal}{a signal number (integer), defaults to \link[tools:pskill]{tools::SIGTERM}.} 63 | } 64 | \description{ 65 | Get or set attributes of the current process. 66 | } 67 | \details{ 68 | Acronyms stand for: 69 | \itemize{ 70 | \item \code{pid} Process ID 71 | \item \code{ppid} Parent-Process ID 72 | \item \code{pgid} Process-Group ID 73 | \item \code{uid} User ID 74 | \item \code{euid} Effective User ID 75 | \item \code{gid} Group ID 76 | \item \code{egid} Effective Group ID 77 | \item \code{prio} Priority level 78 | } 79 | 80 | An unprivileged (non-root) process cannot change it's \code{uid} and only lower 81 | process priority (higher value). 82 | } 83 | \examples{ 84 | # Current User: 85 | getuid() 86 | # Current UserGroup: 87 | getgid() 88 | # Current UserGroup: 89 | geteuid() 90 | # Current UserGroup: 91 | getegid() 92 | # Process ID 93 | getpid() 94 | # parent PID: 95 | getppid() 96 | # Process group id: 97 | getpgid() 98 | 99 | # Detach process group 100 | setpgid(0) 101 | getpgid() 102 | # Process priority: 103 | getpriority() 104 | # Decrease priority 105 | setpriority(getpriority() + 1) 106 | } 107 | \references{ 108 | \href{https://man7.org/linux/man-pages/man2/getuid.2.html}{GETUID(2)} 109 | \href{https://man7.org/linux/man-pages/man2/getpid.2.html}{GETPID(2)} 110 | \href{https://man7.org/linux/man-pages/man2/getpgid.2.html}{GETPGID(2)} 111 | \href{https://man7.org/linux/man-pages/man2/getpriority.2.html}{GETPRIORITY(2)} 112 | } 113 | -------------------------------------------------------------------------------- /man/rlimit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rlimit.R 3 | \name{rlimit} 4 | \alias{rlimit} 5 | \alias{rlimit_all} 6 | \alias{rlimit_as} 7 | \alias{rlimit_core} 8 | \alias{rlimit_cpu} 9 | \alias{rlimit_data} 10 | \alias{rlimit_fsize} 11 | \alias{rlimit_memlock} 12 | \alias{rlimit_nofile} 13 | \alias{rlimit_nproc} 14 | \alias{rlimit_stack} 15 | \title{Resource Limits} 16 | \usage{ 17 | rlimit_all() 18 | 19 | rlimit_as(cur = NULL, max = NULL) 20 | 21 | rlimit_core(cur = NULL, max = NULL) 22 | 23 | rlimit_cpu(cur = NULL, max = NULL) 24 | 25 | rlimit_data(cur = NULL, max = NULL) 26 | 27 | rlimit_fsize(cur = NULL, max = NULL) 28 | 29 | rlimit_memlock(cur = NULL, max = NULL) 30 | 31 | rlimit_nofile(cur = NULL, max = NULL) 32 | 33 | rlimit_nproc(cur = NULL, max = NULL) 34 | 35 | rlimit_stack(cur = NULL, max = NULL) 36 | } 37 | \arguments{ 38 | \item{cur}{set the current (soft) limit for this resource. See details.} 39 | 40 | \item{max}{set the max (hard) limit for this resource. See details.} 41 | } 42 | \description{ 43 | Get and set process resource limits. Each function returns the current limits, and 44 | can optionally update the limit by passing argument values. The \code{rlimit_all()} 45 | function is a convenience wrapper which prints all current hard and soft limits. 46 | } 47 | \details{ 48 | Each resource has an associated soft and hard limit. The soft limit is the value 49 | that the kernel enforces for the corresponding resource. The hard limit acts as a 50 | ceiling for the soft limit: an unprivileged process may set only its soft limit to 51 | a value in the range from 0 up to the hard limit, and (irreversibly) lower its hard 52 | limit. 53 | 54 | Definitons from the \href{https://man7.org/linux/man-pages/man2/setrlimit.2.html}{Linux manual page} 55 | are as follows: 56 | \itemize{ 57 | \item \code{RLIMIT_AS} : the maximum size of the process's virtual memory (address space) in bytes. 58 | \item \code{RLIMIT_CORE} : the maximum size of a core file that the process may dump. 59 | \item \code{RLIMIT_CPU} : a limit in seconds on the amount of CPU time (\strong{not} elapsed time) that 60 | the process may consume. When the process reaches the soft limit, it is sent a \code{SIGXCPU} signal. 61 | \item \code{RLIMIT_DATA} : the maximum size of the process's data segment (initialized data, uninitialized 62 | data, and heap). 63 | \item \code{RLIMIT_FSIZE} : the maximum size of files that the process may create. Attempts to extend a 64 | file beyond this limit result in delivery of a SIGXFSZ signal. 65 | \item \code{RLIMIT_MEMLOCK} : the maximum number of bytes of memory that may be locked into RAM. 66 | \item \code{RLIMIT_NOFILE} : a value one greater than the maximum file descriptor number that can be opened 67 | by this process. 68 | \item \code{RLIMIT_NPROC} : the maximum number of processes that can be created for the real user ID of the 69 | calling process. Upon encountering this limit, fork fails with the error EAGAIN. Not enforced for 70 | root user. 71 | \item \code{RLIMIT_STACK} : the maximum size of the process stack, in bytes. 72 | } 73 | 74 | Note that the support for enforcing limits very widely by system. In particular 75 | \code{RLIMIT_AS} has a different meaning depending on how memory allocation is managed 76 | by the operating system (and doesn't work at all on MacOS). 77 | } 78 | \examples{ 79 | # Print all limits 80 | rlimit_all() 81 | 82 | # Get one limit 83 | rlimit_as() 84 | 85 | \dontrun{ 86 | # Set a soft limit 87 | lim <- rlimit_as(1e9) 88 | print(lim) 89 | 90 | # Reset the limit to max 91 | rlimit_as(cur = lim$max) 92 | 93 | # Set a hard limit (irreversible) 94 | rlimit_as(max = 1e10) 95 | } 96 | } 97 | \references{ 98 | \href{https://man7.org/linux/man-pages/man2/setrlimit.2.html}{GETRLIMIT(2)} 99 | } 100 | -------------------------------------------------------------------------------- /man/userinfo.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/userinfo.R 3 | \name{userinfo} 4 | \alias{userinfo} 5 | \alias{user_info} 6 | \alias{group_info} 7 | \title{User / Group Info} 8 | \usage{ 9 | user_info(uid = getuid()) 10 | 11 | group_info(gid = getgid()) 12 | } 13 | \arguments{ 14 | \item{uid}{user ID (integer) or name (string)} 15 | 16 | \item{gid}{group ID (integer) or name (string)} 17 | } 18 | \description{ 19 | Lookup a user or group info via user uid/name or group gid/name. 20 | } 21 | \examples{ 22 | # Get info current user 23 | user_info() 24 | group_info() 25 | } 26 | \references{ 27 | \href{https://man7.org/linux/man-pages/man3/getpwnam.3.html}{GETPWNAM(3)} 28 | \href{https://man7.org/linux/man-pages/man3/getgrnam.3.html}{GETGRNAM(3)} 29 | } 30 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # unix 2 | 3 | > Unix System Utilities 4 | 5 | [![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/unix)](https://cran.r-project.org/package=unix) 6 | [![CRAN RStudio mirror downloads](http://cranlogs.r-pkg.org/badges/unix)](https://cran.r-project.org/package=unix) 7 | 8 | > Bindings to system utilities found in most Unix systems, 9 | mainly POSIX functions which are not part of the Standard C Library. 10 | 11 | -------------------------------------------------------------------------------- /src/Makevars.in: -------------------------------------------------------------------------------- 1 | PKG_SAFE = $(_R_SHLIB_BUILD_OBJECTS_SYMBOL_TABLES_)$(_R_CHECK_SIZE_OF_TARBALL_) 2 | PKG_CFLAGS = $(C_VISIBILITY) 3 | PKG_CPPFLAGS = @cflags@ -DSYS_BUILD_SAFE$(PKG_SAFE) 4 | PKG_LIBS = @libs@ 5 | 6 | all: clean 7 | 8 | clean: 9 | rm -f $(SHLIB) $(OBJECTS) 10 | -------------------------------------------------------------------------------- /src/chroot.c: -------------------------------------------------------------------------------- 1 | #define R_NO_REMAP 2 | #define STRICT_R_HEADERS 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | extern void bail_if(int err, const char * what); 10 | 11 | SEXP R_chroot(SEXP path){ 12 | bail_if(chroot(CHAR(STRING_ELT(path, 0))), "chroot()"); 13 | return path; 14 | } 15 | -------------------------------------------------------------------------------- /src/fork.c: -------------------------------------------------------------------------------- 1 | #define R_INTERFACE_PTRS 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef __linux__ 18 | #include 19 | #endif 20 | 21 | static const int R_DefaultSerializeVersion = 2; 22 | 23 | #define r 0 24 | #define w 1 25 | 26 | #define waitms 200 27 | 28 | extern Rboolean R_isForkedChild; 29 | extern char * Sys_TempDir; 30 | 31 | void bail_if(int err, const char * what){ 32 | if(err) 33 | Rf_errorcall(R_NilValue, "System failure for: %s (%s)", what, strerror(errno)); 34 | } 35 | 36 | static void warn_if(int err, const char * what){ 37 | if(err) 38 | Rf_warningcall(R_NilValue, "System failure for: %s (%s)", what, strerror(errno)); 39 | } 40 | 41 | void kill_process_group(int signum) { 42 | kill(0, SIGKILL); // kills process group 43 | raise(SIGKILL); // just to be sure 44 | } 45 | 46 | static int wait_for_action2(int fd1, int fd2){ 47 | short events = POLLIN | POLLERR | POLLHUP; 48 | struct pollfd ufds[2] = { 49 | {fd1, events, events}, 50 | {fd2, events, events} 51 | }; 52 | return poll(ufds, 2, waitms); 53 | } 54 | 55 | static void print_if(int err, const char * what){ 56 | if(err){ 57 | FILE *stream = fdopen(STDERR_FILENO, "w"); 58 | if(stream){ 59 | fprintf(stream, "System failure for: %s (%s)\n", what, strerror(errno)); 60 | fclose(stream); 61 | } 62 | } 63 | } 64 | 65 | static void set_output(int target, const char * file){ 66 | close(target); 67 | int fd = open(file, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); 68 | print_if(fd < 0, "open() set_output"); 69 | if(fd == target) 70 | return; 71 | print_if(fcntl(fd, F_DUPFD, target) < 0, "fcntl() set_output"); 72 | close(fd); 73 | } 74 | 75 | static void safe_close(int target){ 76 | set_output(target, "/dev/null"); 77 | } 78 | 79 | static void R_callback(SEXP fun, const char * buf, ssize_t len){ 80 | if(!isFunction(fun)) return; 81 | int ok; 82 | SEXP str = PROTECT(allocVector(RAWSXP, len)); 83 | memcpy(RAW(str), buf, len); 84 | SEXP call = PROTECT(LCONS(fun, LCONS(str, R_NilValue))); 85 | R_tryEval(call, R_GlobalEnv, &ok); 86 | UNPROTECT(2); 87 | } 88 | 89 | static void print_output(int pipe_out[2], SEXP fun){ 90 | static ssize_t len; 91 | static char buffer[65336]; 92 | while ((len = read(pipe_out[r], buffer, sizeof(buffer))) > 0) 93 | R_callback(fun, buffer, len); 94 | } 95 | 96 | static void check_interrupt_fn(void *dummy) { 97 | R_CheckUserInterrupt(); 98 | } 99 | 100 | static int pending_interrupt(void) { 101 | return !(R_ToplevelExec(check_interrupt_fn, NULL)); 102 | } 103 | 104 | static void pipe_set_read(int pipe[2]){ 105 | close(pipe[w]); 106 | bail_if(fcntl(pipe[r], F_SETFL, O_NONBLOCK) < 0, "fcntl() in pipe_set_read"); 107 | } 108 | 109 | static int wait_with_timeout(int fd, int ms){ 110 | short events = POLLIN | POLLERR | POLLHUP; 111 | struct pollfd ufds = {fd, events, 0}; 112 | if(poll(&ufds, 1, ms) > 0) 113 | return ufds.revents; 114 | return 0; 115 | } 116 | 117 | /* Callback functions to serialize/unserialize via the pipe */ 118 | static void OutBytesCB(R_outpstream_t stream, void * raw, int size){ 119 | int * results = stream->data; 120 | char * buf = raw; 121 | ssize_t remaining = size; 122 | while(remaining > 0){ 123 | ssize_t written = write(results[w], buf, remaining); 124 | bail_if(written < 0, "write to pipe"); 125 | remaining -= written; 126 | buf += written; 127 | } 128 | } 129 | 130 | static void InBytesCB(R_inpstream_t stream, void *buf, int length){ 131 | R_CheckUserInterrupt(); 132 | int * results = stream->data; 133 | bail_if(read(results[r], buf, length) < 0, "read from pipe"); 134 | } 135 | 136 | /* Not sure if these are ever needed */ 137 | static void OutCharCB(R_outpstream_t stream, int c){ 138 | OutBytesCB(stream, &c, sizeof(c)); 139 | } 140 | 141 | static int InCharCB(R_inpstream_t stream){ 142 | int val; 143 | InBytesCB(stream, &val, sizeof(val)); 144 | return val; 145 | } 146 | 147 | static SEXP unserialize_from_pipe(int results[2]){ 148 | //unserialize stream 149 | struct R_inpstream_st stream; 150 | R_InitInPStream(&stream, results, R_pstream_xdr_format, InCharCB, InBytesCB, NULL, R_NilValue); 151 | return R_Unserialize(&stream); 152 | } 153 | 154 | static void serialize_to_pipe(SEXP object, int results[2]){ 155 | //serialize output 156 | PROTECT(object); 157 | struct R_outpstream_st stream; 158 | R_InitOutPStream(&stream, results, R_pstream_xdr_format, R_DefaultSerializeVersion, OutCharCB, OutBytesCB, NULL, R_NilValue); 159 | R_Serialize(object, &stream); 160 | UNPROTECT(1); 161 | } 162 | 163 | static void raw_to_pipe(SEXP object, int results[2]){ 164 | R_xlen_t len = Rf_length(object); 165 | bail_if(write(results[w], &len, sizeof(len)) < sizeof(len), "raw_to_pipe: send size-byte"); 166 | bail_if(write(results[w], RAW(object), len) < len, "raw_to_pipe: send raw data"); 167 | } 168 | 169 | SEXP raw_from_pipe(int results[2]){ 170 | R_xlen_t len = 0; 171 | bail_if(read(results[r], &len, sizeof(len)) < sizeof(len), "raw_from_pipe: read size-byte"); 172 | SEXP out = Rf_allocVector(RAWSXP, len); 173 | unsigned char * ptr = RAW(out); 174 | while(len > 0){ 175 | int bufsize = read(results[r], ptr, len); 176 | bail_if(bufsize <= 0, "failed to read from buffer"); 177 | ptr += bufsize; 178 | len -= bufsize; 179 | } 180 | return out; 181 | } 182 | 183 | int Fake_ReadConsole(const char * a, unsigned char * b, int c, int d){ 184 | return 0; 185 | } 186 | 187 | void My_R_Flush(void){ 188 | 189 | } 190 | 191 | /* do not wipe (shared) tempdir or run finalizers in forked process */ 192 | void My_R_CleanUp (SA_TYPE saveact, int status, int RunLast){ 193 | #ifdef SYS_BUILD_SAFE 194 | //R_RunExitFinalizers(); 195 | Rf_KillAllDevices(); 196 | #endif 197 | } 198 | 199 | /* disables console, finalizers, interactivity inside forked procs */ 200 | void prepare_fork(const char * tmpdir, int fd_out, int fd_err){ 201 | #ifdef SYS_BUILD_SAFE 202 | //either set R_Outputfile+R_Consolefile OR ptr_R_WriteConsoleEx() 203 | R_Outputfile = fdopen(fd_out, "wb"); 204 | R_Consolefile = fdopen(fd_err, "wb"); 205 | ptr_R_WriteConsole = NULL; 206 | ptr_R_WriteConsoleEx = NULL; 207 | ptr_R_ResetConsole = My_R_Flush; 208 | ptr_R_FlushConsole = My_R_Flush; 209 | ptr_R_ReadConsole = Fake_ReadConsole; 210 | ptr_R_CleanUp = My_R_CleanUp; 211 | R_isForkedChild = 1; 212 | R_Interactive = 0; 213 | R_TempDir = strdup(tmpdir); 214 | #ifndef HAVE_VISIBILITY_ATTRIBUTE 215 | Sys_TempDir = R_TempDir; 216 | #endif 217 | #endif 218 | } 219 | 220 | SEXP R_eval_fork(SEXP call, SEXP env, SEXP subtmp, SEXP timeout, SEXP outfun, SEXP errfun){ 221 | int results[2]; 222 | int pipe_out[2]; 223 | int pipe_err[2]; 224 | bail_if(pipe(results), "create results pipe"); 225 | bail_if(pipe(pipe_out) || pipe(pipe_err), "create output pipes"); 226 | 227 | //fork the main process 228 | int fail = -1; 229 | pid_t pid = fork(); 230 | bail_if(pid < 0, "fork()"); 231 | 232 | if(pid == 0){ 233 | //prevents signals from being propagated to fork 234 | setpgid(0, 0); 235 | 236 | //close read pipe 237 | close(results[r]); 238 | 239 | //This breaks parallel! See issue #11 240 | safe_close(STDIN_FILENO); 241 | 242 | //Linux only: try to kill proccess group when parent dies 243 | #ifdef PR_SET_PDEATHSIG 244 | if(getenv("KILL_ORPHAN_FORKS")){ 245 | prctl(PR_SET_PDEATHSIG, SIGTERM); 246 | signal(SIGTERM, kill_process_group); 247 | } 248 | #endif 249 | 250 | //this is the hacky stuff 251 | prepare_fork(CHAR(STRING_ELT(subtmp, 0)), pipe_out[w], pipe_err[w]); 252 | 253 | //execute 254 | fail = 99; //not using this yet 255 | SEXP object = R_tryEval(call, env, &fail); 256 | 257 | //special case of raw vector 258 | if(fail == 0 && object != NULL && TYPEOF(object) == RAWSXP) 259 | fail = 1985; 260 | 261 | //try to send the 'success byte' and then output 262 | if(write(results[w], &fail, sizeof(fail)) > 0){ 263 | if(fail == 1985){ 264 | raw_to_pipe(object, results); 265 | } else if(fail == 0 && object){ 266 | serialize_to_pipe(object, results); 267 | } else { 268 | const char * errbuf = NULL; 269 | #ifdef SYS_BUILD_SAFE 270 | errbuf = R_curErrorBuf(); 271 | #endif 272 | serialize_to_pipe(mkString(errbuf ? errbuf : "unknown error in child"), results); 273 | } 274 | } 275 | 276 | //suicide 277 | close(results[w]); 278 | close(pipe_out[w]); 279 | close(pipe_err[w]); 280 | raise(SIGKILL); 281 | } 282 | 283 | //start timer 284 | struct timeval start, end; 285 | gettimeofday(&start, NULL); 286 | 287 | //start listening to child 288 | close(results[w]); 289 | pipe_set_read(pipe_out); 290 | pipe_set_read(pipe_err); 291 | int status = 0; 292 | int killcount = 0; 293 | double elapsed = 0; 294 | int is_timeout = 0; 295 | double totaltime = REAL(timeout)[0]; 296 | while(status == 0){ //mabye test for: is_alive(pid) ? 297 | //wait for pipe to hear from child 298 | if(is_timeout || pending_interrupt()){ 299 | //looks like rstudio always does SIGKILL, regardless 300 | warn_if(kill(pid, killcount == 0 ? SIGINT : killcount == 1 ? SIGTERM : SIGKILL), "kill child"); 301 | status = wait_with_timeout(results[r], 500); 302 | killcount++; 303 | } else { 304 | wait_for_action2(pipe_out[r], pipe_err[r]); 305 | status = wait_with_timeout(results[r], 0); 306 | 307 | //empty pipes 308 | print_output(pipe_out, outfun); 309 | print_output(pipe_err, errfun); 310 | gettimeofday(&end, NULL); 311 | elapsed = (end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec) / 1e6; 312 | is_timeout = (totaltime > 0) && (elapsed > totaltime); 313 | } 314 | } 315 | warn_if(close(pipe_out[r]), "close stdout"); 316 | warn_if(close(pipe_err[r]), "close stderr"); 317 | bail_if(status < 0, "poll() on failure pipe"); 318 | 319 | //read the 'success byte' 320 | SEXP res = R_NilValue; 321 | if(status > 0){ 322 | int child_is_alive = read(results[r], &fail, sizeof(fail)); 323 | bail_if(child_is_alive < 0, "read pipe"); 324 | if(child_is_alive > 0){ 325 | if(fail == 0){ 326 | res = unserialize_from_pipe(results); 327 | } else if(fail == 1985){ 328 | res = raw_from_pipe(results); 329 | fail = 0; 330 | } 331 | } 332 | } 333 | 334 | //cleanup 335 | close(results[r]); 336 | kill(-pid, SIGKILL); //kills entire process group 337 | waitpid(pid, NULL, 0); //wait for zombie(s) to die 338 | 339 | //actual R error 340 | if(status == 0 || fail){ 341 | if(killcount && is_timeout){ 342 | Rf_errorcall(call, "timeout reached (%f sec)", totaltime); 343 | } else if(killcount) { 344 | Rf_errorcall(call, "process interrupted by parent"); 345 | } else if(isString(res) && Rf_length(res) && Rf_length(STRING_ELT(res, 0)) > 8){ 346 | Rf_errorcall(R_NilValue, "%s", CHAR(STRING_ELT(res, 0))); 347 | } 348 | Rf_errorcall(call, "child process has died"); 349 | } 350 | 351 | //add timeout attribute 352 | return res; 353 | } 354 | 355 | SEXP R_freeze(SEXP interrupt) { 356 | int loop = 1; 357 | while(loop){ 358 | if(asLogical(interrupt) && pending_interrupt()) 359 | break; 360 | loop = 1+1; 361 | } 362 | return R_NilValue; 363 | } 364 | 365 | -------------------------------------------------------------------------------- /src/process.c: -------------------------------------------------------------------------------- 1 | #define R_NO_REMAP 2 | #define STRICT_R_HEADERS 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | extern void bail_if(int err, const char * what); 11 | 12 | SEXP R_kill(SEXP pid, SEXP sig){ 13 | bail_if(kill(Rf_asInteger(pid), Rf_asInteger(sig)) < 0, "send kill()"); 14 | return R_NilValue; 15 | } 16 | 17 | SEXP R_getuid(void){ 18 | return Rf_ScalarInteger(getuid()); 19 | } 20 | 21 | SEXP R_setuid(SEXP id){ 22 | bail_if(setuid(Rf_asInteger(id)) < 0, "setuid()"); 23 | return R_getuid(); 24 | } 25 | 26 | SEXP R_geteuid(void){ 27 | return Rf_ScalarInteger(geteuid()); 28 | } 29 | 30 | SEXP R_seteuid(SEXP id){ 31 | bail_if(seteuid(Rf_asInteger(id)) < 0, "setuid()"); 32 | return R_geteuid(); 33 | } 34 | 35 | SEXP R_getgid (void) { 36 | return Rf_ScalarInteger(getgid()); 37 | } 38 | 39 | SEXP R_setgid(SEXP id){ 40 | bail_if(setgid(Rf_asInteger(id)) < 0, "setuid()"); 41 | return R_getgid(); 42 | } 43 | 44 | SEXP R_getegid (void) { 45 | return Rf_ScalarInteger(getegid()); 46 | } 47 | 48 | SEXP R_setegid(SEXP id){ 49 | bail_if(setegid(Rf_asInteger(id)) < 0, "setuid()"); 50 | return R_getegid(); 51 | } 52 | 53 | SEXP R_getpid (void) { 54 | return Rf_ScalarInteger(getpid()); 55 | } 56 | 57 | SEXP R_getppid (void) { 58 | return Rf_ScalarInteger(getppid()); 59 | } 60 | 61 | SEXP R_getpgid (void) { 62 | return Rf_ScalarInteger(getpgid(0)); 63 | } 64 | 65 | SEXP R_setpgid(SEXP pid){ 66 | bail_if(setpgid(0, Rf_asInteger(pid)), "setpgid()"); 67 | return R_getpgid(); 68 | } 69 | 70 | SEXP R_getpriority (void) { 71 | return Rf_ScalarInteger(getpriority(PRIO_PROCESS, 0)); 72 | } 73 | 74 | SEXP R_setpriority(SEXP prio){ 75 | bail_if(setpriority(PRIO_PROCESS, 0, Rf_asInteger(prio)) < 0, "setpriority()"); 76 | return R_getpriority(); 77 | } 78 | -------------------------------------------------------------------------------- /src/register.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include // for NULL 4 | #include 5 | #include 6 | 7 | /* .Call calls */ 8 | extern SEXP R_aa_change_profile(SEXP); 9 | extern SEXP R_aa_getcon(void); 10 | extern SEXP R_aa_is_enabled(void); 11 | extern SEXP R_chroot(SEXP); 12 | extern SEXP R_eval_fork(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); 13 | extern SEXP R_freeze(SEXP); 14 | extern SEXP R_getegid(void); 15 | extern SEXP R_geteuid(void); 16 | extern SEXP R_getgid(void); 17 | extern SEXP R_getpgid(void); 18 | extern SEXP R_getpid(void); 19 | extern SEXP R_getppid(void); 20 | extern SEXP R_getpriority(void); 21 | extern SEXP R_getuid(void); 22 | extern SEXP R_group_info(SEXP); 23 | extern SEXP R_have_apparmor(void); 24 | extern SEXP R_kill(SEXP, SEXP); 25 | extern SEXP R_rlimit_as(SEXP, SEXP); 26 | extern SEXP R_rlimit_core(SEXP, SEXP); 27 | extern SEXP R_rlimit_cpu(SEXP, SEXP); 28 | extern SEXP R_rlimit_data(SEXP, SEXP); 29 | extern SEXP R_rlimit_fsize(SEXP, SEXP); 30 | extern SEXP R_rlimit_memlock(SEXP, SEXP); 31 | extern SEXP R_rlimit_nofile(SEXP, SEXP); 32 | extern SEXP R_rlimit_nproc(SEXP, SEXP); 33 | extern SEXP R_rlimit_stack(SEXP, SEXP); 34 | extern SEXP R_safe_build(void); 35 | extern SEXP R_set_interactive(SEXP); 36 | extern SEXP R_set_rlimits(SEXP); 37 | extern SEXP R_set_tempdir(SEXP); 38 | extern SEXP R_setegid(SEXP); 39 | extern SEXP R_seteuid(SEXP); 40 | extern SEXP R_setgid(SEXP); 41 | extern SEXP R_setpgid(SEXP); 42 | extern SEXP R_setpriority(SEXP); 43 | extern SEXP R_setuid(SEXP); 44 | extern SEXP R_user_info(SEXP); 45 | 46 | static const R_CallMethodDef CallEntries[] = { 47 | {"R_aa_change_profile", (DL_FUNC) &R_aa_change_profile, 1}, 48 | {"R_aa_getcon", (DL_FUNC) &R_aa_getcon, 0}, 49 | {"R_aa_is_enabled", (DL_FUNC) &R_aa_is_enabled, 0}, 50 | {"R_chroot", (DL_FUNC) &R_chroot, 1}, 51 | {"R_eval_fork", (DL_FUNC) &R_eval_fork, 6}, 52 | {"R_freeze", (DL_FUNC) &R_freeze, 1}, 53 | {"R_getegid", (DL_FUNC) &R_getegid, 0}, 54 | {"R_geteuid", (DL_FUNC) &R_geteuid, 0}, 55 | {"R_getgid", (DL_FUNC) &R_getgid, 0}, 56 | {"R_getpgid", (DL_FUNC) &R_getpgid, 0}, 57 | {"R_getpid", (DL_FUNC) &R_getpid, 0}, 58 | {"R_getppid", (DL_FUNC) &R_getppid, 0}, 59 | {"R_getpriority", (DL_FUNC) &R_getpriority, 0}, 60 | {"R_getuid", (DL_FUNC) &R_getuid, 0}, 61 | {"R_group_info", (DL_FUNC) &R_group_info, 1}, 62 | {"R_have_apparmor", (DL_FUNC) &R_have_apparmor, 0}, 63 | {"R_kill", (DL_FUNC) &R_kill, 2}, 64 | {"R_rlimit_as", (DL_FUNC) &R_rlimit_as, 2}, 65 | {"R_rlimit_core", (DL_FUNC) &R_rlimit_core, 2}, 66 | {"R_rlimit_cpu", (DL_FUNC) &R_rlimit_cpu, 2}, 67 | {"R_rlimit_data", (DL_FUNC) &R_rlimit_data, 2}, 68 | {"R_rlimit_fsize", (DL_FUNC) &R_rlimit_fsize, 2}, 69 | {"R_rlimit_memlock", (DL_FUNC) &R_rlimit_memlock, 2}, 70 | {"R_rlimit_nofile", (DL_FUNC) &R_rlimit_nofile, 2}, 71 | {"R_rlimit_nproc", (DL_FUNC) &R_rlimit_nproc, 2}, 72 | {"R_rlimit_stack", (DL_FUNC) &R_rlimit_stack, 2}, 73 | {"R_safe_build", (DL_FUNC) &R_safe_build, 0}, 74 | {"R_set_interactive", (DL_FUNC) &R_set_interactive, 1}, 75 | {"R_set_rlimits", (DL_FUNC) &R_set_rlimits, 1}, 76 | {"R_set_tempdir", (DL_FUNC) &R_set_tempdir, 1}, 77 | {"R_setegid", (DL_FUNC) &R_setegid, 1}, 78 | {"R_seteuid", (DL_FUNC) &R_seteuid, 1}, 79 | {"R_setgid", (DL_FUNC) &R_setgid, 1}, 80 | {"R_setpgid", (DL_FUNC) &R_setpgid, 1}, 81 | {"R_setpriority", (DL_FUNC) &R_setpriority, 1}, 82 | {"R_setuid", (DL_FUNC) &R_setuid, 1}, 83 | {"R_user_info", (DL_FUNC) &R_user_info, 1}, 84 | {NULL, NULL, 0} 85 | }; 86 | 87 | attribute_visible void R_init_unix(DllInfo *dll) { 88 | R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); 89 | R_useDynamicSymbols(dll, FALSE); 90 | } 91 | -------------------------------------------------------------------------------- /src/rlimit.c: -------------------------------------------------------------------------------- 1 | #define R_NO_REMAP 2 | #define STRICT_R_HEADERS 3 | 4 | #include 5 | #include 6 | 7 | extern void bail_if(int err, const char * what); 8 | 9 | SEXP R_rlimit(int resource, SEXP softlim, SEXP hardlim){ 10 | 11 | //get current limit 12 | struct rlimit lim; 13 | bail_if(getrlimit(resource, &lim) < 0, "getrlimit() for current limits"); 14 | 15 | //update 16 | if(Rf_length(softlim) || Rf_length(hardlim)){ 17 | if(Rf_length(softlim)){ 18 | lim.rlim_cur = R_finite(Rf_asReal(softlim)) ? (rlim_t) Rf_asReal(softlim) : RLIM_INFINITY; 19 | 20 | //If max is too small, we need to try and raise it to at least cur 21 | if(lim.rlim_cur > lim.rlim_max) 22 | lim.rlim_max = lim.rlim_cur; 23 | } 24 | if(Rf_length(hardlim)) 25 | lim.rlim_max = R_finite(Rf_asReal(hardlim)) ? (rlim_t) Rf_asReal(hardlim) : RLIM_INFINITY; 26 | bail_if(setrlimit(resource, &lim) < 0, "setrlimit()"); 27 | bail_if(getrlimit(resource, &lim) < 0, "getrlimit() for new limits"); 28 | } 29 | 30 | //return values 31 | SEXP out = Rf_allocVector(REALSXP, 2); 32 | REAL(out)[0] = lim.rlim_cur == RLIM_INFINITY ? R_PosInf : lim.rlim_cur; 33 | REAL(out)[1] = lim.rlim_max == RLIM_INFINITY ? R_PosInf : lim.rlim_max; 34 | return out; 35 | } 36 | 37 | // Missing on OpenBSD 38 | #ifndef RLIMIT_AS 39 | #define RLIMIT_AS RLIMIT_DATA 40 | #endif 41 | 42 | SEXP R_rlimit_as(SEXP a, SEXP b) {return R_rlimit(RLIMIT_AS, a, b);} 43 | SEXP R_rlimit_core(SEXP a, SEXP b) {return R_rlimit(RLIMIT_CORE, a, b);} 44 | SEXP R_rlimit_cpu(SEXP a, SEXP b) {return R_rlimit(RLIMIT_CPU, a, b);} 45 | SEXP R_rlimit_data(SEXP a, SEXP b) {return R_rlimit(RLIMIT_DATA, a, b);} 46 | SEXP R_rlimit_fsize(SEXP a, SEXP b) {return R_rlimit(RLIMIT_FSIZE, a, b);} 47 | SEXP R_rlimit_nofile(SEXP a, SEXP b) {return R_rlimit(RLIMIT_NOFILE, a, b);} 48 | SEXP R_rlimit_stack(SEXP a, SEXP b) {return R_rlimit(RLIMIT_STACK, a, b);} 49 | 50 | /* these are not available on Solaris 10 */ 51 | 52 | SEXP make_navec(void){ 53 | SEXP out = Rf_allocVector(REALSXP, 2); 54 | REAL(out)[0] = NA_REAL; 55 | REAL(out)[1] = NA_REAL; 56 | return(out); 57 | } 58 | 59 | SEXP R_rlimit_nproc(SEXP a, SEXP b) { 60 | #ifdef RLIMIT_NPROC 61 | return R_rlimit(RLIMIT_NPROC, a, b); 62 | #else 63 | Rf_warning("RLIMIT_NPROC not available on this system"); 64 | return make_navec(); 65 | #endif 66 | } 67 | 68 | SEXP R_rlimit_memlock(SEXP a, SEXP b) { 69 | #ifdef RLIMIT_MEMLOCK 70 | return R_rlimit(RLIMIT_MEMLOCK, a, b); 71 | #else 72 | Rf_warning("RLIMIT_MEMLOCK not available on this system"); 73 | return make_navec(); 74 | #endif 75 | } 76 | -------------------------------------------------------------------------------- /src/userinfo.c: -------------------------------------------------------------------------------- 1 | #define R_NO_REMAP 2 | #define STRICT_R_HEADERS 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define make_string(x) x ? Rf_mkString(x) : Rf_ScalarString(NA_STRING) 12 | 13 | extern void bail_if(int err, const char * what); 14 | 15 | SEXP R_user_info(SEXP input){ 16 | errno = 0; 17 | struct passwd * info = Rf_isInteger(input) ? 18 | getpwuid(Rf_asInteger(input)) : 19 | getpwnam(CHAR(STRING_ELT(input, 0))); 20 | bail_if(info == NULL, "getpwuid() / getpwnam()"); 21 | SEXP out = PROTECT(Rf_allocVector(VECSXP, 7)); 22 | SET_VECTOR_ELT(out, 0, make_string(info->pw_name)); 23 | SET_VECTOR_ELT(out, 1, make_string(info->pw_passwd)); 24 | SET_VECTOR_ELT(out, 2, Rf_ScalarInteger(info->pw_uid)); 25 | SET_VECTOR_ELT(out, 3, Rf_ScalarInteger(info->pw_gid)); 26 | SET_VECTOR_ELT(out, 4, make_string(info->pw_gecos)); 27 | SET_VECTOR_ELT(out, 5, make_string(info->pw_dir)); 28 | SET_VECTOR_ELT(out, 6, make_string(info->pw_shell)); 29 | UNPROTECT(1); 30 | return out; 31 | } 32 | 33 | SEXP R_group_info(SEXP input){ 34 | errno = 0; 35 | struct group * info = Rf_isInteger(input) ? 36 | getgrgid(Rf_asInteger(input)) : 37 | getgrnam(CHAR(STRING_ELT(input, 0))); 38 | bail_if(info == NULL, "getgrgid() / getgrnam()"); 39 | SEXP out = PROTECT(Rf_allocVector(VECSXP, 4)); 40 | SET_VECTOR_ELT(out, 0, make_string(info->gr_name)); 41 | SET_VECTOR_ELT(out, 1, make_string(info->gr_passwd)); 42 | SET_VECTOR_ELT(out, 2, Rf_ScalarInteger(info->gr_gid)); 43 | int count = 0; 44 | while(info->gr_mem[count]) 45 | count++; 46 | SET_VECTOR_ELT(out, 3, Rf_allocVector(STRSXP, count)); 47 | for(int i = 0; i < count; i++) 48 | SET_STRING_ELT(VECTOR_ELT(out, 3), i, Rf_mkChar(info->gr_mem[i])); 49 | UNPROTECT(1); 50 | return out; 51 | } 52 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | //Because mkString() segfaulst for NULL 7 | #define make_string(x) x ? Rf_mkString(x) : ScalarString(NA_STRING) 8 | 9 | //Define in exec.c 10 | extern void bail_if(int err, const char * what); 11 | 12 | //For: aa_change_profile() 13 | #ifdef HAVE_APPARMOR 14 | #include 15 | #endif 16 | 17 | //For setuid(), rlimit, etc() 18 | #include 19 | #include 20 | 21 | // Missing on Solaris 22 | #ifndef RLIMIT_NPROC 23 | #define RLIMIT_NPROC -1 24 | #endif 25 | 26 | #ifndef RLIMIT_MEMLOCK 27 | #define RLIMIT_MEMLOCK -1 28 | #endif 29 | 30 | // Missing on OpenBSD 31 | #ifndef RLIMIT_AS 32 | #define RLIMIT_AS RLIMIT_DATA 33 | #endif 34 | 35 | // Order should match the R function 36 | static int rlimit_types[9] = { 37 | RLIMIT_AS, //0 38 | RLIMIT_CORE, //1 39 | RLIMIT_CPU, //2 40 | RLIMIT_DATA, //3 41 | RLIMIT_FSIZE, //4 42 | RLIMIT_MEMLOCK, //5 43 | RLIMIT_NOFILE, //6 44 | RLIMIT_NPROC, //7 45 | RLIMIT_STACK, //8 46 | }; 47 | 48 | SEXP R_safe_build(void){ 49 | #ifdef SYS_BUILD_SAFE 50 | return ScalarLogical(TRUE); 51 | #else 52 | return ScalarLogical(FALSE); 53 | #endif 54 | } 55 | 56 | SEXP R_have_apparmor(void){ 57 | #ifdef HAVE_APPARMOR 58 | return ScalarLogical(TRUE); 59 | #else 60 | return ScalarLogical(FALSE); 61 | #endif 62 | } 63 | 64 | SEXP R_set_tempdir(SEXP path){ 65 | #ifdef SYS_BUILD_SAFE 66 | const char * tmpdir = CHAR(STRING_ELT(path, 0)); 67 | R_TempDir = strdup(tmpdir); 68 | #else 69 | Rf_error("Cannot set tempdir(), sys has been built without SYS_BUILD_SAFE"); 70 | #endif 71 | return path; 72 | } 73 | 74 | SEXP R_set_interactive(SEXP set){ 75 | #ifdef SYS_BUILD_SAFE 76 | extern Rboolean R_Interactive; 77 | R_Interactive = asLogical(set); 78 | #else 79 | Rf_error("Cannot set interactive(), sys has been built without SYS_BUILD_SAFE"); 80 | #endif 81 | return set; 82 | } 83 | 84 | //VECTOR of length n; 85 | SEXP R_set_rlimits(SEXP limitvec){ 86 | if(!Rf_isNumeric(limitvec)) 87 | Rf_error("limitvec is not numeric"); 88 | size_t len = sizeof(rlimit_types)/sizeof(rlimit_types[0]); 89 | if(Rf_length(limitvec) != len) 90 | Rf_error("limitvec wrong size"); 91 | for(int i = 0; i < len; i++){ 92 | int resource = rlimit_types[i]; 93 | double val = REAL(limitvec)[i]; 94 | if(resource < 0 || val == 0 || ISNA(val)) 95 | continue; 96 | rlim_t rlim_val = R_finite(val) ? val : RLIM_INFINITY; 97 | //Rprintf("Setting %d to %d\n", resource, rlim_val); 98 | struct rlimit lim = {rlim_val, rlim_val}; 99 | bail_if(setrlimit(resource, &lim) < 0, "setrlimit()"); 100 | } 101 | return R_NilValue; 102 | } 103 | 104 | /*** Only on Debian/Ubuntu systems ***/ 105 | SEXP R_aa_change_profile(SEXP profile){ 106 | #ifdef HAVE_APPARMOR 107 | const char * profstr = CHAR(STRING_ELT(profile, 0)); 108 | bail_if(aa_change_profile (profstr) < 0, "aa_change_profile()"); 109 | #endif 110 | return R_NilValue; 111 | } 112 | 113 | SEXP R_aa_is_enabled(void){ 114 | #ifndef HAVE_APPARMOR 115 | return R_NilValue; 116 | #else 117 | return ScalarLogical(aa_is_enabled()); 118 | #endif //HAVE_APPARMOR 119 | } 120 | 121 | SEXP R_aa_getcon(void){ 122 | #ifndef HAVE_APPARMOR 123 | return R_NilValue; 124 | #else 125 | char * con = NULL; 126 | char * mode = NULL; 127 | if(!aa_getcon (&con, &mode)) 128 | return R_NilValue; 129 | SEXP out = PROTECT(allocVector(VECSXP, 2)); 130 | SET_VECTOR_ELT(out, 0, make_string(con)); 131 | SET_VECTOR_ELT(out, 1, make_string(mode)); 132 | UNPROTECT(1); 133 | return out; 134 | #endif //HAVE_APPARMOR 135 | } 136 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | 3 | test_check("unix") 4 | -------------------------------------------------------------------------------- /tests/testthat/test-forking.R: -------------------------------------------------------------------------------- 1 | context("eval_fork") 2 | 3 | test_that("eval_fork works", { 4 | # PID must be different 5 | expect_false(Sys.getpid() == eval_fork(Sys.getpid())) 6 | expect_equal(getpid(), eval_fork(getppid())) 7 | 8 | # Priority (requires unix > 1.2) 9 | prio <- getpriority() 10 | expect_equal(eval_safe(getpriority(), priority = prio + 1), prio + 1) 11 | 12 | # initiates RNG with a seed (needed below) 13 | rnorm(1) 14 | 15 | # Test that state is inherited 16 | x <- eval_fork(rnorm(10)) 17 | y <- eval_fork(rnorm(10)) 18 | z <- rnorm(10) 19 | expect_identical(x, y) 20 | expect_identical(x, z) 21 | 22 | # Test cleanups 23 | for(i in 1:300){ 24 | expect_equal(pi, eval_fork(pi)) 25 | } 26 | }) 27 | 28 | test_that("eval_fork serializes large payloads", { 29 | skip_if_not(safe_build()) 30 | 31 | for(i in 1:10){ 32 | x <- rnorm(round(runif(1, 1e5, 1e6))) 33 | rawvec <- serialize(x, NULL) 34 | expect_equal(x, eval_fork(x)) 35 | expect_equal(x, eval_safe(x)) 36 | expect_equal(rawvec, eval_fork(rawvec)) 37 | expect_equal(rawvec, eval_safe(rawvec)) 38 | } 39 | }) 40 | 41 | 42 | test_that("eval_fork gives errors", { 43 | # Test regular errors 44 | expect_error(eval_safe(stop("uhoh")), "uhoh") 45 | expect_error(eval_safe(blablabla()), "could not find function") 46 | 47 | # Test that proc dies 48 | expect_error(eval_fork(tools::pskill(Sys.getpid())), "child process") 49 | expect_error(eval_fork(Sys.sleep(10), timeout = 2), "timeout") 50 | 51 | # Test that tryCatch works 52 | expect_equal(eval_fork(try(pi, silent = TRUE)), pi) 53 | expect_is(eval_fork(try(blabla(), silent = TRUE)), "try-error") 54 | expect_is(eval_fork(tryCatch(blabla(), error = identity)), "simpleError") 55 | }) 56 | 57 | test_that("Fork does not clean tmpdir", { 58 | skip_if_not(safe_build()) 59 | expect_error(eval_fork(q()), "died") 60 | expect_true(file.exists(tempdir())) 61 | }) 62 | 63 | test_that("eval_fork works recursively", { 64 | expect_equal(eval_fork(eval_fork(1+1)), 2) 65 | expect_equal(eval_fork(eval_fork(1+1) + eval_fork(1+1)), 4) 66 | 67 | expect_error(eval_safe(eval_safe(stop("uhoh"))), "uhoh") 68 | expect_error(eval_safe(eval_safe(blablabla())), "could not find function") 69 | 70 | fib_fork <- function(n){ 71 | eval_fork({ 72 | #print(Sys.getpid()) 73 | if(n < 2) n else fib_fork(n-1) + fib_fork(n-2) 74 | }) 75 | } 76 | 77 | #forks 10 deep :o 78 | expect_equal(fib_fork(10), 55) 79 | 80 | fib_safe <- function(n){ 81 | eval_safe({ 82 | #print(Sys.getpid()) 83 | if(n < 2) n else fib_safe(n-1) + fib_safe(n-2) 84 | }) 85 | } 86 | 87 | #forks 10 deep :o 88 | expect_equal(fib_safe(10), 55) 89 | }) 90 | 91 | test_that("compatibility with parallel package", { 92 | square_fork <- function(x){ 93 | parallel::mccollect(parallel::mcparallel(x^2))[[1]] 94 | } 95 | 96 | # Run mcparallel inside sys 97 | expect_equal(square_fork(5), 25) 98 | expect_equal(eval_fork(square_fork(6)), 36) 99 | expect_equal(eval_safe(square_fork(7)), 49) 100 | }) 101 | 102 | test_that("frozen children get killed", { 103 | expect_before <- function(expr, time){ 104 | elapsed <- system.time(try(expr, silent = TRUE))["elapsed"] 105 | expect_lt(elapsed, time) 106 | } 107 | 108 | # test timers 109 | expect_before(eval_fork(freeze(FALSE), timeout = 1), 2) 110 | expect_before(eval_fork(freeze(TRUE), timeout = 1), 2) 111 | }) 112 | 113 | test_that("condition class gets preserved", { 114 | test <- function(){ 115 | e <- structure( 116 | list(message = "some message", call = NULL), 117 | class = c("error", "condition", "my_custom_class") 118 | ) 119 | base::stop(e) 120 | } 121 | 122 | err <- tryCatch(eval_safe(test()), error = function(e){e}) 123 | expect_s3_class(err, "error") 124 | expect_s3_class(err, "my_custom_class") 125 | 126 | }) 127 | 128 | test_that("scope environment is correct", { 129 | (test <- function(){ 130 | mydev <- grDevices::pdf 131 | timer <- 60 132 | x <- 42 133 | blabla <- function(){ 134 | return(x) 135 | } 136 | testfun <- function(){ 137 | blabla() 138 | } 139 | testerr <- function(){ 140 | doesnotexit() 141 | } 142 | expect_equal(42, eval_safe(testfun(), dev = mydev, timeout = timer)) 143 | expect_equal(42, eval_fork(testfun(), timeout = timer)) 144 | expect_error(eval_safe(testerr()), "doesnotexit") 145 | })() 146 | }) 147 | 148 | test_that("rlimits apply in eval_safe", { 149 | if(rlimit_cpu()$max > 100) 150 | expect_equal(eval_safe(rlimit_cpu()$max, rlimits = c(cpu = 100)), 100) 151 | 152 | # unsupported rlimit 153 | expect_error(eval_safe(123, rlimits = c(foo = 123)), "foo") 154 | 155 | # unnamed rlimits 156 | expect_error(eval_safe(123, rlimits = list(123)), "rlimit") 157 | }) 158 | 159 | test_that("stdout gets redirected to parent",{ 160 | skip_if_not(safe_build()) 161 | 162 | outcon <- rawConnection(raw(0), "r+") 163 | errcon <- rawConnection(raw(0), "r+") 164 | 165 | out <- eval_fork({ 166 | cat("foo") 167 | cat("bar", file = stderr()) 168 | 42 169 | }, std_out = outcon, std_err = errcon) 170 | 171 | expect_identical(out, 42) 172 | 173 | expect_identical(rawConnectionValue(outcon), charToRaw("foo")) 174 | expect_identical(rawConnectionValue(errcon), charToRaw("bar")) 175 | 176 | close(outcon) 177 | close(errcon) 178 | }) 179 | 180 | test_that("tempdir/interactivity", { 181 | skip_if_not(safe_build()) 182 | 183 | subtmp <- eval_fork(tempdir()) 184 | expect_equal(normalizePath(tempdir()), normalizePath(dirname(subtmp))) 185 | expect_true(grepl("^fork", basename(subtmp))) 186 | expect_false(eval_fork(interactive())) 187 | expect_equal(eval_safe(readline(), std_out = FALSE, std_err = FALSE), "") 188 | }) 189 | 190 | test_that("fork stdout", { 191 | skip_if_not(safe_build()) 192 | 193 | out <- raw() 194 | res <- eval_fork(cat("foo"), std_out = function(x){ 195 | out <<- c(out, x) 196 | }) 197 | expect_equal("foo", rawToChar(out)) 198 | }) 199 | -------------------------------------------------------------------------------- /tests/testthat/test-process.R: -------------------------------------------------------------------------------- 1 | context("process stuff") 2 | 3 | test_that("process stuff", { 4 | expect_is(getuid(), "integer") 5 | expect_is(getgid(), "integer") 6 | expect_is(getpriority(), "integer") 7 | 8 | setpgid() 9 | expect_equal(getpid(), getpgid()) 10 | }) 11 | -------------------------------------------------------------------------------- /unix.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | BuildType: Package 16 | PackageUseDevtools: Yes 17 | PackageInstallArgs: --no-multiarch --with-keep.source 18 | --------------------------------------------------------------------------------