ps/0000755000176200001440000000000014017000772010672 5ustar liggesusersps/NAMESPACE0000644000176200001440000000267314016767153012135 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(as.character,ps_handle) S3method(format,ps_handle) S3method(print,ps_handle) S3method(print,with_process_cleanup) export(CleanupReporter) export(errno) export(ps) export(ps_boot_time) export(ps_children) export(ps_cmdline) export(ps_connections) export(ps_cpu_count) export(ps_cpu_times) export(ps_create_time) export(ps_cwd) export(ps_descent) export(ps_disk_partitions) export(ps_disk_usage) export(ps_environ) export(ps_environ_raw) export(ps_exe) export(ps_find_tree) export(ps_get_nice) export(ps_gids) export(ps_handle) export(ps_interrupt) export(ps_is_running) export(ps_is_supported) export(ps_kill) export(ps_kill_tree) export(ps_loadavg) export(ps_mark_tree) export(ps_memory_info) export(ps_name) export(ps_num_fds) export(ps_num_threads) export(ps_open_files) export(ps_os_type) export(ps_parent) export(ps_pid) export(ps_pids) export(ps_ppid) export(ps_resume) export(ps_send_signal) export(ps_set_nice) export(ps_shared_lib_users) export(ps_shared_libs) export(ps_status) export(ps_suspend) export(ps_system_cpu_times) export(ps_system_memory) export(ps_system_swap) export(ps_terminal) export(ps_terminate) export(ps_tty_size) export(ps_uids) export(ps_username) export(ps_users) export(ps_windows_nice_values) export(signals) export(with_process_cleanup) importFrom(utils,head) importFrom(utils,read.delim) importFrom(utils,read.table) importFrom(utils,tail) useDynLib(ps, .registration = TRUE) ps/LICENSE.note0000644000176200001440000000022513737276560012664 0ustar liggesusersps is based on psutil, which is under the BSD (3 clause) license. See its full license here: https://github.com/giampaolo/psutil/blob/master/LICENSE ps/LICENSE0000644000176200001440000000013013621001756011674 0ustar liggesusersYEAR: 2009-2020 COPYRIGHT HOLDER: Jay Loden, Dave Daeschler, Giampaolo Rodola', RStudio ps/README.md0000644000176200001440000005520614016776101012166 0ustar liggesusers # ps > List, Query, Manipulate System Processes [![lifecycle](https://lifecycle.r-lib.org/articles/figures/lifecycle-stable.svg)](https://lifecycle.r-lib.org/articles/stages.html) [![R build status](https://github.com/r-lib/ps/workflows/R-CMD-check/badge.svg)](https://github.com/r-lib/ps/actions) [![CRAN status](https://www.r-pkg.org/badges/version/ps)](https://cran.r-project.org/package=ps) [![CRAN RStudio mirror downloads](https://cranlogs.r-pkg.org/badges/ps)](https://www.r-pkg.org/pkg/ps) [![Coverage status](https://codecov.io/gh/r-lib/ps/branch/master/graph/badge.svg)](https://codecov.io/github/r-lib/ps?branch=master) ps implements an API to query and manipulate system processes. Most of its code is based on the [psutil](https://github.com/giampaolo/psutil) Python package. - [Installation](#installation) - [Supported platforms](#supported-platforms) - [Listing all processes](#listing-all-processes) - [Process API](#process-api) - [Query functions](#query-functions) - [Process manipulation](#process-manipulation) - [Finished and zombie processes](#finished-and-zombie-processes) - [Pid reuse](#pid-reuse) - [Recipes](#recipes) - [Find process by name](#find-process-by-name) - [Wait for a process to finish](#wait-for-a-process-to-finish) - [Wait for several processes to finish](#wait-for-several-processes-to-finish) - [Kill process tree](#kill-process-tree) - [Terminate children](#terminate-children) - [Filtering and sorting processes](#filtering-and-sorting-processes) - [Contributions](#contributions) - [License](#license) ## Installation You can install the released version of ps from [CRAN](https://CRAN.R-project.org) with: ``` r install.packages("ps") ``` ## Supported platforms ps currently supports Windows (from Vista), macOS and Linux systems. On unsupported platforms the package can be installed and loaded, but all of its functions fail with an error of class `"not_implemented"`. ## Listing all processes `ps_pids()` returns all process ids on the system. This can be useful to iterate over all processes. ``` r library(ps) ps_pids()[1:20] ``` ## [1] 0 1 51 52 55 56 57 59 62 63 64 65 70 74 76 77 82 83 85 87 `ps()` returns a data frame (tibble if you have the tibble package available), with data about each process. It contains a handle to each process, in the `ps_handle` column, you can use these to perform more queries on the processes. ``` r ps() ``` ## # A tibble: 386 x 11 ## pid ppid name username status user system rss vms created ps_handle ## * ## 1 98737 1 quic… gaborcs… runni… 0.0425 0.0200 2.48e7 3.09e9 2018-07-24 09:41:40 PID=93065, NAME=R, AT=2018-07-23 17:27:55 ### Query functions `ps_pid(p)` returns the pid of the process. ``` r ps_pid(p) ``` ## [1] 93065 `ps_create_time()` returns the creation time of the process (according to the OS). ``` r ps_create_time(p) ``` ## [1] "2018-07-23 17:27:55 GMT" The process id and the creation time uniquely identify a process in a system. ps uses them to make sure that it reports information about, and manipulates the correct process. `ps_is_running(p)` returns whether `p` is still running. It handles pid reuse safely. ``` r ps_is_running(p) ``` ## [1] TRUE `ps_ppid(p)` returns the pid of the parent of `p`. ``` r ps_ppid(p) ``` ## [1] 90285 `ps_parent(p)` returns a process handle to the parent process of `p`. ``` r ps_parent(p) ``` ## PID=90285, NAME=zsh, AT=2018-07-23 16:15:32 `ps_name(p)` returns the name of the program `p` is running. ``` r ps_name(p) ``` ## [1] "R" `ps_exe(p)` returns the full path to the executable the `p` is running. ``` r ps_exe(p) ``` ## [1] "/Library/Frameworks/R.framework/Versions/3.5/Resources/bin/exec/R" `ps_cmdline(p)` returns the command line (executable and arguments) of `p`. ``` r ps_cmdline(p) ``` ## [1] "/Library/Frameworks/R.framework/Resources/bin/exec/R" `ps_status(p)` returns the status of the process. Possible values are OS dependent, but typically there is `"running"` and `"stopped"`. ``` r ps_status(p) ``` ## [1] "running" `ps_username(p)` returns the name of the user the process belongs to. ``` r ps_username(p) ``` ## [1] "gaborcsardi" `ps_uids(p)` and `ps_gids(p)` return the real, effective and saved user ids of the process. They are only implemented on POSIX systems. ``` r if (ps_os_type()[["POSIX"]]) ps_uids(p) ``` ## real effective saved ## 501 501 501 ``` r if (ps_os_type()[["POSIX"]]) ps_gids(p) ``` ## real effective saved ## 20 20 20 `ps_cwd(p)` returns the current working directory of the process. ``` r ps_cwd(p) ``` ## [1] "/Users/gaborcsardi/works/ps" `ps_terminal(p)` returns the name of the terminal of the process, if any. For processes without a terminal, and on Windows it returns `NA_character_`. ``` r ps_terminal(p) ``` ## [1] "/dev/ttys003" `ps_environ(p)` returns the environment variables of the process. `ps_environ_raw(p)` does the same, in a different form. Typically they reflect the environment variables at the start of the process. ``` r ps_environ(p)[c("TERM", "USER", "SHELL", "R_HOME")] ``` ## TERM xterm-256color ## USER gaborcsardi ## SHELL /bin/zsh ## R_HOME /Library/Frameworks/R.framework/Resources `ps_num_threads(p)` returns the current number of threads of the process. ``` r ps_num_threads(p) ``` ## [1] 4 `ps_cpu_times(p)` returns the CPU times of the process, similarly to `proc.time()`. ``` r ps_cpu_times(p) ``` ## user system childen_user children_system ## 8.023153 1.288586 NA NA `ps_memory_info(p)` returns memory usage information. See the manual for details. ``` r ps_memory_info(p) ``` ## rss vms pfaults pageins ## 132501504 2719563776 318180 1028 `ps_children(p)` lists all child processes (potentially recuirsively) of the current process. ``` r ps_children(ps_parent(p)) ``` ## [[1]] ## PID=90291, NAME=zsh, AT=2018-07-23 16:15:32 ## ## [[2]] ## PID=93065, NAME=R, AT=2018-07-23 17:27:55 `ps_num_fds(p)` returns the number of open file descriptors (handles on Windows): ``` r ps_num_fds(p) ``` ## [1] 3 ``` r f <- file(tmp <- tempfile(), "w") ps_num_fds(p) ``` ## [1] 4 ``` r close(f) unlink(tmp) ``` `ps_open_files(p)` lists all open files: ``` r ps_open_files(p) ``` ## # A tibble: 3 x 2 ## fd path ## ## 1 0 /dev/ttys003 ## 2 1 /dev/ttys003 ## 3 2 /dev/ttys003 ``` r f <- file(tmp <- tempfile(), "w") ps_open_files(p) ``` ## # A tibble: 4 x 2 ## fd path ## ## 1 0 /dev/ttys003 ## 2 1 /dev/ttys003 ## 3 2 /dev/ttys003 ## 4 3 /private/var/folders/59/0gkmw1yj2w7bf2dfc3jznv5w0000gn/T/RtmpxkerNt/file16b892817efc1 ``` r close(f) unlink(tmp) ps_open_files(p) ``` ## # A tibble: 3 x 2 ## fd path ## ## 1 0 /dev/ttys003 ## 2 1 /dev/ttys003 ## 3 2 /dev/ttys003 ### Process manipulation `ps_suspend(p)` suspends (stops) the process. On POSIX it sends a SIGSTOP signal. On Windows it stops all threads. `ps_resume(p)` resumes the process. On POSIX it sends a SIGCONT signal. On Windows it resumes all stopped threads. `ps_send_signal(p)` sends a signal to the process. It is implemented on POSIX systems only. It makes an effort to work around pid reuse. `ps_terminate(p)` send SIGTERM to the process. On POSIX systems only. `ps_kill(p)` terminates the process. Sends `SIGKILL` on POSIX systems, uses `TerminateProcess()` on Windows. It make an effort to work around pid reuse. `ps_interrupt(p)` interrupts a process. It sends a `SIGINT` signal on POSIX systems, and it can send a CTRL+C or a CTRL+BREAK event on Windows. ## Finished and zombie processes ps handles finished and Zombie processes as much as possible. The essential `ps_pid()`, `ps_create_time()`, `ps_is_running()` functions and the `format()` and `print()` methods work for all processes, including finished and zombie processes. Other functions fail with an error of class `"no_such_process"` for finished processes. The `ps_ppid()`, `ps_parent()`, `ps_children()`, `ps_name()`, `ps_status()`, `ps_username()`, `ps_uids()`, `ps_gids()`, `ps_terminal()`, `ps_children()` and the signal sending functions work properly for zombie processes. Other functions fail with `"zombie_process"` error. ## Pid reuse ps functions handle pid reuse as well as technically possible. The query functions never return information about the wrong process, even if the process has finished and its process id was re-assigned. On Windows, the process manipulation functions never manipulate the wrong process. On POSIX systems, this is technically impossible, it is not possible to send a signal to a process without creating a race condition. In ps the time window of the race condition is very small, a few microseconds, and the process would need to finish, *and* the OS would need to reuse its pid within this time window to create problems. This is very unlikely to happen. ## Recipes In the spirit of [psutil recipes](http://psutil.readthedocs.io/en/latest/#recipes). ### Find process by name Using `ps()` and dplyr: ``` r library(dplyr) find_procs_by_name <- function(name) { ps() %>% filter(name == !!name) %>% pull(ps_handle) } find_procs_by_name("R") ``` ## [[1]] ## PID=93065, NAME=R, AT=2018-07-23 17:27:55 ## ## [[2]] ## PID=86811, NAME=R, AT=2018-07-23 13:22:12 ## ## [[3]] ## PID=79811, NAME=R, AT=2018-07-23 12:15:12 Without creating the full table of processes: ``` r find_procs_by_name <- function(name) { procs <- lapply(ps_pids(), function(p) { tryCatch({ h <- ps_handle(p) if (ps_name(h) == name) h else NULL }, no_such_process = function(e) NULL, access_denied = function(e) NULL ) }) procs[!vapply(procs, is.null, logical(1))] } find_procs_by_name("R") ``` ## [[1]] ## PID=79811, NAME=R, AT=2018-07-23 12:15:12 ## ## [[2]] ## PID=86811, NAME=R, AT=2018-07-23 13:22:12 ## ## [[3]] ## PID=93065, NAME=R, AT=2018-07-23 17:27:55 ### Wait for a process to finish On POSIX, there is no good way to wait for non-child processes to finish, so we need to write a sleep-wait loop to do it. (On Windows, and BSD systems, including macOS, there are better solutions.) ``` r as_secs <- function(x) as.numeric(x, units = "secs") wait_for_process <- function(proc, timeout = Inf, sleep = 0.1) { sleep <- as_secs(sleep) deadline <- Sys.time() + timeout while (ps_is_running(proc) && (timeout == Inf || Sys.time() < deadline)) { to <- min(as_secs(deadline - Sys.time()), sleep) Sys.sleep(to) } ! ps_is_running(proc) } px <- processx::process$new("sleep", "2") p <- ps_handle(px$get_pid()) wait_for_process(p, 1) ``` ## [1] FALSE ``` r wait_for_process(p) ``` ## [1] TRUE ### Wait for several processes to finish This is similar, but we need to wait on all processes in a loop. ``` r wait_for_processes <- function(procs, timeout = Inf) { gone <- list() alive <- procs deadline <- Sys.time() + timeout check_gone <- function(proc, timeout) { proc_gone <- wait_for_process(proc, timeout = timeout) if (proc_gone) { gone <<- c(gone, list(proc)) alive <<- setdiff(alive, list(proc)) } } while (length(alive)) { if (timeout <= 0) break for (proc in alive) { max_timeout <- 1 / length(alive) if (timeout != Inf) { timeout <- min(as_secs(deadline - Sys.time()), max_timeout) if (timeout <= 0) break check_gone(proc, timeout) } else { check_gone(proc, max_timeout) } } } list(gone = gone, alive = alive) } px1 <- processx::process$new("sleep", "10") px2 <- processx::process$new("sleep", "10") px3 <- processx::process$new("sleep", "1") px4 <- processx::process$new("sleep", "1") p1 <- ps_handle(px1$get_pid()) p2 <- ps_handle(px2$get_pid()) p3 <- ps_handle(px3$get_pid()) p4 <- ps_handle(px4$get_pid()) wait_for_processes(list(p1, p2, p3, p4), timeout = 2) ``` ## $gone ## $gone[[1]] ## PID=98990, NAME=???, AT=2018-07-24 09:45:30 ## ## $gone[[2]] ## PID=98989, NAME=???, AT=2018-07-24 09:45:30 ## ## ## $alive ## $alive[[1]] ## PID=98987, NAME=sleep, AT=2018-07-24 09:45:30 ## ## $alive[[2]] ## PID=98988, NAME=sleep, AT=2018-07-24 09:45:30 ### Kill process tree This sends a signal, so it’ll only work on Unix. Use `ps_kill()` instead of `ps_send_signal()` on Windows. ``` r kill_proc_tree <- function(pid, sig = signals()$SIGTERM, include_parent = TRUE) { if (pid == Sys.getpid() && include_parent) stop("I refuse to kill myself") parent <- ps_handle(pid) children <- ps_children(parent, recursive = TRUE) if (include_parent) children <- c(children, parent) for (p in children) ps_send_signal(p, sig) wait_for_processes(children, timeout = 0.1) } p1 <- processx::process$new("sleep", "10") p2 <- processx::process$new("sleep", "10") p3 <- processx::process$new("sleep", "10") kill_proc_tree(Sys.getpid(), include_parent = FALSE) ``` ## $gone ## $gone[[1]] ## PID=98987, NAME=???, AT=2018-07-24 09:45:30 ## ## ## $alive ## $alive[[1]] ## PID=98988, NAME=???, AT=2018-07-24 09:45:30 ## ## $alive[[2]] ## PID=98991, NAME=???, AT=2018-07-24 09:45:32 ## ## $alive[[3]] ## PID=98992, NAME=???, AT=2018-07-24 09:45:32 ## ## $alive[[4]] ## PID=98993, NAME=???, AT=2018-07-24 09:45:32 ### Terminate children Note, that some R IDEs, including RStudio, run a multithreaded R process, and other threads may start processes as well. `reap_children()` will clean up all these as well, potentially causing the IDE to misbehave or crash. ``` r reap_children <- function(timeout = 3) { procs <- ps_children(ps_handle()) ## SIGTERM lapply(procs, ps_terminate) ga <- wait_for_processes(procs, timeout = timeout) ## SIGKILL to the survivers if (length(ga$alive)) lapply(ga$alive, ps_kill) ga2 <- wait_for_processes(ga$alive, timeout = timeout) ## Some might still survive list(gone = c(ga$gone, ga2$gone), alive = ga2$alive) } pxs <- replicate(3, processx::process$new("sleep", "3")) reap_children() ``` ## $gone ## $gone[[1]] ## PID=98994, NAME=???, AT=2018-07-24 09:45:32 ## ## $gone[[2]] ## PID=98995, NAME=???, AT=2018-07-24 09:45:32 ## ## $gone[[3]] ## PID=98996, NAME=???, AT=2018-07-24 09:45:32 ## ## ## $alive ## list() ### Filtering and sorting processes Process name ending with “sh”: ``` r ps() %>% filter(grepl("sh$", name)) ``` ## # A tibble: 21 x 11 ## pid ppid name username status user system rss vms created ps_handle ## ## 1 94582 94576 zsh gaborcsa… running 0.00595 0.00875 8.19e3 2.52e9 2018-07-23 21:06:28 % filter(username == Sys.info()[["user"]]) %>% select(pid, name) ``` ## # A tibble: 258 x 2 ## pid name ## ## 1 98737 quicklookd ## 2 98318 Google Chrome Helper ## 3 96820 Google Chrome Helper ## 4 96817 Google Chrome Helper ## 5 96816 Google Chrome Helper ## 6 96809 Google Chrome Helper ## 7 96680 Google Chrome Helper ## 8 96679 Google Chrome Helper ## 9 96678 Google Chrome Helper ## 10 96677 Google Chrome Helper ## # ... with 248 more rows Processes consuming more than 100MB of memory: ``` r ps() %>% filter(rss > 100 * 1024 * 1024) ``` ## # A tibble: 16 x 11 ## pid ppid name username status user system rss vms created ps_handle ## ## 1 96817 3718 Google… gaborcsa… runni… 9.53e-1 1.63e-1 1.14e8 3.56e9 2018-07-24 09:09:42 % top_n(3, rss) %>% arrange(desc(rss)) ``` ## # A tibble: 3 x 11 ## pid ppid name username status user system rss vms created ps_handle ## ## 1 95883 95882 Virtual… gaborcsa… running 2034. 1320. 4.73e9 7.53e9 2018-07-23 22:50:14 % mutate(cpu_time = user + system) %>% top_n(3, cpu_time) %>% arrange(desc(cpu_time)) %>% select(pid, name, cpu_time) ``` ## # A tibble: 3 x 3 ## pid name cpu_time ## ## 1 40706 com.docker.hyperkit 31685. ## 2 38474 Google Chrome Helper (GPU) 23568. ## 3 38466 Google Chrome 20589. ## Contributions Please note that this project is released with a [Contributor Code of Conduct](https://github.com/r-lib/ps/blob/master/.github/CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. ## License MIT © RStudio ps/man/0000755000176200001440000000000014016767153011461 5ustar liggesusersps/man/ps_create_time.Rd0000644000176200001440000000323013742034505014722 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_create_time} \alias{ps_create_time} \title{Start time of a process} \usage{ ps_create_time(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ \code{POSIXct} object, start time, in GMT. } \description{ The pid and the start time pair serves as the identifier of the process, as process ids might be reused, but the chance of starting two processes with identical ids within the resolution of the timer is minimal. } \details{ This function works even if the process has already finished. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_create_time(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_connections.Rd0000644000176200001440000000446413742034505014775 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_connections} \alias{ps_connections} \title{List network connections of a process} \usage{ ps_connections(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Data frame, or tibble if the \emph{tibble} package is available, with columns: \itemize{ \item \code{fd}: integer file descriptor on POSIX systems, \code{NA} on Windows. \item \code{family}: Address family, string, typically \code{AF_UNIX}, \code{AF_INET} or \code{AF_INET6}. \item \code{type}: Socket type, string, typically \code{SOCK_STREAM} (TCP) or \code{SOCK_DGRAM} (UDP). \item \code{laddr}: Local address, string, \code{NA} for UNIX sockets. \item \code{lport}: Local port, integer, \code{NA} for UNIX sockets. \item \code{raddr}: Remote address, string, \code{NA} for UNIX sockets. This is always \code{NA} for \code{AF_INET} sockets on Linux. \item \code{rport}: Remote port, integer, \code{NA} for UNIX sockets. \item \code{state}: Socket state, e.g. \code{CONN_ESTABLISHED}, etc. It is \code{NA} for UNIX sockets. } } \description{ For a zombie process it throws a \code{zombie_process} error. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() ps_connections(p) sc <- socketConnection("httpbin.org", port = 80) ps_connections(p) close(sc) ps_connections(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_terminate.Rd0000644000176200001440000000312313742034505014432 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_terminate} \alias{ps_terminate} \title{Terminate a Unix process} \usage{ ps_terminate(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \description{ Send a \code{SIGTERM} signal to the process. Not implemented on Windows. } \details{ Checks if the process is still running, to work around pid reuse. } \examples{ \dontshow{if (ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} px <- processx::process$new("sleep", "10") p <- ps_handle(px$get_pid()) p ps_terminate(p) p ps_is_running(p) px$get_exit_status() \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/CleanupReporter.Rd0000644000176200001440000000641313620625516015062 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/testthat-reporter.R \name{CleanupReporter} \alias{CleanupReporter} \title{testthat reporter that checks if child processes are cleaned up in tests} \usage{ CleanupReporter(reporter = testthat::ProgressReporter) } \arguments{ \item{reporter}{A testthat reporter to wrap into a new \code{CleanupReporter} class.} } \value{ New reporter class that behaves exactly like \code{reporter}, but it checks for, and optionally cleans up child processes, at the specified granularity. } \description{ \code{CleanupReporter} takes an existing testthat \code{Reporter} object, and wraps it, so it checks for leftover child processes, at the specified place, see the \code{proc_unit} argument below. } \details{ Child processes can be reported via a failed expectation, cleaned up silently, or cleaned up and reported (the default). The constructor of the \code{CleanupReporter} class has options: \itemize{ \item \code{file}: the output file, if any, this is passed to \code{reporter}. \item \code{proc_unit}: when to perform the child process check and cleanup. Possible values: \itemize{ \item \code{"test"}: at the end of each \code{\link[testthat:test_that]{testthat::test_that()}} block (the default), \item \code{"testsuite"}: at the end of the test suite. } \item \code{proc_cleanup}: Logical scalar, whether to kill the leftover processes, \code{TRUE} by default. \item \code{proc_fail}: Whether to create an expectation, that fails if there are any processes alive, \code{TRUE} by default. \item \code{proc_timeout}: How long to wait for the processes to quit. This is sometimes needed, because even if some kill signals were sent to child processes, it might take a short time for these to take effect. It defaults to one second. \item \code{rconn_unit}: When to perform the R connection cleanup. Possible values are \code{"test"} and \code{"testsuite"}, like for \code{proc_unit}. \item \code{rconn_cleanup}: Logical scalar, whether to clean up leftover R connections. \code{TRUE} by default. \item \code{rconn_fail}: Whether to fail for leftover R connections. \code{TRUE} by default. \item \code{file_unit}: When to check for open files. Possible values are \code{"test"} and \code{"testsuite"}, like for \code{proc_unit}. \item \code{file_fail}: Whether to fail for leftover open files. \code{TRUE} by default. \item \code{conn_unit}: When to check for open network connections. Possible values are \code{"test"} and \code{"testsuite"}, like for \code{proc_unit}. \item \code{conn_fail}: Whether to fail for leftover network connections. \code{TRUE} by default. } } \note{ Some IDEs, like RStudio, start child processes frequently, and sometimes crash when these are killed, only use this reporter in a terminal session. In particular, you can always use it in the idiomatic \code{testthat.R} file, that calls \code{test_check()} during \verb{R CMD check}. } \section{Examples}{ This is how to use this reporter in \code{testthat.R}:\preformatted{library(testthat) library(mypackage) if (ps::ps_is_supported()) \{ reporter <- ps::CleanupReporter(testthat::ProgressReporter)$new( proc_unit = "test", proc_cleanup = TRUE) \} else \{ ## ps does not support this platform reporter <- "progress" \} test_check("mypackage", reporter = reporter) } } ps/man/signals.Rd0000644000176200001440000000046313330045252013376 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/posix.R \name{signals} \alias{signals} \title{List of all supported signals} \usage{ signals() } \value{ List of integers, named by signal names. } \description{ Only the signals supported by the current platform are included. } ps/man/ps_name.Rd0000644000176200001440000000302713742034505013365 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_name} \alias{ps_name} \title{Process name} \usage{ ps_name(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Character scalar. } \description{ The name of the program, which is typically the name of the executable. } \details{ On on Unix this can change, e.g. via an exec*() system call. \code{ps_name()} works on zombie processes. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_name(p) ps_exe(p) ps_cmdline(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_system_swap.Rd0000644000176200001440000000173413737300630015025 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/memory.R \name{ps_system_swap} \alias{ps_system_swap} \title{System swap memory statistics} \usage{ ps_system_swap() } \value{ Named list. All numbers are in bytes: \itemize{ \item \code{total}: total swap memory. \item \code{used}: used swap memory. \item \code{free}: free swap memory. \item \code{percent}: the percentage usage. \item \code{sin}: the number of bytes the system has swapped in from disk (cumulative). This is \code{NA} on Windows. \item \code{sout}: the number of bytes the system has swapped out from disk (cumulative). This is \code{NA} on Windows. } } \description{ System swap memory statistics } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} ps_system_swap() \dontshow{\}) # examplesIf} } \seealso{ Other memory functions: \code{\link{ps_system_memory}()} } \concept{memory functions} ps/man/ps_terminal.Rd0000644000176200001440000000306513742034505014262 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_terminal} \alias{ps_terminal} \title{Terminal device of the process} \usage{ ps_terminal(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Character scalar. } \description{ Returns the terminal of the process. Not implemented on Windows, always returns \code{NA_character_}. On Unix it returns \code{NA_character_} if the process has no terminal. } \details{ Works for zombie processes. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_terminal(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_cpu_times.Rd0000644000176200001440000000403314016761346014440 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_cpu_times} \alias{ps_cpu_times} \title{CPU times of the process} \usage{ ps_cpu_times(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Named real vector or length four: \code{user}, \code{system}, \code{children_user}, \code{children_system}. The last two are \code{NA} on non-Linux systems. } \description{ All times are measured in seconds: \itemize{ \item \code{user}: Amount of time that this process has been scheduled in user mode. \item \code{system}: Amount of time that this process has been scheduled in kernel mode \item \code{children_user}: On Linux, amount of time that this process's waited-for children have been scheduled in user mode. \item \code{children_system}: On Linux, Amount of time that this process's waited-for children have been scheduled in kernel mode. } } \details{ Throws a \code{zombie_process()} error for zombie processes. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_cpu_times(p) proc.time() \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_get_nice.Rd0000644000176200001440000000323313737120534014223 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_windows_nice_values} \alias{ps_windows_nice_values} \alias{ps_get_nice} \alias{ps_set_nice} \title{Get or set the priority of a process} \usage{ ps_windows_nice_values() ps_get_nice(p = ps_handle()) ps_set_nice(p = ps_handle(), value) } \arguments{ \item{p}{Process handle.} \item{value}{On Windows it must be a string, one of the values of \code{ps_windows_nice_values()}. On Unix it is a priority value that is smaller than or equal to 20.} } \value{ \code{ps_windows_nice_values()} return a character vector of possible priority values on Windows. \code{ps_get_nice()} returns a string from \code{ps_windows_nice_values()} on Windows. On Unix it returns an integer smaller than or equal to 20. \code{ps_set_nice()} return \code{NULL} invisibly. } \description{ \code{ps_get_nice()} returns the current priority, \code{ps_set_nice()} sets a new priority, \code{ps_windows_nice_values()} list the possible priority values on Windows. } \details{ Priority values are different on Windows and Unix. On Unix, priority is an integer, which is maximum 20. 20 is the lowest priority. \subsection{Rules:}{ \itemize{ \item On Windows you can only set the priority of the processes the current user has \code{PROCESS_SET_INFORMATION} access rights to. This typically means your own processes. \item On Unix you can only set the priority of the your own processes. The superuser can set the priority of any process. \item On Unix you cannot set a higher priority, unless you are the superuser. (I.e. you cannot set a lower number.) \item On Unix the default priority of a process is zero. } } } ps/man/ps_num_fds.Rd0000644000176200001440000000322513742034505014100 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_num_fds} \alias{ps_num_fds} \title{Number of open file descriptors} \usage{ ps_num_fds(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Integer scalar. } \description{ Note that in some IDEs, e.g. RStudio or R.app on macOS, the IDE itself opens files from other threads, in addition to the files opened from the main R thread. } \details{ For a zombie process it throws a \code{zombie_process} error. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() ps_num_fds(p) f <- file(tmp <- tempfile(), "w") ps_num_fds(p) close(f) unlink(tmp) ps_num_fds(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_pid.Rd0000644000176200001440000000263313742034505013223 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_pid} \alias{ps_pid} \title{Pid of a process handle} \usage{ ps_pid(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Process id. } \description{ This function works even if the process has already finished. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_pid(p) ps_pid(p) == Sys.getpid() \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_memory_info.Rd0000644000176200001440000000565313742034505014777 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_memory_info} \alias{ps_memory_info} \title{Memory usage information} \usage{ ps_memory_info(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Named real vector. } \description{ A list with information about memory usage. Portable fields: \itemize{ \item \code{rss}: "Resident Set Size", this is the non-swapped physical memory a process has used. On UNIX it matches "top"‘s 'RES' column (see doc). On Windows this is an alias for \code{wset} field and it matches "Memory" column of \code{taskmgr.exe}. \item \code{vmem}: "Virtual Memory Size", this is the total amount of virtual memory used by the process. On UNIX it matches "top"‘s 'VIRT' column (see doc). On Windows this is an alias for the \code{pagefile} field and it matches the "Working set (memory)" column of \code{taskmgr.exe}. } } \details{ Non-portable fields: \itemize{ \item \code{shared}: (Linux) memory that could be potentially shared with other processes. This matches "top"‘s 'SHR' column (see doc). \item \code{text}: (Linux): aka 'TRS' (text resident set) the amount of memory devoted to executable code. This matches "top"‘s 'CODE' column (see doc). \item \code{data}: (Linux): aka 'DRS' (data resident set) the amount of physical memory devoted to other than executable code. It matches "top"‘s 'DATA' column (see doc). \item \code{lib}: (Linux): the memory used by shared libraries. \item \code{dirty}: (Linux): the number of dirty pages. \item \code{pfaults}: (macOS): number of page faults. \item \code{pageins}: (macOS): number of actual pageins. } For on explanation of Windows fields see the \href{https://docs.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-process_memory_counters_ex}{PROCESS_MEMORY_COUNTERS_EX} structure. Throws a \code{zombie_process()} error for zombie processes. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_memory_info(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_cpu_count.Rd0000644000176200001440000000127513737300630014446 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/system.R \name{ps_cpu_count} \alias{ps_cpu_count} \title{Number of logical or physical CPUs} \usage{ ps_cpu_count(logical = TRUE) } \arguments{ \item{logical}{Whether to count logical CPUs.} } \value{ Integer scalar. } \description{ If cannot be determined, it returns \code{NA}. It also returns \code{NA} on older Windows systems, e.g. Vista or older and Windows Server 2008 or older. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} ps_cpu_count(logical = TRUE) ps_cpu_count(logical = FALSE) \dontshow{\}) # examplesIf} } ps/man/errno.Rd0000644000176200001440000000104313737300630013063 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/errno.R \name{errno} \alias{errno} \title{List of 'errno' error codes} \usage{ errno() } \description{ For the errors that are not used on the current platform, \code{value} is \code{NA_integer_}. } \details{ A data frame with columns: \code{name}, \code{value}, \code{description}. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} errno() \dontshow{\}) # examplesIf} } ps/man/ps_disk_partitions.Rd0000644000176200001440000000166513737300630015660 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/disk.R \name{ps_disk_partitions} \alias{ps_disk_partitions} \title{List all mounted partitions} \usage{ ps_disk_partitions(all = FALSE) } \arguments{ \item{all}{Whether to list virtual devices as well. If \code{FALSE}, on Linux it will still list \code{overlay} and \code{grpcfuse} file systems, to provide some useful information in Docker containers.} } \value{ A data frame (tibble) with columns \code{device}, \code{mountpoint}, \code{fstype} and \code{options}. } \description{ The output is similar the Unix \code{mount} and \code{df} commands. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} ps_disk_partitions(all = TRUE) ps_disk_partitions() \dontshow{\}) # examplesIf} } \seealso{ Other disk functions: \code{\link{ps_disk_usage}()} } \concept{disk functions} ps/man/ps_shared_lib_users.Rd0000644000176200001440000000535413742046242015770 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/system.R \name{ps_shared_lib_users} \alias{ps_shared_lib_users} \title{List all processes that loaded a shared library} \usage{ ps_shared_lib_users(paths, user = ps_username(), filter = NULL) } \arguments{ \item{paths}{Character vector of paths of shared libraries to look up. They must be absolute paths. They don't need to exist. Forward slashes are converted to backward slashes on Windows, and the output will always have backward slashes in the paths.} \item{user}{Character scalar or \code{NULL}. If not \code{NULL}, then only the processes of this user are considered. It defaults to the current user.} \item{filter}{Character vector or \code{NULL}. If not NULL, then it is a vector of glob expressions, used to filter the process names.} } \value{ A data frame (tibble) with columns: \itemize{ \item \code{dll}: the file name of the dll file, without the path, \item \code{path}: path to the shared library, \item \code{pid}: process ID of the process, \item \code{name}: name of the process, \item \code{username}: username of process owner, \item \code{ps_handle}: \code{ps_handle} object, that can be used to further query and manipulate the process. } } \description{ List all processes that loaded a shared library } \details{ \subsection{Notes:}{ This function currently only works on Windows. On Windows, a 32 bit R process can only list other 32 bit processes. Similarly, a 64 bit R process can only list other 64 bit processes. This is a limitation of the Windows API. Even though Windows file systems are (almost always) case insensitive, the matching of \code{paths}, \code{user} and also \code{filter} are case sensitive. This might change in the future. This function can be very slow on Windows, because it needs to enumerate all shared libraries of all processes in the system, unless the \code{filter} argument is set. Make sure you set \code{filter} if you can. If you want to look up multiple shared libraries, list all of them in \code{paths}, instead of calling \code{ps_shared_lib_users} for each individually. If you are after libraries loaded by R processes, you might want to set \code{filter} to \code{c("Rgui.exe", "Rterm.exe", "rsession.exe")} The last one is for RStudio. } } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check() && ps::ps_os_type()[["WINDOWS"]]) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} dlls <- vapply(getLoadedDLLs(), "[[", character(1), "path") psdll <- dlls[["ps"]][[1]] r_procs <- c("Rgui.exe", "Rterm.exe", "rsession.exe") ps_shared_lib_users(psdll, filter = r_procs) \dontshow{\}) # examplesIf} } \seealso{ Other shared library tools: \code{\link{ps_shared_libs}()} } \concept{shared library tools} ps/man/ps_cmdline.Rd0000644000176200001440000000316513742034505014063 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_cmdline} \alias{ps_cmdline} \title{Command line of the process} \usage{ ps_cmdline(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Character vector. } \description{ Command line of the process, i.e. the executable and the command line arguments, in a character vector. On Unix the program might change its command line, and some programs actually do it. } \details{ For a zombie process it throws a \code{zombie_process} error. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_name(p) ps_exe(p) ps_cmdline(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_boot_time.Rd0000644000176200001440000000040413313263645014425 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/system.R \name{ps_boot_time} \alias{ps_boot_time} \title{Boot time of the system} \usage{ ps_boot_time() } \value{ A \code{POSIXct} object. } \description{ Boot time of the system } ps/man/ps_resume.Rd0000644000176200001440000000314213742034505013743 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_resume} \alias{ps_resume} \title{Resume (continue) a stopped process} \usage{ ps_resume(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \description{ Resume process execution with SIGCONT preemptively checking whether PID has been reused. On Windows this has the effect of resuming all process threads. } \examples{ \dontshow{if (ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} px <- processx::process$new("sleep", "10") p <- ps_handle(px$get_pid()) p ps_suspend(p) ps_status(p) ps_resume(p) ps_status(p) ps_kill(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_disk_usage.Rd0000644000176200001440000000205513737300630014562 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/disk.R \name{ps_disk_usage} \alias{ps_disk_usage} \title{Disk usage statistics, per partition} \usage{ ps_disk_usage(paths = ps_disk_partitions()$mountpoint) } \arguments{ \item{paths}{The mounted file systems to list. By default all file systems returned by \code{\link[=ps_disk_partitions]{ps_disk_partitions()}} is listed.} } \value{ A data frame with columns \code{mountpoint}, \code{total}, \code{used}, \code{available} and \code{capacity}. } \description{ The output is similar to the Unix \code{df} command. } \details{ Note that on Unix a small percentage of the disk space (5\% typically) is reserved for the superuser. \code{ps_disk_usage()} returns the space available to the calling user. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} ps_disk_usage() \dontshow{\}) # examplesIf} } \seealso{ Other disk functions: \code{\link{ps_disk_partitions}()} } \concept{disk functions} ps/man/ps_system_cpu_times.Rd0000644000176200001440000000315114016767153016046 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/system.R \name{ps_system_cpu_times} \alias{ps_system_cpu_times} \title{System CPU times.} \usage{ ps_system_cpu_times() } \value{ Named list } \description{ Every attribute represents the seconds the CPU has spent in the given mode. The attributes availability varies depending on the platform: \itemize{ \item \code{user}: time spent by normal processes executing in user mode; on Linux this also includes guest time. \item \code{system}: time spent by processes executing in kernel mode. \item \code{idle}: time spent doing nothing. } } \details{ Platform-specific fields: \itemize{ \item \code{nice} (UNIX): time spent by niced (prioritized) processes executing in user mode; on Linux this also includes guest_nice time. \item \code{iowait} (Linux): time spent waiting for I/O to complete. This is not accounted in idle time counter. \item \code{irq} (Linux): time spent for servicing hardware interrupts. \item \code{softirq} (Linux): time spent for servicing software interrupts. \item \code{steal} (Linux 2.6.11+): time spent by other operating systems running in a virtualized environment. \item \code{guest} (Linux 2.6.24+): time spent running a virtual CPU for guest operating systems under the control of the Linux kernel. \item \code{guest_nice} (Linux 3.2.0+): time spent running a niced guest (virtual CPU for guest operating systems under the control of the Linux kernel). } } \examples{ \dontshow{if (ps::ps_is_supported()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} ps_system_cpu_times() \dontshow{\}) # examplesIf} } ps/man/ps_shared_libs.Rd0000644000176200001440000000333413742034505014725 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_shared_libs} \alias{ps_shared_libs} \title{List the dynamically loaded libraries of a process} \usage{ ps_shared_libs(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Data frame with one column currently: \code{path}, the absolute path to the loaded module or shared library. On Windows the list includes the executable file itself. } \description{ Note: this function currently only works on Windows. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check() && ps::ps_os_type()[["WINDOWS"]]) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} # The loaded DLLs of the current process ps_shared_libs() \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} Other shared library tools: \code{\link{ps_shared_lib_users}()} } \concept{process handle functions} \concept{shared library tools} ps/man/ps_exe.Rd0000644000176200001440000000305113742034505013223 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_exe} \alias{ps_exe} \title{Full path of the executable of a process} \usage{ ps_exe(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Character scalar. } \description{ Path to the executable of the process. May also be an empty string or \code{NA} if it cannot be determined. } \details{ For a zombie process it throws a \code{zombie_process} error. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_name(p) ps_exe(p) ps_cmdline(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_username.Rd0000644000176200001440000000311013742034505014255 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_username} \alias{ps_username} \title{Owner of the process} \usage{ ps_username(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ String scalar. } \description{ The name of the user that owns the process. On Unix it is calculated from the real user id. } \details{ On Unix, a numeric uid id returned if the uid is not in the user database, thus a username cannot be determined. Works for zombie processes. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_username(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()} } \concept{process handle functions} ps/man/ps_kill_tree.Rd0000644000176200001440000000636113620625516014426 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/kill-tree.R \name{ps_mark_tree} \alias{ps_mark_tree} \alias{with_process_cleanup} \alias{ps_find_tree} \alias{ps_kill_tree} \title{Mark a process and its (future) child tree} \usage{ ps_mark_tree() with_process_cleanup(expr) ps_find_tree(marker) ps_kill_tree(marker, sig = signals()$SIGKILL) } \arguments{ \item{expr}{R expression to evaluate in the new context.} \item{marker}{String scalar, the name of the environment variable to use to find the marked processes.} \item{sig}{The signal to send to the marked processes on Unix. On Windows this argument is ignored currently.} } \value{ \code{ps_mark_tree()} returns the name of the environment variable, which can be used as the \code{marker} in \code{ps_kill_tree()}. \code{ps_find_tree()} returns a list of \code{ps_handle} objects. \code{ps_kill_tree()} returns the pids of the killed processes, in a named integer vector. The names are the file names of the executables, when available. \code{with_process_cleanup()} returns the value of the evaluated expression. } \description{ \code{ps_mark_tree()} generates a random environment variable name and sets it in the current R process. This environment variable will be (by default) inherited by all child (and grandchild, etc.) processes, and will help finding these processes, even if and when they are (no longer) related to the current R process. (I.e. they are not connected in the process tree.) } \details{ \code{ps_find_tree()} finds the processes that set the supplied environment variable and returns them in a list. \code{ps_kill_tree()} finds the processes that set the supplied environment variable, and kills them (or sends them the specified signal on Unix). \code{with_process_cleanup()} evaluates an R expression, and cleans up all external processes that were started by the R process while evaluating the expression. This includes child processes of child processes, etc., recursively. It returns a list with entries: \code{result} is the result of the expression, \code{visible} is TRUE if the expression should be printed to the screen, and \code{process_cleanup} is a named integer vector of the cleaned pids, names are the process names. If \code{expr} throws an error, then so does \code{with_process_cleanup()}, the same error. Nevertheless processes are still cleaned up. } \section{Note}{ Note that \code{with_process_cleanup()} is problematic if the R process is multi-threaded and the other threads start subprocesses. \code{with_process_cleanup()} cleans up those processes as well, which is probably not what you want. This is an issue for example in RStudio. Do not use \code{with_process_cleanup()}, unless you are sure that the R process is single-threaded, or the other threads do not start subprocesses. E.g. using it in package test cases is usually fine, because RStudio runs these in a separate single-threaded process. The same holds for manually running \code{ps_mark_tree()} and then \code{ps_find_tree()} or \code{ps_kill_tree()}. A safe way to use process cleanup is to use the processx package to start subprocesses, and set the \code{cleanup_tree = TRUE} in \code{\link[processx:run]{processx::run()}} or the \link[processx:process]{processx::process} constructor. } ps/man/ps_pids.Rd0000644000176200001440000000042113312173134013372 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/system.R \name{ps_pids} \alias{ps_pids} \title{Ids of all processes on the system} \usage{ ps_pids() } \value{ Integer vector of process ids. } \description{ Ids of all processes on the system } ps/man/ps_handle.Rd0000644000176200001440000000345413742034505013704 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_handle} \alias{ps_handle} \alias{as.character.ps_handle} \alias{format.ps_handle} \alias{print.ps_handle} \title{Create a process handle} \usage{ ps_handle(pid = NULL, time = NULL) \method{as.character}{ps_handle}(x, ...) \method{format}{ps_handle}(x, ...) \method{print}{ps_handle}(x, ...) } \arguments{ \item{pid}{Process id. Integer scalar. \code{NULL} means the current R process.} \item{time}{Start time of the process. Usually \code{NULL} and ps will query the start time.} \item{x}{Process handle.} \item{...}{Not used currently.} } \value{ \code{ps_handle()} returns a process handle (class \code{ps_handle}). } \description{ Create a process handle } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_uids.Rd0000644000176200001440000000352613742034505013415 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_uids} \alias{ps_uids} \alias{ps_gids} \title{User ids and group ids of the process} \usage{ ps_uids(p = ps_handle()) ps_gids(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Named integer vector of length 3, with names: \code{real}, \code{effective} and \code{saved}. } \description{ User ids and group ids of the process. Both return integer vectors with names: \code{real}, \code{effective} and \code{saved}. } \details{ Both work for zombie processes. They are not implemented on Windows, they throw a \code{not_implemented} error. } \examples{ \dontshow{if (ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_uids(p) ps_gids(p) \dontshow{\}) # examplesIf} } \seealso{ \code{\link[=ps_username]{ps_username()}} returns a user \emph{name} and works on all platforms. Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_cwd.Rd0000644000176200001440000000264313742034505013225 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_cwd} \alias{ps_cwd} \title{Process current working directory as an absolute path.} \usage{ ps_cwd(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ String scalar. } \description{ For a zombie process it throws a \code{zombie_process} error. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_cwd(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_status.Rd0000644000176200001440000000417113742034505013771 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_status} \alias{ps_status} \title{Current process status} \usage{ ps_status(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Character scalar. } \description{ One of the following: \itemize{ \item \code{"idle"}: Process being created by fork, macOS only. \item \code{"running"}: Currently runnable on macOS and Windows. Actually running on Linux. \item \code{"sleeping"} Sleeping on a wait or poll. \item \code{"disk_sleep"} Uninterruptible sleep, waiting for an I/O operation (Linux only). \item \code{"stopped"} Stopped, either by a job control signal or because it is being traced. \item \code{"tracing_stop"} Stopped for tracing (Linux only). \item \code{"zombie"} Zombie. Finished, but parent has not read out the exit status yet. \item \code{"dead"} Should never be seen (Linux). \item \code{"wake_kill"} Received fatal signal (Linux only). \item \code{"waking"} Paging (Linux only, not valid since the 2.6.xx kernel). } } \details{ Works for zombie processes. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_status(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_loadavg.Rd0000644000176200001440000000360214016401671014056 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/system.R \name{ps_loadavg} \alias{ps_loadavg} \title{Return the average system load over the last 1, 5 and 15 minutes as a tuple. The “load” represents the processes which are in a runnable state, either using the CPU or waiting to use the CPU (e.g. waiting for disk I/O). On Windows this is emulated by using a Windows API that spawns a thread which keeps running in background and updates results every 5 seconds, mimicking the UNIX behavior. Thus, on Windows, the first time this is called and for the next 5 seconds it will return a meaningless (0.0, 0.0, 0.0) vector. The numbers returned only make sense if related to the number of CPU cores installed on the system. So, for instance, a value of 3.14 on a system with 10 logical CPUs means that the system load was 31.4\% percent over the last N minutes.} \usage{ ps_loadavg() } \value{ Numeric vector of length 3. } \description{ Return the average system load over the last 1, 5 and 15 minutes as a tuple. The “load” represents the processes which are in a runnable state, either using the CPU or waiting to use the CPU (e.g. waiting for disk I/O). On Windows this is emulated by using a Windows API that spawns a thread which keeps running in background and updates results every 5 seconds, mimicking the UNIX behavior. Thus, on Windows, the first time this is called and for the next 5 seconds it will return a meaningless (0.0, 0.0, 0.0) vector. The numbers returned only make sense if related to the number of CPU cores installed on the system. So, for instance, a value of 3.14 on a system with 10 logical CPUs means that the system load was 31.4\% percent over the last N minutes. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} ps_loadavg() \dontshow{\}) # examplesIf} } ps/man/ps_environ.Rd0000644000176200001440000000404013742034505014121 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_environ} \alias{ps_environ} \alias{ps_environ_raw} \title{Environment variables of a process} \usage{ ps_environ(p = ps_handle()) ps_environ_raw(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ \code{ps_environ()} returns a named character vector (that has a \code{Dlist} class, so it is printed nicely), \code{ps_environ_raw()} returns a character vector. } \description{ \code{ps_environ()} returns the environment variables of the process, in a named vector, similarly to the return value of \code{Sys.getenv()} (without arguments). } \details{ Note: this usually does not reflect changes made after the process started. \code{ps_environ_raw()} is similar to \code{p$environ()} but returns the unparsed \code{"var=value"} strings. This is faster, and sometimes good enough. These functions throw a \code{zombie_process} error for zombie processes. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p env <- ps_environ(p) env[["R_HOME"]] \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_ppid.Rd0000644000176200001440000000357713742034505013413 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_ppid} \alias{ps_ppid} \alias{ps_parent} \title{Parent pid or parent process of a process} \usage{ ps_ppid(p = ps_handle()) ps_parent(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ \code{ps_ppid()} returns and integer scalar, the pid of the parent of \code{p}. \code{ps_parent()} returns a \code{ps_handle}. } \description{ \code{ps_ppid()} returns the parent pid, \code{ps_parent()} returns a \code{ps_handle} of the parent. } \details{ On POSIX systems, if the parent process terminates, another process (typically the pid 1 process) is marked as parent. \code{ps_ppid()} and \code{ps_parent()} will return this process then. Both \code{ps_ppid()} and \code{ps_parent()} work for zombie processes. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_ppid(p) ps_parent(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_send_signal.Rd0000644000176200001440000000347413742034505014741 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_send_signal} \alias{ps_send_signal} \title{Send signal to a process} \usage{ ps_send_signal(p = ps_handle(), sig) } \arguments{ \item{p}{Process handle.} \item{sig}{Signal number, see \code{\link[=signals]{signals()}}.} } \description{ Send a signal to the process. Not implemented on Windows. See \code{\link[=signals]{signals()}} for the list of signals on the current platform. } \details{ It checks if the process is still running, before sending the signal, to avoid signalling the wrong process, because of pid reuse. } \examples{ \dontshow{if (ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} px <- processx::process$new("sleep", "10") p <- ps_handle(px$get_pid()) p ps_send_signal(p, signals()$SIGINT) p ps_is_running(p) px$get_exit_status() \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_open_files.Rd0000644000176200001440000000356013742034505014572 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_open_files} \alias{ps_open_files} \title{Open files of a process} \usage{ ps_open_files(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Data frame, or tibble if the \emph{tibble} package is available, with columns: \code{fd} and \code{path}. \code{fd} is numeric file descriptor on POSIX systems, \code{NA} on Windows. \code{path} is an absolute path to the file. } \description{ Note that in some IDEs, e.g. RStudio or R.app on macOS, the IDE itself opens files from other threads, in addition to the files opened from the main R thread. } \details{ For a zombie process it throws a \code{zombie_process} error. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() ps_open_files(p) f <- file(tmp <- tempfile(), "w") ps_open_files(p) close(f) unlink(tmp) ps_open_files(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_system_memory.Rd0000644000176200001440000000375313737300630015366 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/memory.R \name{ps_system_memory} \alias{ps_system_memory} \title{Statistics about system memory usage} \usage{ ps_system_memory() } \value{ Named list. All numbers are in bytes: \itemize{ \item \code{total}: total physical memory (exclusive swap). \item \code{avail} the memory that can be given instantly to processes without the system going into swap. This is calculated by summing different memory values depending on the platform and it is supposed to be used to monitor actual memory usage in a cross platform fashion. \item \code{percent}: Percentage of memory that is taken. \item \code{used}: memory used, calculated differently depending on the platform and designed for informational purposes only. \code{total} - \code{free} does not necessarily match \code{used}. \item \code{free}: memory not being used at all (zeroed) that is readily available; note that this doesn’t reflect the actual memory available (use \code{available} instead). \code{total} - \code{used} does not necessarily match \code{free}. \item \code{active}: (Unix only) memory currently in use or very recently used, and so it is in RAM. \item \code{inactive}: (Unix only) memory that is marked as not used. \item \code{wired}: (macOS only) memory that is marked to always stay in RAM. It is never moved to disk. \item \code{buffers}: (Linux only) cache for things like file system metadata. \item \code{cached}: (Linux only) cache for various things. \item \code{shared}: (Linux only) memory that may be simultaneously accessed by multiple processes. \item \code{slab}: (Linux only) in-kernel data structures cache. } } \description{ Statistics about system memory usage } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} ps_system_memory() \dontshow{\}) # examplesIf} } \seealso{ Other memory functions: \code{\link{ps_system_swap}()} } \concept{memory functions} ps/man/ps_os_type.Rd0000644000176200001440000000076213330045252014124 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/os.R \name{ps_os_type} \alias{ps_os_type} \alias{ps_is_supported} \title{Query the type of the OS} \usage{ ps_os_type() ps_is_supported() } \value{ \code{ps_os_type} returns a named logical vector. The rest of the functions return a logical scalar. \code{ps_is_supported()} returns \code{TRUE} if ps supports the current platform. } \description{ Query the type of the OS } \examples{ ps_os_type() ps_is_supported() } ps/man/ps_suspend.Rd0000644000176200001440000000314513742034505014127 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_suspend} \alias{ps_suspend} \title{Suspend (stop) the process} \usage{ ps_suspend(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \description{ Suspend process execution with \code{SIGSTOP} preemptively checking whether PID has been reused. On Windows this has the effect of suspending all process threads. } \examples{ \dontshow{if (ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} px <- processx::process$new("sleep", "10") p <- ps_handle(px$get_pid()) p ps_suspend(p) ps_status(p) ps_resume(p) ps_status(p) ps_kill(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_kill.Rd0000644000176200001440000000305213742034505013376 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_kill} \alias{ps_kill} \title{Kill a process} \usage{ ps_kill(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \description{ Kill the current process with SIGKILL preemptively checking whether PID has been reused. On Windows it uses \code{TerminateProcess()}. } \examples{ \dontshow{if (ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} px <- processx::process$new("sleep", "10") p <- ps_handle(px$get_pid()) p ps_kill(p) p ps_is_running(p) px$get_exit_status() \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_is_running.Rd0000644000176200001440000000276613742034505014631 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_is_running} \alias{ps_is_running} \title{Checks whether a process is running} \usage{ ps_is_running(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Logical scalar. } \description{ It returns \code{FALSE} if the process has already finished. } \details{ It uses the start time of the process to work around pid reuse. I.e. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_is_running(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_users.Rd0000644000176200001440000000101413332411414013571 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/system.R \name{ps_users} \alias{ps_users} \title{List users connected to the system} \usage{ ps_users() } \value{ A data frame (tibble) with columns \code{username}, \code{tty}, \code{hostname}, \code{start_time}, \code{pid}. \code{tty} and \code{pid} are \code{NA} on Windows. \code{pid} is the process id of the login process. For local users the \code{hostname} column is the empty string. } \description{ List users connected to the system } ps/man/ps.Rd0000644000176200001440000000215713357347616012404 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/ps.R \name{ps} \alias{ps} \title{Process table} \usage{ ps(user = NULL, after = NULL) } \arguments{ \item{user}{Username, to filter the results to matching processes.} \item{after}{Start time (\code{POSIXt}), to filter the results to processes that started after this.} } \value{ Data frame (tibble), see columns below. Columns: \itemize{ \item \code{pid}: Process ID. \item \code{ppid}: Process ID of parent process. \item \code{name}: Process name. \item \code{username}: Name of the user (real uid on POSIX). \item \code{status}: I.e. \emph{running}, \emph{sleeping}, etc. \item \code{user}: User CPU time. \item \code{system}: System CPU time. \item \code{rss}: Resident set size, the amount of memory the process currently uses. Does not include memory that is swapped out. It does include shared libraries. \item \code{vms}: Virtual memory size. All memory the process has access to. \item \code{created}: Time stamp when the process was created. \item \code{ps_handle}: \code{ps_handle} objects, in a list column. } } \description{ Process table } ps/man/ps_children.Rd0000644000176200001440000000337113742034505014237 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_children} \alias{ps_children} \title{List of child processes (process objects) of the process. Note that this typically requires enumerating all processes on the system, so it is a costly operation.} \usage{ ps_children(p = ps_handle(), recursive = FALSE) } \arguments{ \item{p}{Process handle.} \item{recursive}{Whether to include the children of the children, etc.} } \value{ List of \code{ps_handle} objects. } \description{ List of child processes (process objects) of the process. Note that this typically requires enumerating all processes on the system, so it is a costly operation. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_parent(ps_handle()) ps_children(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_descent.Rd0000644000176200001440000000320313742034505014066 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_descent} \alias{ps_descent} \title{Query the ancestry of a process} \usage{ ps_descent(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ A list of process handles, starting with \code{p}, each one is the parent process of the previous one. } \description{ Query the parent processes recursively, up to the first process. (On some platforms, like Windows, the process tree is not a tree and may contain loops, in which case \code{ps_descent()} only goes up until the first repetition.) } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} ps_descent() \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_num_threads.Rd0000644000176200001440000000262613742034505014762 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_num_threads} \alias{ps_num_threads} \title{Number of threads} \usage{ ps_num_threads(p = ps_handle()) } \arguments{ \item{p}{Process handle.} } \value{ Integer scalar. } \description{ Throws a \code{zombie_process()} error for zombie processes. } \examples{ \dontshow{if (ps::ps_is_supported() && ! ps:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} p <- ps_handle() p ps_num_threads(p) \dontshow{\}) # examplesIf} } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_interrupt}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_tty_size.Rd0000644000176200001440000000207213734654253014327 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/system.R \name{ps_tty_size} \alias{ps_tty_size} \title{Query the size of the current terminal} \usage{ ps_tty_size() } \description{ If the standard output of the current R process is not a terminal, e.g. because it is redirected to a file, or the R process is running in a GUI, then it will throw an error. You need to handle this error if you want to use this function in a package. } \details{ If an error happens, the error message is different depending on what type of device the standard output is. Some common error messages are: \itemize{ \item "Inappropriate ioctl for device." \item "Operation not supported on socket." \item "Operation not supported by device." } Whatever the error message, \code{ps_tty_size} always fails with an error of class \code{ps_unknown_tty_size}, which you can catch. } \examples{ # An example that falls back to the 'width' option tryCatch( ps_tty_size(), ps_unknown_tty_size = function(err) { c(width = getOption("width"), height = NA_integer_) } ) } ps/man/ps_interrupt.Rd0000644000176200001440000000250213742034505014476 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/low-level.R \name{ps_interrupt} \alias{ps_interrupt} \title{Interrupt a process} \usage{ ps_interrupt(p = ps_handle(), ctrl_c = TRUE) } \arguments{ \item{p}{Process handle.} \item{ctrl_c}{On Windows, whether to send 'CTRL+C'. If \code{FALSE}, then 'CTRL+BREAK' is sent. Ignored on non-Windows platforms.} } \description{ Sends \code{SIGINT} on POSIX, and 'CTRL+C' or 'CTRL+BREAK' on Windows. } \seealso{ Other process handle functions: \code{\link{ps_children}()}, \code{\link{ps_cmdline}()}, \code{\link{ps_connections}()}, \code{\link{ps_cpu_times}()}, \code{\link{ps_create_time}()}, \code{\link{ps_cwd}()}, \code{\link{ps_descent}()}, \code{\link{ps_environ}()}, \code{\link{ps_exe}()}, \code{\link{ps_handle}()}, \code{\link{ps_is_running}()}, \code{\link{ps_kill}()}, \code{\link{ps_memory_info}()}, \code{\link{ps_name}()}, \code{\link{ps_num_fds}()}, \code{\link{ps_num_threads}()}, \code{\link{ps_open_files}()}, \code{\link{ps_pid}()}, \code{\link{ps_ppid}()}, \code{\link{ps_resume}()}, \code{\link{ps_send_signal}()}, \code{\link{ps_shared_libs}()}, \code{\link{ps_status}()}, \code{\link{ps_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/DESCRIPTION0000644000176200001440000000202114017000772012373 0ustar liggesusersPackage: ps Version: 1.6.0 Title: List, Query, Manipulate System Processes Description: List, query and manipulate all system processes, on 'Windows', 'Linux' and 'macOS'. Authors@R: c( person("Jay", "Loden", role = "aut"), person("Dave", "Daeschler", role = "aut"), person("Giampaolo", "Rodola'", role = "aut"), person("Gábor", "Csárdi", , "csardi.gabor@gmail.com", c("aut", "cre")), person("RStudio", role = "cph")) License: MIT + file LICENSE URL: https://github.com/r-lib/ps#readme BugReports: https://github.com/r-lib/ps/issues Encoding: UTF-8 Depends: R (>= 3.1) Imports: utils Suggests: callr, covr, curl, pingr, processx (>= 3.1.0), R6, rlang, testthat, tibble RoxygenNote: 7.1.1.9001 Biarch: true NeedsCompilation: yes Packaged: 2021-02-28 20:25:46 UTC; gaborcsardi Author: Jay Loden [aut], Dave Daeschler [aut], Giampaolo Rodola' [aut], Gábor Csárdi [aut, cre], RStudio [cph] Maintainer: Gábor Csárdi Repository: CRAN Date/Publication: 2021-02-28 20:50:02 UTC ps/tests/0000755000176200001440000000000013620625516012044 5ustar liggesusersps/tests/testthat/0000755000176200001440000000000014017000772013674 5ustar liggesusersps/tests/testthat/test-linux.R0000644000176200001440000000216513330045252016136 0ustar liggesusers if (!ps_os_type()[["LINUX"]]) return() context("linux") test_that("status", { ## Argument check expect_error(ps_status(123), class = "invalid_argument") p1 <- processx::process$new("sleep", "10") on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) wait_for_status(ps, "sleeping") expect_equal(ps_status(ps), "sleeping") ps_suspend(ps) wait_for_status(ps, "stopped") expect_equal(ps_status(ps), "stopped") ps_resume(ps) wait_for_status(ps, "sleeping") expect_equal(ps_status(ps), "sleeping") ## TODO: rest? }) ## TODO: cpu_times ??? We apparently cannot get them from ps test_that("memory_info", { ## Argument check expect_error(ps_memory_info(123), class = "invalid_argument") skip_on_cran() p1 <- processx::process$new("ls", c("-lR", "/")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) Sys.sleep(0.2) ps_suspend(ps) mem <- ps_memory_info(ps) mem2 <- scan(sprintf("/proc/%d/statm", ps_pid(ps)), what = integer(), quiet = TRUE) expect_equal(mem[["rss"]], mem2[[1]]) expect_equal(mem[["vms"]], mem2[[2]]) }) ps/tests/testthat/test-winver.R0000644000176200001440000000155013620625516016317 0ustar liggesusers context("winver") test_that("winver_ver", { cases <- list( list(c("", "Microsoft Windows [Version 6.3.9600]"), "6.3.9600"), list("Microsoft Windows [version 6.1.7601]", "6.1.7601"), list("Microsoft Windows [vers\u00e3o 10.0.18362.207]", "10.0.18362.207")) source(system.file("tools", "winver.R", package = "ps"), local = TRUE) for (x in cases) expect_identical(winver_ver(x[[1]]), x[[2]]) }) test_that("winver_wmic", { cases <- list( list(c("\r", "\r", "Version=6.3.9600\r", "\r", "\r", "\r"), "6.3.9600"), list(c("\r", "\r", "version=6.3.9600\r", "\r", "\r", "\r"), "6.3.9600"), list(c("\r", "\r", "vers\u00e3o=6.3.9600\r", "\r", "\r", "\r"), "6.3.9600")) source(system.file("tools", "winver.R", package = "ps"), local = TRUE) for (x in cases) expect_identical(winver_wmic(x[[1]]), x[[2]]) }) ps/tests/testthat/test-connections.R0000644000176200001440000002067713356424172017343 0ustar liggesusers context("connections") test_that("empty set", { px <- processx::process$new( px(), c("sleep", "5"), poll_connection = FALSE) on.exit(cleanup_process(px), add = TRUE) pid <- px$get_pid() p <- ps_handle(pid) cl <- ps_connections(p) expect_equal(nrow(cl), 0) expect_s3_class(cl, "data.frame") expect_s3_class(cl, "tbl_df") expect_equal( names(cl), c("fd", "family", "type", "laddr", "lport", "raddr", "rport", "state")) }) test_that("UNIX sockets", { if (!ps_os_type()[["POSIX"]]) skip("No UNIX sockets") px <- processx::process$new(px(), c("sleep", "5"), stdout = "|") on.exit(cleanup_process(px), add = TRUE) pid <- px$get_pid() p <- ps_handle(pid) cl <- ps_connections(p) expect_equal(nrow(cl), 1) expect_s3_class(cl, "data.frame") expect_s3_class(cl, "tbl_df") expect_equal(cl$fd, 1) expect_equal(cl$family, "AF_UNIX") expect_equal(cl$type, "SOCK_STREAM") expect_identical(cl$laddr, NA_character_) expect_identical(cl$lport, NA_integer_) expect_identical(cl$raddr, NA_character_) expect_identical(cl$lport, NA_integer_) expect_identical(cl$state, NA_character_) }) test_that("UNIX sockets with path", { if (!ps_os_type()[["POSIX"]]) skip("No UNIX sockets") skip_without_program("socat") skip_if_no_processx() sfile <- tempfile() sfile <- file.path(normalizePath(dirname(sfile)), basename(sfile)) on.exit(unlink(sfile, recursive = TRUE), add = TRUE) nc <- processx::process$new( "socat", c("-", paste0("UNIX-LISTEN:", sfile)), stdin = "|") on.exit(cleanup_process(nc), add = TRUE) p <- nc$as_ps_handle() ## Might need to wait for socat to start listening on the socket deadline <- Sys.time() + as.difftime(5, units = "secs") while (nc$is_alive() && !file.exists(sfile) && Sys.time() < deadline) { Sys.sleep(0.1) } cl <- ps_connections(p) cl <- cl[!is.na(cl$laddr) & cl$laddr == sfile, ] expect_equal(nrow(cl), 1) }) test_that("TCP", { skip_if_offline() before <- ps_connections(ps_handle()) cx <- curl::curl(httpbin_url(), open = "r") on.exit({ close(cx); rm(cx); gc() }, add = TRUE) after <- ps_connections(ps_handle()) new <- after[! after$lport %in% before$lport, ] expect_equal(new$family, "AF_INET") expect_equal(new$type, "SOCK_STREAM") expect_true(is_ipv4_address(new$laddr)) expect_true(is.integer(new$lport)) expect_equal(new$rport, 80L) expect_equal(new$state, "CONN_ESTABLISHED") }) test_that("TCP on loopback", { skip_without_program("socat") skip_if_no_processx() nc <- processx::process$new( "socat", c("-d", "-d", "-ls", "-", "TCP4-LISTEN:0"), stdin = "|", stderr = "|") on.exit(cleanup_process(nc), add = TRUE) p <- nc$as_ps_handle() wait_for_string(nc, "listening on", timeout = 2000) cl <- ps_connections(p) cl <- cl[!is.na(cl$state) & cl$state == "CONN_LISTEN", ] expect_equal(nrow(cl), 1) expect_true(cl$state == "CONN_LISTEN") port <- cl$lport nc2 <- processx::process$new( "socat", c("-", paste0("TCP4-CONNECT:127.0.0.1:", port)), stdin = "|") on.exit(cleanup_process(nc2), add = TRUE) p2 <- nc2$as_ps_handle() deadline <- Sys.time() + as.difftime(5, units = "secs") while (Sys.time() < deadline && ! port %in% (cl2 <- ps_connections(p2))$rport) Sys.sleep(0.1) cl2 <- cl2[!is.na(cl2$rport & cl2$rport == port), ] expect_equal(cl2$family, "AF_INET") expect_equal(cl2$type, "SOCK_STREAM") expect_equal(cl2$state, "CONN_ESTABLISHED") }) test_that("UDP", { skip_if_offline() skip_without_program("socat") skip_if_no_processx() nc <- processx::process$new( "socat", c("-", "UDP4-CONNECT:8.8.8.8:53,pf=ip4"), stdin = "|") on.exit(cleanup_process(nc), add = TRUE) p <- nc$as_ps_handle() deadline <- Sys.time() + as.difftime(5, units = "secs") while (Sys.time() < deadline && ! 53 %in% (cl <- ps_connections(p))$rport) { Sys.sleep(.1) } expect_true(deadline > Sys.time()) cl <- cl[!is.na(cl$rport) & cl$rport == 53, ] expect_equal(nrow(cl), 1) expect_equal(cl$family, "AF_INET") expect_equal(cl$type, "SOCK_DGRAM") expect_equal(cl$raddr, "8.8.8.8") }) test_that("UDP on loopback", { skip_without_program("socat") skip_if_no_processx() nc <- processx::process$new( "socat", c("-d", "-d", "-ls", "-", "UDP4-LISTEN:0"), stdin = "|", stderr = "|") on.exit(cleanup_process(nc), add = TRUE) p <- nc$as_ps_handle() wait_for_string(nc, "listening on", timeout = 2000) cl <- ps_connections(p) cl <- cl[!is.na(cl$lport) & cl$type == "SOCK_DGRAM", ] port <- cl$lport expect_equal(cl$family, "AF_INET") expect_equal(cl$type, "SOCK_DGRAM") nc2 <- processx::process$new( "socat", c("-", paste0("UDP4-CONNECT:127.0.0.1:", port)), stdin = "|") on.exit(cleanup_process(nc2), add = TRUE) p2 <- nc2$as_ps_handle() deadline <- Sys.time() + as.difftime(5, units = "secs") while (Sys.time() < deadline && ! port %in% (cl2 <- ps_connections(p2))$rport) Sys.sleep(0.1) cl2 <- cl2[!is.na(cl2$rport & cl2$rport == port), ] expect_equal(cl2$family, "AF_INET") expect_equal(cl2$type, "SOCK_DGRAM") }) test_that("TCP6", { skip_if_offline() skip_without_program("socat") skip_if_no_processx() skip_without_ipv6() skip_without_ipv6_connection() nc <- processx::process$new( "socat", c("-d", "-d", "-", paste0("TCP6:", ipv6_host(), ":443")), stdin = "|", stderr = "|") on.exit(cleanup_process(nc), add = TRUE) p <- nc$as_ps_handle() wait_for_string(nc, "starting data transfer", timeout = 3000) cl <- ps_connections(p) cl <- cl[!is.na(cl$rport) & cl$rport == 443, ] expect_equal(nrow(cl), 1) expect_equal(cl$family, "AF_INET6") expect_equal(cl$type, "SOCK_STREAM") }) test_that("TCP6 on loopback", { skip_without_program("socat") skip_if_no_processx() skip_without_ipv6() nc <- processx::process$new( "socat", c("-d", "-d", "-", "TCP6-LISTEN:0"), stdin = "|", stderr = "|") on.exit(cleanup_process(nc), add = TRUE) p <- nc$as_ps_handle() wait_for_string(nc, "listening on", timeout = 2000) cl <- ps_connections(p) cl <- cl[!is.na(cl$state) & cl$state == "CONN_LISTEN", ] expect_equal(nrow(cl), 1) expect_true(cl$state == "CONN_LISTEN") port <- cl$lport nc2 <- processx::process$new( "socat", c("-d", "-d", "-", paste0("TCP6-CONNECT:\\:\\:1:", port)), stdin = "|", stderr = "|") on.exit(cleanup_process(nc2), add = TRUE) p2 <- nc2$as_ps_handle() err <- FALSE tryCatch( wait_for_string(nc2, "starting data transfer", timeout = 2000), error = function(e) err <<- TRUE) if (err) skip("Could not bind to IPv6 address") cl2 <- ps_connections(p2) cl2 <- cl2[!is.na(cl2$rport & cl2$rport == port), ] expect_equal(cl2$family, "AF_INET6") expect_equal(cl2$type, "SOCK_STREAM") }) test_that("UDP6", { skip_if_offline() skip_without_ipv6() skip_without_ipv6_connection() skip_without_program("socat") skip_if_no_processx() nc <- processx::process$new( "socat", c("-", "UDP6:2001\\:4860\\:4860\\:8888:53"), stdin = "|") on.exit(cleanup_process(nc), add = TRUE) p <- nc$as_ps_handle() deadline <- Sys.time() + as.difftime(5, units = "secs") while (Sys.time() < deadline && ! 53 %in% (cl <- ps_connections(p))$rport) { Sys.sleep(.1) } expect_true(deadline > Sys.time()) cl <- cl[!is.na(cl$rport) & cl$rport == 53, ] expect_equal(nrow(cl), 1) expect_equal(cl$family, "AF_INET6") expect_equal(cl$type, "SOCK_DGRAM") expect_match(cl$raddr, "2001:4860:4860:8888", fixed = TRUE) }) test_that("UDP6 on loopback", { skip_without_program("socat") skip_if_no_processx() skip_without_ipv6() nc <- processx::process$new( "socat", c("-d", "-d", "-ls", "-", "UDP6-LISTEN:0"), stdin = "|", stderr = "|") on.exit(cleanup_process(nc), add = TRUE) p <- nc$as_ps_handle() wait_for_string(nc, "listening on", timeout = 2000) cl <- ps_connections(p) cl <- cl[!is.na(cl$lport) & cl$type == "SOCK_DGRAM", ] port <- cl$lport expect_equal(cl$family, "AF_INET6") expect_equal(cl$type, "SOCK_DGRAM") nc2 <- processx::process$new( "socat", c("-d", "-d", "-", paste0("UDP6-CONNECT:\\:\\:1:", port)), stdin = "|", stderr = "|") on.exit(cleanup_process(nc2), add = TRUE) p2 <- nc2$as_ps_handle() err <- FALSE tryCatch( wait_for_string(nc2, "starting data transfer", timeout = 2000), error = function(e) err <<- TRUE) if (err) skip("Could not bind to IPv6 address") cl2 <- ps_connections(p2) cl2 <- cl2[!is.na(cl2$rport & cl2$rport == port), ] expect_equal(cl2$family, "AF_INET6") expect_equal(cl2$type, "SOCK_DGRAM") }) ps/tests/testthat/helpers.R0000644000176200001440000000623113620625516015473 0ustar liggesusers format_regexp <- function() { " PID=[0-9]+, NAME=.*, AT=" } parse_ps <- function(args) { out <- processx::run("ps", args)$stdout sub(" *$", "", strsplit(out, "\n")[[1]][[2]]) } parse_time <- function(x) { x <- utils::tail(c(0, 0, 0, as.numeric(strsplit(x, ":")[[1]])), 3) x[1] * 60 * 60 + x[2] * 60 + x[3] } wait_for_status <- function(ps, status, timeout = 5) { limit <- Sys.time() + timeout while (ps_status(ps) != status && Sys.time() < limit) Sys.sleep(0.05) } px <- function() get_tool("px") skip_in_rstudio <- function() { if (Sys.getenv("RSTUDIO") != "") skip("Cannot test in RStudio") } has_processx <- function() { requireNamespace("processx", quietly = TRUE) && package_version(getNamespaceVersion("processx")) >= "3.1.0.9005" } skip_if_no_processx <- function() { if (!has_processx()) skip("Needs processx >= 3.1.0.9005 to run") } skip_without_program <- function(prog) { if (Sys.which(prog) == "") skip(paste(prog, "is not available")) } have_ipv6_support <- function() { ps_os_type()[["WINDOWS"]] || !is.null(ps_env$constants$address_families$AF_INET6) } skip_without_ipv6 <- function() { if (!have_ipv6_support()) skip("Needs IPv6") } ipv6_url <- function() { paste0("https://", ipv6_host()) } ipv6_host <- function() { "ipv6.test-ipv6.com" } have_ipv6_connection <- local({ ok <- NULL myurl <- NULL function(url = ipv6_url()) { if (is.null(ok) || myurl != url) { myurl <<- url tryCatch({ cx <- curl::curl(url) open(cx) ok <<- TRUE }, error = function(x) ok <<- FALSE, finally = close(cx)) } ok } }) skip_without_ipv6_connection <- function() { if (!have_ipv6_connection()) skip("Needs working IPv6 connection") } httpbin_url <- function() { "eu.httpbin.org" } is_offline <- (function() { offline <- NULL function() { if (is.null(offline)) { offline <<- tryCatch( is.na(pingr::ping_port(httpbin_url(), port = 443, count = 1L)), error = function(e) TRUE ) } offline } })() skip_if_offline <- function() { if (is_offline()) skip("Offline") } wait_for_string <- function(proc, string, timeout) { deadline <- Sys.time() + as.difftime(timeout / 1000, units = "secs") str <- "" repeat { left <- max(as.double(deadline - Sys.time(), units = "secs"), 0) pr <- processx::poll(list(proc), as.integer(left * 1000)) str <- paste(str, proc$read_error()) if (grepl(string, str)) return() if (proc$has_output_connection()) read_output() if (deadline < Sys.time()) stop("Cannot start proces") if (!proc$is_alive()) stop("Cannot start process") } } ## This is not perfect, e.g. we don't check that the numbers are <255, ## but will do for our purposes is_ipv4_address <- function(x) { grepl("^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$", x) } cleanup_process <- function(p) { tryCatch(close(p$get_input_connection()), error = function(x) x) tryCatch(close(p$get_output_connection()), error = function(x) x) tryCatch(close(p$get_error_connection()), error = function(x) x) tryCatch(close(p$get_poll_connection()), error = function(x) x) tryCatch(p$kill(), error = function(x) x) gc() } ps/tests/testthat/test-pid-reuse.R0000644000176200001440000000273613356424172016712 0ustar liggesusers context("pid reuse") test_that("pid reuse", { ## This is simulated, because it is quite some work to force a pid ## reuse on some systems. So we create a handle with the pid of a ## running process, but wrong (earlier) create time stamp. z <- processx::process$new(px(), c("sleep", "600")) on.exit(z$kill(), add = TRUE) zpid <- z$get_pid() ctime <- Sys.time() - 60 attr(ctime, "tzone") <- "GMT" p <- ps_handle(zpid, ctime) expect_match(format(p), format_regexp()) expect_output(print(p), format_regexp()) expect_equal(ps_pid(p), zpid) expect_equal(ps_create_time(p), ctime) expect_false(ps_is_running(p)) chk <- function(expr) { err <- tryCatch(expr, error = function(e) e) expect_s3_class(err, "no_such_process") expect_s3_class(err, "ps_error") expect_equal(err$pid, zpid) } ## All these error out with "no_such_process" chk(ps_status(p)) chk(ps_ppid(p)) chk(ps_parent(p)) chk(ps_name(p)) if (ps_os_type()[["POSIX"]]) chk(ps_uids(p)) chk(ps_username(p)) if (ps_os_type()[["POSIX"]]) chk(ps_gids(p)) chk(ps_terminal(p)) if (ps_os_type()[["POSIX"]]) chk(ps_send_signal(p, signals()$SIGINT)) chk(ps_suspend(p)) chk(ps_resume(p)) if (ps_os_type()[["POSIX"]]) chk(ps_terminate(p)) chk(ps_kill(p)) chk(ps_exe(p)) chk(ps_cmdline(p)) chk(ps_environ(p)) chk(ps_cwd(p)) chk(ps_memory_info(p)) chk(ps_cpu_times(p)) chk(ps_num_threads(p)) chk(ps_num_fds(p)) chk(ps_open_files(p)) chk(ps_connections(p)) }) ps/tests/testthat/test-posix-zombie.R0000644000176200001440000000325613737120534017436 0ustar liggesusers if (!ps_os_type()[["POSIX"]]) return() context("posix-zombie") test_that("zombie api", { zpid <- zombie() on.exit(waitpid(zpid), add = TRUE) p <- ps_handle(zpid) me <- ps_handle() expect_match(format(p), format_regexp()) expect_output(print(p), format_regexp()) expect_equal(ps_pid(p), zpid) expect_true(ps_create_time(p) > ps_create_time(me)) expect_true(ps_is_running(p)) expect_equal(ps_status(p), "zombie") expect_equal(ps_ppid(p), Sys.getpid()) expect_equal(ps_pid(ps_parent(p)), Sys.getpid()) expect_equal(ps_name(p), ps_name(me)) expect_identical(ps_uids(p), ps_uids(me)) expect_identical(ps_username(p), ps_username(me)) expect_identical(ps_gids(p), ps_gids(me)) expect_identical(ps_terminal(p), ps_terminal(me)) expect_silent(ps_children(p)) ## You can still send signals if you like expect_silent(ps_send_signal(p, signals()$SIGINT)) expect_equal(ps_status(p), "zombie") expect_silent(ps_suspend(p)) expect_equal(ps_status(p), "zombie") expect_silent(ps_resume(p)) expect_equal(ps_status(p), "zombie") expect_silent(ps_terminate(p)) expect_equal(ps_status(p), "zombie") expect_silent(ps_kill(p)) expect_equal(ps_status(p), "zombie") chk <- function(expr) { err <- tryCatch(expr, error = function(e) e) expect_s3_class(err, "zombie_process") expect_s3_class(err, "ps_error") expect_equal(err$pid, zpid) } ## These raise zombie_process errors chk(ps_exe(p)) chk(ps_cmdline(p)) chk(ps_environ(p)) chk(ps_cwd(p)) chk(ps_memory_info(p)) chk(ps_cpu_times(p)) chk(ps_num_threads(p)) chk(ps_num_fds(p)) chk(ps_open_files(p)) chk(ps_connections(p)) chk(ps_get_nice(p)) chk(ps_set_nice(p, 20L)) }) ps/tests/testthat/test-windows.R0000644000176200001440000000143613330045252016471 0ustar liggesusers if (!ps_os_type()[["WINDOWS"]]) return() context("windows") test_that("uids, gids", { p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) err <- tryCatch(ps_uids(ps), error = function(e) e) expect_s3_class(err, "not_implemented") expect_s3_class(err, "ps_error") err <- tryCatch(ps_gids(ps), error = function(e) e) expect_s3_class(err, "not_implemented") expect_s3_class(err, "ps_error") }) test_that("terminal", { p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) expect_identical(ps_terminal(ps), NA_character_) }) ## TODO: username ## TODO: cpu_times ## TODO: memory_info ps/tests/testthat/test-cleanup-reporter.R0000644000176200001440000002643713735101717020306 0ustar liggesusers context("cleanup testthat reporter") test_that("unit: test, mode: cleanup-fail", { out <- list() on.exit(if (!is.null(out$p)) out$p$kill(), add = TRUE) expect_failure( with_reporter( CleanupReporter(testthat::SilentReporter)$new(proc_unit = "test"), { test_that("foobar", { out$p <<- processx::process$new(px(), c("sleep", "5")) out$running <<- out$p$is_alive() }) } ), "did not clean up processes" ) expect_true(out$running) deadline <- Sys.time() + 2 while (out$p$is_alive() && Sys.time() < deadline) Sys.sleep(0.05) expect_true(Sys.time() < deadline) expect_false(out$p$is_alive()) }) test_that("unit: test, multiple processes", { out <- list() on.exit(if (!is.null(out$p1)) out$p1$kill(), add = TRUE) on.exit(if (!is.null(out$p2)) out$p2$kill(), add = TRUE) expect_failure( with_reporter( CleanupReporter(testthat::SilentReporter)$new(proc_unit = "test"), { test_that("foobar", { out$p1 <<- processx::process$new(px(), c("sleep", "5")) out$p2 <<- processx::process$new(px(), c("sleep", "5")) out$running <<- out$p1$is_alive() && out$p2$is_alive() }) } ), "px.*px" ) expect_true(out$running) expect_false(out$p1$is_alive()) expect_false(out$p2$is_alive()) }) test_that("on.exit() works", { out <- list() on.exit(if (!is.null(out$p)) out$p$kill(), add = TRUE) expect_success( with_reporter( CleanupReporter(testthat::SilentReporter)$new(proc_unit = "test"), { test_that("foobar", { out$p <<- processx::process$new(px(), c("sleep", "5")) on.exit(out$p$kill(), add = TRUE) out$running <<- out$p$is_alive() }) } ) ) expect_true(out$running) expect_false(out$p$is_alive()) }) test_that("only report", { out <- list() on.exit(if (!is.null(out$p)) out$p$kill(), add = TRUE) expect_failure( with_reporter( CleanupReporter(testthat::SilentReporter)$new( proc_unit = "test", proc_cleanup = FALSE, proc_fail = TRUE), { test_that("foobar", { out$p <<- processx::process$new(px(), c("sleep", "5")) out$running <<- out$p$is_alive() }) } ), "did not clean up processes" ) expect_true(out$running) expect_true(out$p$is_alive()) out$p$kill() }) test_that("only kill", { out <- list() on.exit(if (!is.null(out$p)) out$p$kill(), add = TRUE) with_reporter( CleanupReporter(testthat::SilentReporter)$new( proc_unit = "test", proc_cleanup = TRUE, proc_fail = FALSE, conn_fail = FALSE), { test_that("foobar", { out$p <<- processx::process$new(px(), c("sleep", "5")) out$running <<- out$p$is_alive() }) ## It must be killed by now test_that("foobar2", { deadline <- Sys.time() + 3 while (out$p$is_alive() && Sys.time() < deadline) Sys.sleep(0.05) out$running2 <<- out$p$is_alive() }) } ) expect_true(out$running) expect_false(out$running2) expect_false(out$p$is_alive()) }) test_that("unit: testsuite", { out <- list() on.exit(if (!is.null(out$p)) out$p$kill(), add = TRUE) expect_failure( with_reporter( CleanupReporter(testthat::SilentReporter)$new( proc_unit = "testsuite", rconn_fail = FALSE, file_fail = FALSE, conn_fail = FALSE), { test_that("foobar", { out$p <<- processx::process$new(px(), c("sleep", "5")) out$running <<- out$p$is_alive() }) test_that("foobar2", { ## Still alive out$running2 <<- out$p$is_alive() }) } ), "did not clean up processes" ) expect_true(out$running) expect_true(out$running2) deadline <- Sys.time() + 3 while (out$p$is_alive() && Sys.time() < deadline) Sys.sleep(0.05) expect_false(out$p$is_alive()) }) test_that("R connection cleanup, test, close, fail", { out <- list() tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) on.exit(try(close(out$conn), silent = TRUE), add = TRUE) expect_failure( with_reporter( CleanupReporter(testthat::SilentReporter)$new(proc_fail = FALSE), { test_that("foobar", { out$conn <<- file(tmp, open = "w") out$open <<- isOpen(out$conn) }) } ), "did not close R connections" ) expect_true(out$open) expect_error(isOpen(out$conn)) }) test_that("R connection cleanup, test, do not close, fail", { out <- list() tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) on.exit(try(close(out$conn), silent = TRUE), add = TRUE) expect_failure( with_reporter( CleanupReporter(testthat::SilentReporter)$new( proc_fail = FALSE, rconn_cleanup = FALSE), { test_that("foobar", { out$conn <<- file(tmp, open = "w") out$open <<- isOpen(out$conn) }) } ), "did not close R connections" ) expect_true(out$open) expect_true(isOpen(out$conn)) expect_silent(close(out$conn)) }) test_that("R connection cleanup, test, close, do not fail", { out <- list() tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) on.exit(try(close(out$conn), silent = TRUE), add = TRUE) with_reporter( CleanupReporter(testthat::SilentReporter)$new( proc_fail = FALSE, rconn_fail = FALSE), { test_that("foobar", { out$conn <<- file(tmp, open = "w") out$open <<- isOpen(out$conn) }) } ) expect_true(out$open) expect_error(isOpen(out$conn)) }) test_that("R connections, unit: testsuite", { out <- list() tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) on.exit(try(close(out$conn), silent = TRUE), add = TRUE) expect_failure( with_reporter( CleanupReporter(testthat::SilentReporter)$new( rconn_unit = "testsuite", proc_fail = FALSE, file_fail = FALSE, conn_fail = FALSE), { test_that("foobar", { out$conn <<- file(tmp, open = "w") out$open <<- isOpen(out$conn) }) test_that("foobar2", { ## Still alive out$open2 <<- isOpen(out$conn) }) } ), "did not close R connections" ) expect_true(out$open) expect_true(out$open2) expect_error(isOpen(out$conn)) }) test_that("connections already open are ignored", { tmp2 <- tempfile() on.exit(unlink(tmp2), add = TRUE) conn <- file(tmp2, open = "w") on.exit(try(close(conn), silent = TRUE), add = TRUE) out <- list() tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) on.exit(try(close(out$conn), silent = TRUE), add = TRUE) expect_success( with_reporter( CleanupReporter(testthat::SilentReporter)$new(proc_fail = FALSE), { test_that("foobar", { out$conn <<- file(tmp, open = "w") out$open <<- isOpen(out$conn) close(out$conn) }) } ) ) expect_error(isOpen(out$conn)) expect_true(isOpen(conn)) expect_silent(close(conn)) }) test_that("File cleanup, test, fail", { out <- list() tmp <- tempfile() cat("data\ndata2\n", file = tmp) on.exit(unlink(tmp), add = TRUE) on.exit(try(close(out$conn), silent = TRUE), add = TRUE) expect_failure( with_reporter( CleanupReporter(testthat::SilentReporter)$new( proc_fail = FALSE, rconn_cleanup = FALSE, rconn_fail = FALSE), { test_that("foobar", { out$conn <<- file(tmp, open = "r") out$open <<- isOpen(out$conn) }) } ), "did not close open files" ) expect_true(out$open) expect_true(isOpen(out$conn)) close(out$conn) }) test_that("File cleanup, unit: testsuite", { out <- list() tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) on.exit(try(close(out$conn), silent = TRUE), add = TRUE) expect_failure( with_reporter( CleanupReporter(testthat::SilentReporter)$new( file_unit = "testsuite", proc_fail = FALSE, rconn_fail = FALSE, rconn_cleanup = FALSE, conn_fail = FALSE), { test_that("foobar", { out$conn <<- file(tmp, open = "w") out$open <<- isOpen(out$conn) }) test_that("foobar2", { ## Still alive out$open2 <<- isOpen(out$conn) }) } ), "did not close open files" ) expect_true(out$open) expect_true(out$open2) expect_true(isOpen(out$conn)) close(out$conn) }) test_that("files already open are ignored", { tmp2 <- tempfile() on.exit(unlink(tmp2), add = TRUE) conn <- file(tmp2, open = "w") on.exit(try(close(conn), silent = TRUE), add = TRUE) out <- list() tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) on.exit(try(close(out$conn), silent = TRUE), add = TRUE) expect_success( with_reporter( CleanupReporter(testthat::SilentReporter)$new( proc_fail = FALSE, rconn_fail = FALSE, rconn_cleanup = FALSE), { test_that("foobar", { out$conn <<- file(tmp, open = "w") out$open <<- isOpen(out$conn) close(out$conn) }) } ) ) expect_error(isOpen(out$conn)) expect_true(isOpen(conn)) expect_silent(close(conn)) }) # libcurl will open a pipe pair, when we call it the first time, # so we do a dummy download here, to avoid the false positive of # the cleanup reporter offline <- is_offline() if (!offline) { conn <- curl::curl(httpbin_url(), open = "r") close(conn) } test_that("Network cleanup, test, fail", { if (offline) skip("Offline") out <- list() on.exit({ try(close(out$conn), silent = TRUE); gc() }, add = TRUE) expect_failure( with_reporter( CleanupReporter(testthat::SilentReporter)$new( proc_fail = FALSE, rconn_cleanup = FALSE, rconn_fail = FALSE, file_fail = FALSE), { test_that("foobar", { out$conn <<- curl::curl(httpbin_url(), open = "r") out$open <<- isOpen(out$conn) }) } ), "did not close network" ) expect_true(out$open) expect_true(isOpen(out$conn)) close(out$conn) }) test_that("Network cleanup, unit: testsuite", { if (offline) skip("Offline") out <- list() on.exit(try(close(out$conn), silent = TRUE), add = TRUE) expect_failure( with_reporter( CleanupReporter(testthat::SilentReporter)$new( conn_unit = "testsuite", proc_fail = FALSE, rconn_fail = FALSE, rconn_cleanup = FALSE, file_fail = FALSE), { test_that("foobar", { out$conn <<- curl::curl(httpbin_url(), open = "r") out$open <<- isOpen(out$conn) }) test_that("foobar2", { ## Still alive out$open2 <<- isOpen(out$conn) }) } ), "did not close network connections" ) expect_true(out$open) expect_true(out$open2) expect_true(isOpen(out$conn)) close(out$conn) }) test_that("Network connections already open are ignored", { if (offline) skip("Offline") conn <- curl::curl(httpbin_url(), open = "r") on.exit(try(close(conn), silent = TRUE), add = TRUE) out <- list() on.exit(try(close(out$conn), silent = TRUE), add = TRUE) expect_success( with_reporter( CleanupReporter(testthat::SilentReporter)$new( proc_fail = FALSE, rconn_fail = FALSE, rconn_cleanup = FALSE), { test_that("foobar", { out$conn <<- curl::curl(httpbin_url(), open = "r") out$open <<- isOpen(out$conn) close(out$conn) }) } ) ) expect_error(isOpen(out$conn)) expect_true(isOpen(conn)) expect_silent(close(conn)) }) ps/tests/testthat/test-macos.R0000644000176200001440000000325013356424172016107 0ustar liggesusers if (!ps_os_type()[["MACOS"]]) return() context("macos") test_that("status", { ## Argument check expect_error(ps_status(123), class = "invalid_argument") p1 <- processx::process$new("sleep", "10") on.exit(cleanup_process(p1), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) expect_equal(ps_status(ps), "running") ps_suspend(ps) expect_equal(ps_status(ps), "stopped") ps_resume(ps) expect_equal(ps_status(ps), "running") ## TODO: can't easily test 'sleeping' and 'idle' }) test_that("cpu_times", { skip_on_cran() ## Argument check expect_error(ps_cpu_times(123), class = "invalid_argument") p1 <- processx::process$new("ls", c("-lR", "/")) on.exit(cleanup_process(p1), add = TRUE) ps <- ps_handle(p1$get_pid()) Sys.sleep(0.2) ps_suspend(ps) ct <- ps_cpu_times(ps) ps2_user <- parse_time(parse_ps(c("-o", "utime", "-p", ps_pid(ps)))) ps2_total <- parse_time(parse_ps(c("-o", "time", "-p", ps_pid(ps)))) expect_true(abs(round(ct[["user"]], 2) - ps2_user) < 0.1) expect_true(abs(round(ct[["system"]], 2) - (ps2_total - ps2_user)) < 0.1) }) test_that("memory_info", { skip_on_cran() ## Argument check expect_error(ps_memory_info(123), class = "invalid_argument") p1 <- processx::process$new("ls", c("-lR", "/")) on.exit(cleanup_process(p1), add = TRUE) ps <- ps_handle(p1$get_pid()) Sys.sleep(0.2) ps_suspend(ps) mem <- ps_memory_info(ps) ps2_rss <- as.numeric(parse_ps(c("-o", "rss", "-p", ps_pid(ps)))) ps2_vms <- as.numeric(parse_ps(c("-o", "vsize", "-p", ps_pid(ps)))) expect_equal(mem[["rss"]] / 1024, ps2_rss, tolerance = 10) expect_equal(mem[["vms"]] / 1024, ps2_vms, tolerance = 10) }) ps/tests/testthat/test-utils.R0000644000176200001440000000147113623757674016165 0ustar liggesusers context("utils") test_that("errno", { err <- errno() expect_true(is.data.frame(err)) expect_true("EINVAL" %in% err$name) expect_true("EBADF" %in% err$name) }) test_that("str_strip", { tcs <- list( list("", ""), list(" ", ""), list("a ", "a"), list(" a", "a"), list(" a ", "a"), list(" a ", "a"), list(character(), character()), list(c("", NA, "a "), c("", NA, "a")), list("\ta\n", "a") ) for (tc in tcs) { expect_identical(str_strip(tc[[1]]), tc[[2]]) } }) test_that("NA_time", { nat <- NA_time() expect_s3_class(nat, "POSIXct") expect_true(length(nat) == 1 && is.na(nat)) }) test_that("read_lines", { tmp <- tempfile() cat("foo\nbar\nfoobar", file = tmp) expect_silent(l <- read_lines(tmp)) expect_equal(l, c("foo", "bar", "foobar")) }) ps/tests/testthat/test-finished.R0000644000176200001440000000266513737306323016607 0ustar liggesusers context("process finished") test_that("process already finished", { skip_on_cran() px <- processx::process$new(px(), c("sleep", "5")) on.exit(px$kill(), add = TRUE) pid <- px$get_pid() p <- ps_handle(pid) ct <- ps_create_time(p) px$kill() expect_false(px$is_alive()) if (ps_os_type()[["POSIX"]]) { expect_equal(px$get_exit_status(), -9) } expect_match(format(p), format_regexp()) expect_output(print(p), format_regexp()) expect_equal(ps_pid(p), pid) if (has_processx()) expect_equal(ps_create_time(p), ct) expect_false(ps_is_running(p)) chk <- function(expr) { err <- tryCatch(expr, error = function(e) e) expect_s3_class(err, "no_such_process") expect_s3_class(err, "ps_error") expect_equal(err$pid, pid) } ## All these error out with "no_such_process" chk(ps_status(p)) chk(ps_ppid(p)) chk(ps_parent(p)) chk(ps_name(p)) if (ps_os_type()[["POSIX"]]) chk(ps_uids(p)) chk(ps_username(p)) if (ps_os_type()[["POSIX"]]) chk(ps_gids(p)) chk(ps_terminal(p)) if (ps_os_type()[["POSIX"]]) chk(ps_send_signal(p, signals()$SIGINT)) chk(ps_suspend(p)) chk(ps_resume(p)) if (ps_os_type()[["POSIX"]]) chk(ps_terminate(p)) chk(ps_kill(p)) chk(ps_exe(p)) chk(ps_cmdline(p)) chk(ps_environ(p)) chk(ps_cwd(p)) chk(ps_memory_info(p)) chk(ps_cpu_times(p)) chk(ps_num_threads(p)) chk(ps_children(p)) chk(ps_num_fds(p)) chk(ps_open_files(p)) chk(ps_connections(p)) }) ps/tests/testthat/test-common.R0000644000176200001440000001774313370263274016311 0ustar liggesusers context("common") test_that("create self process", { expect_error(ps_handle("foobar"), class = "invalid_argument") expect_error(ps_handle(time = 123), class = "invalid_argument") ps <- ps_handle() expect_identical(ps_pid(ps), Sys.getpid()) }) test_that("format", { ps <- ps_handle() expect_match(format(ps), format_regexp()) }) test_that("print", { ps <- ps_handle() expect_output(print(ps), format_regexp()) }) test_that("pid", { ## Argument check expect_error(ps_pid(123), class = "invalid_argument") ## Self ps <- ps_handle() expect_identical(ps_pid(ps), Sys.getpid()) ## Child p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_identical(ps_pid(ps), p1$get_pid()) skip_if_no_processx() ## Even if it has quit already p2 <- processx::process$new(px(), c("sleep", "10")) on.exit(p2$kill(), add = TRUE) pid2 <- p2$get_pid() ps <- ps_handle(pid2) p2$kill() expect_false(p2$is_alive()) expect_identical(ps_pid(ps), pid2) }) test_that("create_time", { ## Argument check expect_error(ps_create_time(123), class = "invalid_argument") skip_if_no_processx() p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_identical(p1$get_start_time(), ps_create_time(ps)) }) test_that("is_running", { ## Argument check expect_error(ps_is_running(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) p1$kill() timeout <- Sys.time() + 5 while (ps_is_running(ps) && Sys.time() < timeout) Sys.sleep(0.05) expect_false(ps_is_running(ps)) }) test_that("parent", { ## Argument check expect_error(ps_parent(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) pp <- ps_parent(ps) expect_equal(ps_pid(pp), Sys.getpid()) }) test_that("ppid", { ## Argument check expect_error(ps_ppid(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) expect_equal(ps_ppid(ps), Sys.getpid()) }) test_that("name", { ## Argument check expect_error(ps_name(123), class = "invalid_argument") skip_if_no_processx() p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) expect_true(ps_name(ps) %in% c("px", "px.exe")) ## Long names are not truncated file.copy( px(), tmp <- paste0(tempfile(pattern = "file1234567890123456"), ".bat")) on.exit(unlink(tmp), add = TRUE) Sys.chmod(tmp, "0755") p2 <- processx::process$new(tmp, c("sleep", "10")) on.exit(p2$kill(), add = TRUE) ps <- ps_handle(p2$get_pid()) expect_true(ps_is_running(ps)) expect_equal(ps_name(ps), basename(tmp)) }) test_that("exe", { ## Argument check expect_error(ps_exe(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) expect_equal(ps_exe(ps), realpath(px())) }) test_that("cmdline", { ## Argument check expect_error(ps_cmdline(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) expect_equal(ps_cmdline(ps), c(px(), "sleep", "10")) }) test_that("cwd", { ## Argument check expect_error(ps_cwd(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10"), wd = tempdir()) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) expect_equal(normalizePath(ps_cwd(ps)), normalizePath(tempdir())) }) test_that("environ, environ_raw", { ## Argument check expect_error(ps_environ(123), class = "invalid_argument") skip_if_no_processx() rnd <- basename(tempfile()) p1 <- processx::process$new(px(), c("sleep", "10"), env = c(FOO = rnd)) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) expect_equal(ps_environ(ps)[["FOO"]], rnd) expect_true(paste0("FOO=", rnd) %in% ps_environ_raw(ps)) }) test_that("num_threads", { ## Argument check expect_error(ps_num_threads(123), class = "invalid_argument") ## sleep should be single-threaded p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) expect_equal(ps_num_threads(ps), 1) ## TODO: more threads? }) test_that("suspend, resume", { ## Argument check expect_error(ps_suspend(123), class = "invalid_argument") expect_error(ps_resume(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) ps_suspend(ps) timeout <- Sys.time() + 60 while (Sys.time() < timeout && ps_status(ps) != "stopped") Sys.sleep(0.05) expect_equal(ps_status(ps), "stopped") expect_true(p1$is_alive()) expect_true(ps_is_running(ps)) ps_resume(ps) timeout <- Sys.time() + 60 while (Sys.time() < timeout && ps_status(ps) == "stopped") Sys.sleep(0.05) expect_true(ps_status(ps) %in% c("running", "sleeping")) expect_true(p1$is_alive()) expect_true(ps_is_running(ps)) ps_kill(ps) }) test_that("kill", { ## Argument check expect_error(ps_kill(123), class = "invalid_argument") p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) ps_kill(ps) timeout <- Sys.time() + 5 while (Sys.time() < timeout && ps_is_running(ps)) Sys.sleep(0.05) expect_false(p1$is_alive()) expect_false(ps_is_running(ps)) if (ps_os_type()[["POSIX"]]) { expect_equal(p1$get_exit_status(), - signals()$SIGKILL) } }) test_that("children", { ## Argument check expect_error(ps_children(123), class = "invalid_argument") skip_if_no_processx() p1 <- processx::process$new(px(), c("sleep", "10")) on.exit(p1$kill(), add = TRUE) p2 <- processx::process$new(px(), c("sleep", "10")) on.exit(p2$kill(), add = TRUE) ch <- ps_children(ps_handle()) expect_true(length(ch) >= 2) pids <- map_int(ch, ps_pid) expect_true(p1$get_pid() %in% pids) expect_true(p2$get_pid() %in% pids) ## We don't do this on Windows, because the parent process might be ## gone by now, and then it fails with no_such_process if (ps_os_type()[["POSIX"]]) { ch3 <- ps_children(ps_parent(ps_handle()), recursive = TRUE) pids3 <- map_int(ch3, ps_pid) expect_true(Sys.getpid() %in% pids3) expect_true(p1$get_pid() %in% pids3) expect_true(p2$get_pid() %in% pids3) } }) test_that("num_fds", { skip_in_rstudio() skip_on_cran() tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) me <- ps_handle() orig <- ps_num_fds(me) f <- file(tmp, open = "w") on.exit(close(f), add = TRUE) expect_equal(ps_num_fds(me), orig + 1) }) test_that("open_files", { skip_in_rstudio() skip_on_cran() tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) f <- file(tmp, open = "w") on.exit(try(close(f), silent = TRUE), add = TRUE) files <- ps_open_files(ps_handle()) expect_true(basename(tmp) %in% basename(files$path)) close(f) files <- ps_open_files(ps_handle()) expect_false(basename(tmp) %in% basename(files$path)) }) test_that("interrupt", { skip_on_cran() px <- processx::process$new(px(), c("sleep", "10")) on.exit(px$kill(), add = TRUE) ps <- ps_handle(px$get_pid()) expect_true(ps_is_running(ps)) ps_interrupt(ps) deadline <- Sys.time() + 3 while (ps_is_running(ps) && Sys.time() < deadline) Sys.sleep(0.05) expect_true(Sys.time() < deadline) expect_false(ps_is_running(ps)) if (ps_os_type()[["POSIX"]]) expect_equal(px$get_exit_status(), -2) }) ps/tests/testthat/test-kill-tree.R0000644000176200001440000001713013742046242016674 0ustar liggesusers context("kill-tree") test_that("ps_mark_tree", { id <- ps_mark_tree() on.exit(Sys.unsetenv(id), add = TRUE) expect_true(is.character(id)) expect_true(length(id) == 1) expect_false(is.na(id)) expect_false(Sys.getenv(id) == "") }) test_that("kill_tree", { skip_on_cran() skip_in_rstudio() res <- ps_kill_tree(get_id()) expect_equal(length(res), 0) expect_true(is.integer(res)) ## Child processes id <- ps_mark_tree() on.exit(Sys.unsetenv(id), add = TRUE) dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE), add = TRUE) p <- lapply(1:5, function(x) { out <- file.path(tmp, basename(tempfile())) processx::process$new( px(), c("outln", "ready", "sleep", "10"), stdout = out ) }) on.exit(lapply(p, function(x) x$kill()), add = TRUE) timeout <- Sys.time() + 5 while (sum(file_size(dir(tmp, full.names = TRUE)) > 0) < 5 && Sys.time() < timeout) Sys.sleep(0.1) expect_true(Sys.time() < timeout) res <- ps_kill_tree(id) res <- res[names(res) %in% c("px", "px.exe")] expect_equal(length(res), 5) expect_equal( sort(as.integer(res)), sort(map_int(p, function(x) x$get_pid()))) ## We need to wait a bit here, potentially, because the process ## might be a zombie, which is technically alive. now <- Sys.time() timeout <- now + 5 while (any(map_lgl(p, function(pp) pp$is_alive())) && Sys.time() < timeout) Sys.sleep(0.1) expect_true(Sys.time() < timeout) lapply(p, function(pp) expect_false(pp$is_alive())) }) test_that("kill_tree, grandchild", { skip_on_cran() skip_in_rstudio() id <- ps_mark_tree() on.exit(Sys.unsetenv(id), add = TRUE) dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE), add = TRUE) N <- 3 p <- lapply(1:N, function(x) { callr::r_bg( function(d) { cat("OK\n", file = file.path(d, Sys.getpid())) # We ignore error from the grandchild, in case it gets # killed first. The child still runs on, because of the sleep. try(callr::r( function(d) { cat("OK\n", file = file.path(d, Sys.getpid())) Sys.sleep(5) }, args = list(d = d))) Sys.sleep(5) }, args = list(d = tmp), cleanup = FALSE ) }) on.exit(lapply(p, function(x) x$kill()), add = TRUE) timeout <- Sys.time() + 10 while (length(dir(tmp)) < 2*N && Sys.time() < timeout) Sys.sleep(0.1) expect_true(Sys.time() < timeout) res <- ps_kill_tree(id) ## Older processx versions do not close the connections on kill, ## so the cleanup reporter picks them up lapply(p, function(pp) { close(pp$get_output_connection()) close(pp$get_error_connection()) }) res <- res[names(res) %in% c("R", "Rterm.exe")] ## We might miss some processes, because grandchildren can be ## are in the same job object and they are cleaned up automatically. ## To fix the, processx would need an option _not_ to create a job ## object. expect_true(length(res) <= N * 2) expect_true(all(names(res) %in% c("R", "Rterm.exe"))) cpids <- map_int(p, function(x) x$get_pid()) expect_true(all(cpids %in% res)) ccpids <- as.integer(dir(tmp)) ## Again, the opposite might not be true, because we might miss some ## grandchildren. expect_true(all(res %in% ccpids)) ## Nevertheless none of them should be alive. ## (Taking the risk of pid reuse here...) timeout <- Sys.time() + 5 while (any(ccpids %in% ps_pids()) && Sys.time() < timeout) Sys.sleep(0.1) expect_true(Sys.time() < timeout) }) test_that("kill_tree, orphaned grandchild", { skip_on_cran() skip_in_rstudio() id <- ps_mark_tree() on.exit(Sys.unsetenv(id), add = TRUE) dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE), add = TRUE) cmdline <- paste(px(), "sleep 5") N <- 3 lapply(1:N, function(x) { system2(px(), c("outln", "ok","sleep", "5"), stdout = file.path(tmp, x), wait = FALSE) }) timeout <- Sys.time() + 10 while (sum(file_size(dir(tmp, full.names = TRUE)) > 0) < N && Sys.time() < timeout) Sys.sleep(0.1) res <- ps_kill_tree(id) res <- res[names(res) %in% c("px", "px.exe")] expect_equal(length(res), N) expect_true(all(names(res) %in% c("px", "px.exe"))) }) test_that("with_process_cleanup", { skip_on_cran() skip_in_rstudio() p <- NULL with_process_cleanup({ p <- lapply(1:3, function(x) { processx::process$new(px(), c("sleep", "10")) }) expect_equal(length(p), 3) lapply(p, function(pp) expect_true(pp$is_alive())) }) expect_equal(length(p), 3) ## We need to wait a bit here, potentially, because the process ## might be a zombie, which is technically alive. now <- Sys.time() timeout <- now + 5 while (any(map_lgl(p, function(pp) pp$is_alive())) && Sys.time() < timeout) Sys.sleep(0.05) lapply(p, function(pp) expect_false(pp$is_alive())) rm(p) gc() }) test_that("find_tree", { skip_on_cran() skip_in_rstudio() skip_if_no_processx() res <- ps_find_tree(get_id()) expect_equal(length(res), 0) expect_true(is.list(res)) ## Child processes id <- ps_mark_tree() on.exit(Sys.unsetenv(id), add = TRUE) p <- lapply(1:5, function(x) processx::process$new(px(), c("sleep", "10"))) on.exit(lapply(p, function(x) x$kill()), add = TRUE) res <- ps_find_tree(id) names <- not_null(lapply(res, function(p) fallback(ps_name(p), NULL))) res <- res[names %in% c("px", "px.exe")] expect_equal(length(res), 5) expect_equal( sort(map_int(res, ps_pid)), sort(map_int(p, function(x) x$get_pid()))) lapply(p, function(x) x$kill()) }) test_that("find_tree, grandchild", { skip_on_cran() skip_in_rstudio() id <- ps_mark_tree() on.exit(Sys.unsetenv(id), add = TRUE) dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE), add = TRUE) N <- 3 p <- lapply(1:N, function(x) { callr::r_bg( function(d) { callr::r( function(d) { cat("OK\n", file = file.path(d, Sys.getpid())) Sys.sleep(5) }, args = list(d = d)) }, args = list(d = tmp)) }) on.exit(lapply(p, function(x) x$kill()), add = TRUE) on.exit(ps_kill_tree(id), add = TRUE) timeout <- Sys.time() + 10 while (length(dir(tmp)) < N && Sys.time() < timeout) Sys.sleep(0.1) res <- ps_find_tree(id) names <- not_null(lapply(res, function(p) fallback(ps_name(p), NULL))) res <- res[names %in% c("R", "Rterm.exe")] expect_equal(length(res), N * 2) cpids <- map_int(p, function(x) x$get_pid()) res_pids <- map_int(res, ps_pid) expect_true(all(cpids %in% res_pids)) ccpids <- as.integer(dir(tmp)) expect_true(all(ccpids %in% res_pids)) ## Older processx versions do not close the connections on kill, ## so the cleanup reporter picks them up lapply(p, function(pp) { pp$kill() close(pp$get_output_connection()) close(pp$get_error_connection()) }) }) test_that("find_tree, orphaned grandchild", { skip_on_cran() skip_in_rstudio() id <- ps_mark_tree() on.exit(Sys.unsetenv(id), add = TRUE) dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE), add = TRUE) cmdline <- paste(px(), "sleep 5") N <- 3 lapply(1:N, function(x) { system2(px(), c("outln", "ok","sleep", "5"), stdout = file.path(tmp, x), wait = FALSE) }) on.exit(ps_kill_tree(id), add = TRUE) timeout <- Sys.time() + 10 while (sum(file_size(dir(tmp, full.names = TRUE)) > 0) < N && Sys.time() < timeout) Sys.sleep(0.1) res <- ps_find_tree(id) names <- not_null(lapply(res, function(p) fallback(ps_name(p), NULL))) res <- res[names %in% c("px", "px.exe")] expect_equal(length(res), N) }) ps/tests/testthat/test-posix.R0000644000176200001440000000362613736704417016163 0ustar liggesusers if (!ps_os_type()[["POSIX"]]) return() context("posix") test_that("is_running", { ## Zombie is running zpid <- zombie() on.exit(waitpid(zpid), add = TRUE) ps <- ps_handle(zpid) expect_true(ps_is_running(ps)) }) test_that("terminal", { tty <- ps_terminal(ps_handle()) if (is.na(tty)) skip("no terminal") expect_true(file.exists(tty)) ## It is a character special file out <- processx::run("ls", c("-l", tty))$stdout expect_equal(substr(out, 1, 1), "c") gc() }) test_that("username, uids, gids", { if (Sys.which("ps") == "") skip("No ps program") ret <- system("ps -p 1 >/dev/null 2>/dev/null") if (ret != 0) skip("ps does not work properly") p1 <- processx::process$new("sleep", "10") on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) expect_true(ps_is_running(ps)) ps2_username <- parse_ps(c("-o", "user", "-p", ps_pid(ps))) expect_equal(ps_username(ps), ps2_username) ps2_uid <- parse_ps(c("-o", "uid", "-p", ps_pid(ps))) expect_equal(ps_uids(ps)[["real"]], as.numeric(ps2_uid)) ps2_gid <- parse_ps(c("-o", "rgid", "-p", ps_pid(ps))) expect_equal(ps_gids(ps)[["real"]], as.numeric(ps2_gid)) gc() }) test_that("send_signal", { p1 <- processx::process$new("sleep", "10") on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) ps_send_signal(ps, signals()$SIGINT) timeout <- Sys.time() + 60 while (Sys.time() < timeout && p1$is_alive()) Sys.sleep(0.05) expect_false(p1$is_alive()) expect_false(ps_is_running(ps)) expect_equal(p1$get_exit_status(), - signals()$SIGINT) }) test_that("terminate", { p1 <- processx::process$new("sleep", "10") on.exit(p1$kill(), add = TRUE) ps <- ps_handle(p1$get_pid()) ps_terminate(ps) timeout <- Sys.time() + 60 while (Sys.time() < timeout && p1$is_alive()) Sys.sleep(0.05) expect_false(p1$is_alive()) expect_false(ps_is_running(ps)) expect_equal(p1$get_exit_status(), - signals()$SIGTERM) }) ps/tests/testthat/test-system.R0000644000176200001440000000305313406773313016332 0ustar liggesusers context("system") test_that("ps_pids", { pp <- ps_pids() expect_true(is.integer(pp)) expect_true(Sys.getpid() %in% pp) }) test_that("ps", { pp <- ps() expect_true(tibble::is_tibble(pp)) expect_true(Sys.getpid() %in% pp$pid) px <- processx::process$new(px(), c("sleep", "5")) x <- ps_handle(px$get_pid()) on.exit(px$kill(), add = TRUE) pp <- ps(after = Sys.time() - 60 * 60) ct <- lapply(pp$pid, function(p) { tryCatch(ps_create_time(ps_handle(p)), error = function(e) NULL) }) ct <- not_null(ct) expect_true(all(map_lgl(ct, function(x) x > Sys.time() - 60 * 60))) pp <- ps(user = ps_username(ps_handle())) expect_true(all(pp$username == ps_username(ps_handle()))) }) test_that("ps_boot_time", { bt <- ps_boot_time() expect_s3_class(bt, "POSIXct") expect_true(bt < Sys.time()) }) test_that("ps_os_type", { os <- ps_os_type() expect_true(is.logical(os)) expect_true(any(os)) expect_equal( names(os), c("POSIX", "WINDOWS", "LINUX", "MACOS")) }) test_that("ps_is_supported", { expect_equal(any(ps_os_type()), ps_is_supported()) }) test_that("supported_str", { expect_equal(supported_str(), "Windows, Linux, Macos") }) test_that("ps_os_name", { expect_true(ps_os_name() %in% names(ps_os_type())) }) test_that("ps_users runs", { expect_error(ps_users(), NA) }) test_that("ps_cpu_count", { log <- ps_cpu_count(logical = TRUE) phy <- ps_cpu_count(logical = FALSE) if (!is.na(log) && !is.na(phy)) expect_true(log >= phy) if (!is.na(log)) expect_true(log > 0) if (!is.na(phy)) expect_true(phy > 0) }) ps/tests/testthat.R0000644000176200001440000000052313620625516014027 0ustar liggesuserslibrary(testthat) library(ps) if (ps::ps_is_supported() && Sys.getenv("R_COVR", "") != "true" && Sys.getenv("NOT_CRAN") != "" ) { reporter <- ps::CleanupReporter(testthat::SummaryReporter)$new() } else { ## ps does not support this platform reporter <- "progress" } if (ps_is_supported()) test_check("ps", reporter = reporter) ps/src/0000755000176200001440000000000014016776112011470 5ustar liggesusersps/src/install.libs.R0000644000176200001440000000104613332411414014201 0ustar liggesusers progs <- if (WINDOWS) { c("px.exe", "interrupt.exe") } else { "px" } dest <- file.path(R_PACKAGE_DIR, paste0("bin", R_ARCH)) dir.create(dest, recursive = TRUE, showWarnings = FALSE) file.copy(progs, dest, overwrite = TRUE) files <- Sys.glob(paste0("*", SHLIB_EXT)) dest <- file.path(R_PACKAGE_DIR, paste0('libs', R_ARCH)) dir.create(dest, recursive = TRUE, showWarnings = FALSE) file.copy(files, dest, overwrite = TRUE) if (file.exists("symbols.rds")) { file.copy("symbols.rds", dest, overwrite = TRUE) } ps/src/interrupt.c0000644000176200001440000000152313332411414013660 0ustar liggesusers #include #include #include int main(int argc, const char **argv) { int ctrlbreak = 1; int pid; int ret; BOOL bret; if (argc == 1) return 1; ret = sscanf(argv[1], "%d", &pid); if (ret != 1) return 1; printf("Pid: %d\n", pid); if (argc == 3 && !strcmp(argv[2], "c")) ctrlbreak = 0; printf("Event: %s\n", ctrlbreak ? "ctrl+break" : "ctrl+c"); printf("Free console\n"); bret = FreeConsole(); if (!bret) return GetLastError(); printf("Attach console\n"); bret = AttachConsole(pid); if (!bret) return GetLastError(); printf("Set console ctrl handler\n"); SetConsoleCtrlHandler(NULL, TRUE); printf("Send event\n"); bret = GenerateConsoleCtrlEvent( ctrlbreak ? CTRL_BREAK_EVENT : CTRL_C_EVENT, 0); if (!bret) return GetLastError(); printf("Done\n"); return 0; } ps/src/posix.c0000644000176200001440000002367013737276560013021 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #ifdef PS__SUNOS10 #include "arch/solaris/v10/ifaddrs.h" #elif PS__AIX #include "arch/aix/ifaddrs.h" #else #include #endif #if defined(PS__LINUX) #include #include #include #elif defined(PS__BSD) || defined(PS__MACOS) #include #include #include #include #include #include #elif defined(PS__SUNOS) #include #include #elif defined(PS__AIX) #include #endif /* * Check if PID exists. Return values: * 1: exists * 0: does not exist * -1: error (exception is set) */ int ps__pid_exists(long pid) { int ret; // No negative PID exists, plus -1 is an alias for sending signal // too all processes except system ones. Not what we want. if (pid < 0) return 0; // As per "man 2 kill" PID 0 is an alias for sending the signal to // every process in the process group of the calling process. // Not what we want. Some platforms have PID 0, some do not. // We decide that at runtime. if (pid == 0) { #if defined(PS__LINUX) || defined(PS__FREEBSD) return 0; #else return 1; #endif } #if defined(PS__MACOS) ret = kill((pid_t)pid , 0); #else ret = kill(pid , 0); #endif if (ret == 0) return 1; else { if (errno == ESRCH) { // ESRCH == No such process return 0; } else if (errno == EPERM) { // EPERM clearly indicates there's a process to deny // access to. return 1; } else { // According to "man 2 kill" possible error values are // (EINVAL, EPERM, ESRCH) therefore we should never get // here. If we do let's be explicit in considering this // an error. ps__set_error_from_errno(); return -1; } } } SEXP psp__pid_exists(SEXP r_pid) { return ScalarLogical(ps__pid_exists(INTEGER(r_pid)[0])); } /* * Utility used for those syscalls which do not return a meaningful * error that we can translate into an exception which makes sense. * As such, we'll have to guess. * On UNIX, if errno is set, we return that one (OSError). * Else, if PID does not exist we assume the syscall failed because * of that so we raise NoSuchProcess. * If none of this is true we giveup and raise RuntimeError(msg). * This will always set an exception and return NULL. */ int ps__raise_for_pid(long pid, char *syscall_name) { // Set exception to AccessDenied if pid exists else NoSuchProcess. if (errno != 0) { // Unlikely we get here. ps__set_error_from_errno(); return 0; } else if (ps__pid_exists(pid) == 0) { ps__debug("%s syscall failed and PID %i no longer exists; " "assume NoSuchProcess", syscall_name, pid); ps__no_such_process(pid, 0); } else { ps__set_error("%s syscall failed", syscall_name); } return 0; } SEXP ps__get_pw_uid(SEXP r_uid) { struct passwd *pwd; errno = 0; pwd = getpwuid(INTEGER(r_uid)[0]); if (pwd == NULL) { ps__set_error_from_errno(); ps__throw_error(); } return ps__build_named_list( "ssiiss", "pw_name", pwd->pw_name, "pw_passwd", pwd->pw_passwd, "pw_uid", pwd->pw_uid, "pw_gid", pwd->pw_gid, "pw_dir", pwd->pw_dir, "pw_shell", pwd->pw_shell); } SEXP psp__stat_st_rdev(SEXP files) { size_t i, len = LENGTH(files); struct stat buf; SEXP result; int ret; PROTECT(result = allocVector(INTSXP, len)); for (i = 0; i < len; i++) { ret = stat(CHAR(STRING_ELT(files, i)), &buf); if (ret == -1) { if (errno == ENOENT) { INTEGER(result)[i] = 0; } else { ps__set_error_from_errno(); ps__throw_error(); } } else { INTEGER(result)[i] = (int) buf.st_rdev; } } UNPROTECT(1); return result; } pid_t ps__zombie() { pid_t child_pid; child_pid = fork(); if (child_pid <= 0) raise(SIGKILL); return child_pid; } SEXP psp__zombie() { return ScalarInteger((int) ps__zombie()); } int ps__waitpid(pid_t pid) { int wp, wstat; do { wp = waitpid(pid, &wstat, WNOHANG); } while (wp == -1 && errno == EINTR); if (wp == pid) { /* Get exit status */ if (WIFEXITED(wstat)) { return WEXITSTATUS(wstat); } else { return - WTERMSIG(wstat); } } else if (wp == -1 && errno == ECHILD) { return NA_INTEGER; } else { ps__set_error_from_errno(); ps__throw_error(); } return 0; } SEXP psp__waitpid(SEXP r_pid) { pid_t pid = INTEGER(r_pid)[0]; return ScalarInteger(ps__waitpid(pid)); } SEXP ps__define_signals() { SEXP signalenv = PROTECT(Rf_allocSExp(ENVSXP)); #define PS_ADD_SIGNAL(sig) \ defineVar(install(#sig), PROTECT(ScalarInteger(sig)), signalenv); \ UNPROTECT(1) /* macOS */ #ifdef SIGHUP PS_ADD_SIGNAL(SIGHUP); #endif #ifdef SIGINT PS_ADD_SIGNAL(SIGINT); #endif #ifdef SIGQUIT PS_ADD_SIGNAL(SIGQUIT); #endif #ifdef SIGILL PS_ADD_SIGNAL(SIGILL); #endif #ifdef SIGTRAP PS_ADD_SIGNAL(SIGTRAP); #endif #ifdef SIGABRT PS_ADD_SIGNAL(SIGABRT); #endif #ifdef SIGEMT PS_ADD_SIGNAL(SIGEMT); #endif #ifdef SIGFPE PS_ADD_SIGNAL(SIGFPE); #endif #ifdef SIGKILL PS_ADD_SIGNAL(SIGKILL); #endif #ifdef SIGBUS PS_ADD_SIGNAL(SIGBUS); #endif #ifdef SIGSEGV PS_ADD_SIGNAL(SIGSEGV); #endif #ifdef SIGSYS PS_ADD_SIGNAL(SIGSYS); #endif #ifdef SIGPIPE PS_ADD_SIGNAL(SIGPIPE); #endif #ifdef SIGALRM PS_ADD_SIGNAL(SIGALRM); #endif #ifdef SIGTERM PS_ADD_SIGNAL(SIGTERM); #endif #ifdef SIGURG PS_ADD_SIGNAL(SIGURG); #endif #ifdef SIGSTOP PS_ADD_SIGNAL(SIGSTOP); #endif #ifdef SIGTSTP PS_ADD_SIGNAL(SIGTSTP); #endif #ifdef SIGCONT PS_ADD_SIGNAL(SIGCONT); #endif #ifdef SIGCHLD PS_ADD_SIGNAL(SIGCHLD); #endif #ifdef SIGTTIN PS_ADD_SIGNAL(SIGTTIN); #endif #ifdef SIGTTOU PS_ADD_SIGNAL(SIGTTOU); #endif #ifdef SIGIO PS_ADD_SIGNAL(SIGIO); #endif #ifdef SIGXCPU PS_ADD_SIGNAL(SIGXCPU); #endif #ifdef SIGXFSZ PS_ADD_SIGNAL(SIGXFSZ); #endif #ifdef SIGVTALRM PS_ADD_SIGNAL(SIGVTALRM); #endif #ifdef SIGPROF PS_ADD_SIGNAL(SIGPROF); #endif #ifdef SIGWINCH PS_ADD_SIGNAL(SIGWINCH); #endif #ifdef SIGINFO PS_ADD_SIGNAL(SIGINFO); #endif #ifdef SIGUSR1 PS_ADD_SIGNAL(SIGUSR1); #endif #ifdef SIGUSR2 PS_ADD_SIGNAL(SIGUSR2); #endif /* Linux */ #ifdef SIGPOLL PS_ADD_SIGNAL(SIGPOLL); #endif #ifdef SIGIOT PS_ADD_SIGNAL(SIGIOT); #endif #ifdef SIGSTKFLT PS_ADD_SIGNAL(SIGSTKFLT); #endif #ifdef SIGCLD PS_ADD_SIGNAL(SIGCLD); #endif #ifdef SIGPWR PS_ADD_SIGNAL(SIGPWR); #endif #ifdef SIGLOST PS_ADD_SIGNAL(SIGLOST); #endif #ifdef SIGUNUSED PS_ADD_SIGNAL(SIGUNUSED); #endif #undef PS_ADD_SIGNAL UNPROTECT(1); return signalenv; } SEXP ps__define_socket_address_families() { SEXP afenv = PROTECT(Rf_allocSExp(ENVSXP)); #define PS_ADD_AF(af) \ defineVar(install(#af), PROTECT(ScalarInteger(af)), afenv); \ UNPROTECT(1) #ifdef AF_UNSPEC PS_ADD_AF(AF_UNSPEC); #endif #ifdef AF_INET PS_ADD_AF(AF_INET); #endif #if defined(AF_UNIX) PS_ADD_AF(AF_UNIX); #endif #ifdef AF_AX25 /* Amateur Radio AX.25 */ PS_ADD_AF(AF_AX25); #endif #ifdef AF_IPX PS_ADD_AF(AF_IPX); /* Novell IPX */ #endif #ifdef AF_APPLETALK /* Appletalk DDP */ PS_ADD_AF(AF_APPLETALK); #endif #ifdef AF_NETROM /* Amateur radio NetROM */ PS_ADD_AF(AF_NETROM); #endif #ifdef AF_BRIDGE /* Multiprotocol bridge */ PS_ADD_AF(AF_BRIDGE); #endif #ifdef AF_ATMPVC /* ATM PVCs */ PS_ADD_AF(AF_ATMPVC); #endif #ifdef AF_AAL5 /* Reserved for Werner's ATM */ PS_ADD_AF(AF_AAL5); #endif #ifdef HAVE_SOCKADDR_ALG PS_ADD_AF(AF_ALG); /* Linux crypto */ #endif #ifdef AF_X25 /* Reserved for X.25 project */ PS_ADD_AF(AF_X25); #endif #ifdef AF_INET6 PS_ADD_AF(AF_INET6); /* IP version 6 */ #endif #ifdef AF_ROSE /* Amateur Radio X.25 PLP */ PS_ADD_AF(AF_ROSE); #endif #ifdef AF_DECnet /* Reserved for DECnet project */ PS_ADD_AF(AF_DECnet); #endif #ifdef AF_NETBEUI /* Reserved for 802.2LLC project */ PS_ADD_AF(AF_NETBEUI); #endif #ifdef AF_SECURITY /* Security callback pseudo AF */ PS_ADD_AF(AF_SECURITY); #endif #ifdef AF_KEY /* PF_KEY key management API */ PS_ADD_AF(AF_KEY); #endif #ifdef AF_NETLINK PS_ADD_AF(AF_NETLINK); #endif /* AF_NETLINK */ #ifdef AF_VSOCK PS_ADD_AF(AF_VSOCK); #endif #ifdef AF_ROUTE /* Alias to emulate 4.4BSD */ PS_ADD_AF(AF_ROUTE); #endif #ifdef AF_LINK PS_ADD_AF(AF_LINK); #endif #ifdef AF_ASH /* Ash */ PS_ADD_AF(AF_ASH); #endif #ifdef AF_ECONET /* Acorn Econet */ PS_ADD_AF(AF_ECONET); #endif #ifdef AF_ATMSVC /* ATM SVCs */ PS_ADD_AF(AF_ATMSVC); #endif #ifdef AF_SNA /* Linux SNA Project (nutters!) */ PS_ADD_AF(AF_SNA); #endif #ifdef AF_IRDA /* IRDA sockets */ PS_ADD_AF(AF_IRDA); #endif #ifdef AF_PPPOX /* PPPoX sockets */ PS_ADD_AF(AF_PPPOX); #endif #ifdef AF_WANPIPE /* Wanpipe API Sockets */ PS_ADD_AF(AF_WANPIPE); #endif #ifdef AF_LLC /* Linux LLC */ PS_ADD_AF(AF_LLC); #endif #ifdef USE_BLUETOOTH PS_ADD_AF(AF_BLUETOOTH); #endif #ifdef AF_CAN /* Controller Area Network */ PS_ADD_AF(AF_CAN); #endif /* Reliable Datagram Sockets */ #ifdef AF_RDS PS_ADD_AF(AF_RDS); #endif #ifdef AF_SYSTEM PS_ADD_AF(AF_SYSTEM); #endif #ifdef AF_PACKET PS_ADD_AF(AF_PACKET); #endif UNPROTECT(1); return afenv; } SEXP ps__define_socket_types() { SEXP env = PROTECT(Rf_allocSExp(ENVSXP)); #define PS_ADD_SOCKET_TYPE(type) \ defineVar(install(#type), PROTECT(ScalarInteger(type)), env); \ UNPROTECT(1) PS_ADD_SOCKET_TYPE(SOCK_STREAM); PS_ADD_SOCKET_TYPE(SOCK_DGRAM); #ifdef SOCK_RAW PS_ADD_SOCKET_TYPE(SOCK_RAW); #endif PS_ADD_SOCKET_TYPE(SOCK_SEQPACKET); #if defined(SOCK_RDM) PS_ADD_SOCKET_TYPE(SOCK_RDM); #endif #ifdef SOCK_CLOEXEC PS_ADD_SOCKET_TYPE(SOCK_CLOEXEC); #endif #ifdef SOCK_NONBLOCK PS_ADD_SOCKET_TYPE(SOCK_NONBLOCK); #endif UNPROTECT(1); return env; } ps/src/api-posix.c0000644000176200001440000001006213737120534013544 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include #include #include #include #include #include #include "ps-internal.h" SEXP psll_send_signal(SEXP p, SEXP sig) { ps_handle_t *handle = R_ExternalPtrAddr(p); int csig = INTEGER(sig)[0]; int ret; SEXP running; if (!handle) error("Process pointer cleaned up already"); if (handle->pid == 0) { error("preventing sending signal to process with PID 0 as it " "would affect every process in the process group of the " "calling process (Sys.getpid()) instead of PID 0"); } running = psll_is_running(p); if (!LOGICAL(running)[0]) { ps__no_such_process(handle->pid, 0); ps__throw_error(); } /* TODO: this is still a race here. We would need to SIGSTOP the process first, then check the timestamp, and then send the signal (if not SIGSTOP), or send a SIGCONT. */ ret = kill(handle->pid, csig); if (ret == -1) { if (errno == ESRCH) { ps__no_such_process(handle->pid, 0); } else if (errno == EPERM || errno == EACCES) { ps__access_denied(""); } else { ps__set_error_from_errno(); } ps__throw_error(); } return R_NilValue; } SEXP psll_suspend(SEXP p) { SEXP res, s; PROTECT(s = ScalarInteger(SIGSTOP)); PROTECT(res = psll_send_signal(p, s)); UNPROTECT(2); return res; } SEXP psll_resume(SEXP p) { SEXP res, s; PROTECT(s = ScalarInteger(SIGCONT)); PROTECT(res = psll_send_signal(p, s)); UNPROTECT(2); return res; } SEXP psll_terminate(SEXP p) { SEXP res, s; PROTECT(s = ScalarInteger(SIGTERM)); PROTECT(res = psll_send_signal(p, s)); UNPROTECT(2); return res; } SEXP psll_kill(SEXP p) { SEXP res, s; PROTECT(s = ScalarInteger(SIGKILL)); PROTECT(res = psll_send_signal(p, s)); UNPROTECT(2); return res; } SEXP psll_interrupt(SEXP p, SEXP ctrlc, SEXP interrupt_path) { SEXP res, s; PROTECT(s = ScalarInteger(SIGINT)); PROTECT(res = psll_send_signal(p, s)); UNPROTECT(2); return res; } SEXP ps__tty_size() { struct winsize w; int err = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); if (err == -1) { ps__set_error_from_errno(); ps__throw_error(); } SEXP result = Rf_allocVector(INTSXP, 2); INTEGER(result)[0] = w.ws_col; INTEGER(result)[1] = w.ws_row; return result; } SEXP ps__disk_usage(SEXP paths) { struct statvfs stat; int i, n = Rf_length(paths); SEXP result = PROTECT(allocVector(VECSXP, n)); for (i = 0; i < n; i++) { const char *cpath = CHAR(STRING_ELT(paths, i)); int ret = statvfs(cpath, &stat); if (ret == -1) { ps__set_error_from_errno(); ps__throw_error(); } SET_VECTOR_ELT( result, i, ps__build_list("idddddd", (int) stat.f_frsize, (double) stat.f_files, (double) stat.f_favail, (double) stat.f_ffree, (double) stat.f_blocks, (double) stat.f_bavail, (double) stat.f_bfree)); } UNPROTECT(1); return result; } SEXP psll_get_nice(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); pid_t pid; int priority; errno = 0; if (!handle) error("Process pointer cleaned up already"); pid = handle->pid; #ifdef PS__MACOS priority = getpriority(PRIO_PROCESS, (id_t)pid); #else priority = getpriority(PRIO_PROCESS, pid); #endif if (errno != 0) { ps__check_for_zombie(handle, 1); ps__set_error_from_errno(); ps__throw_error(); } else { ps__check_for_zombie(handle, 0); } return ScalarInteger(priority); } SEXP psll_set_nice(SEXP p, SEXP value) { ps_handle_t *handle = R_ExternalPtrAddr(p); pid_t pid; int priority = INTEGER(value)[0]; int retval; if (!handle) error("Process pointer cleaned up already"); pid = handle->pid; #ifdef PSUTIL_OSX retval = setpriority(PRIO_PROCESS, (id_t)pid, priority); #else retval = setpriority(PRIO_PROCESS, pid, priority); #endif if (retval == -1) { ps__check_for_zombie(handle, 1); ps__set_error_from_errno(); ps__throw_error(); } else { ps__check_for_zombie(handle, 0); } return R_NilValue; } ps/src/linux.c0000644000176200001440000000546613737276560013021 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "posix.h" int ps__read_file(const char *path, char **buffer, size_t buffer_size) { int fd = -1; ssize_t ret; char *ptr; size_t rem_size = buffer_size; *buffer = 0; fd = open(path, O_RDONLY); if (fd == -1) goto error; ptr = *buffer = R_alloc(buffer_size, 1); if (!*buffer) goto error; do { if (rem_size == 0) { *buffer = S_realloc(*buffer, buffer_size * 2, buffer_size, 1); if (!*buffer) goto error; ptr = *buffer + buffer_size; rem_size = buffer_size; buffer_size *= 2; } ret = read(fd, ptr, rem_size); if (ret == -1) goto error; ptr += ret; rem_size -= ret; } while (ret > 0); close(fd); return buffer_size - rem_size; error: if (fd >= 0) close(fd); *buffer = 0; return -1; } SEXP ps__inet_ntop(SEXP raw, SEXP fam) { char dst[INET6_ADDRSTRLEN]; int af = INTEGER(fam)[0]; const char *ret = inet_ntop(af, RAW(raw), dst, INET6_ADDRSTRLEN); if (!ret) { return R_NilValue; } else { return mkString(dst); } } SEXP ps__define_tcp_statuses() { SEXP result, names; PROTECT(result = ps__build_string("01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", NULL)); PROTECT(names = ps__build_string("CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT_1", "CONN_FIN_WAIT_2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", "PS__CONN_NONE", NULL)); setAttrib(result, R_NamesSymbol, names); UNPROTECT(2); return result; } SEXP ps__init(SEXP psenv, SEXP constenv) { SEXP sig, err, tcp, af, st; /* Signals */ PROTECT(sig = ps__define_signals()); defineVar(install("signals"), sig, constenv); /* errno values */ PROTECT(err = ps__define_errno()); defineVar(install("errno"), err, constenv); /* Connection statuses */ PROTECT(tcp = ps__define_tcp_statuses()); defineVar(install("tcp_statuses"), tcp, constenv); /* Socket address families */ PROTECT(af = ps__define_socket_address_families()); defineVar(install("address_families"), af, constenv); /* Socket address families */ PROTECT(st = ps__define_socket_types()); defineVar(install("socket_types"), st, constenv); UNPROTECT(5); return R_NilValue; } ps/src/api-linux.c0000644000176200001440000007127014016767153013556 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" double psll_linux_boot_time = 0; double psll_linux_clock_period = 0; typedef struct { char state; int ppid, pgrp, session, tty_nr, tpgid; unsigned int flags; unsigned long minflt, cminflt, majflt, cmajflt, utime, stime; long int cutime, cstime, priority, nice, num_threads, itrealvalue; unsigned long long starttime; } psl_stat_t; #define PS__TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) #define PS__CHECK_STAT(stat, handle) \ do { \ double starttime = psll_linux_boot_time + \ ((double)(stat.starttime) * psll_linux_clock_period); \ double diff = starttime - (handle)->create_time; \ if (fabs(diff) > psll_linux_clock_period) { \ ps__no_such_process((handle)->pid, 0); \ ps__throw_error(); \ } \ } while (0) #define PS__CHECK_HANDLE(handle) \ do { \ psl_stat_t stat; \ if (psll__parse_stat_file(handle->pid, &stat, 0)) { \ ps__wrap_linux_error(handle); \ ps__throw_error(); \ } \ PS__CHECK_STAT(stat, handle); \ } while (0) #define PS__GET_STATUS(stat, result, error) \ switch(stat) { \ case 'R': result = mkString("running"); break; \ case 'S': result = mkString("sleeping"); break; \ case 'D': result = mkString("disk_sleep"); break; \ case 'T': result = mkString("stopped"); break; \ case 't': result = mkString("tracing_stop"); break; \ case 'Z': result = mkString("zombie"); break; \ case 'X': result = mkString("dead"); break; \ case 'x': result = mkString("dead"); break; \ case 'K': result = mkString("wake_kill"); break; \ case 'W': result = mkString("waking"); break; \ default: error; \ } int ps__read_file(const char *path, char **buffer, size_t buffer_size); static void *ps__memmem(const void *haystack, size_t n1, const void *needle, size_t n2) { const unsigned char *p1 = haystack; const unsigned char *p2 = needle; const unsigned char *p3 = p1 + n1 - n2 + 1; const unsigned char *p; if (n2 == 0) return (void*)p1; if (n2 > n1) return NULL; for (p = p1; (p = memchr(p, *p2, p3 - p)) != NULL; p++) { if (!memcmp(p, p2, n2)) return (void*)p; } return NULL; } void psll_finalizer(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); if (handle) free(handle); } void ps__wrap_linux_error(ps_handle_t *handle) { char path[512]; int ret; if (errno == ENOENT || errno == ESRCH) { /* no such file error; might be raised also if the */ /* path actually exists for system processes with */ /* low pids (about 0-20) */ struct stat st; snprintf(path, sizeof(path), "/proc/%i", handle->pid); ret = lstat(path, &st); if (!ret) { /* process exists, other error */ ps__set_error_from_errno(); } else if (errno == ENOENT) { ps__no_such_process(handle->pid, 0); } else if (errno == EPERM || errno == EACCES) { ps__access_denied(""); } else { ps__set_error_from_errno(); } } else { ps__set_error_from_errno(); } ps__throw_error(); } int psll__readlink(const char *path, char **linkname) { size_t size = 1024; ssize_t r; char *dpos; *linkname = R_alloc(size, 1); while (1) { r = readlink(path, *linkname, size - 1); if (r == (ssize_t) -1) { return -1; } else if (r < (ssize_t)1) { errno = ENOENT; return -1; } else if (r < (ssize_t)(size - 1)) { break; } *linkname = S_realloc(*linkname, size + 1024, size, 1); size += 1024; } (*linkname)[r] = '\0'; /* readlink() might return paths containing null bytes ('\x00') resulting in "TypeError: must be encoded string without NULL bytes, not str" errors when the string is passed to other fs-related functions (os.*, open(), ...). Apparently everything after '\x00' is garbage (we can have ' (deleted)', 'new' and possibly others), see: https://github.com/giampaolo/psutil/issues/717 For us this is not a problem, because mkString uses the string up to the first zero byte, anyway. Certain paths have ' (deleted)' appended. Usually this is bogus as the file actually exists. Even if it doesn't we don't care. */ if ((dpos = strstr(*linkname, " (deleted)")) != NULL && !strcmp(dpos, " (deleted)")) { struct stat st; int ret = stat(*linkname, &st); if (!ret) *dpos = '\0'; } return 0; } int psll__parse_stat_file(long pid, psl_stat_t *stat, char **name) { char path[512]; int ret; char *buf; char *l, *r; ret = snprintf(path, sizeof(path), "/proc/%ld/stat", pid); if (ret >= sizeof(path)) { ps__set_error("Cannot read proc, path buffer too small"); return -1; } else if (ret < 0) { return -1; } ret = ps__read_file(path, &buf, /* buffer= */ 2048); if (ret == -1) return -1; /* This removes the last character, but that's a \n anyway. At least we have a zero terminated string... */ *(buf + ret - 1) = '\0'; /* Find the first '(' and last ')', that's the end of the command */ l = strchr(buf, '('); r = strrchr(buf, ')'); if (!l || !r) { ps__set_error("Cannot parse stat file"); ps__throw_error(); } *r = '\0'; if (name) *name = l + 1; ret = sscanf(r+2, "%c %d %d %d %d %d %u %lu %lu %lu %lu %lu %lu %ld %ld %ld %ld %ld %ld %llu", &stat->state, &stat->ppid, &stat->pgrp, &stat->session, &stat->tty_nr, &stat->tpgid, &stat->flags, &stat->minflt, &stat->cminflt, &stat->majflt, &stat->cmajflt, &stat->utime, &stat->stime, &stat->cutime, &stat->cstime, &stat->priority, &stat->nice, &stat->num_threads, &stat->itrealvalue, &stat->starttime); if (ret == -1) { return -1; } else if (ret != 20) { ps__set_error("Cannot parse stat file, parsed: %i/20 fields", ret); return -1; } return 0; } void ps__check_for_zombie(ps_handle_t *handle, int err) { psl_stat_t stat; double diff; if (!handle) error("Process pointer cleaned up already"); if (psll__parse_stat_file(handle->pid, &stat, 0)) { ps__wrap_linux_error(handle); ps__throw_error(); } diff = (psll_linux_boot_time + ((double)stat.starttime * psll_linux_clock_period)) - handle->create_time; if (fabs(diff) > psll_linux_clock_period) { ps__no_such_process(handle->pid, 0); err = 1; } else if (stat.state == 'Z') { ps__zombie_process(handle->pid); err = 1; } else { ps__set_error_from_errno(); } if (err) ps__throw_error(); } int psll_linux_get_boot_time() { int ret; char *buf; char *needle = "\nbtime "; size_t needle_len = strlen(needle); char *hit; unsigned long btime; ret = ps__read_file("/proc/stat", &buf, /* buffer= */ 2048); if (ret == -1) return -1; *(buf + ret - 1) = '\0'; hit = ps__memmem(buf, ret, needle, needle_len); if (!hit) return -1; ret = sscanf(hit + needle_len, "%lu", &btime); if (ret != 1) return -1; psll_linux_boot_time = (double) btime; return 0; } int psll_linux_get_clock_period(void) { double psll_linux_clock_ticks = sysconf(_SC_CLK_TCK); if (psll_linux_clock_ticks == -1) { ps__set_error_from_errno(); return -1; } psll_linux_clock_period = 1.0 / psll_linux_clock_ticks; return 0; } int psll_linux_ctime(long pid, double *ctime) { psl_stat_t stat; int ret = psll__parse_stat_file(pid, &stat, 0); if (ret) return ret; if (!psll_linux_boot_time) { ret = psll_linux_get_boot_time(); if (ret) return ret; } if (!psll_linux_clock_period) { ret = psll_linux_get_clock_period(); if (ret) { ps__throw_error(); } } *ctime = psll_linux_boot_time + ((double)stat.starttime * psll_linux_clock_period); return 0; } SEXP psll_handle(SEXP pid, SEXP time) { pid_t cpid = isNull(pid) ? getpid() : INTEGER(pid)[0]; double ctime; ps_handle_t *handle; SEXP res; if (!isNull(time)) { ctime = REAL(time)[0]; } else { if (psll_linux_ctime(cpid, &ctime)) ps__throw_error(); } handle = malloc(sizeof(ps_handle_t)); if (!handle) { ps__no_memory(""); ps__throw_error(); } handle->pid = cpid; handle->create_time = ctime; handle->gone = 0; PROTECT(res = R_MakeExternalPtr(handle, R_NilValue, R_NilValue)); R_RegisterCFinalizerEx(res, psll_finalizer, /* onexit */ 0); setAttrib(res, R_ClassSymbol, mkString("ps_handle")); UNPROTECT(1); return res; } SEXP psll_format(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); psl_stat_t stat; SEXP name, status, result; char *cname; if (!handle) error("Process pointer cleaned up already"); if (psll__parse_stat_file(handle->pid, &stat, &cname)) { PROTECT(name = mkString("???")); PROTECT(status = mkString("terminated")); } else { PROTECT(name = ps__str_to_utf8(cname)); PS__GET_STATUS(stat.state, status, status = mkString("unknown")); PROTECT(status); } PROTECT(result = ps__build_list("OldO", name, (long) handle->pid, handle->create_time, status)); /* We do not check that the pid is still valid here, because we want to be able to format & print processes that have finished already. */ UNPROTECT(3); return result; } SEXP psll_parent(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); psl_stat_t stat; SEXP ppid, parent; if (!handle) error("Process pointer cleaned up already"); if (psll__parse_stat_file(handle->pid, &stat, 0)) { ps__wrap_linux_error(handle); ps__throw_error(); } PS__CHECK_STAT(stat, handle); /* TODO: this is a race condition, because the parent process might have just quit, so psll_handle() might fail. If this happens, then we should try to query the ppid again. */ PROTECT(ppid = ScalarInteger(stat.ppid)); PROTECT(parent = psll_handle(ppid, R_NilValue)); UNPROTECT(2); return parent; } SEXP psll_ppid(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); psl_stat_t stat; if (!handle) error("Process pointer cleaned up already"); if (psll__parse_stat_file(handle->pid, &stat, 0)) { ps__wrap_linux_error(handle); ps__throw_error(); } PS__CHECK_STAT(stat, handle); return ScalarInteger(stat.ppid); } SEXP psll_is_running(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); double ctime; int ret; if (!handle) error("Process pointer cleaned up already"); ret = psll_linux_ctime(handle->pid, &ctime); if (ret) return ScalarLogical(0); return ScalarLogical(ctime == handle->create_time); } SEXP psll_name(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); psl_stat_t stat; char *name; if (!handle) error("Process pointer cleaned up already"); if (psll__parse_stat_file(handle->pid, &stat, &name)) { ps__wrap_linux_error(handle); ps__throw_error(); } PS__CHECK_STAT(stat, handle); return ps__str_to_utf8(name); } SEXP psll_exe(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); char path[512]; int ret; char *linkname; if (!handle) error("Process pointer cleaned up already"); ret = snprintf(path, sizeof(path), "/proc/%i/exe", handle->pid); if (ret < 0) ps__throw_error(); ret = psll__readlink(path, &linkname); if (ret) { if (errno == ENOENT || errno == ESRCH) { /* no such file error; might be raised also if the */ /* path actually exists for system processes with */ /* low pids (about 0-20) */ struct stat st; snprintf(path, sizeof(path), "/proc/%i", handle->pid); ret = lstat(path, &st); if (!ret) { /* process exists, but can't get exe */ ps__check_for_zombie(handle, 0); return ScalarString(NA_STRING); } else if (errno == ENOENT) { ps__no_such_process(handle->pid, 0); ps__throw_error(); } } ps__check_for_zombie(handle, 1); } PS__CHECK_HANDLE(handle); return ps__str_to_utf8(linkname); } SEXP psll_cmdline(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); char path[512]; int ret; char *buf, *ptr, *end, *prev; char sep = '\0'; int nstr = 0; SEXP result; if (!handle) error("Process pointer cleaned up already"); ret = snprintf(path, sizeof(path), "/proc/%d/cmdline", handle->pid); if (ret >= sizeof(path)) { ps__set_error("Cannot read proc, path buffer too small"); ps__throw_error(); } else if (ret < 0) { ps__set_error_from_errno(); ps__throw_error(); } ret = ps__read_file(path, &buf, 1024); if (ret <= 0) { ps__check_for_zombie(handle, 1); } PS__CHECK_HANDLE(handle); /* 'man proc' states that args are separated by null bytes '\0' */ /* and last char is supposed to be a null byte. Nevertheless */ /* some processes may change their cmdline after being started */ /* (via setproctitle() or similar), they are usually not */ /* compliant with this rule and use spaces instead. Google */ /* Chrome process is an example. See: */ /* https://github.com/giampaolo/psutil/issues/1179 */ if (buf[ret - 1] != '\0') sep = ' '; /* Count number of vars first, then convert to strings */ for (ptr = buf, end = buf + ret; ptr < end; ptr++) { if (*ptr == sep) nstr++; } PROTECT(result = allocVector(STRSXP, nstr)); for (ptr = prev = buf, nstr = 0; ptr < end; ptr++) { if (!*ptr) { SET_STRING_ELT(result, nstr++, mkCharLen(prev, ptr - prev)); prev = ptr + 1; } } UNPROTECT(1); return result; } SEXP psll_status(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); psl_stat_t stat; SEXP result; if (!handle) error("Process pointer cleaned up already"); if (psll__parse_stat_file(handle->pid, &stat, 0)) { ps__wrap_linux_error(handle); ps__throw_error(); } PS__CHECK_STAT(stat, handle); PS__GET_STATUS(stat.state, result, error("Unknown process status")); return result; } SEXP psll_username(SEXP p) { SEXP ids, ruid, pw, result; PROTECT(ids = psll_uids(p)); PROTECT(ruid = ScalarInteger(INTEGER(ids)[0])); PROTECT(pw = ps__get_pw_uid(ruid)); PROTECT(result = VECTOR_ELT(pw, 0)); UNPROTECT(4); return result; } SEXP psll_cwd(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); char path[512]; int ret; char *linkname; if (!handle) error("Process pointer cleaned up already"); ret = snprintf(path, sizeof(path), "/proc/%d/cwd", handle->pid); if (ret >= sizeof(path)) { ps__set_error("Cannot read proc, path buffer too small"); ps__throw_error(); } else if (ret < 0) { ps__set_error_from_errno(); ps__throw_error(); } ret = psll__readlink(path, &linkname); if (ret) { ps__check_for_zombie(handle, 1); } PS__CHECK_HANDLE(handle); return ps__str_to_utf8(linkname); } SEXP psll__ids(SEXP p, const char *needle) { ps_handle_t *handle = R_ExternalPtrAddr(p); char path[512]; int ret; char *buf; size_t needle_len = strlen(needle); char *hit; unsigned long real, eff, saved; SEXP result, names; if (!handle) error("Process pointer cleaned up already"); ret = snprintf(path, sizeof(path), "/proc/%i/status", handle->pid); if (ret >= sizeof(path)) { ps__set_error("Cannot read proc, path buffer too small"); ps__throw_error(); } else if (ret < 0) { ps__set_error_from_errno(); ps__throw_error(); } ret = ps__read_file(path, &buf, /* buffer= */ 2048); if (ret == -1) ps__check_for_zombie(handle, 1); *(buf + ret - 1) = '\0'; hit = ps__memmem(buf, ret, needle, needle_len); if (!hit) { ps__set_error("Cannot read process status file"); ps__throw_error(); } ret = sscanf(hit + needle_len, " %lu %lu %lu", &real, &eff, &saved); if (ret != 3) { ps__set_error("Cannot read process status file"); ps__throw_error(); } PS__CHECK_HANDLE(handle); PROTECT(result = allocVector(INTSXP, 3)); INTEGER(result)[0] = real; INTEGER(result)[1] = eff; INTEGER(result)[2] = saved; PROTECT(names = ps__build_string("real", "effective", "saved", NULL)); setAttrib(result, R_NamesSymbol, names); UNPROTECT(2); return result; } SEXP psll_uids(SEXP p) { return psll__ids(p, "\nUid:"); } SEXP psll_gids(SEXP p) { return psll__ids(p, "\nGid:"); } SEXP psll_terminal(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); psl_stat_t stat; if (!handle) error("Process pointer cleaned up already"); if (psll__parse_stat_file(handle->pid, &stat, 0)) { ps__wrap_linux_error(handle); ps__throw_error(); } PS__CHECK_STAT(stat, handle); if (stat.tty_nr == 0) { return ScalarInteger(NA_INTEGER); } else { return ScalarInteger(stat.tty_nr); } } SEXP psll_environ(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); char path[512]; int ret; char *buf, *ptr, *end, *prev; SEXP result = R_NilValue; int nstr = 0; if (!handle) error("Process pointer cleaned up already"); ret = snprintf(path, sizeof(path), "/proc/%d/environ", handle->pid); if (ret >= sizeof(path)) { ps__set_error("Cannot read proc, path buffer too small"); ps__throw_error(); } else if (ret < 0) { ps__set_error_from_errno(); ps__throw_error(); } ret = ps__read_file(path, &buf, /* buffer= */ 1024 * 32); if (ret <= 0) { ps__check_for_zombie(handle, 1); } else { PS__CHECK_HANDLE(handle); } *(buf + ret - 1) = '\0'; /* Count number of vars first, then convert to strings */ for (ptr = buf, end = buf + ret; ptr < end; ptr++) if (!*ptr) nstr++; PROTECT(result = allocVector(STRSXP, nstr)); for (ptr = prev = buf, nstr = 0; ptr < end; ptr++) { if (!*ptr) { SET_STRING_ELT(result, nstr++, mkCharLen(prev, ptr - prev)); prev = ptr + 1; } } UNPROTECT(1); return result; } SEXP psll_num_threads(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); psl_stat_t stat; int ret; if (!handle) error("Process pointer cleaned up already"); ret = psll__parse_stat_file(handle->pid, &stat, 0); ps__check_for_zombie(handle, ret < 0); PS__CHECK_STAT(stat, handle); return ScalarInteger(stat.num_threads); } SEXP psll_cpu_times(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); psl_stat_t stat; SEXP result, names; int ret; if (!handle) error("Process pointer cleaned up already"); ret = psll__parse_stat_file(handle->pid, &stat, 0); ps__check_for_zombie(handle, ret < 0); PS__CHECK_STAT(stat, handle); PROTECT(result = allocVector(REALSXP, 4)); REAL(result)[0] = stat.utime * psll_linux_clock_period; REAL(result)[1] = stat.stime * psll_linux_clock_period; REAL(result)[2] = stat.cutime * psll_linux_clock_period; REAL(result)[3] = stat.cstime * psll_linux_clock_period; PROTECT(names = ps__build_string("user", "system", "children_user", "children_system", NULL)); setAttrib(result, R_NamesSymbol, names); UNPROTECT(2); return result; } SEXP psll_memory_info(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); unsigned long rss, vms, shared, text, lib, data, dirty; char path[512]; char *buf; int ret; SEXP result, names; if (!handle) error("Process pointer cleaned up already"); ret = snprintf(path, sizeof(path), "/proc/%d/statm", handle->pid); if (ret >= sizeof(path)) { ps__set_error("Cannot read statm, path buffer too small"); ps__throw_error(); } else if (ret < 0) { ps__set_error_from_errno(); ps__throw_error(); } ret = ps__read_file(path, &buf, /* buffer= */ 1024); ps__check_for_zombie(handle, ret <= 0); *(buf + ret - 1) = '\0'; ret = sscanf(buf, "%lu %lu %lu %lu %lu %lu %lu", &rss, &vms, &shared, &text, &lib, &data, &dirty); if (ret != 7) { ps__set_error_from_errno(); ps__throw_error(); } PS__CHECK_HANDLE(handle); PROTECT(result = allocVector(INTSXP, 7)); INTEGER(result)[0] = rss; INTEGER(result)[1] = vms; INTEGER(result)[2] = shared; INTEGER(result)[3] = text; INTEGER(result)[4] = lib; INTEGER(result)[5] = data; INTEGER(result)[6] = dirty; PROTECT(names = ps__build_string("rss", "vms", "shared", "text", "lib", "data", "dirty", NULL)); setAttrib(result, R_NamesSymbol, names); UNPROTECT(2); return result; } SEXP ps__boot_time() { if (psll_linux_boot_time == 0) { if (psll_linux_get_boot_time()) { ps__set_error_from_errno(); ps__throw_error(); } } return ScalarReal(psll_linux_boot_time); } SEXP ps__cpu_count_logical() { int n = sysconf(_SC_NPROCESSORS_ONLN); if (n >= 1) return ScalarInteger(n); return(ScalarInteger(NA_INTEGER)); } /* This is not used, because it is much easier to parse this file from R */ SEXP ps__cpu_count_physical() { return R_NilValue; } static int psl__linux_match_environ(SEXP r_marker, SEXP r_pid) { const char *marker = CHAR(STRING_ELT(r_marker, 0)); pid_t pid = INTEGER(r_pid)[0]; char path[512]; int ret; char *buf; ret = snprintf(path, sizeof(path), "/proc/%d/environ", (int) pid); if (ret >= sizeof(path)) { ps__set_error("Cannot read proc, path buffer too small"); return -1; } else if (ret < 0) { ps__set_error_from_errno(); return -1; } ret = ps__read_file(path, &buf, /* buffer= */ 1024 * 32); if (ret == -1) { ps__set_error_from_errno(); return -1; } return ps__memmem(buf, ret, marker, strlen(marker)) != NULL; } SEXP ps__kill_if_env(SEXP r_marker, SEXP r_after, SEXP r_pid, SEXP r_sig) { pid_t pid = INTEGER(r_pid)[0]; int sig = INTEGER(r_sig)[0]; int ret; int match; match = psl__linux_match_environ(r_marker, r_pid); if (match == -1) ps__throw_error(); if (match) { psl_stat_t stat; char *name; int stret = psll__parse_stat_file(pid, &stat, &name); ret = kill(pid, sig); if (ret == -1) { if (errno == ESRCH) { ps__no_such_process(pid, 0); } else if (errno == EPERM || errno == EACCES) { ps__access_denied(""); } else { ps__set_error_from_errno(); } ps__throw_error(); } if (stret != -1) { return ps__str_to_utf8(name); } else { return mkString("???"); } } return R_NilValue; } SEXP ps__find_if_env(SEXP r_marker, SEXP r_after, SEXP r_pid) { SEXP phandle; int match; ps_handle_t *handle; PROTECT(phandle = psll_handle(r_pid, R_NilValue)); handle = R_ExternalPtrAddr(phandle); match = psl__linux_match_environ(r_marker, r_pid); if (match == -1) ps__throw_error(); if (match) { PS__CHECK_HANDLE(handle); UNPROTECT(1); return phandle; } UNPROTECT(1); return R_NilValue; } SEXP psll_num_fds(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); char path[512]; DIR *dirs; int ret; int num = 0; if (!handle) error("Process pointer cleaned up already"); ret = snprintf(path, sizeof(path), "/proc/%i/fd", handle->pid); if (ret < 0) ps__throw_error(); dirs = opendir(path); if (!dirs) ps__check_for_zombie(handle, 1); do { errno = 0; struct dirent *entry = readdir(dirs); if (!entry) { closedir(dirs); if (!errno) break; ps__check_for_zombie(handle, 1); } if (strncmp(".", entry->d_name, 2) && strncmp("..", entry->d_name, 3)) num++; } while (1); /* OSX throws on zombies, so for consistency we do the same here*/ ps__check_for_zombie(handle, 0); PS__CHECK_HANDLE(handle); return ScalarInteger(num); } SEXP psll_open_files(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); char path[512]; DIR *dirs; int ret; int len = 10, num = 0; char *linkname; int fd, dfd; PROTECT_INDEX pidx; SEXP result; if (!handle) error("Process pointer cleaned up already"); ret = snprintf(path, sizeof(path), "/proc/%i/fd", handle->pid); if (ret < 0) ps__throw_error(); dirs = opendir(path); if (!dirs) ps__check_for_zombie(handle, 1); dfd = dirfd(dirs); PROTECT_WITH_INDEX(result = allocVector(VECSXP, len), &pidx); do { errno = 0; struct dirent *entry = readdir(dirs); if (entry == NULL) { closedir(dirs); if (errno == 0) break; ps__check_for_zombie(handle, 1); } if (!strncmp(".", entry->d_name, 2) || !strncmp("..", entry->d_name, 3)) continue; ret = snprintf(path, sizeof(path), "/proc/%i/fd/%s", handle->pid, entry->d_name); if (ret < 0) { closedir(dirs); ps__throw_error(); } ret = psll__readlink(path, &linkname); if (ret) { closedir(dirs); if (errno == ENOENT || errno == ESRCH || errno == EINVAL) continue; ps__check_for_zombie(handle, 1); } if (strncmp("socket:", linkname, 7) == 0) continue; fd = strtol(entry->d_name, NULL, 10); if (fd == dfd) continue; if (++num == len) { len *= 2; REPROTECT(result = Rf_lengthgets(result, len), pidx); } SET_VECTOR_ELT(result, num, ps__build_list("si", linkname, fd)); } while (1); /* OSX throws on zombies, so for consistency we do the same here*/ ps__check_for_zombie(handle, 0); PS__CHECK_HANDLE(handle); UNPROTECT(1); return result; } SEXP psll_connections(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); char path[512]; DIR *dirs; int ret; char *linkname; size_t l; SEXP result; PROTECT_INDEX pidx; int len = 10, num = 0; PROTECT_WITH_INDEX(result = allocVector(VECSXP, len), &pidx); if (!handle) error("Process pointer cleaned up already"); ret = snprintf(path, sizeof(path), "/proc/%d/fd", handle->pid); if (ret < 0) ps__throw_error(); dirs = opendir(path); if (!dirs) ps__check_for_zombie(handle, 1); do { errno = 0; struct dirent *entry = readdir(dirs); if (!entry) { closedir(dirs); if (!errno) break; ps__check_for_zombie(handle, 1); } if (!strncmp(".", entry->d_name, 2) || !strncmp("..", entry->d_name, 3)) continue; ret = snprintf(path, sizeof(path), "/proc/%i/fd/%s", handle->pid, entry->d_name); if (ret < 0) { closedir(dirs); ps__throw_error(); } ret = psll__readlink(path, &linkname); if (ret) { if (errno == ENOENT || errno == ESRCH || errno == EINVAL) continue; closedir(dirs); ps__check_for_zombie(handle, 1); } l = strlen(linkname); if (l < 10) continue; linkname[7] = '\0'; if (strcmp(linkname, "socket:")) continue; if (++num == len) { len *= 2; REPROTECT(result = Rf_lengthgets(result, len), pidx); } linkname[l - 1] = '\0'; SET_VECTOR_ELT( result, num, ps__build_list("ss", entry->d_name, linkname + 8)); } while (1); /* OSX throws on zombies, so for consistency we do the same here*/ ps__check_for_zombie(handle, 0); PS__CHECK_HANDLE(handle); UNPROTECT(1); return result; } SEXP ps__users() { struct utmp *ut; SEXP result; PROTECT_INDEX pidx; int len = 10, num = 0; PROTECT_WITH_INDEX(result = allocVector(VECSXP, len), &pidx); setutent(); while ((ut = getutent()) != NULL) { if (ut->ut_type != USER_PROCESS) continue; if (++num == len) { len *= 2; REPROTECT(result = Rf_lengthgets(result, len), pidx); } SET_VECTOR_ELT( result, num, ps__build_list("sssdi", ut->ut_user, ut->ut_line, ut->ut_host, (double) PS__TV2DOUBLE(ut->ut_tv), ut->ut_pid)); } endutent(); UNPROTECT(1); return result; } SEXP ps__disk_partitions(SEXP all) { FILE *file = NULL; struct mntent *entry; SEXP result; PROTECT_INDEX pidx; int len = 30, num = -1; file = setmntent("/etc/mtab", "r"); if ((file == 0) || (file == NULL)) { ps__set_error_from_errno(); goto error; } PROTECT_WITH_INDEX(result = allocVector(VECSXP, len), &pidx); while ((entry = getmntent(file))) { if (entry == NULL) { ps__set_error_from_errno(); goto error; } if (++num == len) { len *= 2; REPROTECT(result = Rf_lengthgets(result, len), pidx); } SET_VECTOR_ELT( result, num, ps__build_list("ssss", entry->mnt_fsname, entry->mnt_dir, entry->mnt_type, entry->mnt_opts)); } endmntent(file); UNPROTECT(1); return result; error: if (file != NULL) endmntent(file); ps__throw_error(); /* These are never called, but R CMD check and rchk cannot handle this */ error("nah"); return R_NilValue; } SEXP ps__loadavg(SEXP counter_name) { /* Try /proc first, if fails we try sysinfo() */ struct sysinfo info; char *buf; int ret = ps__read_file("/proc/loadavg", &buf, 128); if (ret == -1) goto sysinfo; SEXP avg = PROTECT(allocVector(REALSXP, 3)); if (sscanf(buf, "%lf %lf %lf", REAL(avg), REAL(avg) + 1, REAL(avg) + 2) == 3) { UNPROTECT(1); return avg; } sysinfo: if (sysinfo(&info) < 0) { ps__set_error_from_errno(); ps__throw_error(); } REAL(avg)[0] = (double) info.loads[0] / 65536.0; REAL(avg)[1] = (double) info.loads[1] / 65536.0; REAL(avg)[2] = (double) info.loads[2] / 65536.0; UNPROTECT(1); return avg; } SEXP ps__system_memory() { // This is implemented in R on Linux ps__throw_error(); return R_NilValue; } SEXP ps__system_swap() { // TODO return R_NilValue; } SEXP ps__system_cpu_times() { // This is implemented in R on Linux ps__throw_error(); return R_NilValue; } ps/src/macos.c0000644000176200001440000000733613737276560012762 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "posix.h" #include "arch/macos/process_info.h" #define PS__TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) /* * Return an integer vector of all the PIDs running on the system. */ SEXP ps__pids() { kinfo_proc *proclist = NULL; kinfo_proc *orig_address = NULL; size_t num_processes; size_t idx; SEXP retlist = R_NilValue; if (ps__get_proc_list(&proclist, &num_processes) != 0) { if (errno != 0) { ps__set_error_from_errno(); } else { ps__set_error("failed to retrieve process list"); } goto error; } retlist = PROTECT(allocVector(INTSXP, num_processes)); if (num_processes > 0) { // save the address of proclist so we can free it later orig_address = proclist; for (idx = 0; idx < num_processes; idx++) { INTEGER(retlist)[idx] = proclist->kp_proc.p_pid; proclist++; } free(orig_address); } UNPROTECT(1); return retlist; error: if (orig_address != NULL) free(orig_address); ps__throw_error(); return R_NilValue; } SEXP ps__define_tcp_statuses() { SEXP result, names; PROTECT(result = allocVector(INTSXP, 12)); PROTECT(names = allocVector(STRSXP, 12)); INTEGER(result)[0] = TCPS_CLOSED; SET_STRING_ELT(names, 0, mkChar("CONN_CLOSE")); INTEGER(result)[1] = TCPS_CLOSING; SET_STRING_ELT(names, 1, mkChar("CONN_CLOSING")); INTEGER(result)[2] = TCPS_CLOSE_WAIT; SET_STRING_ELT(names, 2, mkChar("CONN_CLOSE_WAIT")); INTEGER(result)[3] = TCPS_LISTEN; SET_STRING_ELT(names, 3, mkChar("CONN_LISTEN")); INTEGER(result)[4] = TCPS_ESTABLISHED; SET_STRING_ELT(names, 4, mkChar("CONN_ESTABLISHED")); INTEGER(result)[5] = TCPS_SYN_SENT; SET_STRING_ELT(names, 5, mkChar("CONN_SYN_SENT")); INTEGER(result)[6] = TCPS_SYN_RECEIVED; SET_STRING_ELT(names, 6, mkChar("CONN_SYN_RECV")); INTEGER(result)[7] = TCPS_FIN_WAIT_1; SET_STRING_ELT(names, 7, mkChar("CONN_FIN_WAIT_1")); INTEGER(result)[8] = TCPS_FIN_WAIT_2; SET_STRING_ELT(names, 8, mkChar("CONN_FIN_WAIT_2")); INTEGER(result)[9] = TCPS_LAST_ACK; SET_STRING_ELT(names, 9, mkChar("CONN_LAST_ACK")); INTEGER(result)[10] = TCPS_TIME_WAIT; SET_STRING_ELT(names, 10, mkChar("CONN_TIME_WAIT")); INTEGER(result)[11] = PS__CONN_NONE; SET_STRING_ELT(names, 11, mkChar("PS__CONN_NONE")); setAttrib(result, R_NamesSymbol, names); UNPROTECT(2); return result; } SEXP ps__init(SEXP psenv, SEXP constenv) { /* Signals */ defineVar(install("signals"), ps__define_signals(), constenv); /* errno values */ defineVar(install("errno"), ps__define_errno(), constenv); /* Connection statuses */ defineVar(install("tcp_statuses"), ps__define_tcp_statuses(), constenv); /* Socket address families */ defineVar(install("address_families"), ps__define_socket_address_families(), constenv); /* Socket address families */ defineVar(install("socket_types"), ps__define_socket_types(), constenv); return R_NilValue; } ps/src/windows.c0000644000176200001440000005030313737276560013342 0ustar liggesusers // Fixes clash between winsock2.h and windows.h #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #if (_WIN32_WINNT >= 0x0600) // Windows Vista and above #include #endif #if defined(__MINGW32__) #include #endif #include #include #include #include #include #include "common.h" #include "arch/windows/process_info.h" #ifndef __MINGW32__ #include "arch/windows/ntextapi.h" #else #include #include #endif #include #include "windows.h" /* * ============================================================================ * Utilities * ============================================================================ */ #ifndef AF_INET6 #define AF_INET6 23 #endif #define LO_T 1e-7 #define HI_T 429.4967296 /* * Return an integer vector of all the PIDs running on the system. */ SEXP ps__pids() { DWORD *proclist = NULL; DWORD numberOfReturnedPIDs; DWORD i; SEXP retlist; proclist = ps__get_pids(&numberOfReturnedPIDs); if (proclist == NULL) ps__throw_error(); PROTECT(retlist = allocVector(INTSXP, numberOfReturnedPIDs)); for (i = 0; i < numberOfReturnedPIDs; i++) { INTEGER(retlist)[i] = proclist[i]; } // free C array allocated for PIDs free(proclist); UNPROTECT(1); return retlist; } /* * Kill a process given its PID. */ SEXP ps__proc_kill(DWORD pid) { HANDLE hProcess; DWORD err; if (pid == 0) { ps__access_denied(""); return R_NilValue; } hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); if (hProcess == NULL) { if (GetLastError() == ERROR_INVALID_PARAMETER) { // see https://github.com/giampaolo/psutil/issues/24 ps__debug("OpenProcess -> ERROR_INVALID_PARAMETER turned " "into NoSuchProcess"); ps__no_such_process(pid, 0); } else { ps__set_error_from_windows_error(0); } return R_NilValue; } // kill the process if (! TerminateProcess(hProcess, SIGTERM)) { err = GetLastError(); // See: https://github.com/giampaolo/psutil/issues/1099 if (err != ERROR_ACCESS_DENIED) { CloseHandle(hProcess); ps__set_error_from_windows_error(0); return R_NilValue; } } CloseHandle(hProcess); return ScalarLogical(1); } /* * Return a list (user_time, kernel_time) */ SEXP ps__proc_cpu_times(DWORD pid) { HANDLE hProcess; FILETIME ftCreate, ftExit, ftKernel, ftUser; SEXP result, names; hProcess = ps__handle_from_pid(pid); if (hProcess == NULL) return R_NilValue; if (! GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser)) { CloseHandle(hProcess); if (GetLastError() == ERROR_ACCESS_DENIED) { // usually means the process has died so we throw a NoSuchProcess // here ps__no_such_process(pid, 0); } else { ps__set_error_from_windows_error(0); } return R_NilValue; } CloseHandle(hProcess); /* * User and kernel times are represented as a FILETIME structure * wich contains a 64-bit value representing the number of * 100-nanosecond intervals since January 1, 1601 (UTC): * http://msdn.microsoft.com/en-us/library/ms724284(VS.85).aspx * To convert it into a float representing the seconds that the * process has executed in user/kernel mode I borrowed the code * below from Python's Modules/posixmodule.c */ PROTECT(result = allocVector(REALSXP, 4)); REAL(result)[0] = (double)(ftUser.dwHighDateTime * 429.4967296 + ftUser.dwLowDateTime * 1e-7), REAL(result)[1] = (double)(ftKernel.dwHighDateTime * 429.4967296 + ftKernel.dwLowDateTime * 1e-7), REAL(result)[2] = REAL(result)[3] = NA_REAL; PROTECT(names = ps__build_string("user", "system", "children_user", "children_system", NULL)); setAttrib(result, R_NamesSymbol, names); UNPROTECT(2); return result; } SEXP ps__exe(DWORD pid) { HANDLE hProcess; wchar_t exe[MAX_PATH]; wchar_t *bs = exe; if (pid == 0 || pid == 4) return ScalarString(NA_STRING); hProcess = ps__handle_from_pid_waccess(pid, PROCESS_QUERY_INFORMATION); if (NULL == hProcess) { return R_NilValue; } if (GetProcessImageFileNameW(hProcess, exe, MAX_PATH) == 0) { CloseHandle(hProcess); ps__set_error_from_windows_error(0); return R_NilValue; } CloseHandle(hProcess); /* Convert to DOS path */ return ps__convert_dos_path(bs); } SEXP ps__convert_dos_path(WCHAR *wstr) { WCHAR *bs = wstr; int nbs = 0; wchar_t d = 'A'; wchar_t device[3] = { d, ':', '\0' }; wchar_t target[512]; while (nbs < 3 && *bs) { if (*bs == L'\\') nbs++; if (nbs == 3) break; bs++; } *bs = L'\0'; while (d <= 'Z') { device[0] = d; memset(target, 0, sizeof(wchar_t) * 512); if (QueryDosDeviceW(device, target, 511) != 0) { if (wcscmp(wstr, target) == 0) break; } d++; } if (d > 'Z') { ps__set_error("Cannot find device for executable path"); return R_NilValue; } *(bs - 2) = d; *(bs - 1) = ':'; *bs = '\\'; return ScalarString(ps__utf16_to_charsxp(bs - 2, -1)); } SEXP ps__name(DWORD pid) { SEXP exe, name; /* This is how PIDs 0 and 4 are always represented in taskmgr */ /* and process-hacker. */ if (pid == 0) return mkString("System Idle Process"); if (pid == 4) return mkString("System"); exe = ps__exe(pid); if (!isNull(exe)) { const char *cexe; char *last; PROTECT(exe); cexe = CHAR(STRING_ELT(exe, 0)); last = strrchr(cexe, '\\'); if (!last) { UNPROTECT(1); return exe; } else { name = mkString(last + 1); UNPROTECT(1); return name; } } return ps__proc_name(pid); } /* * Return process base name. * Note: ps__proc_exe() is attempted first because it's faster * but it raise AccessDenied for processes owned by other users * in which case we fall back on using this. */ SEXP ps__proc_name(DWORD pid) { int ok; PROCESSENTRY32W pentry; HANDLE hSnapShot; hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapShot == INVALID_HANDLE_VALUE) { ps__set_error_from_windows_error(0); return R_NilValue; } pentry.dwSize = sizeof(PROCESSENTRY32W); ok = Process32FirstW(hSnapShot, &pentry); if (! ok) { CloseHandle(hSnapShot); ps__set_error_from_windows_error(0); return R_NilValue; } while (ok) { if (pentry.th32ProcessID == pid) { CloseHandle(hSnapShot); return ScalarString(ps__utf16_to_charsxp(pentry.szExeFile, -1)); } ok = Process32NextW(hSnapShot, &pentry); } CloseHandle(hSnapShot); ps__no_such_process(pid, 0); return R_NilValue; } /* * Resume or suspends a process */ int ps__proc_suspend_or_resume(DWORD pid, int suspend) { // a huge thanks to http://www.codeproject.com/KB/threads/pausep.aspx HANDLE hThreadSnap = NULL; HANDLE hThread; THREADENTRY32 te32 = {0}; if (pid == 0) { ps__access_denied(""); return FALSE; } hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (hThreadSnap == INVALID_HANDLE_VALUE) { ps__set_error_from_windows_error(0); return FALSE; } // Fill in the size of the structure before using it te32.dwSize = sizeof(THREADENTRY32); if (! Thread32First(hThreadSnap, &te32)) { ps__set_error_from_windows_error(0); CloseHandle(hThreadSnap); return FALSE; } // Walk the thread snapshot to find all threads of the process. // If the thread belongs to the process, add its information // to the display list. do { if (te32.th32OwnerProcessID == pid) { hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID); if (hThread == NULL) { ps__set_error_from_windows_error(0); CloseHandle(hThread); CloseHandle(hThreadSnap); return FALSE; } if (suspend == 1) { if (SuspendThread(hThread) == (DWORD) - 1) { ps__set_error_from_windows_error(0); CloseHandle(hThread); CloseHandle(hThreadSnap); return FALSE; } } else { if (ResumeThread(hThread) == (DWORD) - 1) { ps__set_error_from_windows_error(0); CloseHandle(hThread); CloseHandle(hThreadSnap); return FALSE; } } CloseHandle(hThread); } } while (Thread32Next(hThreadSnap, &te32)); CloseHandle(hThreadSnap); return TRUE; } SEXP ps__proc_suspend(DWORD pid) { int suspend = 1; if (! ps__proc_suspend_or_resume(pid, suspend)) { return R_NilValue; } else { return ScalarLogical(1); } } SEXP ps__proc_resume(DWORD pid) { int suspend = 0; if (! ps__proc_suspend_or_resume(pid, suspend)) { return R_NilValue; } else { return ScalarLogical(1); } } /* * Return process username as a "DOMAIN//USERNAME" string. */ SEXP ps__proc_username(DWORD pid) { HANDLE processHandle = NULL; HANDLE tokenHandle = NULL; PTOKEN_USER user = NULL; ULONG bufferSize; WCHAR *name = NULL; WCHAR *domainName = NULL; ULONG nameSize; ULONG domainNameSize; SID_NAME_USE nameUse; SEXP ret; processHandle = ps__handle_from_pid_waccess(pid, PROCESS_QUERY_INFORMATION); if (processHandle == NULL) { return R_NilValue; } if (!OpenProcessToken(processHandle, TOKEN_QUERY, &tokenHandle)) { ps__set_error_from_windows_error(0); return R_NilValue; } CloseHandle(processHandle); processHandle = NULL; // Get the user SID. bufferSize = 0x100; while (1) { user = malloc(bufferSize); if (user == NULL) { ps__no_memory(""); goto error; } if (!GetTokenInformation(tokenHandle, TokenUser, user, bufferSize, &bufferSize)) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { free(user); continue; } else { ps__set_error_from_windows_error(0); goto error; } } break; } CloseHandle(tokenHandle); tokenHandle = NULL; // resolve the SID to a name nameSize = 0x100; domainNameSize = 0x100; while (1) { name = malloc(nameSize * sizeof(WCHAR)); if (name == NULL) { ps__no_memory(""); goto error; } domainName = malloc(domainNameSize * sizeof(WCHAR)); if (domainName == NULL) { ps__no_memory(""); goto error; } if (!LookupAccountSidW(NULL, user->User.Sid, name, &nameSize, domainName, &domainNameSize, &nameUse)) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { free(name); free(domainName); continue; } else { ps__set_error_from_windows_error(0); goto error; } } break; } PROTECT(ret = allocVector(STRSXP, 2)); SET_STRING_ELT(ret, 0, ps__utf16_to_charsxp(domainName, -1)); SET_STRING_ELT(ret, 1, ps__utf16_to_charsxp(name, -1)); free(name); free(domainName); free(user); UNPROTECT(1); return ret; error: if (processHandle != NULL) CloseHandle(processHandle); if (tokenHandle != NULL) CloseHandle(tokenHandle); if (name != NULL) free(name); if (domainName != NULL) free(domainName); if (user != NULL) free(user); return R_NilValue; } SEXP ps__proc_num_threads(DWORD pid) { PSYSTEM_PROCESS_INFORMATION process; PVOID buffer; int nt; if (! ps__get_proc_info(pid, &process, &buffer)) { return R_NilValue; } nt = process->NumberOfThreads; free(buffer); return ScalarInteger(nt); } /* * Get various process information by using NtQuerySystemInformation. * We use this as a fallback when faster functions fail with access * denied. This is slower because it iterates over all processes. * Returned tuple includes the following process info: * * - num_threads() * - ctx_switches() * - num_handles() (fallback) * - cpu_times() (fallback) * - create_time() (fallback) * - io_counters() (fallback) * - memory_info() (fallback) */ SEXP ps__proc_info(DWORD pid) { PSYSTEM_PROCESS_INFORMATION process; PVOID buffer; ULONG i; ULONG ctx_switches = 0; double user_time; double kernel_time; double create_time; SIZE_T mem_private; SEXP retlist; if (! ps__get_proc_info(pid, &process, &buffer)) { return R_NilValue; } for (i = 0; i < process->NumberOfThreads; i++) ctx_switches += process->Threads[i].ContextSwitches; user_time = (double)process->UserTime.HighPart * HI_T + \ (double)process->UserTime.LowPart * LO_T; kernel_time = (double)process->KernelTime.HighPart * HI_T + \ (double)process->KernelTime.LowPart * LO_T; if (0 == pid || 4 == pid) { SEXP bt = PROTECT(ps__boot_time()); create_time = REAL(bt)[0]; UNPROTECT(1); } else { FILETIME ft; ft.dwHighDateTime = process->CreateTime.HighPart; ft.dwLowDateTime = process->CreateTime.LowPart; create_time = ps__filetime_to_unix(ft); } #if (_WIN32_WINNT >= 0x0501) // Windows XP with SP2 mem_private = process->PrivatePageCount; #else mem_private = 0; #endif #if defined(_WIN64) #define BUILD_NAMED_LIST_PROTO "kkdddiKKKKKK" "kKKKKKKKKK" #else #define BUILD_NAMED_LIST_PROTO "kkdddiKKKKKK" "kIIIIIIIII" #endif PROTECT(retlist = ps__build_named_list( BUILD_NAMED_LIST_PROTO, "num_handles", process->HandleCount, /* 0 */ "ctx_switches", ctx_switches, /* 1 */ "user_time", user_time, /* 2 */ "kernel_time", kernel_time, /* 3 */ "create_time", create_time, /* 4 */ "num_threads", (int)process->NumberOfThreads, /* 5 */ // IO counters "io_rcount", process->ReadOperationCount.QuadPart, /* 6 */ "io_wcount", process->WriteOperationCount.QuadPart, /* 7 */ "io_rbytes", process->ReadTransferCount.QuadPart, /* 8 */ "io_wbytes", process->WriteTransferCount.QuadPart, /* 9 */ "io_count_others", process->OtherOperationCount.QuadPart, /* 10 */ "io_bytes_others", process->OtherTransferCount.QuadPart, /* 11 */ // memory "num_page_faults", process->PageFaultCount, /* 12 */ "peak_wset", process->PeakWorkingSetSize, /* 13 */ "wset", process->WorkingSetSize, /* 14 */ "peak_paged_pool", process->QuotaPeakPagedPoolUsage, /* 15 */ "paged_pool", process->QuotaPagedPoolUsage, /* 16 */ "peak_non_paged_pool", process->QuotaPeakNonPagedPoolUsage, /* 17 */ "non_paged_oool", process->QuotaNonPagedPoolUsage, /* 18 */ "pagefile", process->PagefileUsage, /* 19 */ "peak_pagefile", process->PeakPagefileUsage, /* 20 */ "mem_private", mem_private /* 21 */ )); free(buffer); UNPROTECT(1); return retlist; } SEXP ps__ppid(DWORD pid) { HANDLE handle = NULL; PROCESSENTRY32W pe = { 0 }; pe.dwSize = sizeof(PROCESSENTRY32W); handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (handle == INVALID_HANDLE_VALUE) { ps__set_error_from_windows_error(0); return R_NilValue; } if (Process32FirstW(handle, &pe)) { do { if (pe.th32ProcessID == pid) { DWORD ppid = pe.th32ParentProcessID; CloseHandle(handle); return ScalarInteger(ppid); } } while (Process32NextW(handle, &pe)); } CloseHandle(handle); ps__no_such_process(pid, 0); return R_NilValue; } const WCHAR LONG_PATH_PREFIX[] = L"\\\\?\\"; const WCHAR LONG_PATH_PREFIX_LEN = 4; const WCHAR UNC_PATH_PREFIX[] = L"\\\\?\\UNC\\"; const WCHAR UNC_PATH_PREFIX_LEN = 8; typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD); static PFGetFinalPathNameByHandleW ps__get_fpnbyhandle(void) { static PFGetFinalPathNameByHandleW pFunc = NULL; PFGetFinalPathNameByHandleW toReturn = pFunc; if (!toReturn) { HMODULE hModule = GetModuleHandleW(L"kernel32"); if (hModule) toReturn = (PFGetFinalPathNameByHandleW) GetProcAddress(hModule, "GetFinalPathNameByHandleW"); pFunc = toReturn; } return toReturn; } static SEXP psw__realpath_handle(HANDLE handle) { DWORD w_realpath_len; WCHAR* w_realpath_ptr = NULL; WCHAR* w_realpath_buf; PFGetFinalPathNameByHandleW GetFinalPathNameByHandleW = ps__get_fpnbyhandle(); w_realpath_len = GetFinalPathNameByHandleW(handle, NULL, 0, VOLUME_NAME_DOS); if (w_realpath_len == 0) { return R_NilValue; } w_realpath_buf = (WCHAR *) R_alloc(w_realpath_len + 1, sizeof(WCHAR)); if (w_realpath_buf == NULL) { SetLastError(ERROR_OUTOFMEMORY); return R_NilValue; } w_realpath_ptr = w_realpath_buf; if (GetFinalPathNameByHandleW( handle, w_realpath_ptr, w_realpath_len, VOLUME_NAME_DOS) == 0) { SetLastError(ERROR_INVALID_HANDLE); return R_NilValue; } /* convert UNC path to long path */ if (wcsncmp(w_realpath_ptr, UNC_PATH_PREFIX, UNC_PATH_PREFIX_LEN) == 0) { w_realpath_ptr += 6; *w_realpath_ptr = L'\\'; w_realpath_len -= 6; } else if (wcsncmp(w_realpath_ptr, LONG_PATH_PREFIX, LONG_PATH_PREFIX_LEN) == 0) { w_realpath_ptr += 4; w_realpath_len -= 4; } else { SetLastError(ERROR_INVALID_HANDLE); return R_NilValue; } return ps__utf16_to_strsxp(w_realpath_ptr, w_realpath_len); } SEXP psw__realpath(SEXP path) { WCHAR *wpath; HANDLE handle; SEXP res; ps__utf8_to_utf16(CHAR(STRING_ELT(path, 0)), &wpath); handle = CreateFileW(wpath, 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL); if (handle == INVALID_HANDLE_VALUE) { ps__set_error_from_windows_error(0); ps__throw_error(); } PROTECT(res = psw__realpath_handle(handle)); CloseHandle(handle); if (isNull(res)) { ps__set_error_from_windows_error(0); ps__throw_error(); } UNPROTECT(1); return res; } SEXP ps__define_tcp_statuses() { SEXP result, names; PROTECT(result = allocVector(INTSXP, 13)); PROTECT(names = allocVector(STRSXP, 13)); INTEGER(result)[0] = MIB_TCP_STATE_CLOSED; SET_STRING_ELT(names, 0, mkChar("CONN_CLOSE")); INTEGER(result)[1] = MIB_TCP_STATE_CLOSING; SET_STRING_ELT(names, 1, mkChar("CONN_CLOSING")); INTEGER(result)[2] = MIB_TCP_STATE_CLOSE_WAIT; SET_STRING_ELT(names, 2, mkChar("CONN_CLOSE_WAIT")); INTEGER(result)[3] = MIB_TCP_STATE_LISTEN; SET_STRING_ELT(names, 3, mkChar("CONN_LISTEN")); INTEGER(result)[4] = MIB_TCP_STATE_ESTAB; SET_STRING_ELT(names, 4, mkChar("CONN_ESTABLISHED")); INTEGER(result)[5] = MIB_TCP_STATE_SYN_SENT; SET_STRING_ELT(names, 5, mkChar("CONN_SYN_SENT")); INTEGER(result)[6] = MIB_TCP_STATE_SYN_RCVD; SET_STRING_ELT(names, 6, mkChar("CONN_SYN_RECV")); INTEGER(result)[7] = MIB_TCP_STATE_FIN_WAIT1; SET_STRING_ELT(names, 7, mkChar("CONN_FIN_WAIT_1")); INTEGER(result)[8] = MIB_TCP_STATE_FIN_WAIT2; SET_STRING_ELT(names, 8, mkChar("CONN_FIN_WAIT_2")); INTEGER(result)[9] = MIB_TCP_STATE_LAST_ACK; SET_STRING_ELT(names, 9, mkChar("CONN_LAST_ACK")); INTEGER(result)[10] = MIB_TCP_STATE_TIME_WAIT; SET_STRING_ELT(names, 10, mkChar("CONN_TIME_WAIT")); INTEGER(result)[11] = MIB_TCP_STATE_DELETE_TCB; SET_STRING_ELT(names, 11, mkChar("CONN_DELETE_TCB")); INTEGER(result)[12] = PS__CONN_NONE; SET_STRING_ELT(names, 12, mkChar("PS__CONN_NONE")); setAttrib(result, R_NamesSymbol, names); UNPROTECT(2); return result; } SEXP ps__define_socket_address_families() { SEXP afenv = PROTECT(Rf_allocSExp(ENVSXP)); defineVar(install("AF_INET"), PROTECT(ScalarInteger(AF_INET)), afenv); UNPROTECT(1); defineVar(install("AF_INET6"), PROTECT(ScalarInteger(AF_INET6)), afenv); UNPROTECT(1); UNPROTECT(1); return afenv; } SEXP ps__define_socket_types() { SEXP env = PROTECT(Rf_allocSExp(ENVSXP)); defineVar(install("SOCK_STREAM"), PROTECT(ScalarInteger(SOCK_STREAM)), env); UNPROTECT(1); defineVar(install("SOCK_DGRAM"), PROTECT(ScalarInteger(SOCK_DGRAM)), env); UNPROTECT(1); UNPROTECT(1); return env; } SEXP ps__init(SEXP psenv, SEXP constenv) { /* Connection statuses */ defineVar(install("tcp_statuses"), ps__define_tcp_statuses(), constenv); /* Socket address families */ defineVar(install("address_families"), ps__define_socket_address_families(), constenv); /* Socket types */ defineVar(install("socket_types"), ps__define_socket_types(), constenv); /* errno values */ defineVar(install("errno"), ps__define_errno(), constenv); return R_NilValue; } ps/src/init.c0000644000176200001440000001000014016767153012573 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include #include "ps-internal.h" #include "common.h" static const R_CallMethodDef callMethods[] = { /* System api */ { "ps__os_type", (DL_FUNC) ps__os_type, 0 }, { "ps__pids", (DL_FUNC) ps__pids, 0 }, { "ps__boot_time", (DL_FUNC) ps__boot_time, 0 }, { "ps__cpu_count_logical", (DL_FUNC) ps__cpu_count_logical, 0 }, { "ps__cpu_count_physical", (DL_FUNC) ps__cpu_count_physical, 0 }, { "ps__system_cpu_times", (DL_FUNC) ps__system_cpu_times, 0 }, { "ps__users", (DL_FUNC) ps__users, 0 }, { "ps__loadavg", (DL_FUNC) ps__loadavg, 1 }, { "ps__tty_size", (DL_FUNC) ps__tty_size, 0 }, { "ps__disk_partitions", (DL_FUNC) ps__disk_partitions, 1 }, { "ps__disk_usage", (DL_FUNC) ps__disk_usage, 1 }, { "ps__system_memory", (DL_FUNC) ps__system_memory, 0 }, { "ps__system_swap", (DL_FUNC) ps__system_swap, 0 }, /* ps_handle API */ { "psll_pid", (DL_FUNC) psll_pid, 1 }, { "psll_handle", (DL_FUNC) psll_handle, 2 }, { "psll_format", (DL_FUNC) psll_format, 1 }, { "psll_parent", (DL_FUNC) psll_parent, 1 }, { "psll_ppid", (DL_FUNC) psll_ppid, 1 }, { "psll_is_running", (DL_FUNC) psll_is_running, 1 }, { "psll_name", (DL_FUNC) psll_name, 1 }, { "psll_exe", (DL_FUNC) psll_exe, 1 }, { "psll_cmdline", (DL_FUNC) psll_cmdline, 1 }, { "psll_status", (DL_FUNC) psll_status, 1 }, { "psll_username", (DL_FUNC) psll_username, 1 }, { "psll_create_time", (DL_FUNC) psll_create_time, 1 }, { "psll_cwd", (DL_FUNC) psll_cwd, 1 }, { "psll_uids", (DL_FUNC) psll_uids, 1 }, { "psll_gids", (DL_FUNC) psll_gids, 1 }, { "psll_terminal", (DL_FUNC) psll_terminal, 1 }, { "psll_environ", (DL_FUNC) psll_environ, 1 }, { "psll_num_threads", (DL_FUNC) psll_num_threads , 1 }, { "psll_cpu_times", (DL_FUNC) psll_cpu_times, 1 }, { "psll_memory_info", (DL_FUNC) psll_memory_info , 1 }, { "psll_send_signal", (DL_FUNC) psll_send_signal , 2 }, { "psll_suspend", (DL_FUNC) psll_suspend, 1 }, { "psll_resume", (DL_FUNC) psll_resume, 1 }, { "psll_terminate", (DL_FUNC) psll_terminate, 1 }, { "psll_kill", (DL_FUNC) psll_kill, 1 }, { "psll_num_fds", (DL_FUNC) psll_num_fds, 1 }, { "psll_open_files", (DL_FUNC) psll_open_files, 1 }, { "psll_interrupt", (DL_FUNC) psll_interrupt, 3 }, { "psll_connections", (DL_FUNC) psll_connections, 1 }, { "psll_get_nice", (DL_FUNC) psll_get_nice, 1 }, { "psll_set_nice", (DL_FUNC) psll_set_nice, 2 }, { "psll_dlls", (DL_FUNC) psll_dlls, 1 }, /* Utils */ { "ps__init", (DL_FUNC) ps__init, 2 }, { "ps__kill_if_env", (DL_FUNC) ps__kill_if_env, 4 }, { "ps__find_if_env", (DL_FUNC) ps__find_if_env, 3 }, { "ps__inet_ntop", (DL_FUNC) ps__inet_ntop, 2 }, { "psp__pid_exists", (DL_FUNC) psp__pid_exists, 1 }, { "psp__stat_st_rdev", (DL_FUNC) psp__stat_st_rdev, 1 }, { "psp__zombie", (DL_FUNC) psp__zombie, 0 }, { "psp__waitpid", (DL_FUNC) psp__waitpid, 1 }, { "psw__realpath", (DL_FUNC) psw__realpath, 1 }, { NULL, NULL, 0 } }; /* * Called on module import on all platforms. */ void R_init_ps(DllInfo *dll) { if (getenv("R_PS_DEBUG") != NULL) PS__DEBUG = 1; if (getenv("R_PS_TESTING") != NULL) PS__TESTING = 1; PROTECT(ps__last_error = ps__build_named_list( "ssii", "message", "Unknown error", "class", "fs_error", "errno", 0, "pid", NA_INTEGER )); R_PreserveObject(ps__last_error); UNPROTECT(1); R_registerRoutines(dll, NULL, callMethods, NULL, NULL); R_useDynamicSymbols(dll, FALSE); R_forceSymbols(dll, TRUE); } ps/src/arch/0000755000176200001440000000000013315014440012373 5ustar liggesusersps/src/arch/macos/0000755000176200001440000000000014016776111013506 5ustar liggesusersps/src/arch/macos/process_info.c0000644000176200001440000001676613737276560016377 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include #include #include // for INT_MAX #include #include #include #include #include #include #include #include "process_info.h" #include "../../common.h" #include "../../posix.h" /* * Returns a list of all BSD processes on the system. This routine * allocates the list and puts it in *procList and a count of the * number of entries in *procCount. You are responsible for freeing * this list (use "free" from System framework). * On success, the function returns 0. * On error, the function returns a BSD errno value. */ int ps__get_proc_list(kinfo_proc **procList, size_t *procCount) { int mib3[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL }; size_t size, size2; void *ptr; int err; int lim = 8; // some limit assert( procList != NULL); assert(*procList == NULL); assert(procCount != NULL); *procCount = 0; /* * We start by calling sysctl with ptr == NULL and size == 0. * That will succeed, and set size to the appropriate length. * We then allocate a buffer of at least that size and call * sysctl with that buffer. If that succeeds, we're done. * If that call fails with ENOMEM, we throw the buffer away * and try again. * Note that the loop calls sysctl with NULL again. This is * is necessary because the ENOMEM failure case sets size to * the amount of data returned, not the amount of data that * could have been returned. */ while (lim-- > 0) { size = 0; if (sysctl((int *)mib3, 3, NULL, &size, NULL, 0) == -1) return errno; size2 = size + (size >> 3); // add some if (size2 > size) { ptr = malloc(size2); if (ptr == NULL) ptr = malloc(size); else size = size2; } else { ptr = malloc(size); } if (ptr == NULL) return ENOMEM; if (sysctl((int *)mib3, 3, ptr, &size, NULL, 0) == -1) { err = errno; free(ptr); if (err != ENOMEM) return err; } else { *procList = (kinfo_proc *)ptr; *procCount = size / sizeof(kinfo_proc); return 0; } } return ENOMEM; } // Read the maximum argument size for processes int ps__get_argmax() { int argmax; int mib[] = { CTL_KERN, KERN_ARGMAX }; size_t size = sizeof(argmax); if (sysctl(mib, 2, &argmax, &size, NULL, 0) == 0) return argmax; return 0; } // return process args as a character vector SEXP ps__get_cmdline(long pid) { int mib[3]; int nargs; int idx; size_t len; char *procargs = NULL; char *arg_ptr; char *arg_end; char *curr_arg; size_t argmax; SEXP arg = R_NilValue; SEXP retlist = R_NilValue; // special case for PID 0 (kernel_task) where cmdline cannot be fetched if (pid == 0) { ps__access_denied(""); return R_NilValue; } // read argmax and allocate memory for argument space. argmax = ps__get_argmax(); if (! argmax) { ps__set_error_from_errno(); return R_NilValue; } procargs = (char *) malloc(argmax); if (NULL == procargs) { ps__no_memory(""); return R_NilValue; } PROTECT_PTR(procargs); // read argument space mib[0] = CTL_KERN; mib[1] = KERN_PROCARGS2; mib[2] = (pid_t)pid; if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) { // In case of zombie process we'll get EINVAL, we fix this. if (errno == EINVAL) errno = ESRCH; UNPROTECT(1); ps__set_error_from_errno(); return R_NilValue; } arg_end = &procargs[argmax]; // copy the number of arguments to nargs memcpy(&nargs, procargs, sizeof(nargs)); arg_ptr = procargs + sizeof(nargs); len = strlen(arg_ptr); arg_ptr += len + 1; if (arg_ptr == arg_end) { UNPROTECT(1); return allocVector(STRSXP, 0); } // skip ahead to the first argument for (; arg_ptr < arg_end; arg_ptr++) { if (*arg_ptr != '\0') break; } // iterate through arguments curr_arg = arg_ptr; idx = 0; PROTECT(retlist = allocVector(STRSXP, nargs)); while (arg_ptr < arg_end && nargs > 0) { if (*arg_ptr++ == '\0') { PROTECT(arg = ps__str_to_utf8(curr_arg)); SET_STRING_ELT(retlist, idx++, STRING_ELT(arg, 0)); UNPROTECT(1); // iterate to next arg and decrement # of args curr_arg = arg_ptr; nargs--; } } UNPROTECT(2); return retlist; } // return process environment as a character vector SEXP ps__get_environ(long pid) { int mib[3]; int nargs, nenv; char *procargs = NULL; char *arg_ptr; char *arg_end; char *env_start; size_t argmax; SEXP ret = NULL; // special case for PID 0 (kernel_task) where cmdline cannot be fetched if (pid == 0) { ps__access_denied(""); goto ret; } // read argmax and allocate memory for argument space. argmax = ps__get_argmax(); if (! argmax) { ps__set_error_from_errno(); return R_NilValue; } procargs = (char *) malloc(argmax); if (NULL == procargs) { ps__no_memory(""); return R_NilValue; } PROTECT_PTR(procargs); // read argument space mib[0] = CTL_KERN; mib[1] = KERN_PROCARGS2; mib[2] = (pid_t)pid; if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) { // In case of zombie process we'll get EINVAL, fix this. if (errno == EINVAL) errno = ESRCH; UNPROTECT(1); ps__set_error_from_errno(); return R_NilValue; } arg_end = &procargs[argmax]; // copy the number of arguments to nargs memcpy(&nargs, procargs, sizeof(nargs)); // skip executable path arg_ptr = procargs + sizeof(nargs); arg_ptr = memchr(arg_ptr, '\0', arg_end - arg_ptr); if (arg_ptr == NULL || arg_ptr == arg_end) goto empty; // skip ahead to the first argument for (; arg_ptr < arg_end; arg_ptr++) { if (*arg_ptr != '\0') break; } // iterate through arguments while (arg_ptr < arg_end && nargs > 0) { if (*arg_ptr++ == '\0') nargs--; } // build an environment variable block env_start = arg_ptr; /* Count the number of env vars first */ nenv = 0; while (*arg_ptr != '\0' && arg_ptr < arg_end) { char *s = memchr(arg_ptr + 1, '\0', arg_end - arg_ptr); if (s == NULL) break; nenv++; arg_ptr = s + 1; } PROTECT(ret = allocVector(STRSXP, nenv)); arg_ptr = env_start; nenv = 0; while (*arg_ptr != '\0' && arg_ptr < arg_end) { char *s = memchr(arg_ptr + 1, '\0', arg_end - arg_ptr); if (s == NULL) break; SET_STRING_ELT(ret, nenv++, Rf_mkCharLen(arg_ptr, (int)(s - arg_ptr))); arg_ptr = s + 1; } UNPROTECT(2); return ret; empty: UNPROTECT(1); ret: return allocVector(STRSXP, 0); } int ps__get_kinfo_proc(long pid, struct kinfo_proc *kp) { int mib[4]; size_t len; mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = (pid_t) pid; // fetch the info with sysctl() len = sizeof(struct kinfo_proc); // now read the data from sysctl if (sysctl(mib, 4, kp, &len, NULL, 0) == -1) { // raise an exception and throw errno as the error ps__set_error_from_errno(); return -1; } // sysctl succeeds but len is zero, happens when process has gone away if (len == 0) { ps__no_such_process(pid, 0); return -1; } return 0; } /* * A wrapper around proc_pidinfo(). * Returns 0 on failure (and error gets already set). */ int ps__proc_pidinfo(long pid, int flavor, uint64_t arg, void *pti, int size) { errno = 0; int ret = proc_pidinfo((int)pid, flavor, arg, pti, size); if ((ret <= 0) || ((unsigned long)ret < sizeof(pti))) { ps__raise_for_pid(pid, "proc_pidinfo()"); return 0; } return ret; } ps/src/arch/macos/process_info.h0000644000176200001440000000055313737276560016367 0ustar liggesusers #include "../../common.h" typedef struct kinfo_proc kinfo_proc; int ps__get_argmax(void); int ps__get_kinfo_proc(long pid, struct kinfo_proc *kp); int ps__get_proc_list(kinfo_proc **procList, size_t *procCount); int ps__proc_pidinfo( long pid, int flavor, uint64_t arg, void *pti, int size); SEXP ps__get_cmdline(long pid); SEXP ps__get_environ(long pid); ps/src/arch/windows/0000755000176200001440000000000014016676565014112 5ustar liggesusersps/src/arch/windows/process_info.c0000644000176200001440000005422513737276560016757 0ustar liggesusers #include #include #include #include "process_info.h" #include "ntextapi.h" #include "../../common.h" #include "../../ps-internal.h" // ==================================================================== // Helper structures to access the memory correctly. // Some of these might also be defined in the winternl.h header file // but unfortunately not in a usable way. // ==================================================================== // see http://msdn2.microsoft.com/en-us/library/aa489609.aspx #ifndef NT_SUCCESS #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) #endif // http://msdn.microsoft.com/en-us/library/aa813741(VS.85).aspx typedef struct { BYTE Reserved1[16]; PVOID Reserved2[5]; UNICODE_STRING CurrentDirectoryPath; PVOID CurrentDirectoryHandle; UNICODE_STRING DllPath; UNICODE_STRING ImagePathName; UNICODE_STRING CommandLine; LPCWSTR env; } RTL_USER_PROCESS_PARAMETERS_, *PRTL_USER_PROCESS_PARAMETERS_; // https://msdn.microsoft.com/en-us/library/aa813706(v=vs.85).aspx #ifdef _WIN64 typedef struct { BYTE Reserved1[2]; BYTE BeingDebugged; BYTE Reserved2[21]; PVOID LoaderData; PRTL_USER_PROCESS_PARAMETERS_ ProcessParameters; /* More fields ... */ } PEB_; #else typedef struct { BYTE Reserved1[2]; BYTE BeingDebugged; BYTE Reserved2[1]; PVOID Reserved3[2]; PVOID Ldr; PRTL_USER_PROCESS_PARAMETERS_ ProcessParameters; /* More fields ... */ } PEB_; #endif #ifdef _WIN64 /* When we are a 64 bit process accessing a 32 bit (WoW64) process we need to use the 32 bit structure layout. */ typedef struct { USHORT Length; USHORT MaxLength; DWORD Buffer; } UNICODE_STRING32; typedef struct { BYTE Reserved1[16]; DWORD Reserved2[5]; UNICODE_STRING32 CurrentDirectoryPath; DWORD CurrentDirectoryHandle; UNICODE_STRING32 DllPath; UNICODE_STRING32 ImagePathName; UNICODE_STRING32 CommandLine; DWORD env; } RTL_USER_PROCESS_PARAMETERS32; typedef struct { BYTE Reserved1[2]; BYTE BeingDebugged; BYTE Reserved2[1]; DWORD Reserved3[2]; DWORD Ldr; DWORD ProcessParameters; /* More fields ... */ } PEB32; #else /* When we are a 32 bit (WoW64) process accessing a 64 bit process we need to use the 64 bit structure layout and a special function to read its memory. */ typedef NTSTATUS (NTAPI *_NtWow64ReadVirtualMemory64)( IN HANDLE ProcessHandle, IN PVOID64 BaseAddress, OUT PVOID Buffer, IN ULONG64 Size, OUT PULONG64 NumberOfBytesRead); typedef enum { MemoryInformationBasic } MEMORY_INFORMATION_CLASS; typedef NTSTATUS (NTAPI *_NtWow64QueryVirtualMemory64)( IN HANDLE ProcessHandle, IN PVOID64 BaseAddress, IN MEMORY_INFORMATION_CLASS MemoryInformationClass, OUT PMEMORY_BASIC_INFORMATION64 MemoryInformation, IN ULONG64 Size, OUT PULONG64 ReturnLength OPTIONAL); typedef struct { PVOID Reserved1[2]; PVOID64 PebBaseAddress; PVOID Reserved2[4]; PVOID UniqueProcessId[2]; PVOID Reserved3[2]; } PROCESS_BASIC_INFORMATION64; typedef struct { USHORT Length; USHORT MaxLength; PVOID64 Buffer; } UNICODE_STRING64; typedef struct { BYTE Reserved1[16]; PVOID64 Reserved2[5]; UNICODE_STRING64 CurrentDirectoryPath; PVOID64 CurrentDirectoryHandle; UNICODE_STRING64 DllPath; UNICODE_STRING64 ImagePathName; UNICODE_STRING64 CommandLine; PVOID64 env; } RTL_USER_PROCESS_PARAMETERS64; typedef struct { BYTE Reserved1[2]; BYTE BeingDebugged; BYTE Reserved2[21]; PVOID64 LoaderData; PVOID64 ProcessParameters; /* More fields ... */ } PEB64; #endif #define PS__FIRST_PROCESS(Processes) ( \ (PSYSTEM_PROCESS_INFORMATION)(Processes)) #define PS__NEXT_PROCESS(Process) ( \ ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset ? \ (PSYSTEM_PROCESS_INFORMATION)((PCHAR)(Process) + \ ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset) : NULL) const int STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; const int STATUS_BUFFER_TOO_SMALL = 0xC0000023L; // ==================================================================== // Process and PIDs utiilties. // ==================================================================== /* * Return 1 if PID exists, 0 if not, -1 on error. */ int ps__pid_in_pids(DWORD pid) { DWORD *proclist = NULL; DWORD numberOfReturnedPIDs; DWORD i; proclist = ps__get_pids(&numberOfReturnedPIDs); if (proclist == NULL) return -1; for (i = 0; i < numberOfReturnedPIDs; i++) { if (proclist[i] == pid) { free(proclist); return 1; } } free(proclist); return 0; } /* * Given a process HANDLE checks whether it's actually running. * Returns: * - 1: running * - 0: not running * - -1: WindowsError * - -2: AssertionError */ int ps__is_phandle_running(HANDLE hProcess, DWORD pid) { DWORD processExitCode = 0; if (hProcess == NULL) { if (GetLastError() == ERROR_INVALID_PARAMETER) { // Yeah, this is the actual error code in case of // "no such process". if (! ps__assert_pid_not_exists( pid, "iphr: OpenProcess() -> ERROR_INVALID_PARAMETER")) { return -2; } return 0; } return -1; } if (GetExitCodeProcess(hProcess, &processExitCode)) { // XXX - maybe STILL_ACTIVE is not fully reliable as per: // http://stackoverflow.com/questions/1591342/#comment47830782_1591379 if (processExitCode == STILL_ACTIVE) { if (! ps__assert_pid_exists( pid, "iphr: GetExitCodeProcess() -> STILL_ACTIVE")) { CloseHandle(hProcess); return -2; } return 1; } else { // We can't be sure so we look into pids. if (ps__pid_in_pids(pid) == 1) { return 1; } else { CloseHandle(hProcess); return 0; } } } CloseHandle(hProcess); if (! ps__assert_pid_not_exists( pid, "iphr: exit fun")) { return -2; } return -1; } /* * Given a process HANDLE checks whether it's actually running and if * it does return it, else return NULL with the proper exception * set. */ HANDLE ps__check_phandle(HANDLE hProcess, DWORD pid) { int ret = ps__is_phandle_running(hProcess, pid); if (ret == 1){ return hProcess; } else if (ret == 0){ ps__no_such_process(pid, 0); return NULL; } else if (ret == -1) { ps__set_error_from_windows_error(0); return NULL; } else { // -2 return NULL; } } /* * A wrapper around OpenProcess setting NSP exception if process * no longer exists. * "pid" is the process pid, "dwDesiredAccess" is the first argument * exptected by OpenProcess. * Return a process handle or NULL. */ HANDLE ps__handle_from_pid_waccess(DWORD pid, DWORD dwDesiredAccess) { HANDLE hProcess; if (pid == 0) { // otherwise we'd get NoSuchProcess ps__access_denied(""); return NULL; } hProcess = OpenProcess(dwDesiredAccess, FALSE, pid); return ps__check_phandle(hProcess, pid); ; } /* * Same as ps__handle_from_pid_waccess but implicitly uses * PROCESS_QUERY_INFORMATION | PROCESS_VM_READ as dwDesiredAccess * parameter for OpenProcess. */ HANDLE ps__handle_from_pid(DWORD pid) { DWORD dwDesiredAccess = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; return ps__handle_from_pid_waccess(pid, dwDesiredAccess); } DWORD * ps__get_pids(DWORD *numberOfReturnedPIDs) { // Win32 SDK says the only way to know if our process array // wasn't large enough is to check the returned size and make // sure that it doesn't match the size of the array. // If it does we allocate a larger array and try again // Stores the actual array DWORD *procArray = NULL; DWORD procArrayByteSz; int procArraySz = 0; // Stores the byte size of the returned array from enumprocesses DWORD enumReturnSz = 0; do { procArraySz += 1024; free(procArray); procArrayByteSz = procArraySz * sizeof(DWORD); procArray = malloc(procArrayByteSz); if (procArray == NULL) { ps__no_memory(""); return NULL; } if (! EnumProcesses(procArray, procArrayByteSz, &enumReturnSz)) { free(procArray); ps__set_error_from_windows_error(0); return NULL; } } while (enumReturnSz == procArraySz * sizeof(DWORD)); // The number of elements is the returned size / size of each element *numberOfReturnedPIDs = enumReturnSz / sizeof(DWORD); return procArray; } int ps__assert_pid_exists(DWORD pid, char *err) { if (PS__TESTING) { if (ps__pid_in_pids(pid) == 0) { ps__set_error(err); return 0; } } return 1; } int ps__assert_pid_not_exists(DWORD pid, char *err) { if (PS__TESTING) { if (ps__pid_in_pids(pid) == 1) { ps__set_error(err); return 0; } } return 1; } /* Given a pointer into a process's memory, figure out how much data can be * read from it. */ static int ps__get_process_region_size(HANDLE hProcess, LPCVOID src, SIZE_T *psize) { MEMORY_BASIC_INFORMATION info; if (!VirtualQueryEx(hProcess, src, &info, sizeof(info))) { ps__set_error_from_windows_error(0); return -1; } *psize = info.RegionSize - ((char*)src - (char*)info.BaseAddress); return 0; } #ifndef _WIN64 /* Given a pointer into a process's memory, figure out how much data can be * read from it. */ static int ps__get_process_region_size64(HANDLE hProcess, const PVOID64 src64, PULONG64 psize) { static _NtWow64QueryVirtualMemory64 NtWow64QueryVirtualMemory64 = NULL; MEMORY_BASIC_INFORMATION64 info64; if (NtWow64QueryVirtualMemory64 == NULL) { NtWow64QueryVirtualMemory64 = (_NtWow64QueryVirtualMemory64)GetProcAddress( GetModuleHandleA("ntdll.dll"), "NtWow64QueryVirtualMemory64"); if (NtWow64QueryVirtualMemory64 == NULL) { ps__set_error("NtWow64QueryVirtualMemory64 missing"); return -1; } } if (!NT_SUCCESS(NtWow64QueryVirtualMemory64( hProcess, src64, 0, &info64, sizeof(info64), NULL))) { ps__set_error_from_windows_error(0); return -1; } #ifdef _WIN64 *psize = info64.RegionSize - ((char*)src64 - (char*)info64.BaseAddress); #else *psize = info64.RegionSize - ((DWORD32)src64 - (DWORD32)info64.BaseAddress); #endif return 0; } #endif enum ps__process_data_kind { KIND_CMDLINE, KIND_CWD, KIND_ENVIRON, }; /* Get data from the process with the given pid. The data is returned in the pdata output member as a nul terminated string which must be freed on success. On success 0 is returned. On error the output parameter is not touched, -1 is returned, and an appropriate exception is set. */ static int ps__get_process_data(long pid, enum ps__process_data_kind kind, WCHAR **pdata, SIZE_T *psize) { /* This function is quite complex because there are several cases to be considered: Two cases are really simple: we (i.e. the R interpreter) and the target process are both 32 bit or both 64 bit. In that case the memory layout of the structures matches up and all is well. When we are 64 bit and the target process is 32 bit we need to use custom 32 bit versions of the structures. When we are 32 bit and the target process is 64 bit we need to use custom 64 bit version of the structures. Also we need to use separate Wow64 functions to get the information. A few helper structs are defined above so that the compiler can handle calculating the correct offsets. Additional help also came from the following sources: https://github.com/kohsuke/winp and http://wj32.org/wp/2009/01/24/howto-get-the-command-line-of-processes/ http://stackoverflow.com/a/14012919 http://www.drdobbs.com/embracing-64-bit-windows/184401966 */ static _NtQueryInformationProcess NtQueryInformationProcess = NULL; #ifndef _WIN64 static _NtQueryInformationProcess NtWow64QueryInformationProcess64 = NULL; static _NtWow64ReadVirtualMemory64 NtWow64ReadVirtualMemory64 = NULL; #endif HANDLE hProcess = NULL; LPCVOID src = NULL; SIZE_T size = 0; WCHAR *buffer = NULL; #ifdef _WIN64 LPVOID ppeb32 = NULL; #else PVOID64 src64 = NULL; BOOL weAreWow64; BOOL theyAreWow64; #endif hProcess = ps__handle_from_pid(pid); if (hProcess == NULL) return -1; if (NtQueryInformationProcess == NULL) { NtQueryInformationProcess = (_NtQueryInformationProcess)GetProcAddress( GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess"); } #ifdef _WIN64 /* 64 bit case. Check if the target is a 32 bit process running in WoW64 * mode. */ if (!NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessWow64Information, &ppeb32, sizeof(LPVOID), NULL))) { ps__set_error_from_windows_error(0); goto error; } if (ppeb32 != NULL) { /* We are 64 bit. Target process is 32 bit running in WoW64 mode. */ PEB32 peb32; RTL_USER_PROCESS_PARAMETERS32 procParameters32; // read PEB if (!ReadProcessMemory(hProcess, ppeb32, &peb32, sizeof(peb32), NULL)) { ps__set_error_from_windows_error(0); goto error; } // read process parameters if (!ReadProcessMemory(hProcess, UlongToPtr(peb32.ProcessParameters), &procParameters32, sizeof(procParameters32), NULL)) { ps__set_error_from_windows_error(0); goto error; } switch (kind) { case KIND_CMDLINE: src = UlongToPtr(procParameters32.CommandLine.Buffer), size = procParameters32.CommandLine.Length; break; case KIND_CWD: src = UlongToPtr(procParameters32.CurrentDirectoryPath.Buffer); size = procParameters32.CurrentDirectoryPath.Length; break; case KIND_ENVIRON: src = UlongToPtr(procParameters32.env); break; } } else #else /* 32 bit case. Check if the target is also 32 bit. */ if (!IsWow64Process(GetCurrentProcess(), &weAreWow64) || !IsWow64Process(hProcess, &theyAreWow64)) { ps__set_error_from_windows_error(0); goto error; } if (weAreWow64 && !theyAreWow64) { /* We are 32 bit running in WoW64 mode. Target process is 64 bit. */ PROCESS_BASIC_INFORMATION64 pbi64; PEB64 peb64; RTL_USER_PROCESS_PARAMETERS64 procParameters64; if (NtWow64QueryInformationProcess64 == NULL) { NtWow64QueryInformationProcess64 = (_NtQueryInformationProcess)GetProcAddress( GetModuleHandleA("ntdll.dll"), "NtWow64QueryInformationProcess64"); if (NtWow64QueryInformationProcess64 == NULL) { ps__set_error("NtWow64QueryInformationProcess64 missing"); goto error; } } if (!NT_SUCCESS(NtWow64QueryInformationProcess64( hProcess, ProcessBasicInformation, &pbi64, sizeof(pbi64), NULL))) { ps__set_error_from_windows_error(0); goto error; } // read peb if (NtWow64ReadVirtualMemory64 == NULL) { NtWow64ReadVirtualMemory64 = (_NtWow64ReadVirtualMemory64)GetProcAddress( GetModuleHandleA("ntdll.dll"), "NtWow64ReadVirtualMemory64"); if (NtWow64ReadVirtualMemory64 == NULL) { ps__set_error("NtWow64ReadVirtualMemory64 missing"); goto error; } } if (!NT_SUCCESS(NtWow64ReadVirtualMemory64(hProcess, pbi64.PebBaseAddress, &peb64, sizeof(peb64), NULL))) { ps__set_error_from_windows_error(0); goto error; } // read process parameters if (!NT_SUCCESS(NtWow64ReadVirtualMemory64(hProcess, peb64.ProcessParameters, &procParameters64, sizeof(procParameters64), NULL))) { ps__set_error_from_windows_error(0); goto error; } switch (kind) { case KIND_CMDLINE: src64 = procParameters64.CommandLine.Buffer; size = procParameters64.CommandLine.Length; break; case KIND_CWD: src64 = procParameters64.CurrentDirectoryPath.Buffer, size = procParameters64.CurrentDirectoryPath.Length; break; case KIND_ENVIRON: src64 = procParameters64.env; break; } } else #endif /* Target process is of the same bitness as us. */ { PROCESS_BASIC_INFORMATION pbi; PEB_ peb; RTL_USER_PROCESS_PARAMETERS_ procParameters; if (!NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL))) { ps__set_error_from_windows_error(0); goto error; } // read peb if (!ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL)) { ps__set_error_from_windows_error(0); goto error; } // read process parameters if (!ReadProcessMemory(hProcess, peb.ProcessParameters, &procParameters, sizeof(procParameters), NULL)) { ps__set_error_from_windows_error(0); goto error; } switch (kind) { case KIND_CMDLINE: src = procParameters.CommandLine.Buffer; size = procParameters.CommandLine.Length; break; case KIND_CWD: src = procParameters.CurrentDirectoryPath.Buffer; size = procParameters.CurrentDirectoryPath.Length; break; case KIND_ENVIRON: src = procParameters.env; break; } } if (kind == KIND_ENVIRON) { #ifndef _WIN64 if (weAreWow64 && !theyAreWow64) { ULONG64 size64; if (ps__get_process_region_size64(hProcess, src64, &size64) != 0) goto error; size = (SIZE_T)size64; } else #endif if (ps__get_process_region_size(hProcess, src, &size) != 0) goto error; } buffer = calloc(size + 2, 1); if (buffer == NULL) { ps__no_memory(""); goto error; } #ifndef _WIN64 if (weAreWow64 && !theyAreWow64) { if (!NT_SUCCESS(NtWow64ReadVirtualMemory64(hProcess, src64, buffer, size, NULL))) { ps__set_error_from_windows_error(0); goto error; } } else #endif if (!ReadProcessMemory(hProcess, src, buffer, size, NULL)) { ps__set_error_from_windows_error(0); goto error; } CloseHandle(hProcess); *pdata = buffer; *psize = size; return 0; error: if (hProcess != NULL) CloseHandle(hProcess); if (buffer != NULL) free(buffer); return -1; } /* * returns a list representing the arguments for the process * with given pid or NULL on error. */ SEXP ps__get_cmdline(DWORD pid) { WCHAR *data = NULL; SIZE_T size; SEXP retlist = R_NilValue; LPWSTR *szArglist = NULL; int nArgs, i; if (ps__get_process_data(pid, KIND_CMDLINE, &data, &size) != 0) { return R_NilValue; } PROTECT_PTR(data); // attempt to parse the command line using Win32 API szArglist = CommandLineToArgvW(data, &nArgs); if (szArglist == NULL) { ps__set_error_from_windows_error(0); free(data); return R_NilValue; } /* TODO: We cannot PROTECT_PTR szArglist, because it has to be freed with LocalFree(). */ // arglist parsed as array of UNICODE_STRING, so convert each to // R string object and add to arg list PROTECT(retlist = allocVector(STRSXP, nArgs)); for (i = 0; i < nArgs; i++) { SET_STRING_ELT(retlist, i, ps__utf16_to_charsxp(szArglist[i], -1)); } LocalFree(szArglist); UNPROTECT(2); return retlist; } SEXP ps__get_cwd(DWORD pid) { SEXP ret; WCHAR *data = NULL; SIZE_T size; if (ps__get_process_data(pid, KIND_CWD, &data, &size) != 0) { return R_NilValue; } PROTECT_PTR(data); /* Usually has a trailing \ */ size = wcslen(data); if (data[size - 1] == L'\\') data[size - 1] = L'\0'; // convert wchar array to an R unicode string PROTECT(ret = ScalarString(ps__utf16_to_charsxp(data, -1))); UNPROTECT(2); return ret; } /* * returns an R string containing the environment variable data for the * process with given pid or NULL on error. */ SEXP ps__get_environ(DWORD pid) { SEXP ret = NULL; WCHAR *data = NULL; SIZE_T size; int numzero = 0; WCHAR *ptr, *end; if (ps__get_process_data(pid, KIND_ENVIRON, &data, &size) != 0) { return R_NilValue; } PROTECT_PTR(data); // The first zero length variable means that we can quit, // the rest seems to be garbage ptr = data; end = data + size; while (ptr < end) { if (*ptr) { numzero = 0; } else { numzero++; if (numzero == 2) break; } ptr++; } // convert wchar array to an R unicode string PROTECT(ret = ps__utf16_to_strsxp(data, (ptr - data))); UNPROTECT(2); return ret; } /* * Given a process PID and a PSYSTEM_PROCESS_INFORMATION structure * fills the structure with various process information by using * NtQuerySystemInformation. * We use this as a fallback when faster functions fail with access * denied. This is slower because it iterates over all processes. * On success return 1, else 0 with exception already set. */ int ps__get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, PVOID *retBuffer) { static ULONG initialBufferSize = 0x4000; NTSTATUS status; PVOID buffer; ULONG bufferSize; PSYSTEM_PROCESS_INFORMATION process; // get NtQuerySystemInformation typedef DWORD (_stdcall * NTQSI_PROC) (int, PVOID, ULONG, PULONG); NTQSI_PROC NtQuerySystemInformation; HINSTANCE hNtDll; hNtDll = LoadLibrary(TEXT("ntdll.dll")); NtQuerySystemInformation = (NTQSI_PROC)GetProcAddress( hNtDll, "NtQuerySystemInformation"); bufferSize = initialBufferSize; buffer = malloc(bufferSize); if (buffer == NULL) { ps__no_memory(""); goto error; } while (TRUE) { status = NtQuerySystemInformation(SystemProcessInformation, buffer, bufferSize, &bufferSize); if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_INFO_LENGTH_MISMATCH) { free(buffer); buffer = malloc(bufferSize); if (buffer == NULL) { ps__no_memory(""); goto error; } } else { break; } } if (status != 0) { ps__set_error("NtQuerySystemInformation() syscall failed"); goto error; } if (bufferSize <= 0x20000) initialBufferSize = bufferSize; process = PS__FIRST_PROCESS(buffer); do { #ifdef _WIN64 if ((DWORD64)(process->UniqueProcessId) == (DWORD64) pid) { #else if (process->UniqueProcessId == (HANDLE) pid) { #endif *retProcess = process; *retBuffer = buffer; return 1; } } while ( (process = PS__NEXT_PROCESS(process)) ); ps__no_such_process(pid, 0); goto error; error: FreeLibrary(hNtDll); if (buffer != NULL) free(buffer); return 0; } ps/src/arch/windows/ntextapi.h0000644000176200001440000002174113737276560016124 0ustar liggesusers #if !defined(__NTEXTAPI_H__) #define __NTEXTAPI_H__ #include typedef struct { LARGE_INTEGER IdleTime; LARGE_INTEGER KernelTime; LARGE_INTEGER UserTime; LARGE_INTEGER DpcTime; LARGE_INTEGER InterruptTime; ULONG InterruptCount; } _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION; typedef struct { LARGE_INTEGER IdleProcessTime; LARGE_INTEGER IoReadTransferCount; LARGE_INTEGER IoWriteTransferCount; LARGE_INTEGER IoOtherTransferCount; ULONG IoReadOperationCount; ULONG IoWriteOperationCount; ULONG IoOtherOperationCount; ULONG AvailablePages; ULONG CommittedPages; ULONG CommitLimit; ULONG PeakCommitment; ULONG PageFaultCount; ULONG CopyOnWriteCount; ULONG TransitionCount; ULONG CacheTransitionCount; ULONG DemandZeroCount; ULONG PageReadCount; ULONG PageReadIoCount; ULONG CacheReadCount; ULONG CacheIoCount; ULONG DirtyPagesWriteCount; ULONG DirtyWriteIoCount; ULONG MappedPagesWriteCount; ULONG MappedWriteIoCount; ULONG PagedPoolPages; ULONG NonPagedPoolPages; ULONG PagedPoolAllocs; ULONG PagedPoolFrees; ULONG NonPagedPoolAllocs; ULONG NonPagedPoolFrees; ULONG FreeSystemPtes; ULONG ResidentSystemCodePage; ULONG TotalSystemDriverPages; ULONG TotalSystemCodePages; ULONG NonPagedPoolLookasideHits; ULONG PagedPoolLookasideHits; ULONG AvailablePagedPoolPages; ULONG ResidentSystemCachePage; ULONG ResidentPagedPoolPage; ULONG ResidentSystemDriverPage; ULONG CcFastReadNoWait; ULONG CcFastReadWait; ULONG CcFastReadResourceMiss; ULONG CcFastReadNotPossible; ULONG CcFastMdlReadNoWait; ULONG CcFastMdlReadWait; ULONG CcFastMdlReadResourceMiss; ULONG CcFastMdlReadNotPossible; ULONG CcMapDataNoWait; ULONG CcMapDataWait; ULONG CcMapDataNoWaitMiss; ULONG CcMapDataWaitMiss; ULONG CcPinMappedDataCount; ULONG CcPinReadNoWait; ULONG CcPinReadWait; ULONG CcPinReadNoWaitMiss; ULONG CcPinReadWaitMiss; ULONG CcCopyReadNoWait; ULONG CcCopyReadWait; ULONG CcCopyReadNoWaitMiss; ULONG CcCopyReadWaitMiss; ULONG CcMdlReadNoWait; ULONG CcMdlReadWait; ULONG CcMdlReadNoWaitMiss; ULONG CcMdlReadWaitMiss; ULONG CcReadAheadIos; ULONG CcLazyWriteIos; ULONG CcLazyWritePages; ULONG CcDataFlushes; ULONG CcDataPages; ULONG ContextSwitches; ULONG FirstLevelTbFills; ULONG SecondLevelTbFills; ULONG SystemCalls; } _SYSTEM_PERFORMANCE_INFORMATION; typedef struct { ULONG ContextSwitches; ULONG DpcCount; ULONG DpcRate; ULONG TimeIncrement; ULONG DpcBypassCount; ULONG ApcBypassCount; } _SYSTEM_INTERRUPT_INFORMATION; typedef enum _KTHREAD_STATE { Initialized, Ready, Running, Standby, Terminated, Waiting, Transition, DeferredReady, GateWait, MaximumThreadState } KTHREAD_STATE, *PKTHREAD_STATE; #ifndef __MINGW32__ typedef enum _KWAIT_REASON { Executive = 0, FreePage = 1, PageIn = 2, PoolAllocation = 3, DelayExecution = 4, Suspended = 5, UserRequest = 6, WrExecutive = 7, WrFreePage = 8, WrPageIn = 9, WrPoolAllocation = 10, WrDelayExecution = 11, WrSuspended = 12, WrUserRequest = 13, WrEventPair = 14, WrQueue = 15, WrLpcReceive = 16, WrLpcReply = 17, WrVirtualMemory = 18, WrPageOut = 19, WrRendezvous = 20, Spare2 = 21, Spare3 = 22, Spare4 = 23, Spare5 = 24, WrCalloutStack = 25, WrKernel = 26, WrResource = 27, WrPushLock = 28, WrMutex = 29, WrQuantumEnd = 30, WrDispatchInt = 31, WrPreempted = 32, WrYieldExecution = 33, WrFastMutex = 34, WrGuardedMutex = 35, WrRundown = 36, MaximumWaitReason = 37 } KWAIT_REASON, *PKWAIT_REASON; typedef struct _CLIENT_ID2 { HANDLE UniqueProcess; HANDLE UniqueThread; } CLIENT_ID2, *PCLIENT_ID2; #define CLIENT_ID CLIENT_ID2 #define PCLIENT_ID PCLIENT_ID2 #endif typedef struct _SYSTEM_THREAD_INFORMATION2 { LARGE_INTEGER KernelTime; LARGE_INTEGER UserTime; LARGE_INTEGER CreateTime; ULONG WaitTime; PVOID StartAddress; CLIENT_ID ClientId; LONG Priority; LONG BasePriority; ULONG ContextSwitches; ULONG ThreadState; KWAIT_REASON WaitReason; } SYSTEM_THREAD_INFORMATION2, *PSYSTEM_THREAD_INFORMATION2; #define SYSTEM_THREAD_INFORMATION SYSTEM_THREAD_INFORMATION2 #define PSYSTEM_THREAD_INFORMATION PSYSTEM_THREAD_INFORMATION2 typedef struct _TEB *PTEB; // private typedef struct _SYSTEM_EXTENDED_THREAD_INFORMATION { SYSTEM_THREAD_INFORMATION ThreadInfo; PVOID StackBase; PVOID StackLimit; PVOID Win32StartAddress; PTEB TebBase; ULONG_PTR Reserved2; ULONG_PTR Reserved3; ULONG_PTR Reserved4; } SYSTEM_EXTENDED_THREAD_INFORMATION, *PSYSTEM_EXTENDED_THREAD_INFORMATION; typedef struct _SYSTEM_PROCESS_INFORMATION2 { ULONG NextEntryOffset; ULONG NumberOfThreads; LARGE_INTEGER SpareLi1; LARGE_INTEGER SpareLi2; LARGE_INTEGER SpareLi3; LARGE_INTEGER CreateTime; LARGE_INTEGER UserTime; LARGE_INTEGER KernelTime; UNICODE_STRING ImageName; LONG BasePriority; HANDLE UniqueProcessId; HANDLE InheritedFromUniqueProcessId; ULONG HandleCount; ULONG SessionId; ULONG_PTR PageDirectoryBase; SIZE_T PeakVirtualSize; SIZE_T VirtualSize; DWORD PageFaultCount; SIZE_T PeakWorkingSetSize; SIZE_T WorkingSetSize; SIZE_T QuotaPeakPagedPoolUsage; SIZE_T QuotaPagedPoolUsage; SIZE_T QuotaPeakNonPagedPoolUsage; SIZE_T QuotaNonPagedPoolUsage; SIZE_T PagefileUsage; SIZE_T PeakPagefileUsage; SIZE_T PrivatePageCount; LARGE_INTEGER ReadOperationCount; LARGE_INTEGER WriteOperationCount; LARGE_INTEGER OtherOperationCount; LARGE_INTEGER ReadTransferCount; LARGE_INTEGER WriteTransferCount; LARGE_INTEGER OtherTransferCount; SYSTEM_THREAD_INFORMATION Threads[1]; } SYSTEM_PROCESS_INFORMATION2, *PSYSTEM_PROCESS_INFORMATION2; #define SYSTEM_PROCESS_INFORMATION SYSTEM_PROCESS_INFORMATION2 #define PSYSTEM_PROCESS_INFORMATION PSYSTEM_PROCESS_INFORMATION2 // ================================================ // psutil.users() support // ================================================ typedef struct _WINSTATION_INFO { BYTE Reserved1[72]; ULONG SessionId; BYTE Reserved2[4]; FILETIME ConnectTime; FILETIME DisconnectTime; FILETIME LastInputTime; FILETIME LoginTime; BYTE Reserved3[1096]; FILETIME CurrentTime; } WINSTATION_INFO, *PWINSTATION_INFO; typedef BOOLEAN (WINAPI * PWINSTATIONQUERYINFORMATIONW) (HANDLE,ULONG,WINSTATIONINFOCLASS,PVOID,ULONG,PULONG); /* * NtQueryInformationProcess code taken from * http://wj32.wordpress.com/2009/01/24/howto-get-the-command-line-of-processes/ * typedefs needed to compile against ntdll functions not exposted in the API */ typedef LONG NTSTATUS; typedef NTSTATUS (NTAPI *_NtQueryInformationProcess)( HANDLE ProcessHandle, DWORD ProcessInformationClass, PVOID ProcessInformation, DWORD ProcessInformationLength, PDWORD ReturnLength ); typedef NTSTATUS (NTAPI *_NtSetInformationProcess)( HANDLE ProcessHandle, DWORD ProcessInformationClass, PVOID ProcessInformation, DWORD ProcessInformationLength ); #ifndef __MINGW32__ typedef enum _PROCESSINFOCLASS2 { _ProcessBasicInformation, ProcessQuotaLimits, ProcessIoCounters, ProcessVmCounters, ProcessTimes, ProcessBasePriority, ProcessRaisePriority, _ProcessDebugPort, ProcessExceptionPort, ProcessAccessToken, ProcessLdtInformation, ProcessLdtSize, ProcessDefaultHardErrorMode, ProcessIoPortHandlers, ProcessPooledUsageAndLimits, ProcessWorkingSetWatch, ProcessUserModeIOPL, ProcessEnableAlignmentFaultFixup, ProcessPriorityClass, ProcessWx86Information, ProcessHandleCount, ProcessAffinityMask, ProcessPriorityBoost, ProcessDeviceMap, ProcessSessionInformation, ProcessForegroundInformation, _ProcessWow64Information, /* added after XP+ */ _ProcessImageFileName, ProcessLUIDDeviceMapsEnabled, _ProcessBreakOnTermination, ProcessDebugObjectHandle, ProcessDebugFlags, ProcessHandleTracing, ProcessIoPriority, ProcessExecuteFlags, ProcessResourceManagement, ProcessCookie, ProcessImageInformation, MaxProcessInfoClass } PROCESSINFOCLASS2; #define PROCESSINFOCLASS PROCESSINFOCLASS2 #define ProcessBasicInformation _ProcessBasicInformation #define ProcessWow64Information _ProcessWow64Information #define ProcessDebugPort _ProcessDebugPort #define ProcessImageFileName _ProcessImageFileName #define ProcessBreakOnTermination _ProcessBreakOnTermination #else // values from https://msdn.microsoft.com/en-us/library/windows/desktop/ms684280(v=vs.85).aspx #define ProcessBasicInformation 0 #define ProcessWow64Information 26 #define ProcessDebugPort 7 #define ProcessImageFileName 27 #define ProcessBreakOnTermination 29 #endif #endif // __NTEXTAPI_H__ ps/src/arch/windows/process_handles.c0000644000176200001440000001672413742003274017426 0ustar liggesusers #include "process_handles.h" #include "../../common.h" #include "../../windows.h" static _NtQuerySystemInformation __NtQuerySystemInformation = NULL; static _NtQueryObject __NtQueryObject = NULL; BOOL g_initialized = FALSE; NTSTATUS g_status; HANDLE g_hFile = NULL; HANDLE g_hEvtStart = NULL; HANDLE g_hEvtFinish = NULL; HANDLE g_hThread = NULL; PUNICODE_STRING g_pNameBuffer = NULL; ULONG g_dwSize = 0; ULONG g_dwLength = 0; PVOID GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName) { return GetProcAddress(GetModuleHandleA(LibraryName), ProcName); } VOID ps__get_open_files_init() { if (g_initialized == TRUE) return; // Resolve the Windows API calls __NtQuerySystemInformation = GetLibraryProcAddress("ntdll.dll", "NtQuerySystemInformation"); __NtQueryObject = GetLibraryProcAddress("ntdll.dll", "NtQueryObject"); g_hEvtStart = CreateEvent(NULL, FALSE, FALSE, NULL); g_hEvtFinish = CreateEvent(NULL, FALSE, FALSE, NULL); g_initialized = TRUE; } SEXP ps__get_open_files(long dwPid, HANDLE hProcess) { NTSTATUS status; PSYSTEM_HANDLE_INFORMATION_EX pHandleInfo = NULL; DWORD dwInfoSize = 0x10000; DWORD dwRet = 0; PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX hHandle = NULL; DWORD i = 0; BOOLEAN error = FALSE; DWORD dwWait = 0; SEXP retlist = NULL; SEXP path = NULL; DWORD len = 20; DWORD num = 0; PROTECT_INDEX pidx; PROTECT_WITH_INDEX(retlist = allocVector(VECSXP, len), &pidx); if (g_initialized == FALSE) ps__get_open_files_init(); if (__NtQuerySystemInformation == NULL || __NtQueryObject == NULL || g_hEvtStart == NULL || g_hEvtFinish == NULL) { ps__set_error_from_windows_error(0); error = TRUE; goto cleanup; } do { if (pHandleInfo != NULL) { HeapFree(GetProcessHeap(), 0, pHandleInfo); pHandleInfo = NULL; } // NtQuerySystemInformation won't give us the correct buffer size, // so we guess by doubling the buffer size. dwInfoSize *= 2; pHandleInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwInfoSize); if (pHandleInfo == NULL) { ps__no_memory(""); error = TRUE; goto cleanup; } } while ((status = __NtQuerySystemInformation( SystemExtendedHandleInformation, pHandleInfo, dwInfoSize, &dwRet)) == STATUS_INFO_LENGTH_MISMATCH); // NtQuerySystemInformation stopped giving us STATUS_INFO_LENGTH_MISMATCH if (!NT_SUCCESS(status)) { ps__set_error_from_windows_error(HRESULT_FROM_NT(status)); error = TRUE; goto cleanup; } for (i = 0; i < pHandleInfo->NumberOfHandles; i++) { hHandle = &pHandleInfo->Handles[i]; // Check if this hHandle belongs to the PID the user specified. #ifdef _WIN64 if ((DWORD64)(hHandle->UniqueProcessId) != (DWORD64)dwPid) { #else if (hHandle->UniqueProcessId != (HANDLE)dwPid) { #endif goto loop_cleanup; } if (!DuplicateHandle(hProcess, hHandle->HandleValue, GetCurrentProcess(), &g_hFile, 0, TRUE, DUPLICATE_SAME_ACCESS)) { goto loop_cleanup; } // Guess buffer size is MAX_PATH + 1 g_dwLength = (MAX_PATH+1) * sizeof(WCHAR); do { // Release any previously allocated buffer if (g_pNameBuffer != NULL) { HeapFree(GetProcessHeap(), 0, g_pNameBuffer); g_pNameBuffer = NULL; g_dwSize = 0; } // NtQueryObject puts the required buffer size in g_dwLength // WinXP edge case puts g_dwLength == 0, just skip this handle if (g_dwLength == 0) goto loop_cleanup; g_dwSize = g_dwLength; if (g_dwSize > 0) { g_pNameBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, g_dwSize); if (g_pNameBuffer == NULL) goto loop_cleanup; } dwWait = ps__NtQueryObject(); // If the call does not return, skip this handle if (dwWait != WAIT_OBJECT_0) goto loop_cleanup; } while (g_status == STATUS_INFO_LENGTH_MISMATCH); // NtQueryObject stopped returning STATUS_INFO_LENGTH_MISMATCH if (!NT_SUCCESS(g_status)) goto loop_cleanup; // Convert to UTF-8 and append it to the return list if (g_pNameBuffer->Length > 0) { PROTECT(path = ps__convert_dos_path(g_pNameBuffer->Buffer)); if (!isNull(path)) { if (++num == len) { len *= 2; REPROTECT(retlist = Rf_lengthgets(retlist, len), pidx); } SET_VECTOR_ELT(retlist, num, ps__build_list("Oi", path, NA_INTEGER)); } UNPROTECT(1); } loop_cleanup: if (g_pNameBuffer != NULL) HeapFree(GetProcessHeap(), 0, g_pNameBuffer); g_pNameBuffer = NULL; g_dwSize = 0; g_dwLength = 0; if (g_hFile != NULL) CloseHandle(g_hFile); g_hFile = NULL; } cleanup: if (g_pNameBuffer != NULL) HeapFree(GetProcessHeap(), 0, g_pNameBuffer); g_pNameBuffer = NULL; g_dwSize = 0; g_dwLength = 0; if (g_hFile != NULL) CloseHandle(g_hFile); g_hFile = NULL; if (pHandleInfo != NULL) HeapFree(GetProcessHeap(), 0, pHandleInfo); pHandleInfo = NULL; UNPROTECT(1); if (error) return R_NilValue; return retlist; } DWORD ps__NtQueryObject() { DWORD dwWait = 0; if (g_hThread == NULL) g_hThread = CreateThread( NULL, 0, ps__NtQueryObjectThread, NULL, 0, NULL); if (g_hThread == NULL) return GetLastError(); // Signal the worker thread to start SetEvent(g_hEvtStart); // Wait for the worker thread to finish dwWait = WaitForSingleObject(g_hEvtFinish, NTQO_TIMEOUT); // If the thread hangs, kill it and cleanup if (dwWait == WAIT_TIMEOUT) { SuspendThread(g_hThread); TerminateThread(g_hThread, 1); WaitForSingleObject(g_hThread, INFINITE); CloseHandle(g_hThread); g_hThread = NULL; } return dwWait; } DWORD WINAPI ps__NtQueryObjectThread(LPVOID lpvParam) { // Loop infinitely waiting for work while (TRUE) { WaitForSingleObject(g_hEvtStart, INFINITE); g_status = __NtQueryObject(g_hFile, ObjectNameInformation, g_pNameBuffer, g_dwSize, &g_dwLength); SetEvent(g_hEvtFinish); } } SEXP ps__get_modules(HANDLE hProcess) { unsigned int nalloc = 1024; HMODULE *hMods = (HMODULE*) R_alloc(nalloc, sizeof(HMODULE)); DWORD cbNeeded; unsigned int i; SEXP result = R_NilValue; while (1) { BOOL ret = EnumProcessModules( hProcess, hMods, sizeof(HMODULE) * nalloc, &cbNeeded ); unsigned int got = cbNeeded / sizeof(HMODULE); if (ret && got <= nalloc) { result = PROTECT(Rf_allocVector(VECSXP, got)); for (i = 0; i < got; i++) { wchar_t szModName[MAX_PATH]; ret = GetModuleFileNameExW(hProcess, hMods[i], szModName, MAX_PATH); if (!ret) { UNPROTECT(1); return R_NilValue; } SEXP utf8name = PROTECT(ps__utf16_to_charsxp(szModName, -1)); SET_VECTOR_ELT(result, i, ScalarString(utf8name)); UNPROTECT(1); } break; } else if (got > nalloc) { hMods = (HMODULE*) S_realloc((char*) hMods, got + 100, nalloc, sizeof(HMODULE)); nalloc = got + 100; } else { UNPROTECT(1); return R_NilValue; } } UNPROTECT(1); return result; } ps/src/arch/windows/process_info.h0000644000176200001440000000112213737276560016750 0ustar liggesusers #if !defined(__PROCESS_INFO_H) #define __PROCESS_INFO_H #include #include "ntextapi.h" #include DWORD* ps__get_pids(DWORD *numberOfReturnedPIDs); HANDLE ps__handle_from_pid(DWORD pid); HANDLE ps__handle_from_pid_waccess(DWORD pid, DWORD dwDesiredAccess); int ps__get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, PVOID *retBuffer); int ps__assert_pid_exists(DWORD pid, char *err); int ps__assert_pid_not_exists(DWORD pid, char *err); SEXP ps__get_cmdline(DWORD pid); SEXP ps__get_cwd(DWORD pid); SEXP ps__get_environ(DWORD pid); #endif ps/src/arch/windows/wmi.c0000644000176200001440000000662414016674721015052 0ustar liggesusers/* * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * Functions related to the Windows Management Instrumentation API. */ #include "../../common.h" #include "../../windows.h" #include #include // We use an exponentially weighted moving average, just like Unix systems do // https://en.wikipedia.org/wiki/Load_(computing)#Unix-style_load_calculation // // These constants serve as the damping factor and are calculated with // 1 / exp(sampling interval in seconds / window size in seconds) // // This formula comes from linux's include/linux/sched/loadavg.h // https://github.com/torvalds/linux/blob/345671ea0f9258f410eb057b9ced9cefbbe5dc78/include/linux/sched/loadavg.h#L20-L23 #define LOADAVG_FACTOR_1F 0.9200444146293232478931553241 #define LOADAVG_FACTOR_5F 0.9834714538216174894737477501 #define LOADAVG_FACTOR_15F 0.9944598480048967508795473394 // The time interval in seconds between taking load counts, same as Linux #define SAMPLING_INTERVAL 5 double load_avg_1m = 0; double load_avg_5m = 0; double load_avg_15m = 0; int load_avg_inited = 0; VOID CALLBACK LoadAvgCallback(PVOID hCounter, BOOLEAN timedOut) { PDH_FMT_COUNTERVALUE displayValue; double currentLoad; PDH_STATUS err; err = PdhGetFormattedCounterValue( (PDH_HCOUNTER)hCounter, PDH_FMT_DOUBLE, 0, &displayValue); // Skip updating the load if we can't get the value successfully if (err != ERROR_SUCCESS) { return; } currentLoad = displayValue.doubleValue; load_avg_1m = load_avg_1m * LOADAVG_FACTOR_1F + currentLoad * \ (1.0 - LOADAVG_FACTOR_1F); load_avg_5m = load_avg_5m * LOADAVG_FACTOR_5F + currentLoad * \ (1.0 - LOADAVG_FACTOR_5F); load_avg_15m = load_avg_15m * LOADAVG_FACTOR_15F + currentLoad * \ (1.0 - LOADAVG_FACTOR_15F); } void ps__init_loadavg_counter(SEXP counter_name) { WCHAR *szCounterPath = NULL; PDH_STATUS s; BOOL ret; HQUERY hQuery; HCOUNTER hCounter; HANDLE event; HANDLE waitHandle; ps__utf8_to_utf16(CHAR(STRING_ELT(counter_name, 0)), &szCounterPath); if ((PdhOpenQueryW(NULL, 0, &hQuery)) != ERROR_SUCCESS) { ps__set_error_from_windows_error(0); ps__throw_error(); } s = PdhAddCounterW(hQuery, szCounterPath, 0, &hCounter); if (s != ERROR_SUCCESS) { ps__set_error_from_windows_error(0); ps__throw_error(); } event = CreateEventW(NULL, FALSE, FALSE, L"LoadUpdateEvent"); if (event == NULL) { ps__set_error_from_windows_error(0); ps__throw_error(); } s = PdhCollectQueryDataEx(hQuery, SAMPLING_INTERVAL, event); if (s != ERROR_SUCCESS) { ps__set_error_from_windows_error(0); ps__throw_error(); } ret = RegisterWaitForSingleObject( &waitHandle, event, (WAITORTIMERCALLBACK)LoadAvgCallback, (PVOID) hCounter, INFINITE, WT_EXECUTEDEFAULT); if (ret == 0) { ps__set_error_from_windows_error(0); ps__throw_error(); } load_avg_inited = 1; } /* * Gets the emulated 1 minute, 5 minute and 15 minute load averages * (processor queue length) for the system. * `init_loadavg_counter` must be called before this function to engage the * mechanism that records load values. */ void ps__get_loadavg(double avg[3], SEXP counter_name) { if (!load_avg_inited) ps__init_loadavg_counter(counter_name); avg[0] = load_avg_1m; avg[1] = load_avg_5m; avg[2] = load_avg_15m; } ps/src/arch/windows/process_handles.h0000644000176200001440000000561714016674721017440 0ustar liggesusers #ifndef __PROCESS_HANDLES_H__ #define __PROCESS_HANDLES_H__ #ifndef UNICODE #define UNICODE #endif #include #include #include #include #include #ifndef NT_SUCCESS #define NT_SUCCESS(x) ((x) >= 0) #endif #define STATUS_INFO_LENGTH_MISMATCH 0xc0000004 #define ObjectBasicInformation 0 #define ObjectNameInformation 1 #define ObjectTypeInformation 2 #define HANDLE_TYPE_FILE 28 #define NTQO_TIMEOUT 100 typedef NTSTATUS (NTAPI *_NtQuerySystemInformation)( ULONG SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength ); typedef NTSTATUS (NTAPI *_NtQueryObject)( HANDLE ObjectHandle, ULONG ObjectInformationClass, PVOID ObjectInformation, ULONG ObjectInformationLength, PULONG ReturnLength ); // Undocumented FILE_INFORMATION_CLASS: FileNameInformation static const SYSTEM_INFORMATION_CLASS SystemExtendedHandleInformation = (SYSTEM_INFORMATION_CLASS)64; typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX { PVOID Object; HANDLE UniqueProcessId; HANDLE HandleValue; ULONG GrantedAccess; USHORT CreatorBackTraceIndex; USHORT ObjectTypeIndex; ULONG HandleAttributes; ULONG Reserved; } SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX, *PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX; typedef struct _SYSTEM_HANDLE_INFORMATION_EX { ULONG_PTR NumberOfHandles; ULONG_PTR Reserved; SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1]; } SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX; typedef enum _POOL_TYPE { NonPagedPool, PagedPool, NonPagedPoolMustSucceed, DontUseThisType, NonPagedPoolCacheAligned, PagedPoolCacheAligned, NonPagedPoolCacheAlignedMustS } POOL_TYPE, *PPOOL_TYPE; #ifndef __MINGW32__ typedef struct _OBJECT_TYPE_INFORMATION { UNICODE_STRING Name; ULONG TotalNumberOfObjects; ULONG TotalNumberOfHandles; ULONG TotalPagedPoolUsage; ULONG TotalNonPagedPoolUsage; ULONG TotalNamePoolUsage; ULONG TotalHandleTableUsage; ULONG HighWaterNumberOfObjects; ULONG HighWaterNumberOfHandles; ULONG HighWaterPagedPoolUsage; ULONG HighWaterNonPagedPoolUsage; ULONG HighWaterNamePoolUsage; ULONG HighWaterHandleTableUsage; ULONG InvalidAttributes; GENERIC_MAPPING GenericMapping; ULONG ValidAccess; BOOLEAN SecurityRequired; BOOLEAN MaintainHandleCount; USHORT MaintainTypeList; POOL_TYPE PoolType; ULONG PagedPoolUsage; ULONG NonPagedPoolUsage; } OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; #endif PVOID GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName); VOID ps__get_open_files_init(); SEXP ps__get_open_files(long pid, HANDLE processHandle); DWORD ps__NtQueryObject(void); DWORD WINAPI ps__NtQueryObjectThread(LPVOID lpvParam); SEXP ps__get_modules(HANDLE hProcess); SEXP ps__get_loadavg(double avg[3], SEXP counter_name); #endif // __PROCESS_HANDLES_H__ ps/src/windows.h0000644000176200001440000000113213356424172013332 0ustar liggesusers /* Non-throwing internal API */ SEXP ps__exe(DWORD pid); SEXP ps__name(DWORD pid); SEXP ps__ppid(DWORD pid); SEXP ps__proc_name(DWORD pid); SEXP ps__get_cmdline(DWORD pid); SEXP ps__get_cwd(DWORD pid); SEXP ps__get_environ(DWORD pid); SEXP ps__proc_num_threads(DWORD pid); SEXP ps__proc_cpu_times(DWORD pid); SEXP ps__proc_info(DWORD pid); SEXP ps__proc_username(DWORD pid); SEXP ps__proc_suspend(DWORD pid); SEXP ps__proc_resume(DWORD pid); SEXP ps__proc_kill(DWORD pid); double ps__filetime_to_unix(FILETIME ft); SEXP ps__convert_dos_path(WCHAR *wstr); void PS__CHECK_HANDLE(ps_handle_t *handle); ps/src/ps.h0000644000176200001440000000345114016767153012273 0ustar liggesusers #ifndef R_PS_H #define R_PS_H #define R_USE_C99_IN_CXX 1 #include /* API to be used from R */ /* ps_handle class */ SEXP psll_handle(SEXP pid, SEXP time); SEXP psll_pid(SEXP p); SEXP psll_create_time(SEXP p); SEXP psll_format(SEXP p); SEXP psll_parent(SEXP p); SEXP psll_ppid(SEXP p); SEXP psll_is_running(SEXP p); SEXP psll_name(SEXP p); SEXP psll_exe(SEXP p); SEXP psll_cmdline(SEXP p); SEXP psll_status(SEXP p); SEXP psll_username(SEXP p); SEXP psll_cwd(SEXP p); SEXP psll_uids(SEXP p); SEXP psll_gids(SEXP p); SEXP psll_terminal(SEXP p); SEXP psll_environ(SEXP p); SEXP psll_num_threads(SEXP p); SEXP psll_cpu_times(SEXP p); SEXP psll_memory_info(SEXP p); SEXP psll_send_signal(SEXP p, SEXP sig); SEXP psll_suspend(SEXP p); SEXP psll_resume(SEXP p); SEXP psll_terminate(SEXP p); SEXP psll_kill(SEXP p); SEXP psll_num_fds(SEXP p); SEXP psll_open_files(SEXP p); SEXP psll_interrupt(SEXP p, SEXP ctrlc, SEXP interrupt_path); SEXP psll_connections(SEXP p); SEXP psll_get_nice(SEXP p); SEXP psll_set_nice(SEXP p, SEXP value); SEXP psll_dlls(SEXP p); /* System API */ SEXP ps__os_type(); SEXP ps__pids(); SEXP ps__boot_time(); SEXP ps__cpu_count_logical(); SEXP ps__cpu_count_physical(); SEXP ps__system_cpu_times(); SEXP ps__users(); SEXP ps__tty_size(); SEXP ps__disk_partitions(SEXP all); SEXP ps__disk_usage(SEXP paths); SEXP ps__system_memory(); SEXP ps__system_swap(); SEXP ps__loadavg(SEXP counter_name); /* Generic utils used from R */ SEXP ps__init(SEXP psenv, SEXP constenv); SEXP ps__kill_if_env(SEXP marker, SEXP after, SEXP pid, SEXP sig); SEXP ps__find_if_env(SEXP marker, SEXP after, SEXP pid); SEXP ps__inet_ntop(SEXP raw, SEXP fam); SEXP psp__zombie(); SEXP psp__waitpid(SEXP pid); SEXP psp__pid_exists(SEXP r_pid); SEXP psp__stat_st_rdev(SEXP files); SEXP psw__realpath(SEXP path); #endif ps/src/api-macos.c0000644000176200001440000006405514016767250013522 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "ps-internal.h" #include "arch/macos/process_info.h" #define PS__TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) #define PS__CHECK_KINFO(kp, handle) \ if (PS__TV2DOUBLE(kp.kp_proc.p_starttime) != handle->create_time) { \ ps__no_such_process(handle->pid, 0); \ ps__throw_error(); \ } #define PS__CHECK_HANDLE(handle) \ do { \ struct kinfo_proc kp; \ if (ps__get_kinfo_proc(handle->pid, &kp) == -1) { \ ps__set_error_from_errno(); \ ps__throw_error(); \ } \ PS__CHECK_KINFO(kp, handle); \ } while (0) #define PS__GET_STATUS(stat, result, error) \ switch(stat) { \ case SIDL: result = mkString("idle"); break; \ case SRUN: result = mkString("running"); break; \ case SSLEEP: result = mkString("sleeping"); break; \ case SSTOP: result = mkString("stopped"); break; \ case SZOMB: result = mkString("zombie"); break; \ default: error; \ } void ps__check_for_zombie(ps_handle_t *handle, int err) { struct kinfo_proc kp; int ret; if (handle->pid == 0) { ps__access_denied(""); err = 1; } else if (errno == 0 || errno == ESRCH) { ret = ps__get_kinfo_proc(handle->pid, &kp); if ((ret == -1) || (PS__TV2DOUBLE(kp.kp_proc.p_starttime) != handle->create_time)) { ps__no_such_process(handle->pid, 0); err = 1; } else if (kp.kp_proc.p_stat == SZOMB) { ps__zombie_process(handle->pid); err = 1; } else { ps__access_denied(""); } } else { ps__set_error_from_errno(); } if (err) ps__throw_error(); } void psll_finalizer(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); if (handle) free(handle); } SEXP psll_handle(SEXP pid, SEXP time) { pid_t cpid = isNull(pid) ? getpid() : INTEGER(pid)[0]; double ctime; ps_handle_t *handle; SEXP res; if (!isNull(time)) { ctime = REAL(time)[0]; } else { struct kinfo_proc kp; if (ps__get_kinfo_proc(cpid, &kp) == -1) ps__throw_error(); ctime = (double) PS__TV2DOUBLE(kp.kp_proc.p_starttime); } handle = malloc(sizeof(ps_handle_t)); if (!handle) { ps__no_memory(""); ps__throw_error(); } handle->pid = cpid; handle->create_time = ctime; handle->gone = 0; PROTECT(res = R_MakeExternalPtr(handle, R_NilValue, R_NilValue)); R_RegisterCFinalizerEx(res, psll_finalizer, /* onexit */ 0); setAttrib(res, R_ClassSymbol, mkString("ps_handle")); UNPROTECT(1); return res; } SEXP psll_format(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct kinfo_proc kp; SEXP name, status, result; if (!handle) error("Process pointer cleaned up already"); if (ps__get_kinfo_proc(handle->pid, &kp) == -1) { PROTECT(name = mkString("???")); PROTECT(status = mkString("terminated")); } else { PROTECT(name = ps__str_to_utf8(kp.kp_proc.p_comm)); PS__GET_STATUS(kp.kp_proc.p_stat, status, status = mkString("unknown")); PROTECT(status); } PROTECT(result = ps__build_list("OldO", name, (long) handle->pid, handle->create_time, status)); /* We do not check that the pid is still valid here, because we want to be able to format & print processes that have finished already. */ UNPROTECT(3); return result; } SEXP psll_parent(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct kinfo_proc kp; SEXP ppid, parent; if (!handle) error("Process pointer cleaned up already"); if (ps__get_kinfo_proc(handle->pid, &kp) == -1) ps__throw_error(); PS__CHECK_KINFO(kp, handle); /* TODO: this is a race condition, because the parent process might have just quit, so psll_handle() might fail. If this happens, then we should try to query the ppid again. */ PROTECT(ppid = ScalarInteger(kp.kp_eproc.e_ppid)); PROTECT(parent = psll_handle(ppid, R_NilValue)); UNPROTECT(2); return parent; } SEXP psll_ppid(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct kinfo_proc kp; if (!handle) error("Process pointer cleaned up already"); if (ps__get_kinfo_proc(handle->pid, &kp) == -1) ps__throw_error(); PS__CHECK_KINFO(kp, handle); return ScalarInteger(kp.kp_eproc.e_ppid); } SEXP psll_is_running(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct kinfo_proc kp; double ctime; if (!handle) error("Process pointer cleaned up already"); if (handle->gone) return ScalarLogical(0); if (ps__get_kinfo_proc(handle->pid, &kp) == -1) return ScalarLogical(0); ctime = (double) PS__TV2DOUBLE(kp.kp_proc.p_starttime); return ScalarLogical(ctime == handle->create_time); } SEXP psll_name(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct kinfo_proc kp; if (!handle) error("Process pointer cleaned up already"); if (ps__get_kinfo_proc(handle->pid, &kp) == -1) ps__throw_error(); PS__CHECK_KINFO(kp, handle); return ps__str_to_utf8(kp.kp_proc.p_comm); } SEXP psll_exe(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); int ret; char buf[PROC_PIDPATHINFO_MAXSIZE]; if (!handle) error("Process pointer cleaned up already"); ret = proc_pidpath(handle->pid, &buf, sizeof(buf)); if (ret == 0) ps__check_for_zombie(handle, 1); PS__CHECK_HANDLE(handle); return ps__str_to_utf8(buf); } SEXP psll_cmdline(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP result; if (!handle) error("Process pointer cleaned up already"); result = ps__get_cmdline(handle->pid); if (isNull(result)) ps__check_for_zombie(handle, 1); PROTECT(result); PS__CHECK_HANDLE(handle); UNPROTECT(1); return result; } SEXP psll_status(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct kinfo_proc kp; SEXP result; if (!handle) error("Process pointer cleaned up already"); if (ps__get_kinfo_proc(handle->pid, &kp) == -1) { handle->gone = 1; ps__no_such_process(handle->pid, 0); ps__throw_error(); } PS__CHECK_KINFO(kp, handle); PS__GET_STATUS(kp.kp_proc.p_stat, result, error("Unknown process status")); return result; } SEXP psll_username(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct kinfo_proc kp; SEXP ruid, pw, result; if (!handle) error("Process pointer cleaned up already"); if (ps__get_kinfo_proc(handle->pid, &kp) == -1) ps__throw_error(); PS__CHECK_KINFO(kp, handle); PROTECT(ruid = ScalarInteger(kp.kp_eproc.e_pcred.p_ruid)); PROTECT(pw = ps__get_pw_uid(ruid)); PROTECT(result = VECTOR_ELT(pw, 0)); UNPROTECT(3); return result; } SEXP psll_cwd(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); if (!handle) error("Process pointer cleaned up already"); struct proc_vnodepathinfo pathinfo; if (ps__proc_pidinfo(handle->pid, PROC_PIDVNODEPATHINFO, 0, &pathinfo, sizeof(pathinfo)) <= 0) { ps__check_for_zombie(handle, 1); } PS__CHECK_HANDLE(handle); return ps__str_to_utf8(pathinfo.pvi_cdir.vip_path); } SEXP psll_uids(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct kinfo_proc kp; SEXP result, names; if (!handle) error("Process pointer cleaned up already"); if (ps__get_kinfo_proc(handle->pid, &kp) == -1) ps__throw_error(); PS__CHECK_KINFO(kp, handle); PROTECT(result = allocVector(INTSXP, 3)); INTEGER(result)[0] = kp.kp_eproc.e_pcred.p_ruid; INTEGER(result)[1] = kp.kp_eproc.e_ucred.cr_uid; INTEGER(result)[2] = kp.kp_eproc.e_pcred.p_svuid; PROTECT(names = ps__build_string("real", "effective", "saved", NULL)); setAttrib(result, R_NamesSymbol, names); UNPROTECT(2); return result; } SEXP psll_gids(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct kinfo_proc kp; SEXP result, names; if (!handle) error("Process pointer cleaned up already"); if (ps__get_kinfo_proc(handle->pid, &kp) == -1) ps__throw_error(); PS__CHECK_KINFO(kp, handle); PROTECT(result = allocVector(INTSXP, 3)); INTEGER(result)[0] = kp.kp_eproc.e_pcred.p_rgid; INTEGER(result)[1] = kp.kp_eproc.e_ucred.cr_groups[0]; INTEGER(result)[2] = kp.kp_eproc.e_pcred.p_svgid; PROTECT(names = ps__build_string("real", "effective", "saved", NULL)); setAttrib(result, R_NamesSymbol, names); UNPROTECT(2); return result; } SEXP psll_terminal(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct kinfo_proc kp; if (!handle) error("Process pointer cleaned up already"); if (ps__get_kinfo_proc(handle->pid, &kp) == -1) ps__throw_error(); PS__CHECK_KINFO(kp, handle); if (kp.kp_eproc.e_tdev != -1) { return ScalarInteger(kp.kp_eproc.e_tdev); } else { return ScalarInteger(NA_INTEGER); } } SEXP psll_environ(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP result; if (!handle) error("Process pointer cleaned up already"); result = ps__get_environ(handle->pid); if (isNull(result)) ps__check_for_zombie(handle, 1); PROTECT(result); PS__CHECK_HANDLE(handle); UNPROTECT(1); return result; } SEXP psll_num_threads(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct proc_taskinfo pti; if (!handle) error("Process pointer cleaned up already"); if (ps__proc_pidinfo(handle->pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti)) <= 0) { ps__check_for_zombie(handle, 1); } PS__CHECK_HANDLE(handle); return ScalarInteger(pti.pti_threadnum); } SEXP psll_cpu_times(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct proc_taskinfo pti; SEXP result, names; if (!handle) error("Process pointer cleaned up already"); if (ps__proc_pidinfo(handle->pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti)) <= 0) { ps__check_for_zombie(handle, 1); } PS__CHECK_HANDLE(handle); PROTECT(result = allocVector(REALSXP, 4)); REAL(result)[0] = (double) pti.pti_total_user / 1000000000.0; REAL(result)[1] = (double) pti.pti_total_system / 1000000000.0; REAL(result)[2] = REAL(result)[3] = NA_REAL; PROTECT(names = ps__build_string("user", "system", "children_user", "children_system", NULL)); setAttrib(result, R_NamesSymbol, names); UNPROTECT(2); return result; } SEXP psll_memory_info(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct proc_taskinfo pti; SEXP result, names; if (!handle) error("Process pointer cleaned up already"); if (ps__proc_pidinfo(handle->pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti)) <= 0) { ps__check_for_zombie(handle, 1); } PS__CHECK_HANDLE(handle); PROTECT(result = allocVector(REALSXP, 4)); REAL(result)[0] = (double) pti.pti_resident_size; REAL(result)[1] = (double) pti.pti_virtual_size; REAL(result)[2] = (double) pti.pti_faults; REAL(result)[3] = (double) pti.pti_pageins; PROTECT(names = ps__build_string("rss", "vms", "pfaults", "pageins", NULL)); setAttrib(result, R_NamesSymbol, names); UNPROTECT(2); return result; } SEXP ps__boot_time() { #define MIB_SIZE 2 int mib[MIB_SIZE]; size_t size; struct timeval boottime; double unixtime = 0.0; mib[0] = CTL_KERN; mib[1] = KERN_BOOTTIME; size = sizeof(boottime); if (sysctl(mib, MIB_SIZE, &boottime, &size, NULL, 0) != -1) { unixtime = boottime.tv_sec + boottime.tv_usec / 1.e6; } else { ps__set_error_from_errno(); ps__throw_error(); } return ScalarReal(unixtime); } SEXP ps__cpu_count_logical() { int num = 0; size_t size = sizeof(int); if (sysctlbyname("hw.logicalcpu", &num, &size, NULL, 2)) return ScalarInteger(NA_INTEGER); else return ScalarInteger(num); } SEXP ps__cpu_count_physical() { int num = 0; size_t size = sizeof(int); if (sysctlbyname("hw.physicalcpu", &num, &size, NULL, 0)) return ScalarInteger(NA_INTEGER); else return ScalarInteger(num); } SEXP ps__kill_if_env(SEXP marker, SEXP after, SEXP pid, SEXP sig) { const char *cmarker = CHAR(STRING_ELT(marker, 0)); pid_t cpid = INTEGER(pid)[0]; int csig = INTEGER(sig)[0]; SEXP env; size_t i, len; PROTECT(env = ps__get_environ(cpid)); if (isNull(env)) { ps__set_error_from_errno(); ps__throw_error(); } len = LENGTH(env); for (i = 0; i < len; i++) { if (strstr(CHAR(STRING_ELT(env, i)), cmarker)) { struct kinfo_proc kp; int kpret = ps__get_kinfo_proc(cpid, &kp); int ret = kill(cpid, csig); if (ret == -1) { if (errno == ESRCH) { ps__no_such_process(cpid, 0); } else if (errno == EPERM || errno == EACCES) { ps__access_denied(""); } else { ps__set_error_from_errno(); } ps__throw_error(); } UNPROTECT(1); if (kpret != -1) { return ps__str_to_utf8(kp.kp_proc.p_comm); } else { return mkString("???"); } } } UNPROTECT(1); return R_NilValue; } SEXP ps__find_if_env(SEXP marker, SEXP after, SEXP pid) { const char *cmarker = CHAR(STRING_ELT(marker, 0)); pid_t cpid = INTEGER(pid)[0]; SEXP env; size_t i, len; SEXP phandle; ps_handle_t *handle; PROTECT(phandle = psll_handle(pid, R_NilValue)); handle = R_ExternalPtrAddr(phandle); PROTECT(env = ps__get_environ(cpid)); if (isNull(env)) { ps__set_error_from_errno(); ps__throw_error(); } len = LENGTH(env); for (i = 0; i < len; i++) { if (strstr(CHAR(STRING_ELT(env, i)), cmarker)) { UNPROTECT(2); PS__CHECK_HANDLE(handle); return phandle; } } UNPROTECT(2); return R_NilValue; } SEXP psll_num_fds(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); struct proc_fdinfo *fds_pointer; int pidinfo_result; int num; pid_t pid; if (!handle) error("Process pointer cleaned up already"); pid = handle->pid; pidinfo_result = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); if (pidinfo_result <= 0) ps__check_for_zombie(handle, 1); fds_pointer = malloc(pidinfo_result); if (fds_pointer == NULL) { ps__no_memory(""); ps__throw_error(); } pidinfo_result = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fds_pointer, pidinfo_result); if (pidinfo_result <= 0) { free(fds_pointer); ps__check_for_zombie(handle, 1); } num = (pidinfo_result / PROC_PIDLISTFD_SIZE); free(fds_pointer); PS__CHECK_HANDLE(handle); return ScalarInteger(num); } SEXP psll_open_files(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); long pid; int pidinfo_result; int iterations; int i; unsigned long nb; struct proc_fdinfo *fds_pointer = NULL; struct proc_fdinfo *fdp_pointer; struct vnode_fdinfowithpath vi; SEXP result; if (!handle) error("Process pointer cleaned up already"); pid = handle->pid; pidinfo_result = ps__proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); if (pidinfo_result <= 0) goto error; fds_pointer = malloc(pidinfo_result); if (fds_pointer == NULL) { ps__no_memory(""); goto error; } pidinfo_result = ps__proc_pidinfo( pid, PROC_PIDLISTFDS, 0, fds_pointer, pidinfo_result); if (pidinfo_result <= 0) goto error; iterations = (pidinfo_result / PROC_PIDLISTFD_SIZE); PROTECT(result = allocVector(VECSXP, iterations)); for (i = 0; i < iterations; i++) { fdp_pointer = &fds_pointer[i]; if (fdp_pointer->proc_fdtype == PROX_FDTYPE_VNODE) { errno = 0; nb = proc_pidfdinfo((pid_t)pid, fdp_pointer->proc_fd, PROC_PIDFDVNODEPATHINFO, &vi, sizeof(vi)); // --- errors checking if ((nb <= 0) || nb < sizeof(vi)) { if ((errno == ENOENT) || (errno == EBADF)) { // no such file or directory or bad file descriptor; // let's assume the file has been closed or removed continue; } else { ps__set_error( "proc_pidinfo(PROC_PIDFDVNODEPATHINFO) failed for %d", (int) pid); goto error; } } // --- /errors checking SET_VECTOR_ELT( result, i, ps__build_list("si", vi.pvip.vip_path, (int) fdp_pointer->proc_fd)); } } free(fds_pointer); PS__CHECK_HANDLE(handle); UNPROTECT(1); return result; error: if (fds_pointer != NULL) free(fds_pointer); ps__check_for_zombie(handle, 1); return R_NilValue; } SEXP psll_connections(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); long pid; int pidinfo_result; int iterations; int i; unsigned long nb; struct proc_fdinfo *fds_pointer = NULL; struct proc_fdinfo *fdp_pointer; struct socket_fdinfo si; SEXP result; if (!handle) error("Process pointer cleaned up already"); pid = handle->pid; if (pid == 0) return allocVector(VECSXP, 0); pidinfo_result = ps__proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); if (pidinfo_result <= 0) goto error; fds_pointer = malloc(pidinfo_result); if (fds_pointer == NULL) { ps__no_memory(""); ps__throw_error(); } pidinfo_result = ps__proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fds_pointer, pidinfo_result); if (pidinfo_result <= 0) goto error; iterations = (pidinfo_result / PROC_PIDLISTFD_SIZE); PROTECT(result = allocVector(VECSXP, iterations)); for (i = 0; i < iterations; i++) { fdp_pointer = &fds_pointer[i]; if (fdp_pointer->proc_fdtype == PROX_FDTYPE_SOCKET) { errno = 0; nb = proc_pidfdinfo((pid_t)pid, fdp_pointer->proc_fd, PROC_PIDFDSOCKETINFO, &si, sizeof(si)); // --- errors checking if ((nb <= 0) || (nb < sizeof(si))) { if (errno == EBADF) { // let's assume socket has been closed continue; } else { ps__set_error("proc_pidinfo(PROC_PIDFDSOCKETINFO) failed for %d", (int) pid); goto error; } } // --- /errors checking // int fd, family, type, lport, rport, state; char lip[512], rip[512]; SEXP tuple; fd = (int)fdp_pointer->proc_fd; family = si.psi.soi_family; type = si.psi.soi_type; if ((family == AF_INET) || (family == AF_INET6)) { if (family == AF_INET) { inet_ntop(AF_INET, &si.psi.soi_proto.pri_tcp.tcpsi_ini. \ insi_laddr.ina_46.i46a_addr4, lip, sizeof(lip)); inet_ntop(AF_INET, &si.psi.soi_proto.pri_tcp.tcpsi_ini.insi_faddr. \ ina_46.i46a_addr4, rip, sizeof(rip)); } else { inet_ntop(AF_INET6, &si.psi.soi_proto.pri_tcp.tcpsi_ini. \ insi_laddr.ina_6, lip, sizeof(lip)); inet_ntop(AF_INET6, &si.psi.soi_proto.pri_tcp.tcpsi_ini. \ insi_faddr.ina_6, rip, sizeof(rip)); } // check for inet_ntop failures if (errno != 0) { ps__set_error_from_errno(0); goto error; } lport = ntohs(si.psi.soi_proto.pri_tcp.tcpsi_ini.insi_lport); rport = ntohs(si.psi.soi_proto.pri_tcp.tcpsi_ini.insi_fport); if (type == SOCK_STREAM) state = (int)si.psi.soi_proto.pri_tcp.tcpsi_state; else state = NA_INTEGER; // construct the python list PROTECT(tuple = ps__build_list("iiisisii", fd, family, type, lip, lport, rip, rport,state)); SET_VECTOR_ELT(result, i, tuple); UNPROTECT(1); } else if (family == AF_UNIX) { SEXP laddr, raddr; PROTECT(laddr = ps__str_to_utf8(si.psi.soi_proto.pri_un.unsi_addr.ua_sun.sun_path)); PROTECT(raddr = ps__str_to_utf8(si.psi.soi_proto.pri_un.unsi_caddr.ua_sun.sun_path)); // construct the python list PROTECT(tuple = ps__build_list("iiiOiOii", fd, family, type, laddr, 0, raddr, 0, NA_INTEGER)); SET_VECTOR_ELT(result, i, tuple); UNPROTECT(3); } } } free(fds_pointer); PS__CHECK_HANDLE(handle); UNPROTECT(1); return result; error: if (fds_pointer) free(fds_pointer); ps__check_for_zombie(handle, 1); return R_NilValue; } SEXP ps__users() { struct utmpx *utx; SEXP result; PROTECT_INDEX pidx; int len = 10, num = 0; PROTECT_WITH_INDEX(result = allocVector(VECSXP, len), &pidx); while ((utx = getutxent()) != NULL) { if (utx->ut_type != USER_PROCESS) continue; if (++num == len) { len *= 2; REPROTECT(result = Rf_lengthgets(result, len), pidx); } SET_VECTOR_ELT( result, num, ps__build_list("sssdi", utx->ut_user, utx->ut_line, utx->ut_host, (double) PS__TV2DOUBLE(utx->ut_tv), utx->ut_pid)); } endutxent(); UNPROTECT(1); return result; } SEXP ps__disk_partitions(SEXP all) { int num; int i; int len; uint64_t flags; char opts[400]; struct statfs *fs = NULL; SEXP result; // get the number of mount points num = getfsstat(NULL, 0, MNT_NOWAIT); if (num == -1) { ps__set_error_from_errno(); goto error; } len = sizeof(*fs) * num; fs = malloc(len); if (fs == NULL) { ps__no_memory(""); goto error; } num = getfsstat(fs, len, MNT_NOWAIT); if (num == -1) { ps__set_error_from_errno(); goto error; } PROTECT(result = allocVector(VECSXP, num)); for (i = 0; i < num; i++) { opts[0] = 0; flags = fs[i].f_flags; // see sys/mount.h if (flags & MNT_RDONLY) strlcat(opts, "ro", sizeof(opts)); else strlcat(opts, "rw", sizeof(opts)); if (flags & MNT_SYNCHRONOUS) strlcat(opts, ",sync", sizeof(opts)); if (flags & MNT_NOEXEC) strlcat(opts, ",noexec", sizeof(opts)); if (flags & MNT_NOSUID) strlcat(opts, ",nosuid", sizeof(opts)); if (flags & MNT_UNION) strlcat(opts, ",union", sizeof(opts)); if (flags & MNT_ASYNC) strlcat(opts, ",async", sizeof(opts)); if (flags & MNT_EXPORTED) strlcat(opts, ",exported", sizeof(opts)); if (flags & MNT_QUARANTINE) strlcat(opts, ",quarantine", sizeof(opts)); if (flags & MNT_LOCAL) strlcat(opts, ",local", sizeof(opts)); if (flags & MNT_QUOTA) strlcat(opts, ",quota", sizeof(opts)); if (flags & MNT_ROOTFS) strlcat(opts, ",rootfs", sizeof(opts)); if (flags & MNT_DOVOLFS) strlcat(opts, ",dovolfs", sizeof(opts)); if (flags & MNT_DONTBROWSE) strlcat(opts, ",dontbrowse", sizeof(opts)); if (flags & MNT_IGNORE_OWNERSHIP) strlcat(opts, ",ignore-ownership", sizeof(opts)); if (flags & MNT_AUTOMOUNTED) strlcat(opts, ",automounted", sizeof(opts)); if (flags & MNT_JOURNALED) strlcat(opts, ",journaled", sizeof(opts)); if (flags & MNT_NOUSERXATTR) strlcat(opts, ",nouserxattr", sizeof(opts)); if (flags & MNT_DEFWRITE) strlcat(opts, ",defwrite", sizeof(opts)); if (flags & MNT_MULTILABEL) strlcat(opts, ",multilabel", sizeof(opts)); if (flags & MNT_NOATIME) strlcat(opts, ",noatime", sizeof(opts)); if (flags & MNT_UPDATE) strlcat(opts, ",update", sizeof(opts)); if (flags & MNT_RELOAD) strlcat(opts, ",reload", sizeof(opts)); if (flags & MNT_FORCE) strlcat(opts, ",force", sizeof(opts)); if (flags & MNT_CMDFLAGS) strlcat(opts, ",cmdflags", sizeof(opts)); SET_VECTOR_ELT( result, i, ps__build_list("ssss", fs[i].f_mntfromname, fs[i].f_mntonname, fs[i].f_fstypename, opts)); } free(fs); UNPROTECT(1); return result; error: if (fs != NULL) free(fs); ps__throw_error(); return R_NilValue; } int ps__sys_vminfo(vm_statistics_data_t *vmstat) { kern_return_t ret; mach_msg_type_number_t count = sizeof(*vmstat) / sizeof(integer_t); mach_port_t mport = mach_host_self(); ret = host_statistics(mport, HOST_VM_INFO, (host_info_t)vmstat, &count); if (ret != KERN_SUCCESS) { ps__set_error( "host_statistics(HOST_VM_INFO) syscall failed: %s", mach_error_string(ret) ); return 1; } mach_port_deallocate(mach_task_self(), mport); return 0; } SEXP ps__system_memory() { int mib[2]; uint64_t total; size_t len = sizeof(total); vm_statistics_data_t vm; int pagesize = getpagesize(); // physical mem mib[0] = CTL_HW; mib[1] = HW_MEMSIZE; // This is also available as sysctlbyname("hw.memsize"). if (sysctl(mib, 2, &total, &len, NULL, 0)) { if (errno != 0) { ps__set_error_from_errno(); } else { ps__set_error("sysctl(HW_MEMSIZE) syscall failed"); } ps__throw_error(); } // vm if (ps__sys_vminfo(&vm)) ps__throw_error(); return ps__build_named_list( "dddddd", "total", (double) total, "active", (double) vm.active_count * pagesize, "inactive", (double) vm.inactive_count * pagesize, "wired", (double) vm.wire_count * pagesize, "free", (double) vm.free_count * pagesize, "speculative", (double) vm.speculative_count * pagesize ); } SEXP ps__system_swap() { int mib[2]; size_t size; struct xsw_usage totals; vm_statistics_data_t vm; int pagesize = getpagesize(); mib[0] = CTL_VM; mib[1] = VM_SWAPUSAGE; size = sizeof(totals); if (sysctl(mib, 2, &totals, &size, NULL, 0) == -1) { if (errno != 0) { ps__set_error_from_errno(); } else { ps__set_error("sysctl(VM_SWAPUSAGE) syscall failed"); } ps__throw_error(); } // vm if (ps__sys_vminfo(&vm)) ps__throw_error(); return ps__build_named_list( "ddddd", "total", (double) totals.xsu_total, "used", (double) totals.xsu_used, "free", (double) totals.xsu_avail, "sin", (double) vm.pageins * pagesize, "sout", (double) vm.pageouts * pagesize ); } #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) SEXP ps__loadavg(SEXP counter_name) { struct loadavg info; size_t size = sizeof(info); int which[] = {CTL_VM, VM_LOADAVG}; if (sysctl(which, ARRAY_SIZE(which), &info, &size, NULL, 0) < 0) { ps__set_error_from_errno(); ps__throw_error(); } SEXP ret = PROTECT(allocVector(REALSXP, 3)); REAL(ret)[0] = (double) info.ldavg[0] / info.fscale; REAL(ret)[1] = (double) info.ldavg[1] / info.fscale; REAL(ret)[2] = (double) info.ldavg[2] / info.fscale; UNPROTECT(1); return ret; } SEXP ps__system_cpu_times() { mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; kern_return_t error; host_cpu_load_info_data_t r_load; mach_port_t host_port = mach_host_self(); error = host_statistics(host_port, HOST_CPU_LOAD_INFO, (host_info_t)&r_load, &count); mach_port_deallocate(mach_task_self(), host_port); if (error != KERN_SUCCESS) { ps__set_error_from_errno(); ps__throw_error(); } const char *nms[] = { "user", "nice", "system", "idle", "" }; SEXP ret = PROTECT(Rf_mkNamed(REALSXP, nms)); REAL(ret)[0] = (double) r_load.cpu_ticks[CPU_STATE_USER] / CLK_TCK; REAL(ret)[1] = (double) r_load.cpu_ticks[CPU_STATE_NICE] / CLK_TCK; REAL(ret)[2] = (double) r_load.cpu_ticks[CPU_STATE_SYSTEM] / CLK_TCK; REAL(ret)[3] = (double) r_load.cpu_ticks[CPU_STATE_IDLE] / CLK_TCK; UNPROTECT(1); return ret; } ps/src/api-windows-conn.c0000644000176200001440000002445213356424172015041 0ustar liggesusers // Fixes clash between winsock2.h and windows.h #define WIN32_LEAN_AND_MEAN #include "common.h" #include "windows.h" #include #if (_WIN32_WINNT >= 0x0600) // Windows Vista and above #include #endif #include #include #include #define BYTESWAP_USHORT(x) ((((USHORT)(x) << 8) | ((USHORT)(x) >> 8)) & 0xffff) #ifndef AF_INET6 #define AF_INET6 23 #endif static DWORD MyGetExtendedTcpTable(ULONG address_family, PVOID * data, DWORD * size) { // Due to other processes being active on the machine, it's possible // that the size of the table increases between the moment where we // query the size and the moment where we query the data. Therefore, it's // important to call this in a loop to retry if that happens. // // Also, since we may loop a theoretically unbounded number of times here, // release the GIL while we're doing this. DWORD error = ERROR_INSUFFICIENT_BUFFER; *size = 0; *data = NULL; error = GetExtendedTcpTable(NULL, size, FALSE, address_family, TCP_TABLE_OWNER_PID_ALL, 0); while (error == ERROR_INSUFFICIENT_BUFFER) { *data = malloc(*size); if (*data == NULL) { error = ERROR_NOT_ENOUGH_MEMORY; continue; } error = GetExtendedTcpTable(*data, size, FALSE, address_family, TCP_TABLE_OWNER_PID_ALL, 0); if (error != NO_ERROR) { free(*data); *data = NULL; } } return error; } static DWORD MyGetExtendedUdpTable(ULONG address_family, PVOID * data, DWORD * size) { // Due to other processes being active on the machine, it's possible // that the size of the table increases between the moment where we // query the size and the moment where we query the data. Therefore, it's // important to call this in a loop to retry if that happens. // // Also, since we may loop a theoretically unbounded number of times here, // release the GIL while we're doing this. DWORD error = ERROR_INSUFFICIENT_BUFFER; *size = 0; *data = NULL; error = GetExtendedUdpTable(NULL, size, FALSE, address_family, UDP_TABLE_OWNER_PID, 0); while (error == ERROR_INSUFFICIENT_BUFFER) { *data = malloc(*size); if (*data == NULL) { error = ERROR_NOT_ENOUGH_MEMORY; continue; } error = GetExtendedUdpTable(*data, size, FALSE, address_family, UDP_TABLE_OWNER_PID, 0); if (error != NO_ERROR) { free(*data); *data = NULL; } } return error; } SEXP psll_connections(SEXP p) { static long null_address[4] = { 0, 0, 0, 0 }; unsigned long pid; typedef PSTR (NTAPI * _RtlIpv4AddressToStringA)(struct in_addr *, PSTR); _RtlIpv4AddressToStringA rtlIpv4AddressToStringA; typedef PSTR (NTAPI * _RtlIpv6AddressToStringA)(struct in6_addr *, PSTR); _RtlIpv6AddressToStringA rtlIpv6AddressToStringA; PVOID table = NULL; DWORD tableSize; DWORD err; PMIB_TCPTABLE_OWNER_PID tcp4Table; PMIB_UDPTABLE_OWNER_PID udp4Table; PMIB_TCP6TABLE_OWNER_PID tcp6Table; PMIB_UDP6TABLE_OWNER_PID udp6Table; ULONG i; CHAR addressBufferLocal[65]; CHAR addressBufferRemote[65]; SEXP retlist; PROTECT_INDEX ret_idx; int ret_len = 10, ret_num = -1; SEXP conn; char *addr_local = NULL, *addr_remote = NULL; int port_local = 0, port_remote = 0; char *empty_string = ""; ps_handle_t *handle = R_ExternalPtrAddr(p); if (!handle) error("Process pointer cleaned up already"); pid = handle->pid; // Import some functions. { HMODULE ntdll; ntdll = LoadLibrary(TEXT("ntdll.dll")); rtlIpv4AddressToStringA = (_RtlIpv4AddressToStringA)GetProcAddress( ntdll, "RtlIpv4AddressToStringA"); rtlIpv6AddressToStringA = (_RtlIpv6AddressToStringA)GetProcAddress( ntdll, "RtlIpv6AddressToStringA"); /* TODO: Check these two function pointers */ FreeLibrary(ntdll); } PROTECT_WITH_INDEX(retlist = allocVector(VECSXP, ret_len), &ret_idx); // TCP IPv4 table = NULL; conn = R_NilValue; addr_local = empty_string; addr_remote = empty_string; port_local = NA_INTEGER; port_remote = NA_INTEGER; tableSize = 0; err = MyGetExtendedTcpTable(AF_INET, &table, &tableSize); if (err == ERROR_NOT_ENOUGH_MEMORY) { ps__no_memory(""); ps__throw_error(); } if (err == NO_ERROR) { tcp4Table = table; for (i = 0; i < tcp4Table->dwNumEntries; i++) { if (tcp4Table->table[i].dwOwningPid != pid) continue; if (tcp4Table->table[i].dwLocalAddr != 0 || tcp4Table->table[i].dwLocalPort != 0) { struct in_addr addr; addr.S_un.S_addr = tcp4Table->table[i].dwLocalAddr; rtlIpv4AddressToStringA(&addr, addressBufferLocal); addr_local = addressBufferLocal; port_local = BYTESWAP_USHORT(tcp4Table->table[i].dwLocalPort); } // On Windows <= XP, remote addr is filled even if socket // is in LISTEN mode in which case we just ignore it. if ((tcp4Table->table[i].dwRemoteAddr != 0 || tcp4Table->table[i].dwRemotePort != 0) && (tcp4Table->table[i].dwState != MIB_TCP_STATE_LISTEN)) { struct in_addr addr; addr.S_un.S_addr = tcp4Table->table[i].dwRemoteAddr; rtlIpv4AddressToStringA(&addr, addressBufferRemote); addr_remote = addressBufferRemote; port_remote = BYTESWAP_USHORT(tcp4Table->table[i].dwRemotePort); } PROTECT(conn = ps__build_list("iiisisii", NA_INTEGER, AF_INET, SOCK_STREAM, addr_local, port_local, addr_remote, port_remote, tcp4Table->table[i].dwState)); if (++ret_num == ret_len) { ret_len *= 2; REPROTECT(retlist = Rf_lengthgets(retlist, ret_len), ret_idx); } SET_VECTOR_ELT(retlist, ret_num, conn); UNPROTECT(1); } } else { ps__set_error_from_windows_error(err); free(table); ps__throw_error(); } free(table); table = NULL; tableSize = 0; // TCP IPv6 table = NULL; conn = R_NilValue; addr_local = empty_string; addr_remote = empty_string; port_local = NA_INTEGER; port_remote = NA_INTEGER; tableSize = 0; err = MyGetExtendedTcpTable(AF_INET6, &table, &tableSize); if (err == ERROR_NOT_ENOUGH_MEMORY) { ps__no_memory(""); ps__throw_error(); } if (err == NO_ERROR) { tcp6Table = table; for (i = 0; i < tcp6Table->dwNumEntries; i++) { if (tcp6Table->table[i].dwOwningPid != pid) continue; if (memcmp(tcp6Table->table[i].ucLocalAddr, null_address, 16) != 0 || tcp6Table->table[i].dwLocalPort != 0) { struct in6_addr addr; memcpy(&addr, tcp6Table->table[i].ucLocalAddr, 16); rtlIpv6AddressToStringA(&addr, addressBufferLocal); addr_local = addressBufferLocal; port_local = BYTESWAP_USHORT(tcp6Table->table[i].dwLocalPort); } // On Windows <= XP, remote addr is filled even if socket // is in LISTEN mode in which case we just ignore it. if ((memcmp(tcp6Table->table[i].ucRemoteAddr, null_address, 16) != 0 || tcp6Table->table[i].dwRemotePort != 0) && (tcp6Table->table[i].dwState != MIB_TCP_STATE_LISTEN)) { struct in6_addr addr; memcpy(&addr, tcp6Table->table[i].ucRemoteAddr, 16); rtlIpv6AddressToStringA(&addr, addressBufferRemote); addr_remote = addressBufferRemote; port_remote = BYTESWAP_USHORT(tcp6Table->table[i].dwRemotePort); } PROTECT(conn = ps__build_list("iiisisii", NA_INTEGER, AF_INET6, SOCK_STREAM, addr_local, port_local, addr_remote, port_remote, tcp6Table->table[i].dwState)); if (++ret_num == ret_len) { ret_len *= 2; REPROTECT(retlist = Rf_lengthgets(retlist, ret_len), ret_idx); } SET_VECTOR_ELT(retlist, ret_num, conn); UNPROTECT(1); } } else { ps__set_error_from_windows_error(err); free(table); ps__throw_error(); } free(table); table = NULL; tableSize = 0; // UDP IPv4 table = NULL; conn = R_NilValue; addr_local = empty_string; addr_remote = empty_string; port_local = NA_INTEGER; port_remote = NA_INTEGER; tableSize = 0; err = MyGetExtendedUdpTable(AF_INET, &table, &tableSize); if (err == ERROR_NOT_ENOUGH_MEMORY) { ps__no_memory(""); ps__throw_error(); } if (err == NO_ERROR) { udp4Table = table; for (i = 0; i < udp4Table->dwNumEntries; i++) { if (udp4Table->table[i].dwOwningPid != pid) continue; if (udp4Table->table[i].dwLocalAddr != 0 || udp4Table->table[i].dwLocalPort != 0) { struct in_addr addr; addr.S_un.S_addr = udp4Table->table[i].dwLocalAddr; rtlIpv4AddressToStringA(&addr, addressBufferLocal); addr_local = addressBufferLocal; port_local = BYTESWAP_USHORT(udp4Table->table[i].dwLocalPort); } PROTECT(conn = ps__build_list("iiisisii", NA_INTEGER, AF_INET, SOCK_DGRAM, addr_local, port_local, addr_remote, port_remote, PS__CONN_NONE)); if (++ret_num == ret_len) { ret_len *= 2; REPROTECT(retlist = Rf_lengthgets(retlist, ret_len), ret_idx); } SET_VECTOR_ELT(retlist, ret_num, conn); UNPROTECT(1); } } else { ps__set_error_from_windows_error(err); free(table); ps__throw_error(); } free(table); table = NULL; tableSize = 0; // UDP IPv6 table = NULL; conn = R_NilValue; addr_local = empty_string; addr_remote = empty_string; port_local = NA_INTEGER; port_remote = NA_INTEGER; tableSize = 0; err = MyGetExtendedUdpTable(AF_INET6, &table, &tableSize); if (err == ERROR_NOT_ENOUGH_MEMORY) { ps__no_memory(""); ps__throw_error(); } if (err == NO_ERROR) { udp6Table = table; for (i = 0; i < udp6Table->dwNumEntries; i++) { if (udp6Table->table[i].dwOwningPid != pid) continue; if (memcmp(udp6Table->table[i].ucLocalAddr, null_address, 16) != 0 || udp6Table->table[i].dwLocalPort != 0) { struct in6_addr addr; memcpy(&addr, udp6Table->table[i].ucLocalAddr, 16); rtlIpv6AddressToStringA(&addr, addressBufferLocal); addr_local = addressBufferLocal; port_local = BYTESWAP_USHORT(udp6Table->table[i].dwLocalPort); } PROTECT(conn = ps__build_list("iiisisii", NA_INTEGER, AF_INET6, SOCK_DGRAM, addr_local, port_local, addr_remote, port_remote, PS__CONN_NONE)); if (++ret_num == ret_len) { ret_len *= 2; REPROTECT(retlist = Rf_lengthgets(retlist, ret_len), ret_idx); } SET_VECTOR_ELT(retlist, ret_num, conn); UNPROTECT(1); } } else { ps__set_error_from_windows_error(err); free(table); ps__throw_error(); } free(table); table = NULL; tableSize = 0; PS__CHECK_HANDLE(handle); UNPROTECT(1); return retlist; } ps/src/ps-internal.h0000644000176200001440000000415613737120534014103 0ustar liggesusers #ifndef R_PS_INTERNAL_H #define R_PS_INTERNAL_H #include "config.h" #include "ps.h" #ifdef PS__MACOS #include typedef struct { pid_t pid; double create_time; int gone; } ps_handle_t; #endif #ifdef PS__LINUX #include #include #include typedef struct { pid_t pid; double create_time; int gone; } ps_handle_t; #endif #ifdef PS__WINDOWS #include typedef struct { DWORD pid; double create_time; int gone; FILETIME wtime; } ps_handle_t; #endif #ifndef PS__MACOS #ifndef PS__LINUX #ifndef PS__WINDOWS typedef struct { int pid; double create_time; } ps_handle_t; #endif #endif #endif /* Internal utilities */ SEXP psll__is_running(ps_handle_t *handle); SEXP ps__get_pw_uid(SEXP r_uid); SEXP ps__define_signals(); SEXP ps__define_errno(); SEXP ps__define_socket_address_families(); SEXP ps__define_socket_types(); /* Errors */ extern SEXP ps__last_error; void ps__protect_free_finalizer(SEXP ptr); #define PROTECT_PTR(ptr) do { \ SEXP x = PROTECT(R_MakeExternalPtr(ptr, R_NilValue, R_NilValue)); \ R_RegisterCFinalizerEx(x, ps__protect_free_finalizer, 1); \ } while (0) void *ps__set_error(const char *msg, ...); void *ps__set_error_from_errno(); SEXP ps__throw_error(); void *ps__access_denied(const char *msg); void *ps__no_such_process(long pid, const char *name); void *ps__zombie_process(long pid); void *ps__no_memory(const char *msg); void *ps__not_implemented(const char *what); void ps__check_for_zombie(ps_handle_t *handle, int err); void *ps__set_error_from_windows_error(long err); /* Build SEXP values */ SEXP ps__build_string(const char *str, ...); SEXP ps__build_list(const char *template, ...); SEXP ps__build_named_list(const char *template, ...); /* String conversions */ SEXP ps__str_to_utf8(const char *str); SEXP ps__str_to_utf8_size(const char *str, size_t size); #ifdef PS__WINDOWS SEXP ps__utf16_to_rawsxp(const WCHAR* ws, int size); SEXP ps__utf16_to_charsxp(const WCHAR* ws, int size); SEXP ps__utf16_to_strsxp(const WCHAR* ws, int size); int ps__utf8_to_utf16(const char* s, WCHAR** ws_ptr); #endif #endif ps/src/common.c0000644000176200001440000000130013737276560013131 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include #include #include #include "common.h" // Global vars. int PS__DEBUG = 0; int PS__TESTING = 0; /* * Enable testing mode. This has the same effect as setting PS__TESTING * env var. This dual method exists because updating os.environ on * Windows has no effect. Called on unit tests setup. */ void ps__set_testing() { PS__TESTING = 1; } /* * Print a debug message on stderr. No-op if PS__DEBUG env var is not set. */ void ps__debug(const char* format, ...) { va_list argptr; va_start(argptr, format); REprintf("psutil-debug> "); REvprintf(format, argptr); REprintf("\n"); va_end(argptr); } ps/src/dummy.c0000644000176200001440000001075514016770207012776 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include "config.h" #include void *ps__not_implemented(const char *what); SEXP ps__throw_error(); SEXP ps__dummy(const char *what) { ps__not_implemented(what); ps__throw_error(); return R_NilValue; } /* Not implemented on Linux, only on Windows and macOS */ #ifdef PS__LINUX #ifndef PS__MACOS #ifndef PS__WINDOWS SEXP ps__pids() { return ps__dummy("ps_pids"); } #endif #endif #endif /* Not implemented on Windows */ #ifdef PS__WINDOWS #ifndef PS__POSIX SEXP psp__pid_exists(SEXP x) { return ps__dummy("psp__pid_exists"); } SEXP psp__zombie() { return ps__dummy("psp__zombie"); } SEXP psp__waitpid(SEXP x) { return ps__dummy("psp__waitpid"); } SEXP psp__stat_st_rdev(SEXP x) { return ps__dummy("psp__stat_st_rdev"); } #endif #endif /* Only implemented on windows */ #ifdef PS__POSIX #ifndef PS__WINDOWS SEXP psw__realpath(SEXP x) { return ps__dummy("psw__realpath"); } SEXP psll_dlls(SEXP x) { return ps__dummy("psll_dlls"); } #endif #endif #ifndef PS__LINUX #if defined(PS__WINDOWS) || defined(PS__MACOS) SEXP ps__inet_ntop(SEXP x, SEXP y) { return ps__dummy("ps__inet_ntop"); } #endif #endif /* All C functions called from R */ #ifndef PS__MACOS #ifndef PS__LINUX #ifndef PS__WINDOWS SEXP ps__pids() { return ps__dummy("ps_pids"); } SEXP ps__boot_time() { return ps__dummy("ps_boot_time"); } SEXP ps__cpu_count_logical() { return ps__dummy("ps_cpu_count"); } SEXP ps__cpu_count_physical() { return ps__dummy("ps_cpu_count"); } SEXP ps__users() { ps__dummy("ps_users"); } SEXP ps__loadavg(SEXP x) { return ps__dummy("ps_loadavg"); } SEXP ps__tty_size() { ps__dummy("ps_tty_size"); } SEXP ps__disk_partitions(SEXP x) { ps__dummy("ps_disk_partitions"); } SEXP ps__disk_usage() { ps__dummy("ps_disk_usage"); } SEXP ps__system_cpu_times() { return ps__dummy("ps_system_cpu_times"); } SEXP ps__system_memory() { ps__dummy("ps_system_memory"); } SEXP ps__system_swap() { ps__dummy("ps_system_swap"); } SEXP psll_handle(SEXP x, SEXP y) { return ps__dummy("ps_handle"); } SEXP psll_format(SEXP x) { return ps__dummy("ps_format"); } SEXP psll_parent(SEXP x) { return ps__dummy("ps_handle"); } SEXP psll_ppid(SEXP x) { return ps__dummy("ps_handle"); } SEXP psll_is_running(SEXP x) { return ps__dummy("ps_is_running"); } SEXP psll_name(SEXP x) { return ps__dummy("ps_name"); } SEXP psll_exe(SEXP x) { return ps__dummy("ps_exe"); } SEXP psll_cmdline(SEXP x) { return ps__dummy("ps_cmdline"); } SEXP psll_status(SEXP x) { return ps__dummy("ps_status"); } SEXP psll_username(SEXP x) { return ps__dummy("ps_username"); } SEXP psll_cwd(SEXP x) { return ps__dummy("ps_cwd"); } SEXP psll_uids(SEXP x) { return ps__dummy("ps_uids"); } SEXP psll_gids(SEXP x) { return ps__dummy("ps_gids"); } SEXP psll_terminal(SEXP x) { return ps__dummy("ps_terminal"); } SEXP psll_environ(SEXP x) { return ps__dummy("ps_environ"); } SEXP psll_num_threads(SEXP x) { return ps__dummy("ps_num_threads"); } SEXP psll_cpu_times(SEXP x) { return ps__dummy("ps_cpu_times"); } SEXP psll_memory_info() { return ps__dummy("ps_memory_info"); } SEXP psll_send_signal(SEXP x, SEXP y) { return ps__dummy("ps_send_signal"); } SEXP psll_suspend(SEXP x) { return ps__dummy("ps_suspend"); } SEXP psll_resume(SEXP x) { return ps__dummy("ps_resume"); } SEXP psll_terminate(SEXP x) { return ps__dummy("ps_terminate"); } SEXP psll_kill(SEXP x) { return ps__dummy("ps_kill"); } SEXP psll_num_fds(SEXP x) { return ps__dummy("ps_num_fds"); } SEXP psll_open_files(SEXP x) { return ps__dummy("ps_open_files"); } SEXP psll_interrupt(SEXP x, SEXP y, SEXP z) { return ps__dummy("ps_interrupt"); } SEXP psll_connections(SEXP x) { return ps__dummy("ps_connections"); } SEXP psll_get_nice(SEXP x) { return ps__dummy("ps_get_nice"); } SEXP psll_set_nice(SEXP x, SEXP y) { return ps__dummy("ps_set_nice"); } SEXP ps__init(SEXP x, SEXP y) { return R_NilValue; /* this needs to run to load package */ } SEXP ps__kill_if_env(SEXP x, SEXP y, SEXP z, SEXP a) { return ps__dummy("ps__kill_if_env"); } SEXP ps__find_if_env(SEXP x, SEXP y, SEXP z) { return ps__dummy("ps__find_if_env"); } SEXP psp__pid_exists(SEXP x) { return ps__dummy("psp__pid_exists"); } SEXP psp__stat_st_rdev(SEXP x) { return ps__dummy("psp__stat_st_rdev"); } SEXP psp__zombie() { return ps__dummy("psp__zombie"); } SEXP psp__waitpid(SEXP x) { return ps__dummy("psp__waitpid"); } SEXP psw__realpath(SEXP x) { return ps__dummy("psw__realpath"); } SEXP psll_dlls(SEXP x) { return ps__dummy("psll_dlls"); } SEXP ps__inet_ntop(SEXP x, SEXP y) { return ps__dummy("ps__inet_ntop"); } #endif #endif #endif ps/src/px.c0000644000176200001440000001117513620625516012271 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include #include #include #include #include #include #include void usage() { fprintf(stderr, "Usage: px [command arg] [command arg] ...\n\n"); fprintf(stderr, "Commands:\n"); fprintf(stderr, " sleep -- " "sleep for a number os seconds\n"); fprintf(stderr, " out -- " "print string to stdout\n"); fprintf(stderr, " err -- " "print string to stderr\n"); fprintf(stderr, " outln -- " "print string to stdout, add newline\n"); fprintf(stderr, " errln -- " "print string to stderr, add newline\n"); fprintf(stderr, " cat -- " "print file to stdout\n"); fprintf(stderr, " return -- " "return with exitcode\n"); fprintf(stderr, " write -- " "write to file descriptor\n"); fprintf(stderr, " echo -- " "echo from fd to another fd\n"); fprintf(stderr, " getenv -- " "environment variable to stdout\n"); } void cat2(int f, const char *s) { char buf[8192]; long n; while ((n = read(f, buf, (long) sizeof buf)) > 0) { if (write(1, buf, n) != n){ fprintf(stderr, "write error copying %s", s); exit(6); } } if (n < 0) fprintf(stderr, "error reading %s", s); } void cat(const char* filename) { int f = open(filename, O_RDONLY); if (f < 0) { fprintf(stderr, "can't open %s", filename); exit(6); } cat2(f, filename); close(f); } int write_to_fd(int fd, const char *s) { size_t len = strlen(s); ssize_t ret = write(fd, s, len); if (ret != len) { fprintf(stderr, "Cannot write to fd '%d'\n", fd); return 1; } return 0; } int echo_from_fd(int fd1, int fd2, int nbytes) { char buffer[nbytes + 1]; ssize_t ret; buffer[nbytes] = '\0'; ret = read(fd1, buffer, nbytes); if (ret == -1) { fprintf(stderr, "Cannot read from fd '%d', %s\n", fd1, strerror(errno)); return 1; } if (ret != nbytes) { fprintf(stderr, "Cannot read from fd '%d' (%d bytes)\n", fd1, (int) ret); return 1; } if (write_to_fd(fd2, buffer)) return 1; fflush(stdout); fflush(stderr); return 0; } int main(int argc, const char **argv) { int num, idx, ret, fd, fd2, nbytes; double fnum; if (argc == 2 && !strcmp("--help", argv[1])) { usage(); return 0; } for (idx = 1; idx < argc; idx++) { const char *cmd = argv[idx]; if (idx + 1 == argc) { fprintf(stderr, "Missing argument for '%s'\n", argv[idx]); return 5; } if (!strcmp("sleep", cmd)) { ret = sscanf(argv[++idx], "%lf", &fnum); if (ret != 1) { fprintf(stderr, "Invalid seconds for px sleep: '%s'\n", argv[idx]); return 3; } num = (int) fnum; sleep(num); fnum = fnum - num; if (fnum > 0) usleep((useconds_t) (fnum * 1000.0 * 1000.0)); } else if (!strcmp("out", cmd)) { printf("%s", argv[++idx]); fflush(stdout); } else if (!strcmp("err", cmd)) { fprintf(stderr, "%s", argv[++idx]); } else if (!strcmp("outln", cmd)) { printf("%s\n", argv[++idx]); fflush(stdout); } else if (!strcmp("errln", cmd)) { fprintf(stderr, "%s\n", argv[++idx]); } else if (!strcmp("cat", cmd)) { cat(argv[++idx]); } else if (!strcmp("return", cmd)) { ret = sscanf(argv[++idx], "%d", &num); if (ret != 1) { fprintf(stderr, "Invalid exit code for px return: '%s'\n", argv[idx]); return 4; } return num; } else if (!strcmp("write", cmd)) { if (idx + 2 >= argc) { fprintf(stderr, "Missing argument(s) for 'write'\n"); return 5; } ret = sscanf(argv[++idx], "%d", &fd); if (ret != 1) { fprintf(stderr, "Invalid fd for write: '%s'\n", argv[idx]); return 6; } if (write_to_fd(fd, argv[++idx])) return 7; } else if (!strcmp("echo", cmd)) { if (idx + 3 >= argc) { fprintf(stderr, "Missing argument(s) for 'read'\n"); return 7; } ret = sscanf(argv[++idx], "%d", &fd); ret = ret + sscanf(argv[++idx], "%d", &fd2); ret = ret + sscanf(argv[++idx], "%d", &nbytes); if (ret != 3) { fprintf(stderr, "Invalid fd1, fd2 or nbytes for read: '%s', '%s', '%s'\n", argv[idx-2], argv[idx-1], argv[idx]); return 8; } if (echo_from_fd(fd, fd2, nbytes)) return 9; } else if (!strcmp("getenv", cmd)) { printf("%s\n", getenv(argv[++idx])); fflush(stdout); } else { fprintf(stderr, "Unknown px command: '%s'\n", cmd); return 2; } } return 0; } ps/src/extra.c0000644000176200001440000002303613620625516012764 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include #include #include #include #include #include "ps-internal.h" #ifdef PS__WINDOWS #include #endif char ps__last_error_string[1024]; SEXP ps__last_error; /* TODO: these should throw real error objects */ void *ps__set_error_impl(const char *class, int system_errno, long pid, const char *msg, ...) { va_list args; const char *ps_error = "ps_error", *error = "error", *condition = "condition"; SEXP rclass; va_start(args, msg); vsnprintf(ps__last_error_string, sizeof(ps__last_error_string) - 1, msg, args); va_end(args); SET_VECTOR_ELT(ps__last_error, 0, mkString(ps__last_error_string)); if (class) { PROTECT(rclass = ps__build_string(class, ps_error, error, condition, NULL)); } else { PROTECT(rclass = ps__build_string(ps_error, error, condition, NULL)); } SET_VECTOR_ELT(ps__last_error, 1, rclass); UNPROTECT(1); SET_VECTOR_ELT(ps__last_error, 2, ScalarInteger(system_errno)); SET_VECTOR_ELT(ps__last_error, 3, ScalarInteger((int) pid)); return NULL; } void *ps__set_error(const char *msg, ...) { va_list args; va_start(args, msg); ps__set_error_impl(0, 0, NA_INTEGER, msg, args); va_end(args); return NULL; } void *ps__no_such_process(long pid, const char *name) { const char *class = "no_such_process"; return ps__set_error_impl( class, 0, pid, "No such process, pid %ld, %s", pid, name ? name : "???"); } void *ps__access_denied(const char *msg) { return ps__set_error_impl("access_denied", 0, NA_INTEGER, msg && strlen(msg) ? msg : "Permission denied"); } void *ps__zombie_process(long pid) { return ps__set_error_impl("zombie_process", 0, pid, "Process is a zombie, pid %ld", pid); } void *ps__not_implemented(const char *what) { return ps__set_error_impl("not_implemented", 0, NA_INTEGER, "Not implemented on this platform: `%s`", what); } void *ps__no_memory(const char *msg) { return ps__set_error_impl("no_memory", #ifdef PS__WINDOWS ERROR_NOT_ENOUGH_MEMORY, #else ENOMEM, #endif NA_INTEGER, msg && strlen(msg) ? msg : "Out of memory"); } void *ps__set_error_from_errno() { if (errno) { return ps__set_error_impl("os_error", errno, NA_INTEGER, "%s", strerror(errno)); } else { return ps__set_error_impl(0, errno, NA_INTEGER, "run time error"); } } #ifdef PS__WINDOWS void *ps__set_error_from_windows_error(long err) { /* TODO: get the actual message */ if (!err) err = GetLastError(); return ps__set_error_impl("os_error", err, NA_INTEGER, "System error: %i", err); } #endif SEXP ps__throw_error() { SEXP stopfun, call, out; Rf_setAttrib(ps__last_error, R_ClassSymbol, VECTOR_ELT(ps__last_error, 1)); PROTECT(stopfun = Rf_findFun(Rf_install("stop"), R_BaseEnv)); PROTECT(call = Rf_lang2(stopfun, ps__last_error)); PROTECT(out = Rf_eval(call, R_GlobalEnv)); UNPROTECT(3); return out; } void ps__protect_free_finalizer(SEXP ptr) { void *vptr = R_ExternalPtrAddr(ptr); if (!vptr) return; free(vptr); } SEXP ps__str_to_utf8(const char *str) { /* TODO: really convert */ return mkString(str); } SEXP ps__str_to_utf8_size(const char *str, size_t size) { /* TODO: really convert */ return ScalarString(Rf_mkCharLen(str, (int) size)); } #ifdef PS__WINDOWS int ps__utf8_to_utf16(const char* s, WCHAR** ws_ptr) { int ws_len, r; WCHAR* ws; ws_len = MultiByteToWideChar( /* CodePage = */ CP_UTF8, /* dwFlags = */ 0, /* lpMultiByteStr = */ s, /* cbMultiByte = */ -1, /* lpWideCharStr = */ NULL, /* cchWideChar = */ 0); if (ws_len <= 0) { return GetLastError(); } ws = (WCHAR*) R_alloc(ws_len, sizeof(WCHAR)); if (ws == NULL) { return ERROR_OUTOFMEMORY; } r = MultiByteToWideChar( /* CodePage = */ CP_UTF8, /* dwFlags = */ 0, /* lpMultiByteStr = */ s, /* cbMultiBytes = */ -1, /* lpWideCharStr = */ ws, /* cchWideChar = */ ws_len); if (r != ws_len) { error("processx error interpreting UTF8 command or arguments"); } *ws_ptr = ws; return 0; } SEXP ps__utf16_to_rawsxp(const WCHAR* ws, int size) { int s_len, r; SEXP s; s_len = WideCharToMultiByte( /* CodePage = */ CP_UTF8, /* dwFlags = */ 0, /* lpWideCharStr = */ ws, /* cchWideChar = */ size, /* lpMultiByteStr = */ NULL, /* cbMultiByte = */ 0, /* lpDefaultChar = */ NULL, /* lpUsedDefaultChar = */ NULL); if (s_len <= 0) { error("error converting wide chars to UTF-8"); } PROTECT(s = allocVector(RAWSXP, s_len)); r = WideCharToMultiByte( /* CodePage = */ CP_UTF8, /* dwFlags = */ 0, /* lpWideCharStr = */ ws, /* cchWideChar = */ size, /* lpMultiByteStr = */ (char*) RAW(s), /* cbMultiByte = */ s_len, /* lpDefaultChar = */ NULL, /* lpUsedDefaultChar = */ NULL); if (r != s_len) { error("error converting wide chars to UTF-8"); } UNPROTECT(1); return s; } SEXP ps__utf16_to_strsxp(const WCHAR* ws, int size) { SEXP r, s; int r_len, s_len, idx, notr = 0; char *ptr, *end, *prev; PROTECT(r = ps__utf16_to_rawsxp(ws, size)); r_len = LENGTH(r); ptr = (char*) RAW(r); end = ptr + r_len; s_len = 0; while (ptr < end) { if (!*ptr) s_len++; ptr++; } /* If ther is no \0 at the end */ if (r_len > 0 && *(end - 1) != '\0') notr = 1; PROTECT(s = allocVector(STRSXP, s_len + notr)); prev = ptr = (char*) RAW(r); idx = 0; while (ptr < end) { while (ptr < end && *ptr) ptr++; SET_STRING_ELT(s, idx++, mkCharLen(prev, ptr - prev)); prev = ++ptr; } if (notr) { SET_STRING_ELT(s, idx++, mkCharLen(prev, end - prev)); } UNPROTECT(2); return s; } SEXP ps__utf16_to_charsxp(const WCHAR* ws, int size) { SEXP r, s; PROTECT(r = ps__utf16_to_rawsxp(ws, size)); PROTECT(s = mkCharLen((char*) RAW(r), LENGTH(r) - 1)); UNPROTECT(2); return s; } #endif static size_t ps__build_template_length(const char *template) { size_t len = 0; size_t n = strlen(template); size_t i; for (i = 0; i < n; i++) { len += isalpha(template[i]) != 0; } return len; } SEXP ps__build_string(const char *str, ...) { va_list args; size_t len = 1; SEXP res; char *s; /* Length 0 character */ if (!str) return(allocVector(STRSXP, 0)); /* Count the length first */ va_start(args, str); while (va_arg(args, char*)) len++; va_end(args); PROTECT(res = allocVector(STRSXP, len)); SET_STRING_ELT(res, 0, mkChar(str)); len = 1; va_start(args, str); while ((s = va_arg(args, char*))) { SET_STRING_ELT(res, len++, mkChar(s)); } va_end(args); UNPROTECT(1); return res; } static SEXP ps__build_list_impl(const char *template, int named, va_list args) { size_t slen = strlen(template); size_t len = ps__build_template_length(template); SEXP res = PROTECT(allocVector(VECSXP, len)); SEXP names = named ? PROTECT(allocVector(STRSXP, len)) : R_NilValue; int ptr = 0, lptr = 0; char *tmp1; size_t tmp2; char tmp3; while (ptr < slen) { if (named) { SET_STRING_ELT(names, lptr, mkChar(va_arg(args, const char*))); } switch(template[ptr]) { case 's': case 'z': case 'U': tmp1 = va_arg(args, char*); SET_VECTOR_ELT(res, lptr, tmp1 ? mkString(tmp1) : R_NilValue); break; case 'y': tmp1 = va_arg(args, char*); tmp2 = strlen(tmp1); SET_VECTOR_ELT(res, lptr, allocVector(RAWSXP, tmp2)); memcpy(RAW(VECTOR_ELT(res, lptr)), tmp1, tmp2); break; case 'u': error("'u' is not implemented yet"); break; case 'i': case 'b': case 'h': case 'B': case 'H': SET_VECTOR_ELT(res, lptr, ScalarInteger(va_arg(args, int))); break; case 'l': SET_VECTOR_ELT(res, lptr, ScalarReal(va_arg(args, long int))); break; case 'I': SET_VECTOR_ELT(res, lptr, ScalarReal(va_arg(args, unsigned int))); break; case 'k': SET_VECTOR_ELT(res, lptr, ScalarReal(va_arg(args, unsigned long))); break; case 'L': SET_VECTOR_ELT(res, lptr, ScalarReal(va_arg(args, long long))); break; case 'K': SET_VECTOR_ELT(res, lptr, ScalarReal(va_arg(args, unsigned long long))); break; case 'n': SET_VECTOR_ELT(res, lptr, ScalarReal(va_arg(args, size_t))); break; case 'c': tmp3 = (char) va_arg(args, int); SET_VECTOR_ELT(res, lptr, ScalarRaw(tmp3)); break; case 'C': tmp3 = (char) va_arg(args, int); SET_VECTOR_ELT(res, lptr, ScalarString(mkCharLen(&tmp3, 1))); break; case 'd': case 'f': SET_VECTOR_ELT(res, lptr, ScalarReal(va_arg(args, double))); break; case 'D': error("'D' is not implemented yet"); break; case 'S': case 'N': case 'O': SET_VECTOR_ELT(res, lptr, (SEXP) va_arg(args, void*)); break; default: error("Unknown conversion key: `%c`", template[ptr]); } ptr++; lptr++; } if (named) { setAttrib(res, R_NamesSymbol, names); UNPROTECT(1); } UNPROTECT(1); return res; } SEXP ps__build_list(const char *template, ...) { va_list args; SEXP res; va_start(args, template); res = PROTECT(ps__build_list_impl(template, 0, args)); va_end(args); UNPROTECT(1); return res; } SEXP ps__build_named_list(const char *template, ...) { va_list args; SEXP res; va_start(args, template); res = PROTECT(ps__build_list_impl(template, 1, args)); va_end(args); UNPROTECT(1); return res; } ps/src/common.h0000644000176200001440000000121013737276560013136 0ustar liggesusers #ifndef R_PS_COMMON_H #define R_PS_COMMON_H #define R_USE_C99_IN_CXX 1 #include #include #include "ps-internal.h" /* ---------------------------------------------------------------------*/ /* Internals */ /* ---------------------------------------------------------------------*/ extern int PS__TESTING; extern int PS__DEBUG; // a signaler for connections without an actual status static const int PS__CONN_NONE = 128; void ps__set_testing(); void ps__debug(const char* format, ...); void R_init_ps(DllInfo *dll); #endif // PSUTIL_PSUTIL_COMMON_H ps/src/api-windows.c0000644000176200001440000010561514016767153014112 0ustar liggesusers #include "common.h" #include "windows.h" #include "arch/windows/process_info.h" #include "arch/windows/process_handles.h" #include #include #include #include static void psll_finalizer(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); if (handle) free(handle); } static int ps__create_time_raw(DWORD pid, FILETIME *ftCreate) { HANDLE hProcess = ps__handle_from_pid(pid); FILETIME ftExit, ftKernel, ftUser; if (! hProcess) goto error; /* error set already */ if (! GetProcessTimes(hProcess, ftCreate, &ftExit, &ftKernel, &ftUser)) { if (GetLastError() == ERROR_ACCESS_DENIED) { // usually means the process has died so we throw a // NoSuchProcess here ps__no_such_process(pid, 0); } else { ps__set_error_from_windows_error(0); } goto error; } CloseHandle(hProcess); return 0; error: if (hProcess) CloseHandle(hProcess); return -1; } double ps__filetime_to_unix(FILETIME ft) { long long ll, secs, nsecs; ll = ((LONGLONG) ft.dwHighDateTime) << 32; ll += ft.dwLowDateTime - 116444736000000000LL; secs = ll / 10000000; nsecs = ll % 10000000; return (double) secs + ((double) nsecs) / 10000000; } static SEXP ps__is_running(ps_handle_t *handle) { FILETIME ftCreate; int ret = ps__create_time_raw(handle->pid, &ftCreate); if (ret) return ScalarLogical(0); if (handle->wtime.dwHighDateTime) { if (handle->wtime.dwHighDateTime != ftCreate.dwHighDateTime || handle->wtime.dwLowDateTime != ftCreate.dwLowDateTime) { return ScalarLogical(0); } else { return ScalarLogical(1); } } else { double unix_time = ps__filetime_to_unix(ftCreate); if (fabs(unix_time - handle->create_time) > 0.01) { return ScalarLogical(0); } else { return ScalarLogical(1); } } /* Never reached */ return R_NilValue; } void PS__CHECK_HANDLE(ps_handle_t *handle) { SEXP ret = ps__is_running(handle); if (!LOGICAL(ret)[0]) { ps__no_such_process(handle->pid, 0); ps__throw_error(); } } static int ps__proc_is_suspended(DWORD pid) { ULONG i; PSYSTEM_PROCESS_INFORMATION process; PVOID buffer; if (! ps__get_proc_info(pid, &process, &buffer)) return -1; for (i = 0; i < process->NumberOfThreads; i++) { if (process->Threads[i].ThreadState != Waiting || process->Threads[i].WaitReason != Suspended) { free(buffer); return 0; } } free(buffer); return 1; } static SEXP ps__status(DWORD pid) { int ret = ps__proc_is_suspended(pid); if (ret < 0) return R_NilValue; return ret ? mkString("stopped") : mkString("running"); } SEXP psll_handle(SEXP pid, SEXP time) { DWORD cpid = isNull(pid) ? GetCurrentProcessId() : INTEGER(pid)[0]; double ctime; ps_handle_t *handle; SEXP res; FILETIME ftCreate = { 0, 0 }; if (!isNull(time)) { ctime = REAL(time)[0]; } else { int ret = ps__create_time_raw(cpid, &ftCreate); if (ret) ps__throw_error(); ctime = ps__filetime_to_unix(ftCreate); } handle = malloc(sizeof(ps_handle_t)); if (!handle) { ps__no_memory(""); ps__throw_error(); } handle->pid = cpid; handle->create_time = ctime; handle->gone = 0; handle->wtime = ftCreate; PROTECT(res = R_MakeExternalPtr(handle, R_NilValue, R_NilValue)); R_RegisterCFinalizerEx(res, psll_finalizer, /* onexit */ 0); setAttrib(res, R_ClassSymbol, mkString("ps_handle")); UNPROTECT(1); return res; } SEXP psll_format(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP name, status, result; if (!handle) error("Process pointer cleaned up already"); name = ps__name(handle->pid); if (isNull(name)) name = mkString("???"); PROTECT(name); status = ps__status(handle->pid); if (isNull(status)) status = mkString("terminated"); PROTECT(status); PROTECT(result = ps__build_list("OldO", name, (long) handle->pid, handle->create_time, status)); /* We do not check that the pid is still valid here, because we want to be able to format & print processes that have finished already. */ UNPROTECT(3); return result; } SEXP psll_parent(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP pid, parent, time; DWORD ppid; FILETIME pft; double pctime; int ret; if (!handle) error("Process pointer cleaned up already"); PROTECT(pid = ps__ppid(handle->pid)); if (isNull(pid)) ps__throw_error(); PS__CHECK_HANDLE(handle); ppid = INTEGER(pid)[0]; ret = ps__create_time_raw(ppid, &pft); if (ret) ps__throw_error(); pctime = ps__filetime_to_unix(pft); if (pctime > handle->create_time) { ps__no_such_process(ppid, 0); ps__throw_error(); } PROTECT(time = ScalarReal(pctime)); PROTECT(parent = psll_handle(pid, time)); UNPROTECT(3); return parent; } SEXP psll_ppid(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP ret; if (!handle) error("Process pointer cleaned up already"); PROTECT(ret = ps__ppid(handle->pid)); if (isNull(ret)) ps__throw_error(); PS__CHECK_HANDLE(handle); UNPROTECT(1); return ret; } SEXP psll_is_running(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); if (!handle) error("Process pointer cleaned up already"); return ps__is_running(handle); } SEXP psll_name(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP ret; if (!handle) error("Process pointer cleaned up already"); PROTECT(ret = ps__name(handle->pid)); if (isNull(ret)) ps__throw_error(); PS__CHECK_HANDLE(handle); UNPROTECT(1); return ret; } SEXP psll_exe(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP result; if (!handle) error("Process pointer cleaned up already"); result = ps__exe(handle->pid); if (isNull(result)) ps__throw_error(); PS__CHECK_HANDLE(handle); return result; } SEXP psll_cmdline(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP result; if (!handle) error("Process pointer cleaned up already"); result = ps__get_cmdline(handle->pid); if (isNull(result)) ps__throw_error(); PS__CHECK_HANDLE(handle); return result; } SEXP psll_status(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP ret; if (!handle) error("Process pointer cleaned up already"); ret = ps__status(handle->pid); if (isNull(ret)) ps__throw_error(); PS__CHECK_HANDLE(handle); return ret; } SEXP psll_username(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP ret, result; size_t len1, len2; char *str; if (!handle) error("Process pointer cleaned up already"); if (handle->pid == 0 || handle->pid == 4) { return mkString("NT AUTHORITY\\SYSTEM"); } PROTECT(ret = ps__proc_username(handle->pid)); if (isNull(ret)) ps__throw_error(); PS__CHECK_HANDLE(handle); len1 = strlen(CHAR(STRING_ELT(ret, 0))); len2 = strlen(CHAR(STRING_ELT(ret, 1))); str = R_alloc(len1 + len2 + 2, sizeof(char)); memcpy(str, CHAR(STRING_ELT(ret, 0)), len1); *(str + len1) = '\\'; memcpy(str + len1 + 1, CHAR(STRING_ELT(ret, 1)), len2 + 1); PROTECT(result = mkString(str)); UNPROTECT(2); return result; } SEXP psll_cwd(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP result; if (!handle) error("Process pointer cleaned up already"); if (handle->pid == 0 || handle->pid == 4) { ps__access_denied(""); ps__throw_error(); } PROTECT(result = ps__get_cwd(handle->pid)); if (isNull(result)) ps__throw_error(); PS__CHECK_HANDLE(handle); UNPROTECT(1); return result; } SEXP psll_uids(SEXP p) { ps__not_implemented("uids"); ps__throw_error(); return R_NilValue; } SEXP psll_gids(SEXP p) { ps__not_implemented("uids"); ps__throw_error(); return R_NilValue; } SEXP psll_terminal(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); if (!handle) error("Process pointer cleaned up already"); PS__CHECK_HANDLE(handle); return ScalarString(NA_STRING); } SEXP psll_environ(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP result; if (!handle) error("Process pointer cleaned up already"); if (handle->pid == 0 || handle->pid == 4) { ps__access_denied(""); ps__throw_error(); } PROTECT(result = ps__get_environ(handle->pid)); if (isNull(result)) ps__throw_error(); PS__CHECK_HANDLE(handle); UNPROTECT(1); return result; } SEXP psll_num_threads(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP result; if (!handle) error("Process pointer cleaned up already"); PROTECT(result = ps__proc_num_threads(handle->pid)); if (isNull(result)) ps__throw_error(); PS__CHECK_HANDLE(handle); UNPROTECT(1); return result; } SEXP psll_cpu_times(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP result, result2, names; if (!handle) error("Process pointer cleaned up already"); PROTECT(result = ps__proc_cpu_times(handle->pid)); if (!isNull(result)) { PS__CHECK_HANDLE(handle); UNPROTECT(1); return result; } UNPROTECT(1); PROTECT(result2 = ps__proc_info(handle->pid)); if (isNull(result2)) { UNPROTECT(1); ps__throw_error(); } PS__CHECK_HANDLE(handle); PROTECT(result = allocVector(REALSXP, 4)); REAL(result)[0] = REAL(VECTOR_ELT(result2, 2))[0]; REAL(result)[1] = REAL(VECTOR_ELT(result2, 3))[0]; REAL(result)[2] = REAL(result)[3] = NA_REAL; PROTECT(names = ps__build_string("user", "system", "children_user", "children_system", NULL)); setAttrib(result, R_NamesSymbol, names); UNPROTECT(3); return result; } SEXP psll_memory_info(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP result, result2, names; if (!handle) error("Process pointer cleaned up already"); PROTECT(result2 = ps__proc_info(handle->pid)); if (isNull(result2)) { UNPROTECT(1); ps__throw_error(); } PS__CHECK_HANDLE(handle); PROTECT(result = allocVector(REALSXP, 12)); REAL(result)[0] = REAL(VECTOR_ELT(result2, 12))[0]; REAL(result)[1] = REAL(VECTOR_ELT(result2, 13))[0]; REAL(result)[2] = REAL(VECTOR_ELT(result2, 14))[0]; REAL(result)[3] = REAL(VECTOR_ELT(result2, 15))[0]; REAL(result)[4] = REAL(VECTOR_ELT(result2, 16))[0]; REAL(result)[5] = REAL(VECTOR_ELT(result2, 17))[0]; REAL(result)[6] = REAL(VECTOR_ELT(result2, 18))[0]; REAL(result)[7] = REAL(VECTOR_ELT(result2, 19))[0]; REAL(result)[8] = REAL(VECTOR_ELT(result2, 20))[0]; REAL(result)[9] = REAL(VECTOR_ELT(result2, 21))[0]; REAL(result)[10] = REAL(VECTOR_ELT(result2, 14))[0]; REAL(result)[11] = REAL(VECTOR_ELT(result2, 19))[0]; PROTECT(names = ps__build_string( "num_page_faults", "peak_wset", "wset", "peak_paged_pool", "paged_pool", "peak_non_paged_pool", "non_paged_pool", "pagefile", "peak_pagefile", "mem_private", "rss", "vms", NULL)); setAttrib(result, R_NamesSymbol, names); UNPROTECT(3); return result; } SEXP psll_send_signal(SEXP p, SEXP sig) { ps__not_implemented("uids"); ps__throw_error(); return R_NilValue; } SEXP psll_suspend(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP running, ret; if (!handle) error("Process pointer cleaned up already"); HANDLE hProcess = ps__handle_from_pid(handle->pid); if (!hProcess) goto error; running = ps__is_running(handle); if (!LOGICAL(running)[0]) { ps__no_such_process(handle->pid, 0); goto error; } PROTECT(ret = ps__proc_suspend(handle->pid)); if (isNull(ret)) goto error; UNPROTECT(1); return R_NilValue; error: if (hProcess) CloseHandle(hProcess); ps__throw_error(); return R_NilValue; } SEXP psll_resume(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP running, ret; if (!handle) error("Process pointer cleaned up already"); HANDLE hProcess = ps__handle_from_pid(handle->pid); if (!hProcess) goto error; running = ps__is_running(handle); if (!LOGICAL(running)[0]) { ps__no_such_process(handle->pid, 0); goto error; } PROTECT(ret = ps__proc_resume(handle->pid)); if (isNull(ret)) goto error; UNPROTECT(1); return R_NilValue; error: if (hProcess) CloseHandle(hProcess); ps__throw_error(); return R_NilValue; } SEXP psll_terminate(SEXP p) { ps__not_implemented("uids"); ps__throw_error(); return R_NilValue; } SEXP psll_kill(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); SEXP running, ret; if (!handle) error("Process pointer cleaned up already"); HANDLE hProcess = ps__handle_from_pid(handle->pid); if (!hProcess) goto error; running = ps__is_running(handle); if (!LOGICAL(running)[0]) { ps__no_such_process(handle->pid, 0); goto error; } PROTECT(ret = ps__proc_kill(handle->pid)); if (isNull(ret)) ps__throw_error(); UNPROTECT(1); return R_NilValue; error: if (hProcess) CloseHandle(hProcess); ps__throw_error(); return R_NilValue; } static ULONGLONG (*ps__GetTickCount64)(void) = NULL; /* * Return a double representing the system uptime expressed in seconds * since the epoch. */ SEXP ps__boot_time() { double now, uptime; FILETIME fileTime; HINSTANCE hKernel32; ps__GetTickCount64 = NULL; GetSystemTimeAsFileTime(&fileTime); now = ps__filetime_to_unix(fileTime); // GetTickCount64() is Windows Vista+ only. Dinamically load // GetTickCount64() at runtime. We may have used // "#if (_WIN32_WINNT >= 0x0600)" pre-processor but that way // the produced exe/wheels cannot be used on Windows XP, see: // https://github.com/giampaolo/psutil/issues/811#issuecomment-230639178 hKernel32 = GetModuleHandleW(L"KERNEL32"); ps__GetTickCount64 = (void*)GetProcAddress(hKernel32, "GetTickCount64"); if (ps__GetTickCount64 != NULL) { // Windows >= Vista uptime = ps__GetTickCount64() / (ULONGLONG)1000.00f; return ScalarReal(now - uptime); } else { // Windows XP. // GetTickCount() time will wrap around to zero if the // system is run continuously for 49.7 days. uptime = GetTickCount() / (LONGLONG)1000.00f; return ScalarReal(now - uptime); } } /* * Return the number of logical, active CPUs. Return 0 if undetermined. * See discussion at: https://bugs.python.org/issue33166#msg314631 */ #ifndef ALL_PROCESSOR_GROUPS #define ALL_PROCESSOR_GROUPS 0xffff #endif unsigned int ps__get_num_cpus(int fail_on_err) { unsigned int ncpus = 0; SYSTEM_INFO sysinfo; static DWORD(CALLBACK *_GetActiveProcessorCount)(WORD) = NULL; HINSTANCE hKernel32; // GetActiveProcessorCount is available only on 64 bit versions // of Windows from Windows 7 onward. // Windows Vista 64 bit and Windows XP doesn't have it. hKernel32 = GetModuleHandleW(L"KERNEL32"); _GetActiveProcessorCount = (void*)GetProcAddress( hKernel32, "GetActiveProcessorCount"); if (_GetActiveProcessorCount != NULL) { ncpus = _GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); if ((ncpus == 0) && (fail_on_err == 1)) { ps__set_error_from_windows_error(0); } } else { ps__debug("GetActiveProcessorCount() not available; " "using GetNativeSystemInfo()"); GetNativeSystemInfo(&sysinfo); ncpus = (unsigned int) sysinfo.dwNumberOfProcessors; if ((ncpus == 0) && (fail_on_err == 1)) { ps__set_error("GetNativeSystemInfo() failed to retrieve CPU count"); ps__throw_error(); } } return ncpus; } SEXP ps__cpu_count_logical() { unsigned int ncpus; ncpus = ps__get_num_cpus(0); if (ncpus != 0) { return ScalarInteger(ncpus); } else { return ScalarInteger(NA_INTEGER); } } typedef BOOL (WINAPI *LPFN_GLPI)( PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD); // Helper function to count set bits in the processor mask. DWORD ps__count_set_bits(ULONG_PTR bitMask) { DWORD LSHIFT = sizeof(ULONG_PTR)*8 - 1; DWORD bitSetCount = 0; ULONG_PTR bitTest = (ULONG_PTR)1 << LSHIFT; DWORD i; for (i = 0; i <= LSHIFT; ++i) { bitSetCount += ((bitMask & bitTest)?1:0); bitTest/=2; } return bitSetCount; } SEXP ps__cpu_count_physical() { LPFN_GLPI glpi; BOOL done = FALSE; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = NULL; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = NULL; DWORD returnLength = 0; DWORD nproc = 0; DWORD byteOffset = 0; glpi = (LPFN_GLPI) GetProcAddress( GetModuleHandle(TEXT("kernel32")), "GetLogicalProcessorInformation"); if (NULL == glpi) return ScalarInteger(NA_INTEGER); while (!done) { DWORD rc = glpi(buffer, &returnLength); if (FALSE == rc) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { if (buffer) free(buffer); buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION) malloc(returnLength); if (NULL == buffer) { ps__no_memory(""); ps__throw_error(); } } else { ps__set_error_from_windows_error(0); ps__throw_error(); } } else { done = TRUE; } } ptr = buffer; while (byteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= returnLength) { switch (ptr->Relationship) { case RelationProcessorCore: // A hyperthreaded core supplies more than one logical processor. nproc += ps__count_set_bits(ptr->ProcessorMask); break; default: break; } byteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ptr++; } free(buffer); if (nproc > 0) { return ScalarInteger(nproc); } else { return ScalarInteger(NA_INTEGER); } } SEXP ps__kill_if_env(SEXP marker, SEXP after, SEXP pid, SEXP sig) { const char *cmarker = CHAR(STRING_ELT(marker, 0)); double cafter = REAL(after)[0]; DWORD cpid = INTEGER(pid)[0]; SEXP env; size_t i, len; double ctime = 0, ctime2 = 0; /* Filter on start time */ FILETIME ftCreate; int ret = ps__create_time_raw(cpid, &ftCreate); if (ret) ps__throw_error(); ctime = ps__filetime_to_unix(ftCreate); if (ctime < cafter - 1) return R_NilValue; PROTECT(env = ps__get_environ(cpid)); if (isNull(env)) ps__throw_error(); len = LENGTH(env); for (i = 0; i < len; i++) { if (strstr(CHAR(STRING_ELT(env, i)), cmarker)) { HANDLE hProcess = ps__handle_from_pid(cpid); FILETIME ftCreate; SEXP name, ret2; int ret = ps__create_time_raw(cpid, &ftCreate); if (ret) { CloseHandle(hProcess); ps__throw_error(); } ctime2 = ps__filetime_to_unix(ftCreate); if (fabs(ctime - ctime2) < 0.01) { PROTECT(name = ps__name(cpid)); ret2 = ps__proc_kill(cpid); CloseHandle(hProcess); if (isNull(ret2)) ps__throw_error(); if (isNull(name)) { UNPROTECT(2); return mkString("???"); } else { UNPROTECT(2); return name; } } else { CloseHandle(hProcess); UNPROTECT(1); return R_NilValue; } } } UNPROTECT(1); return R_NilValue; } SEXP ps__find_if_env(SEXP marker, SEXP after, SEXP pid) { const char *cmarker = CHAR(STRING_ELT(marker, 0)); double cafter = REAL(after)[0]; long cpid = INTEGER(pid)[0]; SEXP env; size_t i, len; SEXP phandle; ps_handle_t *handle; double ctime = 0; FILETIME ftCreate; /* Filter on start time */ int ret = ps__create_time_raw(cpid, &ftCreate); if (ret) ps__throw_error(); ctime = ps__filetime_to_unix(ftCreate); if (ctime < cafter - 1) return R_NilValue; PROTECT(phandle = psll_handle(pid, R_NilValue)); handle = R_ExternalPtrAddr(phandle); PROTECT(env = ps__get_environ(cpid)); if (isNull(env)) ps__throw_error(); len = LENGTH(env); for (i = 0; i < len; i++) { if (strstr(CHAR(STRING_ELT(env, i)), cmarker)) { UNPROTECT(2); PS__CHECK_HANDLE(handle); return phandle; } } UNPROTECT(2); return R_NilValue; } SEXP psll_num_fds(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); HANDLE hProcess = NULL; DWORD handleCount; if (!handle) error("Process pointer cleaned up already"); hProcess = ps__handle_from_pid(handle->pid); if (hProcess != NULL) { if (GetProcessHandleCount(hProcess, &handleCount)) { CloseHandle(hProcess); PS__CHECK_HANDLE(handle); return ScalarInteger(handleCount); } } /* Cleanup on error */ if (hProcess != NULL) CloseHandle(hProcess); PS__CHECK_HANDLE(handle); ps__set_error_from_windows_error(0); ps__throw_error(); return R_NilValue; } SEXP psll_open_files(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); HANDLE processHandle = NULL; DWORD access = PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION; SEXP result; if (!handle) error("Process pointer cleaned up already"); processHandle = ps__handle_from_pid_waccess(handle->pid, access); if (processHandle == NULL) { PS__CHECK_HANDLE(handle); ps__set_error_from_windows_error(0); ps__throw_error(); } PROTECT(result = ps__get_open_files(handle->pid, processHandle)); CloseHandle(processHandle); PS__CHECK_HANDLE(handle); if (isNull(result)) { ps__set_error_from_windows_error(0); ps__throw_error(); } UNPROTECT(1); return result; } SEXP psll_dlls(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); HANDLE processHandle = NULL; DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; SEXP result; if (!handle) error("Process pointer cleaned up already"); processHandle = ps__handle_from_pid_waccess(handle->pid, access); if (processHandle == NULL) { PS__CHECK_HANDLE(handle); ps__set_error_from_windows_error(0); ps__throw_error(); } PROTECT(result = ps__get_modules(processHandle)); CloseHandle(processHandle); PS__CHECK_HANDLE(handle); if (isNull(result)) { ps__set_error_from_windows_error(0); ps__throw_error(); } UNPROTECT(1); return result; } SEXP psll_interrupt(SEXP p, SEXP ctrlc, SEXP interrupt_path) { ps_handle_t *handle = R_ExternalPtrAddr(p); const char *cinterrupt_path = CHAR(STRING_ELT(interrupt_path, 0)); int cctrlc = LOGICAL(ctrlc)[0]; WCHAR *wpath; SEXP running; int iret; STARTUPINFOW startup = { 0 }; PROCESS_INFORMATION info = { 0 }; HANDLE hProcess; char arguments[100]; WCHAR *warguments; DWORD process_flags; if (!handle) error("Process pointer cleaned up already"); hProcess = ps__handle_from_pid(handle->pid); if (!hProcess) goto error; running = ps__is_running(handle); if (!LOGICAL(running)[0]) { ps__no_such_process(handle->pid, 0); goto error; } iret = ps__utf8_to_utf16(cinterrupt_path, &wpath); if (iret) goto error; iret = snprintf(arguments, sizeof(arguments) - 1, "interrupt.exe %d %s", handle->pid, cctrlc ? "c" : "break"); if (iret < 0) goto error; iret = ps__utf8_to_utf16(arguments, &warguments); if (iret) goto error; startup.cb = sizeof(startup); startup.lpReserved = NULL; startup.lpDesktop = NULL; startup.lpTitle = NULL; startup.dwFlags = 0; startup.cbReserved2 = 0; startup.lpReserved2 = 0; process_flags = CREATE_UNICODE_ENVIRONMENT | CREATE_NO_WINDOW; iret = CreateProcessW( /* lpApplicationName = */ wpath, /* lpCommandLine = */ warguments, /* lpProcessAttributes = */ NULL, /* lpThreadAttributes = */ NULL, /* bInheritHandles = */ 0, /* dwCreationFlags = */ process_flags, /* lpEnvironment = */ NULL, /* lpCurrentDirectory = */ NULL, /* lpStartupInfo = */ &startup, /* lpProcessInformation = */ &info); if (!iret) { ps__set_error_from_errno(0); goto error; } CloseHandle(info.hThread); CloseHandle(info.hProcess); return R_NilValue; error: if (hProcess) CloseHandle(hProcess); ps__throw_error(); return R_NilValue; } SEXP ps__users() { HANDLE hServer = WTS_CURRENT_SERVER_HANDLE; WCHAR *buffer_user = NULL; LPTSTR buffer_addr = NULL; PWTS_SESSION_INFO sessions = NULL; DWORD count; DWORD i; DWORD sessionId; DWORD bytes; PWTS_CLIENT_ADDRESS address; char address_str[50]; double unix_time; PWINSTATIONQUERYINFORMATIONW WinStationQueryInformationW; WINSTATION_INFO station_info; HINSTANCE hInstWinSta = NULL; ULONG returnLen; SEXP retlist, raddress, username; hInstWinSta = LoadLibraryA("winsta.dll"); WinStationQueryInformationW = (PWINSTATIONQUERYINFORMATIONW) \ GetProcAddress(hInstWinSta, "WinStationQueryInformationW"); if (WTSEnumerateSessions(hServer, 0, 1, &sessions, &count) == 0) { ps__set_error_from_windows_error(0); goto error; } PROTECT(retlist = allocVector(VECSXP, count)); for (i = 0; i < count; i++) { SET_VECTOR_ELT(retlist, i, R_NilValue); raddress = R_NilValue; sessionId = sessions[i].SessionId; if (buffer_user != NULL) WTSFreeMemory(buffer_user); if (buffer_addr != NULL) WTSFreeMemory(buffer_addr); buffer_user = NULL; buffer_addr = NULL; // username bytes = 0; if (WTSQuerySessionInformationW(hServer, sessionId, WTSUserName, &buffer_user, &bytes) == 0) { ps__set_error_from_windows_error(0); goto error; } if (bytes <= 2) continue; // address bytes = 0; if (WTSQuerySessionInformation(hServer, sessionId, WTSClientAddress, &buffer_addr, &bytes) == 0) { ps__set_error_from_windows_error(0); goto error; } address = (PWTS_CLIENT_ADDRESS) buffer_addr; if (address->AddressFamily == 0 && // AF_INET (address->Address[0] || address->Address[1] || address->Address[2] || address->Address[3])) { snprintf(address_str, sizeof(address_str), "%u.%u.%u.%u", address->Address[0], address->Address[1], address->Address[2], address->Address[3]); raddress = mkString(address_str); } else { raddress = mkString(""); } PROTECT(raddress); // login time if (!WinStationQueryInformationW(hServer, sessionId, WinStationInformation, &station_info, sizeof(station_info), &returnLen)) { goto error; } unix_time = ps__filetime_to_unix(station_info.ConnectTime); PROTECT(username = ps__utf16_to_strsxp(buffer_user, -1)); SET_VECTOR_ELT( retlist, i, ps__build_list("OOOdi", username, ScalarString(NA_STRING), raddress, unix_time, NA_INTEGER)); UNPROTECT(2); } WTSFreeMemory(sessions); WTSFreeMemory(buffer_user); WTSFreeMemory(buffer_addr); FreeLibrary(hInstWinSta); UNPROTECT(1); return retlist; error: if (hInstWinSta != NULL) FreeLibrary(hInstWinSta); if (sessions != NULL) WTSFreeMemory(sessions); if (buffer_user != NULL) WTSFreeMemory(buffer_user); if (buffer_addr != NULL) WTSFreeMemory(buffer_addr); ps__throw_error(); return R_NilValue; } SEXP ps__tty_size() { CONSOLE_SCREEN_BUFFER_INFO info; BOOL ok = GetConsoleScreenBufferInfo( GetStdHandle(STD_OUTPUT_HANDLE), &info ); if (!ok) { ps__set_error_from_windows_error(0); ps__throw_error(); } SEXP result = Rf_allocVector(INTSXP, 2); INTEGER(result)[0] = info.srWindow.Right - info.srWindow.Left + 1; INTEGER(result)[1] = info.srWindow.Bottom - info.srWindow.Top + 1; return result; } static char *ps__get_drive_type(int type) { switch (type) { case DRIVE_FIXED: return "fixed"; case DRIVE_CDROM: return "cdrom"; case DRIVE_REMOVABLE: return "removable"; case DRIVE_UNKNOWN: return "unknown"; case DRIVE_NO_ROOT_DIR: return "unmounted"; case DRIVE_REMOTE: return "remote"; case DRIVE_RAMDISK: return "ramdisk"; default: return "?"; } } SEXP ps__disk_partitions(SEXP rall) { DWORD num_bytes; char drive_strings[255]; char *drive_letter = drive_strings; char mp_buf[MAX_PATH]; char mp_path[MAX_PATH]; int all = LOGICAL(rall)[0]; int type; int ret; unsigned int old_mode = 0; char opts[20]; HANDLE mp_h; BOOL mp_flag= TRUE; LPTSTR fs_type[MAX_PATH + 1] = { 0 }; DWORD pflags = 0; SEXP result; PROTECT_INDEX pidx; int len = 10, num = -1; PROTECT_WITH_INDEX(result = allocVector(VECSXP, len), &pidx); // avoid to visualize a message box in case something goes wrong // see https://github.com/giampaolo/psutil/issues/264 old_mode = SetErrorMode(SEM_FAILCRITICALERRORS); num_bytes = GetLogicalDriveStrings(254, drive_letter); if (num_bytes == 0) { ps__set_error_from_errno(0); goto error; } while (*drive_letter != 0) { opts[0] = 0; fs_type[0] = 0; type = GetDriveType(drive_letter); // by default we only show hard drives and cd-roms if (all == 0) { if ((type == DRIVE_UNKNOWN) || (type == DRIVE_NO_ROOT_DIR) || (type == DRIVE_REMOTE) || (type == DRIVE_RAMDISK)) { goto next; } // floppy disk: skip it by default as it introduces a // considerable slowdown. if ((type == DRIVE_REMOVABLE) && (strcmp(drive_letter, "A:\\") == 0)) { goto next; } } ret = GetVolumeInformation( (LPCTSTR)drive_letter, NULL, _ARRAYSIZE(drive_letter), NULL, NULL, &pflags, (LPTSTR)fs_type, _ARRAYSIZE(fs_type)); if (ret == 0) { // We might get here in case of a floppy hard drive, in // which case the error is (21, "device not ready"). // Let's pretend it didn't happen as we already have // the drive name and type ('removable'). strcat_s(opts, _countof(opts), ""); SetLastError(0); } else { if (pflags & FILE_READ_ONLY_VOLUME) { strcat_s(opts, _countof(opts), "ro"); } else { strcat_s(opts, _countof(opts), "rw"); } if (pflags & FILE_VOLUME_IS_COMPRESSED) { strcat_s(opts, _countof(opts), ",compressed"); } // Check for mount points on this volume and add/get info // (checks first to know if we can even have mount points) if (pflags & FILE_SUPPORTS_REPARSE_POINTS) { mp_h = FindFirstVolumeMountPoint( drive_letter, mp_buf, MAX_PATH); if (mp_h != INVALID_HANDLE_VALUE) { while (mp_flag) { // Append full mount path with drive letter strcpy_s(mp_path, _countof(mp_path), drive_letter); strcat_s(mp_path, _countof(mp_path), mp_buf); SetErrorMode(old_mode); // TODO: should call FindVolumeMountPointClose on error if (++num == len) { len *= 2; REPROTECT(result = Rf_lengthgets(result, len), pidx); } SET_VECTOR_ELT( result, num, ps__build_list( "ssss", drive_letter, mp_path, fs_type, opts)); SetErrorMode(SEM_FAILCRITICALERRORS); // Continue looking for more mount points mp_flag = FindNextVolumeMountPoint( mp_h, mp_buf, MAX_PATH); } FindVolumeMountPointClose(mp_h); } } } if (strlen(opts) > 0) { strcat_s(opts, _countof(opts), ","); } strcat_s(opts, _countof(opts), ps__get_drive_type(type)); SetErrorMode(old_mode); if (++num == len) { len *= 2; REPROTECT(result = Rf_lengthgets(result, len), pidx); } SET_VECTOR_ELT( result, num, ps__build_list( "ssss", drive_letter, drive_letter, fs_type, opts)); next: drive_letter = strchr(drive_letter, 0) + 1; } UNPROTECT(1); return result; error: SetErrorMode(old_mode); return R_NilValue; } SEXP ps__disk_usage(SEXP paths) { BOOL retval; ULARGE_INTEGER freeuser, total, free; int i, n = Rf_length(paths); SEXP result = PROTECT(allocVector(VECSXP, n)); for (i = 0; i < n; i++) { const char *path = CHAR(STRING_ELT(paths, i)); wchar_t *wpath; int iret = ps__utf8_to_utf16(path, &wpath); if (iret) goto error; retval = GetDiskFreeSpaceExW(wpath, &freeuser, &total, &free); if (!retval) { ps__set_error_from_windows_error(0); goto error; } SET_VECTOR_ELT( result, i, allocVector(REALSXP, 3)); REAL(VECTOR_ELT(result, i))[0] = (double) total.QuadPart; REAL(VECTOR_ELT(result, i))[1] = (double) free.QuadPart; REAL(VECTOR_ELT(result, i))[2] = (double) freeuser.QuadPart; } UNPROTECT(1); return result; error: ps__throw_error(); return R_NilValue; } SEXP ps__system_memory() { MEMORYSTATUSEX memInfo; memInfo.dwLength = sizeof(MEMORYSTATUSEX); if (! GlobalMemoryStatusEx(&memInfo)) { ps__set_error_from_windows_error(0); ps__throw_error(); } return ps__build_named_list( "dddddd", "total", (double) memInfo.ullTotalPhys, "avail", (double) memInfo.ullAvailPhys, "totpagef", (double) memInfo.ullTotalPageFile, "availpagef", (double) memInfo.ullAvailPageFile, "totvirt", (double) memInfo.ullTotalVirtual, "freevirt", (double) memInfo.ullAvailVirtual); } SEXP ps__system_swap() { // We use ps__system_memory instead ps__throw_error(); return R_NilValue; } SEXP psll_get_nice(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); HANDLE hProcess = NULL; DWORD priority; if (!handle) error("Process pointer cleaned up already"); hProcess = ps__handle_from_pid(handle->pid); if (!hProcess) goto error; priority = GetPriorityClass(hProcess); if (priority == 0) { ps__set_error_from_windows_error(0); goto error; } CloseHandle(hProcess); switch(priority) { case REALTIME_PRIORITY_CLASS: return ScalarInteger(1); case HIGH_PRIORITY_CLASS: return ScalarInteger(2); case ABOVE_NORMAL_PRIORITY_CLASS: return ScalarInteger(3); case NORMAL_PRIORITY_CLASS: return ScalarInteger(4); case IDLE_PRIORITY_CLASS: return ScalarInteger(5); case BELOW_NORMAL_PRIORITY_CLASS: return ScalarInteger(6); default: error("Internal error, invalid priority class"); } error: if (hProcess) CloseHandle(hProcess); ps__throw_error(); return R_NilValue; } SEXP psll_set_nice(SEXP p, SEXP value) { ps_handle_t *handle = R_ExternalPtrAddr(p); HANDLE hProcess = NULL; DWORD priority = INTEGER(value)[0]; DWORD classes[] = { REALTIME_PRIORITY_CLASS, HIGH_PRIORITY_CLASS, ABOVE_NORMAL_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS, IDLE_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS }; DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION; if (!handle) error("Process pointer cleaned up already"); hProcess = ps__handle_from_pid_waccess(handle->pid, access); if (!hProcess) { PS__CHECK_HANDLE(handle); ps__set_error_from_windows_error(0); goto error; } BOOL ret = SetPriorityClass(hProcess, classes[priority - 1]); if (!ret) { ps__set_error_from_windows_error(0); goto error; } CloseHandle(hProcess); return R_NilValue; error: if (hProcess) CloseHandle(hProcess); ps__throw_error(); return R_NilValue; } SEXP ps__loadavg(SEXP counter_name) { SEXP ret = PROTECT(allocVector(REALSXP, 3)); ps__get_loadavg(REAL(ret), counter_name); UNPROTECT(1); return ret; } #define LO_T 1e-7 #define HI_T 429.4967296 SEXP ps__system_cpu_times() { double idle, kernel, user, system; FILETIME idle_time, kernel_time, user_time; if (!GetSystemTimes(&idle_time, &kernel_time, &user_time)) { ps__set_error_from_windows_error(0); ps__throw_error(); } idle = (double)((HI_T * idle_time.dwHighDateTime) + (LO_T * idle_time.dwLowDateTime)); user = (double)((HI_T * user_time.dwHighDateTime) + (LO_T * user_time.dwLowDateTime)); kernel = (double)((HI_T * kernel_time.dwHighDateTime) + (LO_T * kernel_time.dwLowDateTime)); // Kernel time includes idle time. // We return only busy kernel time subtracting idle time from // kernel time. system = (kernel - idle); const char *nms[] = { "user", "system", "idle", "" }; SEXP ret = PROTECT(Rf_mkNamed(REALSXP, nms)); REAL(ret)[0] = (double) user; REAL(ret)[1] = (double) system; REAL(ret)[2] = (double) idle; UNPROTECT(1); return ret; } ps/src/posix.h0000644000176200001440000000011413737276560013012 0ustar liggesusers int ps__pid_exists(long pid); void ps__raise_for_pid(long pid, char *msg); ps/src/api-common.c0000644000176200001440000000345113620625516013677 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #define R_USE_C99_IN_CXX 1 #include #include "ps-internal.h" SEXP psll_pid(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); if (!handle) error("Process pointer cleaned up already"); return ScalarInteger(handle->pid); } SEXP psll_create_time(SEXP p) { ps_handle_t *handle = R_ExternalPtrAddr(p); if (!handle) error("Process pointer cleaned up already"); return ScalarReal(handle->create_time); } SEXP ps__os_type() { SEXP res, names; PROTECT(res = allocVector(LGLSXP, 4)); PROTECT(names = allocVector(STRSXP, 4)); SET_STRING_ELT(names, 0, mkChar("POSIX")); SET_STRING_ELT(names, 1, mkChar("WINDOWS")); SET_STRING_ELT(names, 2, mkChar("LINUX")); SET_STRING_ELT(names, 3, mkChar("MACOS")); /* SET_STRING_ELT(names, 4, mkChar("FREEBSD")); */ /* SET_STRING_ELT(names, 5, mkChar("OPENBSD")); */ /* SET_STRING_ELT(names, 6, mkChar("NETBSD")); */ /* SET_STRING_ELT(names, 7, mkChar("BSD")); */ /* SET_STRING_ELT(names, 8, mkChar("SUNOS")); */ /* SET_STRING_ELT(names, 9, mkChar("AIX")); */ LOGICAL(res)[0] = LOGICAL(res)[1] = LOGICAL(res)[2] = LOGICAL(res)[3] = 0; #ifdef PS__POSIX LOGICAL(res)[0] = 1; #endif #ifdef PS__WINDOWS LOGICAL(res)[1] = 1; #endif #ifdef PS__LINUX LOGICAL(res)[2] = 1; #endif #ifdef PS__MACOS LOGICAL(res)[3] = 1; #endif /* #ifdef PS__FREEBSD */ /* LOGICAL(res)[4] = 1; */ /* #endif */ /* #ifdef PS__OPENBSD */ /* LOGICAL(res)[5] = 1; */ /* #endif */ /* #ifdef PS__NETBSD */ /* LOGICAL(res)[6] = 1; */ /* #endif */ /* #ifdef PS__BSD */ /* LOGICAL(res)[7] = 1; */ /* #endif */ /* #ifdef PS__SUNOS */ /* LOGICAL(res)[8] = 1; */ /* #endif */ /* #ifdef PS__AIX */ /* LOGICAL(res)[9] = 1; */ /* #endif */ setAttrib(res, R_NamesSymbol, names); UNPROTECT(2); return res; } ps/src/Makevars.in0000644000176200001440000000042113332411414013555 0ustar liggesusers OBJECTS = @OBJECTS@ PKG_LIBS = @LIBS@ .PHONY: all clean all: px @TARGETS@ $(SHLIB) px: px.c $(CC) $(CFLAGS) -Wall px.c -o px interrupt: interrupt.c $(CC) $(CFLAGS) -Wall interrupt.c -o interrupt clean: rm -rf $(SHLIB) $(OBJECTS) px.exe px interrupt.exe interrupt ps/configure.win0000644000176200001440000000004313313262223013366 0ustar liggesusers#! /usr/bin/env sh sh ./configure ps/R/0000755000176200001440000000000014016770572011106 5ustar liggesusersps/R/disk.R0000644000176200001440000000726513737300630012166 0ustar liggesusers #' List all mounted partitions #' #' The output is similar the Unix `mount` and `df` commands. #' #' @param all Whether to list virtual devices as well. If `FALSE`, on #' Linux it will still list `overlay` and `grpcfuse` file systems, to #' provide some useful information in Docker containers. #' @return A data frame (tibble) with columns `device`, `mountpoint`, #' `fstype` and `options`. #' #' @family disk functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' ps_disk_partitions(all = TRUE) #' ps_disk_partitions() ps_disk_partitions <- function(all = FALSE) { assert_flag(all) l <- not_null(.Call(ps__disk_partitions, all)) d <- data.frame( stringsAsFactors = FALSE, device = vapply(l, "[[", character(1), 1), mountpoint = vapply(l, "[[", character(1), 2), fstype = vapply(l, "[[", character(1), 3), options = vapply(l, "[[", character(1), 4) ) if (!all) d <- ps__disk_partitions_filter(d) requireNamespace("tibble", quietly = TRUE) class(d) <- unique(c("tbl_df", "tbl", class(d))) d } #' @importFrom utils read.delim ps__disk_partitions_filter <- function(pt) { os <- ps_os_name() if (os == "LINUX") { fs <- read.delim("/proc/filesystems", header = FALSE, sep = "\t") goodfs <- c(fs[[2]][fs[[1]] != "nodev"], "zfs") ok <- pt$device != "none" & file.exists(pt$device) & pt$fstype %in% goodfs ok <- ok | pt$device %in% c("overlay", "grpcfuse") pt <- pt[ok, , drop = FALSE] } else if (os == "MACOS") { ok <- substr(pt$device, 1, 1) == "/" & file.exists(pt$device) pt <- pt[ok, , drop = FALSE] } pt } #' Disk usage statistics, per partition #' #' The output is similar to the Unix `df` command. #' #' #' Note that on Unix a small percentage of the disk space (5% typically) #' is reserved for the superuser. `ps_disk_usage()` returns the space #' available to the calling user. #' #' @param paths The mounted file systems to list. By default all file #' systems returned by [ps_disk_partitions()] is listed. #' @return A data frame with columns `mountpoint`, `total`, `used`, #' `available` and `capacity`. #' #' @family disk functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' ps_disk_usage() ps_disk_usage <- function(paths = ps_disk_partitions()$mountpoint) { assert_character(paths) l <- .Call(ps__disk_usage, paths) os <- ps_os_name() if (os == "WINDOWS") { ps__disk_usage_format_windows(paths, l) } else { ps__disk_usage_format_posix(paths, l) } } ps__disk_usage_format_windows <- function(paths, l) { total <- vapply(l, "[[", double(1), 1) free <- vapply(l, "[[", double(1), 2) freeuser <- vapply(l, "[[", double(1), 3) used <- total - free d <- data.frame( stringsAsFactors = FALSE, mountpoint = paths, total = total, used = used, available = freeuser, capacity = used / total ) requireNamespace("tibble", quietly = TRUE) class(d) <- unique(c("tbl_df", "tbl", class(d))) d } ps__disk_usage_format_posix <- function(paths, l) { l2 <- lapply(l, function(fs) { total <- fs[[5]] * fs[[1]] avail_to_root <- fs[[6]] * fs[[1]] avail = fs[[7]] * fs[[1]] used <- total - avail_to_root total_user <- used + avail usage_percent <- used / total_user list(total = total, used = used, free = avail, percent = usage_percent) }) d <- data.frame( stringsAsFactors = FALSE, mountpoint = paths, total = vapply(l2, "[[", double(1), "total"), used = vapply(l2, "[[", double(1), "used"), available = vapply(l2, "[[", double(1), "free"), capacity = vapply(l2, "[[", double(1), "percent") ) requireNamespace("tibble", quietly = TRUE) class(d) <- unique(c("tbl_df", "tbl", class(d))) d } ps/R/utils.R0000644000176200001440000001133513737271557012404 0ustar liggesusers `%||%` <- function(l, r) if (is.null(l)) r else l `%&&%` <- function(l, r) if (is.null(l)) NULL else r not_null <- function(x) x[!map_lgl(x, is.null)] not_zchar <- function(x) x[x != ""] map_chr <- function(.x, .f, ...) { vapply(X = .x, FUN = .f, FUN.VALUE = character(1), ...) } map_lgl <- function(.x, .f, ...) { vapply(X = .x, FUN = .f, FUN.VALUE = logical(1), ...) } map_int <- function(.x, .f, ...) { vapply(X = .x, FUN = .f, FUN.VALUE = integer(1), ...) } map_dbl <- function(.x, .f, ...) { vapply(X = .x, FUN = .f, FUN.VALUE = double(1), ...) } parse_envs <- function(x) { x <- enc2utf8(x) x <- strsplit(x, "=", fixed = TRUE) nms <- map_chr(x, "[[", 1) vls <- map_chr(x, function(x) paste(x[-1], collapse = "=")) ord <- order(nms) structure(vls[ord], names = nms[ord], class = "Dlist") } ## These two are fully vectorized str_starts_with <- function(x, p) { ncp <- nchar(p) substr(x, 1, nchar(p)) == p } str_strip <- function(x) { sub("\\s+$", "", sub("^\\s+", "", x)) } str_tail <- function(x, num) { nc <- nchar(x) substr(x, pmax(nc - num + 1, 1), nc) } r_version <- function(x) { v <- paste0(version[["major"]], ".", version[["minor"]]) package_version(v) } file_size <- function(x) { if (r_version() >= "3.2.0") { file.info(x, extra_cols = FALSE)$size } else { file.info(x)$size } } format_unix_time <- function(z) { structure(z, class = c("POSIXct", "POSIXt"), tzone = "GMT") } NA_time <- function() { x <- Sys.time() x[] <- NA x } fallback <- function(expr, alternative) { tryCatch( expr, error = function(e) alternative ) } read_lines <- function(path) { suppressWarnings(con <- file(path, open = "r")) on.exit(close(con), add = TRUE) suppressWarnings(readLines(con)) } ## We need to wait until the child becomes a zombie, otherwise ## it might still be in a running state zombie <- function() { if (ps_os_type()[["POSIX"]]) { pid <- .Call(psp__zombie) ps <- ps_handle(pid) timeout <- Sys.time() + 5 while (ps_status(ps) != "zombie" && Sys.time() < timeout) { Sys.sleep(0.05) } if (ps_status(ps) == "zombie") pid else stop("Cannot create zombie") } } waitpid <- function(pid) { if (ps_os_type()[["POSIX"]]) .Call(psp__waitpid, as.integer(pid)) } caps <- function(x) { paste0(toupper(substr(x, 1, 1)), tolower(substr(x, 2, nchar(x)))) } assert_string <- function(x) { if (is.character(x) && length(x) == 1 && !is.na(x)) return() stop(ps__invalid_argument(match.call()$x, " is not a string (character scalar)")) } assert_character <- function(x) { if (is.character(x)) return() stop(ps__invalid_argument(match.call()$x, " is not of type character")) } assert_pid <- function(x) { if (is.integer(x) && length(x) == 1 && !is.na(x)) return() stop(ps__invalid_argument(match.call()$x, " is not a process id (integer scalar)")) } assert_time <- function(x) { if (inherits(x, "POSIXct")) return() stop(ps__invalid_argument(match.call()$x, " must be a time stamp (POSIXt)")) } assert_ps_handle <- function(x) { if (inherits(x, "ps_handle")) return() stop(ps__invalid_argument(match.call()$x, " must be a process handle (ps_handle)")) } assert_flag <- function(x) { if (is.logical(x) && length(x) == 1 && !is.na(x)) return() stop(ps__invalid_argument(match.call()$x, " is not a flag (logical scalar)")) } assert_signal <- function(x) { if (is.integer(x) && length(x) == 1 && !is.na(x) && x %in% unlist(signals())) return() stop(ps__invalid_argument(match.call()$x, " is not a signal number (see ?signals())")) } assert_nice_value <- function(x) { if (ps_os_type()[["POSIX"]]) { if (is.integer(x) && length(x) == 1 && !is.na(x) && x <= 20) return() stop(ps__invalid_argument(match.call()$x, " is not a valid priority value")) } else { match.arg(x, ps_windows_nice_values()) } } realpath <- function(x) { if (ps_os_type()[["WINDOWS"]]) .Call(psw__realpath, x) else normalizePath(x) } get_tool <- function(prog) { if (ps_os_type()[["WINDOWS"]]) prog <- paste0(prog, ".exe") exe <- system.file(package = "ps", "bin", .Platform$r_arch, prog) if (exe == "") { pkgpath <- system.file(package = "ps") if (basename(pkgpath) == "inst") pkgpath <- dirname(pkgpath) exe <- file.path(pkgpath, "src", prog) if (!file.exists(exe)) return("") } exe } match_names <- function(map, x) { names(map)[match(x, map)] } is_cran_check <- function() { if (identical(Sys.getenv("NOT_CRAN"), "true")) { FALSE } else { Sys.getenv("_R_CHECK_PACKAGE_NAME_", "") != "" } } ps/R/system.R0000644000176200001440000002600714016770572012562 0ustar liggesusers #' Ids of all processes on the system #' #' @return Integer vector of process ids. #' @export ps_pids <- function() { os <- ps_os_type() pp <- if (os[["MACOS"]]) ps_pids_macos() else if (os[["LINUX"]]) ps_pids_linux() else if (os[["WINDOWS"]]) ps_pids_windows() else stop("Not implemented for this platform") sort(pp) } ps_pids_windows <- function() { sort(.Call(ps__pids)) } ps_pids_macos <- function() { ls <- .Call(ps__pids) ## 0 is missing from the list, usually, even though it is a process if (! 0L %in% ls && ps_pid_exists_macos(0L)) { ls <- c(ls, 0L) } ls } ps_pid_exists_macos <- function(pid) { .Call(psp__pid_exists, as.integer(pid)) } ps_pids_linux <- function() { sort(as.integer(dir("/proc", pattern = "^[0-9]+$"))) } #' Boot time of the system #' #' @return A `POSIXct` object. #' #' @export ps_boot_time <- function() { format_unix_time(.Call(ps__boot_time)) } #' List users connected to the system #' #' @return A data frame (tibble) with columns #' `username`, `tty`, `hostname`, `start_time`, `pid`. `tty` and `pid` #' are `NA` on Windows. `pid` is the process id of the login process. #' For local users the `hostname` column is the empty string. #' #' @export ps_users <- function() { l <- not_null(.Call(ps__users)) d <- data.frame( stringsAsFactors = FALSE, username = vapply(l, "[[", character(1), 1), tty = vapply(l, "[[", character(1), 2), hostname = vapply(l, "[[", character(1), 3), start_time = format_unix_time(vapply(l, "[[", double(1), 4)), pid = vapply(l, "[[", integer(1), 5) ) requireNamespace("tibble", quietly = TRUE) class(d) <- unique(c("tbl_df", "tbl", class(d))) d } #' Number of logical or physical CPUs #' #' If cannot be determined, it returns `NA`. It also returns `NA` on older #' Windows systems, e.g. Vista or older and Windows Server 2008 or older. #' #' @param logical Whether to count logical CPUs. #' @return Integer scalar. #' #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' ps_cpu_count(logical = TRUE) #' ps_cpu_count(logical = FALSE) ps_cpu_count <- function(logical = TRUE) { assert_flag(logical) if (logical) ps_cpu_count_logical() else ps_cpu_count_physical() } ps_cpu_count_logical <- function() { .Call(ps__cpu_count_logical) } ps_cpu_count_physical <- function() { if (ps_os_type()[["LINUX"]]) { ps_cpu_count_physical_linux() } else { .Call(ps__cpu_count_physical) } } #' Query the size of the current terminal #' #' If the standard output of the current R process is not a terminal, #' e.g. because it is redirected to a file, or the R process is running in #' a GUI, then it will throw an error. You need to handle this error if #' you want to use this function in a package. #' #' If an error happens, the error message is different depending on #' what type of device the standard output is. Some common error messages #' are: #' * "Inappropriate ioctl for device." #' * "Operation not supported on socket." #' * "Operation not supported by device." #' #' Whatever the error message, `ps_tty_size` always fails with an error of #' class `ps_unknown_tty_size`, which you can catch. #' #' @export #' @examples #' # An example that falls back to the 'width' option #' tryCatch( #' ps_tty_size(), #' ps_unknown_tty_size = function(err) { #' c(width = getOption("width"), height = NA_integer_) #' } #' ) ps_tty_size <- function() { tryCatch( ret <- .Call(ps__tty_size), error = function(err) { class(err) <- c("ps_unknown_tty_size", class(err)) stop(err) } ) c(width = ret[1], height = ret[2]) } #' List all processes that loaded a shared library #' #' @details #' ## Notes: #' This function currently only works on Windows. #' #' On Windows, a 32 bit R process can only list other 32 bit processes. #' Similarly, a 64 bit R process can only list other 64 bit processes. #' This is a limitation of the Windows API. #' #' Even though Windows file systems are (almost always) case #' insensitive, the matching of `paths`, `user` and also `filter` #' are case sensitive. This might change in the future. #' #' This function can be very slow on Windows, because it needs to #' enumerate all shared libraries of all processes in the system, #' unless the `filter` argument is set. Make sure you set `filter` #' if you can. #' #' If you want to look up multiple shared libraries, list all of them #' in `paths`, instead of calling `ps_shared_lib_users` for each #' individually. #' #' If you are after libraries loaded by R processes, you might want to #' set `filter` to `c("Rgui.exe", "Rterm.exe", "rsession.exe")` The #' last one is for RStudio. #' #' @param paths Character vector of paths of shared libraries to #' look up. They must be absolute paths. They don't need to exist. #' Forward slashes are converted to backward slashes on Windows, and #' the output will always have backward slashes in the paths. #' @param user Character scalar or `NULL`. If not `NULL`, then only #' the processes of this user are considered. It defaults to the #' current user. #' @param filter Character vector or `NULL`. If not NULL, then it is #' a vector of glob expressions, used to filter the process names. #' @return A data frame (tibble) with columns: #' * `dll`: the file name of the dll file, without the path, #' * `path`: path to the shared library, #' * `pid`: process ID of the process, #' * `name`: name of the process, #' * `username`: username of process owner, #' * `ps_handle`: `ps_handle` object, that can be used to further #' query and manipulate the process. #' #' @export #' @family shared library tools #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() && ps::ps_os_type()[["WINDOWS"]] #' dlls <- vapply(getLoadedDLLs(), "[[", character(1), "path") #' psdll <- dlls[["ps"]][[1]] #' r_procs <- c("Rgui.exe", "Rterm.exe", "rsession.exe") #' ps_shared_lib_users(psdll, filter = r_procs) ps_shared_lib_users <- function(paths, user = ps_username(), filter = NULL) { os <- ps_os_type() if (!os[["WINDOWS"]]) { stop("`ps_shared_lib_users()` currently only works on Windows") } assert_character(paths) if (!is.null(user)) assert_string(user) if (!is.null(filter)) assert_character(filter) if (os[["WINDOWS"]]) paths <- gsub("/", "\\", paths, fixed = TRUE) pids <- ps_pids() processes <- not_null(lapply(pids, function(p) { tryCatch(ps_handle(p), error = function(e) NULL) })) nm <- map_chr(processes, function(p) fallback(ps_name(p), NA_character_)) if (!is.null(filter)) { selected <- glob$test_any(filter, nm) processes <- processes[selected] nm <- nm[selected] } us <- map_chr(processes, function(p) fallback(ps_username(p), NA_character_)) if (!is.null(user)) { us2 <- short_username(us) selected <- (!is.na(us) & us == user) | (!is.na(us2) & us2 == user) processes <- processes[selected] nm <- nm[selected] us <- us[selected] } libs <- lapply(processes, function(p) { tryCatch(ps_shared_libs(p)$path, error = function(e) character()) }) # TODO: handle case insensitive OS/FS match <- lapply(libs, intersect, paths) match_len <- map_int(match, length) match_processes <- processes[match_len > 0] match_username <- us[match_len > 0] match_name <- nm[match_len > 0] match_len <- match_len[match_len > 0] match_pids <- map_int(match_processes, ps_pid) d <- data.frame( stringsAsFactors = FALSE, dll = basename(unlist(match)), path = unlist(match), pid = rep(match_pids, match_len), name = rep(match_name, match_len), username = rep(match_username, match_len), ps_handle = I(rep(match_processes, match_len)) ) # The ones without name probably finished already. d <- d[!is.na(d$name), , drop = FALSE] requireNamespace("tibble", quietly = TRUE) class(d) <- unique(c("tbl_df", "tbl", class(d))) d } short_username <- function(x) { xs <- strsplit(x, "\\", fixed = TRUE) p1 <- map_chr(xs, "[", 1) p2 <- map_chr(xs, "[", 2) ifelse(!is.na(p2), p2, x) } # Docs from psutil, thanks! #' Return the average system load over the last 1, 5 and 15 minutes as a #' tuple. The “load” represents the processes which are in a runnable #' state, either using the CPU or waiting to use the CPU (e.g. waiting for #' disk I/O). On Windows this is emulated by using a Windows API that #' spawns a thread which keeps running in background and updates results #' every 5 seconds, mimicking the UNIX behavior. Thus, on Windows, the #' first time this is called and for the next 5 seconds it will return a #' meaningless (0.0, 0.0, 0.0) vector. The numbers returned only make sense #' if related to the number of CPU cores installed on the system. So, for #' instance, a value of 3.14 on a system with 10 logical CPUs means that #' the system load was 31.4% percent over the last N minutes. #' #' @return Numeric vector of length 3. #' #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' ps_loadavg() ps_loadavg <- function() { if (is.null(ps_env$counter_name)) { if (ps_os_type()[["WINDOWS"]]) { ps_env$counter_name <- find_loadavg_counter() } else { ps_env$counter_name <- "" } } .Call(ps__loadavg, ps_env$counter_name) } find_loadavg_counter <- function() { key <- paste0( "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\", "CurrentLanguage" ) tryCatch({ pc <- utils::readRegistry(key) idx <- seq(2, length(pc$Counter), by = 2) cnt <- structure(pc$Counter[idx], names = pc$Counter[idx - 1]) nm <- paste0("\\", cnt["2"], "\\", cnt["44"]) Encoding(nm) <- "" enc2utf8(nm) }, error = function(e) "\\System\\Processor Queue Length") } #' System CPU times. #' #' Every attribute represents the seconds the CPU has spent in the given #' mode. The attributes availability varies depending on the platform: #' * `user`: time spent by normal processes executing in user mode; #' on Linux this also includes guest time. #' * `system`: time spent by processes executing in kernel mode. #' * `idle`: time spent doing nothing. #' #' Platform-specific fields: #' #' * `nice` (UNIX): time spent by niced (prioritized) processes executing #' in user mode; on Linux this also includes guest_nice time. #' * `iowait` (Linux): time spent waiting for I/O to complete. This is not #' accounted in idle time counter. #' * `irq` (Linux): time spent for servicing hardware interrupts. #' * `softirq` (Linux): time spent for servicing software interrupts. #' * `steal` (Linux 2.6.11+): time spent by other operating systems #' running in a virtualized environment. #' * `guest` (Linux 2.6.24+): time spent running a virtual CPU for guest #' operating systems under the control of the Linux kernel. #' * `guest_nice` (Linux 3.2.0+): time spent running a niced guest #' (virtual CPU for guest operating systems under the control of the #' Linux kernel). #' #' @return Named list #' #' @export #' @examplesIf ps::ps_is_supported() #' ps_system_cpu_times() ps_system_cpu_times <- function() { os <- ps_os_name() if (os == "LINUX") { ps__system_cpu_times_linux() } else { .Call(ps__system_cpu_times) } } ps/R/error.R0000644000176200001440000000030413330045252012343 0ustar liggesusers ps__invalid_argument <- function(arg, ...) { msg <- paste0(encodeString(arg, quote = "`"), ...) structure( list(message = msg), class = c("invalid_argument", "error", "condition")) } ps/R/low-level.R0000644000176200001440000006750213742035201013135 0ustar liggesusers #' Create a process handle #' #' @param pid Process id. Integer scalar. `NULL` means the current R #' process. #' @param time Start time of the process. Usually `NULL` and ps will query #' the start time. #' @return `ps_handle()` returns a process handle (class `ps_handle`). #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p ps_handle <- function(pid = NULL, time = NULL) { if (!is.null(pid)) assert_pid(pid) if (!is.null(time)) assert_time(time) .Call(psll_handle, pid, time) } #' @rdname ps_handle #' @export as.character.ps_handle <- function(x, ...) { pieces <- .Call(psll_format, x) paste0(" PID=", pieces[[2]], ", NAME=", pieces[[1]], ", AT=", format_unix_time(pieces[[3]])) } #' @param x Process handle. #' @param ... Not used currently. #' #' @rdname ps_handle #' @export format.ps_handle <- function(x, ...) { as.character(x, ...) } #' @rdname ps_handle #' @export print.ps_handle <- function(x, ...) { cat(format(x, ...), "\n", sep = "") invisible(x) } #' Pid of a process handle #' #' This function works even if the process has already finished. #' #' @param p Process handle. #' @return Process id. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_pid(p) #' ps_pid(p) == Sys.getpid() ps_pid <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_pid, p) } #' Start time of a process #' #' The pid and the start time pair serves as the identifier of the process, #' as process ids might be reused, but the chance of starting two processes #' with identical ids within the resolution of the timer is minimal. #' #' This function works even if the process has already finished. #' #' @param p Process handle. #' @return `POSIXct` object, start time, in GMT. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_create_time(p) ps_create_time <- function(p = ps_handle()) { assert_ps_handle(p) format_unix_time(.Call(psll_create_time, p)) } #' Checks whether a process is running #' #' It returns `FALSE` if the process has already finished. #' #' It uses the start time of the process to work around pid reuse. I.e. # it returns the correct answer, even if the process has finished and # its pid was reused. #' #' @param p Process handle. #' @return Logical scalar. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_is_running(p) ps_is_running <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_is_running, p) } #' Parent pid or parent process of a process #' #' `ps_ppid()` returns the parent pid, `ps_parent()` returns a `ps_handle` #' of the parent. #' #' On POSIX systems, if the parent process terminates, another process #' (typically the pid 1 process) is marked as parent. `ps_ppid()` and #' `ps_parent()` will return this process then. #' #' Both `ps_ppid()` and `ps_parent()` work for zombie processes. #' #' @param p Process handle. #' @return `ps_ppid()` returns and integer scalar, the pid of the parent #' of `p`. `ps_parent()` returns a `ps_handle`. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_ppid(p) #' ps_parent(p) ps_ppid <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_ppid, p) } #' @rdname ps_ppid #' @export ps_parent <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_parent, p) } #' Process name #' #' The name of the program, which is typically the name of the executable. #' #' On on Unix this can change, e.g. via an exec*() system call. #' #' `ps_name()` works on zombie processes. #' #' @param p Process handle. #' @return Character scalar. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_name(p) #' ps_exe(p) #' ps_cmdline(p) ps_name <- function(p = ps_handle()) { assert_ps_handle(p) n <- .Call(psll_name, p) if (nchar(n) >= 15) { ## On UNIX the name gets truncated to the first 15 characters. ## If it matches the first part of the cmdline we return that ## one instead because it's usually more explicative. ## Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon". cmdline <- tryCatch( ps_cmdline(p), error = function(e) NULL ) if (!is.null(cmdline)) { exname <- basename(cmdline[1]) if (str_starts_with(exname, n)) n <- exname } } n } #' Full path of the executable of a process #' #' Path to the executable of the process. May also be an empty string or #' `NA` if it cannot be determined. #' #' For a zombie process it throws a `zombie_process` error. #' #' @param p Process handle. #' @return Character scalar. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_name(p) #' ps_exe(p) #' ps_cmdline(p) ps_exe <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_exe, p) } #' Command line of the process #' #' Command line of the process, i.e. the executable and the command line #' arguments, in a character vector. On Unix the program might change its #' command line, and some programs actually do it. #' #' For a zombie process it throws a `zombie_process` error. #' #' @param p Process handle. #' @return Character vector. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_name(p) #' ps_exe(p) #' ps_cmdline(p) ps_cmdline <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_cmdline, p) } #' Current process status #' #' One of the following: #' * `"idle"`: Process being created by fork, macOS only. #' * `"running"`: Currently runnable on macOS and Windows. Actually #' running on Linux. #' * `"sleeping"` Sleeping on a wait or poll. #' * `"disk_sleep"` Uninterruptible sleep, waiting for an I/O operation #' (Linux only). #' * `"stopped"` Stopped, either by a job control signal or because it #' is being traced. #' * `"tracing_stop"` Stopped for tracing (Linux only). #' * `"zombie"` Zombie. Finished, but parent has not read out the exit #' status yet. #' * `"dead"` Should never be seen (Linux). #' * `"wake_kill"` Received fatal signal (Linux only). #' * `"waking"` Paging (Linux only, not valid since the 2.6.xx kernel). #' #' Works for zombie processes. #' #' @param p Process handle. #' @return Character scalar. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_status(p) ps_status <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_status, p) } #' Owner of the process #' #' The name of the user that owns the process. On Unix it is calculated #' from the real user id. #' #' On Unix, a numeric uid id returned if the uid is not in the user #' database, thus a username cannot be determined. #' #' Works for zombie processes. #' #' @param p Process handle. #' @return String scalar. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_username(p) ps_username <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_username, p) } #' Process current working directory as an absolute path. #' #' For a zombie process it throws a `zombie_process` error. #' #' @param p Process handle. #' @return String scalar. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_cwd(p) ps_cwd <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_cwd, p) } #' User ids and group ids of the process #' #' User ids and group ids of the process. Both return integer vectors with #' names: `real`, `effective` and `saved`. #' #' Both work for zombie processes. #' #' They are not implemented on Windows, they throw a `not_implemented` #' error. #' #' @param p Process handle. #' @return Named integer vector of length 3, with names: `real`, #' `effective` and `saved`. #' #' @seealso [ps_username()] returns a user _name_ and works on all #' platforms. #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_uids(p) #' ps_gids(p) ps_uids <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_uids, p) } #' @rdname ps_uids #' @export ps_gids <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_gids, p) } #' Terminal device of the process #' #' Returns the terminal of the process. Not implemented on Windows, always #' returns `NA_character_`. On Unix it returns `NA_character_` if the #' process has no terminal. #' #' Works for zombie processes. #' #' @param p Process handle. #' @return Character scalar. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_terminal(p) ps_terminal <- function(p = ps_handle()) { assert_ps_handle(p) ttynr <- .Call(psll_terminal, p) if (is.na(ttynr)) { NA_character_ } else { tmap <- get_terminal_map() tmap[[as.character(ttynr)]] } } #' Environment variables of a process #' #' `ps_environ()` returns the environment variables of the process, in a #' named vector, similarly to the return value of `Sys.getenv()` #' (without arguments). #' #' Note: this usually does not reflect changes made after the process #' started. #' #' `ps_environ_raw()` is similar to `p$environ()` but returns the #' unparsed `"var=value"` strings. This is faster, and sometimes good #' enough. #' #' These functions throw a `zombie_process` error for zombie processes. #' #' @param p Process handle. #' @return `ps_environ()` returns a named character vector (that has a #' `Dlist` class, so it is printed nicely), `ps_environ_raw()` returns a #' character vector. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' env <- ps_environ(p) #' env[["R_HOME"]] ps_environ <- function(p = ps_handle()) { assert_ps_handle(p) parse_envs(.Call(psll_environ, p)) } #' @rdname ps_environ #' @export ps_environ_raw <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_environ, p) } #' Number of threads #' #' Throws a `zombie_process()` error for zombie processes. #' #' @param p Process handle. #' @return Integer scalar. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_num_threads(p) ps_num_threads <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_num_threads, p) } #' CPU times of the process #' #' All times are measured in seconds: #' * `user`: Amount of time that this process has been scheduled in user #' mode. #' * `system`: Amount of time that this process has been scheduled in #' kernel mode #' * `children_user`: On Linux, amount of time that this process's #' waited-for children have been scheduled in user mode. #' * `children_system`: On Linux, Amount of time that this process's #' waited-for children have been scheduled in kernel mode. #' #' Throws a `zombie_process()` error for zombie processes. #' #' @param p Process handle. #' @return Named real vector or length four: `user`, `system`, #' `children_user`, `children_system`. The last two are `NA` on #' non-Linux systems. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_cpu_times(p) #' proc.time() ps_cpu_times <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_cpu_times, p) } #' Memory usage information #' #' A list with information about memory usage. Portable fields: #' * `rss`: "Resident Set Size", this is the non-swapped physical memory a #' process has used. On UNIX it matches "top"‘s 'RES' column (see doc). On #' Windows this is an alias for `wset` field and it matches "Memory" #' column of `taskmgr.exe`. #' * `vmem`: "Virtual Memory Size", this is the total amount of virtual #' memory used by the process. On UNIX it matches "top"‘s 'VIRT' column #' (see doc). On Windows this is an alias for the `pagefile` field and #' it matches the "Working set (memory)" column of `taskmgr.exe`. #' #' Non-portable fields: #' * `shared`: (Linux) memory that could be potentially shared with other #' processes. This matches "top"‘s 'SHR' column (see doc). #' * `text`: (Linux): aka 'TRS' (text resident set) the amount of memory #' devoted to executable code. This matches "top"‘s 'CODE' column (see #' doc). #' * `data`: (Linux): aka 'DRS' (data resident set) the amount of physical #' memory devoted to other than executable code. It matches "top"‘s #' 'DATA' column (see doc). #' * `lib`: (Linux): the memory used by shared libraries. #' * `dirty`: (Linux): the number of dirty pages. #' * `pfaults`: (macOS): number of page faults. #' * `pageins`: (macOS): number of actual pageins. #' #' For on explanation of Windows fields see the #' [PROCESS_MEMORY_COUNTERS_EX](https://docs.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-process_memory_counters_ex) #' structure. #' #' Throws a `zombie_process()` error for zombie processes. #' #' @param p Process handle. #' @return Named real vector. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' p #' ps_memory_info(p) ps_memory_info <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_memory_info, p) } #' Send signal to a process #' #' Send a signal to the process. Not implemented on Windows. See #' [signals()] for the list of signals on the current platform. #' #' It checks if the process is still running, before sending the signal, #' to avoid signalling the wrong process, because of pid reuse. #' #' @param p Process handle. #' @param sig Signal number, see [signals()]. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check() #' px <- processx::process$new("sleep", "10") #' p <- ps_handle(px$get_pid()) #' p #' ps_send_signal(p, signals()$SIGINT) #' p #' ps_is_running(p) #' px$get_exit_status() ps_send_signal <- function(p = ps_handle(), sig) { assert_ps_handle(p) assert_signal(sig) .Call(psll_send_signal, p, sig) } #' Suspend (stop) the process #' #' Suspend process execution with `SIGSTOP` preemptively checking #' whether PID has been reused. On Windows this has the effect of #' suspending all process threads. #' #' @param p Process handle. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check() #' px <- processx::process$new("sleep", "10") #' p <- ps_handle(px$get_pid()) #' p #' ps_suspend(p) #' ps_status(p) #' ps_resume(p) #' ps_status(p) #' ps_kill(p) ps_suspend <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_suspend, p) } #' Resume (continue) a stopped process #' #' Resume process execution with SIGCONT preemptively checking #' whether PID has been reused. On Windows this has the effect of resuming #' all process threads. #' #' @param p Process handle. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check() #' px <- processx::process$new("sleep", "10") #' p <- ps_handle(px$get_pid()) #' p #' ps_suspend(p) #' ps_status(p) #' ps_resume(p) #' ps_status(p) #' ps_kill(p) ps_resume <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_resume, p) } #' Terminate a Unix process #' #' Send a `SIGTERM` signal to the process. Not implemented on Windows. #' #' Checks if the process is still running, to work around pid reuse. #' #' @param p Process handle. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check() #' px <- processx::process$new("sleep", "10") #' p <- ps_handle(px$get_pid()) #' p #' ps_terminate(p) #' p #' ps_is_running(p) #' px$get_exit_status() ps_terminate <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_terminate, p) } #' Kill a process #' #' Kill the current process with SIGKILL preemptively checking #' whether PID has been reused. On Windows it uses `TerminateProcess()`. #' #' @param p Process handle. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ps::ps_os_type()["POSIX"] && ! ps:::is_cran_check() #' px <- processx::process$new("sleep", "10") #' p <- ps_handle(px$get_pid()) #' p #' ps_kill(p) #' p #' ps_is_running(p) #' px$get_exit_status() ps_kill <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_kill, p) } #' List of child processes (process objects) of the process. Note that #' this typically requires enumerating all processes on the system, so #' it is a costly operation. #' #' @param p Process handle. #' @param recursive Whether to include the children of the children, etc. #' @return List of `ps_handle` objects. #' #' @family process handle functions #' @export #' @importFrom utils head tail #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_parent(ps_handle()) #' ps_children(p) ps_children <- function(p = ps_handle(), recursive = FALSE) { assert_ps_handle(p) assert_flag(recursive) mypid <- ps_pid(p) mytime <- ps_create_time(p) map <- ps_ppid_map() ret <- list() if (!recursive) { for (i in seq_len(nrow(map))) { if (map$ppid[i] == mypid) { tryCatch({ child <- ps_handle(map$pid[i]) if (mytime <= ps_create_time(child)) { ret <- c(ret, child) } }, no_such_process = function(e) NULL, zombie_process = function(e) NULL) } } } else { seen <- integer() stack <- mypid while (length(stack)) { pid <- tail(stack, 1) stack <- head(stack, -1) if (pid %in% seen) next # nocov (happens _very_ rarely) seen <- c(seen, pid) child_pids <- map[ map[,2] == pid, 1] for (child_pid in child_pids) { tryCatch({ child <- ps_handle(child_pid) if (mytime <= ps_create_time(child)) { ret <- c(ret, child) stack <- c(stack, child_pid) } }, no_such_process = function(e) NULL, zombie_process = function(e) NULL) } } } ## This will throw if p has finished ps_ppid(p) ret } #' Query the ancestry of a process #' #' Query the parent processes recursively, up to the first process. #' (On some platforms, like Windows, the process tree is not a tree #' and may contain loops, in which case `ps_descent()` only goes up #' until the first repetition.) #' #' @param p Process handle. #' @return A list of process handles, starting with `p`, each one #' is the parent process of the previous one. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' ps_descent() ps_descent <- function(p = ps_handle()) { assert_ps_handle(p) windows <- ps_os_type()[["WINDOWS"]] branch <- list() branch_pids <- integer() current <- p current_pid <- ps_pid(p) if (windows) current_time <- ps_create_time(p) while (TRUE) { branch <- c(branch, list(current)) branch_pids <- c(branch_pids, current_pid) parent <- fallback(ps_parent(current), NULL) # Might fail on Windows, if the process does not exist if (is.null(parent)) break; # If the parent pid is the same, we stop. # Also, Windows might have loops parent_pid <- ps_pid(parent) if (parent_pid %in% branch_pids) break; # Need to check for pid reuse on Windows if (windows) { parent_time <- ps_create_time(parent) if (current_time <= parent_time) break current_time <- parent_time } current <- parent current_pid <- parent_pid } branch } ps_ppid_map <- function() { pids <- ps_pids() processes <- not_null(lapply(pids, function(p) { tryCatch(ps_handle(p), error = function(e) NULL) })) pids <- map_int(processes, ps_pid) ppids <- map_int(processes, function(p) fallback(ps_ppid(p), NA_integer_)) ok <- !is.na(ppids) data.frame( pid = pids[ok], ppid = ppids[ok] ) } #' Number of open file descriptors #' #' Note that in some IDEs, e.g. RStudio or R.app on macOS, the IDE itself #' opens files from other threads, in addition to the files opened from the #' main R thread. #' #' For a zombie process it throws a `zombie_process` error. #' #' @param p Process handle. #' @return Integer scalar. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' ps_num_fds(p) #' f <- file(tmp <- tempfile(), "w") #' ps_num_fds(p) #' close(f) #' unlink(tmp) #' ps_num_fds(p) ps_num_fds <- function(p = ps_handle()) { assert_ps_handle(p) .Call(psll_num_fds, p) } #' Open files of a process #' #' Note that in some IDEs, e.g. RStudio or R.app on macOS, the IDE itself #' opens files from other threads, in addition to the files opened from the #' main R thread. #' #' For a zombie process it throws a `zombie_process` error. #' #' @param p Process handle. #' @return Data frame, or tibble if the _tibble_ package is available, #' with columns: `fd` and `path`. `fd` is numeric file descriptor on #' POSIX systems, `NA` on Windows. `path` is an absolute path to the #' file. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' ps_open_files(p) #' f <- file(tmp <- tempfile(), "w") #' ps_open_files(p) #' close(f) #' unlink(tmp) #' ps_open_files(p) ps_open_files <- function(p = ps_handle()) { assert_ps_handle(p) l <- not_null(.Call(psll_open_files, p)) d <- data.frame( stringsAsFactors = FALSE, fd = vapply(l, "[[", integer(1), 2), path = vapply(l, "[[", character(1), 1)) requireNamespace("tibble", quietly = TRUE) class(d) <- unique(c("tbl_df", "tbl", class(d))) d } #' List network connections of a process #' #' For a zombie process it throws a `zombie_process` error. #' #' @param p Process handle. #' @return Data frame, or tibble if the _tibble_ package is available, #' with columns: #' * `fd`: integer file descriptor on POSIX systems, `NA` on Windows. #' * `family`: Address family, string, typically `AF_UNIX`, `AF_INET` or #' `AF_INET6`. #' * `type`: Socket type, string, typically `SOCK_STREAM` (TCP) or #' `SOCK_DGRAM` (UDP). #' * `laddr`: Local address, string, `NA` for UNIX sockets. #' * `lport`: Local port, integer, `NA` for UNIX sockets. #' * `raddr`: Remote address, string, `NA` for UNIX sockets. This is #' always `NA` for `AF_INET` sockets on Linux. #' * `rport`: Remote port, integer, `NA` for UNIX sockets. #' * `state`: Socket state, e.g. `CONN_ESTABLISHED`, etc. It is `NA` #' for UNIX sockets. #' #' @family process handle functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' p <- ps_handle() #' ps_connections(p) #' sc <- socketConnection("httpbin.org", port = 80) #' ps_connections(p) #' close(sc) #' ps_connections(p) ps_connections <- function(p = ps_handle()) { assert_ps_handle(p) if (ps_os_type()[["LINUX"]]) return(psl_connections(p)) l <- not_null(.Call(psll_connections, p)) d <- data.frame( stringsAsFactors = FALSE, fd = vapply(l, "[[", integer(1), 1), family = match_names(ps_env$constants$address_families, vapply(l, "[[", integer(1), 2)), type = match_names(ps_env$constants$socket_types, vapply(l, "[[", integer(1), 3)), laddr = vapply(l, "[[", character(1), 4), lport = vapply(l, "[[", integer(1), 5), raddr = vapply(l, "[[", character(1), 6), rport = vapply(l, "[[", integer(1), 7), state = match_names(ps_env$constants$tcp_statuses, vapply(l, "[[", integer(1), 8))) d$laddr[d$laddr == ""] <- NA_character_ d$raddr[d$raddr == ""] <- NA_character_ d$lport[d$lport == 0] <- NA_integer_ d$rport[d$rport == 0] <- NA_integer_ requireNamespace("tibble", quietly = TRUE) class(d) <- unique(c("tbl_df", "tbl", class(d))) d } #' Interrupt a process #' #' Sends `SIGINT` on POSIX, and 'CTRL+C' or 'CTRL+BREAK' on Windows. #' #' @param p Process handle. #' @param ctrl_c On Windows, whether to send 'CTRL+C'. If `FALSE`, then #' 'CTRL+BREAK' is sent. Ignored on non-Windows platforms. #' #' @family process handle functions #' @export ps_interrupt <- function(p = ps_handle(), ctrl_c = TRUE) { assert_ps_handle(p) assert_flag(ctrl_c) if (ps_os_type()[["WINDOWS"]]) { interrupt <- get_tool("interrupt") .Call(psll_interrupt, p, ctrl_c, interrupt) } else { .Call(psll_interrupt, p, ctrl_c, NULL) } } #' @return `ps_windows_nice_values()` return a character vector of possible #' priority values on Windows. #' @export #' @rdname ps_get_nice ps_windows_nice_values <- function() { c("realtime", "high", "above_normal", "normal", "idle", "below_normal") } #' Get or set the priority of a process #' #' `ps_get_nice()` returns the current priority, `ps_set_nice()` sets a #' new priority, `ps_windows_nice_values()` list the possible priority #' values on Windows. #' #' Priority values are different on Windows and Unix. #' #' On Unix, priority is an integer, which is maximum 20. 20 is the lowest #' priority. #' #' ## Rules: #' * On Windows you can only set the priority of the processes the current #' user has `PROCESS_SET_INFORMATION` access rights to. This typically #' means your own processes. #' * On Unix you can only set the priority of the your own processes. #' The superuser can set the priority of any process. #' * On Unix you cannot set a higher priority, unless you are the superuser. #' (I.e. you cannot set a lower number.) #' * On Unix the default priority of a process is zero. #' #' @param p Process handle. #' @return `ps_get_nice()` returns a string from #' `ps_windows_nice_values()` on Windows. On Unix it returns an integer #' smaller than or equal to 20. #' #' @export ps_get_nice <- function(p = ps_handle()) { assert_ps_handle(p) code <- .Call(psll_get_nice, p) if (ps_os_type()[["WINDOWS"]]) { ps_windows_nice_values()[code] } else { code } } #' @param value On Windows it must be a string, one of the values of #' `ps_windows_nice_values()`. On Unix it is a priority value that is #' smaller than or equal to 20. #' @return `ps_set_nice()` return `NULL` invisibly. #' #' @export #' @rdname ps_get_nice ps_set_nice <- function(p = ps_handle(), value) { assert_ps_handle(p) assert_nice_value(value) if (ps_os_type()[["POSIX"]]) { value <- as.integer(value) } else { value <- match(value, ps_windows_nice_values()) } invisible(.Call(psll_set_nice, p, value)) } #' List the dynamically loaded libraries of a process #' #' Note: this function currently only works on Windows. #' @param p Process handle. #' @return Data frame with one column currently: `path`, the #' absolute path to the loaded module or shared library. On Windows #' the list includes the executable file itself. #' #' @export #' @family process handle functions #' @family shared library tools #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() && ps::ps_os_type()[["WINDOWS"]] #' # The loaded DLLs of the current process #' ps_shared_libs() ps_shared_libs <- function(p = ps_handle()) { assert_ps_handle(p) if (!ps_os_type()[["WINDOWS"]]) { stop("`ps_shared_libs()` is currently only supported on Windows") } l <- .Call(psll_dlls, p) d <- data.frame( stringsAsFactors = FALSE, path = map_chr(l, "[[", 1) ) requireNamespace("tibble", quietly = TRUE) class(d) <- unique(c("tbl_df", "tbl", class(d))) d } ps/R/kill-tree.R0000644000176200001440000001124513620625516013121 0ustar liggesusers #' Mark a process and its (future) child tree #' #' `ps_mark_tree()` generates a random environment variable name and sets #' it in the current R process. This environment variable will be (by #' default) inherited by all child (and grandchild, etc.) processes, and #' will help finding these processes, even if and when they are (no longer) #' related to the current R process. (I.e. they are not connected in the #' process tree.) #' #' `ps_find_tree()` finds the processes that set the supplied environment #' variable and returns them in a list. #' #' `ps_kill_tree()` finds the processes that set the supplied environment #' variable, and kills them (or sends them the specified signal on Unix). #' #' `with_process_cleanup()` evaluates an R expression, and cleans up all #' external processes that were started by the R process while evaluating #' the expression. This includes child processes of child processes, etc., #' recursively. It returns a list with entries: `result` is the result of #' the expression, `visible` is TRUE if the expression should be printed #' to the screen, and `process_cleanup` is a named integer vector of the #' cleaned pids, names are the process names. #' #' If `expr` throws an error, then so does `with_process_cleanup()`, the #' same error. Nevertheless processes are still cleaned up. #' #' @section Note: #' Note that `with_process_cleanup()` is problematic if the R process is #' multi-threaded and the other threads start subprocesses. #' `with_process_cleanup()` cleans up those processes as well, which is #' probably not what you want. This is an issue for example in RStudio. #' Do not use `with_process_cleanup()`, unless you are sure that the #' R process is single-threaded, or the other threads do not start #' subprocesses. E.g. using it in package test cases is usually fine, #' because RStudio runs these in a separate single-threaded process. #' #' The same holds for manually running `ps_mark_tree()` and then #' `ps_find_tree()` or `ps_kill_tree()`. #' #' A safe way to use process cleanup is to use the processx package to #' start subprocesses, and set the `cleanup_tree = TRUE` in #' [processx::run()] or the [processx::process] constructor. #' #' @return `ps_mark_tree()` returns the name of the environment variable, #' which can be used as the `marker` in `ps_kill_tree()`. #' #' `ps_find_tree()` returns a list of `ps_handle` objects. #' #' `ps_kill_tree()` returns the pids of the killed processes, in a named #' integer vector. The names are the file names of the executables, when #' available. #' #' `with_process_cleanup()` returns the value of the evaluated expression. #' #' @rdname ps_kill_tree #' @export ps_mark_tree <- function() { id <- get_id() do.call(Sys.setenv, structure(list("YES"), names = id)) id } get_id <- function() { paste0( "PS", paste( sample(c(LETTERS, 0:9), 10, replace = TRUE), collapse = "" ), "_", as.integer(Internal(Sys.time())) ) } #' @param expr R expression to evaluate in the new context. #' #' @rdname ps_kill_tree #' @export with_process_cleanup <- function(expr) { id <- ps_mark_tree() stat <- NULL do <- function() { on.exit(stat <<- ps_kill_tree(id), add = TRUE) withVisible(expr) } res <- do() ret <- list( result = res$value, visible = res$visible, process_cleanup = stat) class(ret) <- "with_process_cleanup" ret } #' @export print.with_process_cleanup <- function(x, ...) { if (x$visible) print(x$result) if (length(x$process_cleanup)) { cat("!! Cleaned up the following processes:\n") print(x$process_cleanup) } else { cat("-- No leftover processes to clean up.\n") } invisible(x) } #' @rdname ps_kill_tree #' @export ps_find_tree <- function(marker) { assert_string(marker) after <- as.numeric(strsplit(marker, "_", fixed = TRUE)[[1]][2]) pids <- setdiff(ps_pids(), Sys.getpid()) not_null(lapply(pids, function(p) { tryCatch( .Call(ps__find_if_env, marker, after, p), error = function(e) NULL ) })) } #' @param marker String scalar, the name of the environment variable to #' use to find the marked processes. #' @param sig The signal to send to the marked processes on Unix. On #' Windows this argument is ignored currently. #' #' @rdname ps_kill_tree #' @export ps_kill_tree <- function(marker, sig = signals()$SIGKILL) { assert_string(marker) after <- as.numeric(strsplit(marker, "_", fixed = TRUE)[[1]][2]) pids <- setdiff(ps_pids(), Sys.getpid()) ret <- lapply(pids, function(p) { tryCatch( .Call(ps__kill_if_env, marker, after, p, sig), error = function(e) e ) }) gone <- map_lgl(ret, function(x) is.character(x)) structure(pids[gone], names = unlist(ret[gone])) } ps/R/posix.R0000644000176200001440000000073613623757674012414 0ustar liggesusers #' List of all supported signals #' #' Only the signals supported by the current platform are included. #' @return List of integers, named by signal names. #' #' @export signals <- function() { as.list(ps_env$constants$signals) } get_terminal_map <- function() { ls <- c( dir("/dev", pattern = "^tty.*", full.names = TRUE), dir("/dev/pts", full.names = TRUE)) ret <- structure(ls, names = as.character(.Call(psp__stat_st_rdev, ls))) ret[names(ret) != "0"] } ps/R/testthat-reporter.R0000644000176200001440000002532013735074641014734 0ustar liggesusers globalVariables("private") #' testthat reporter that checks if child processes are cleaned up in tests #' #' `CleanupReporter` takes an existing testthat `Reporter` object, and #' wraps it, so it checks for leftover child processes, at the specified #' place, see the `proc_unit` argument below. #' #' Child processes can be reported via a failed expectation, cleaned up #' silently, or cleaned up and reported (the default). #' #' The constructor of the `CleanupReporter` class has options: #' * `file`: the output file, if any, this is passed to `reporter`. #' * `proc_unit`: when to perform the child process check and cleanup. #' Possible values: #' * `"test"`: at the end of each [testthat::test_that()] block #' (the default), #' * `"testsuite"`: at the end of the test suite. #' * `proc_cleanup`: Logical scalar, whether to kill the leftover #' processes, `TRUE` by default. #' * `proc_fail`: Whether to create an expectation, that fails if there #' are any processes alive, `TRUE` by default. #' * `proc_timeout`: How long to wait for the processes to quit. This is #' sometimes needed, because even if some kill signals were sent to #' child processes, it might take a short time for these to take effect. #' It defaults to one second. #' * `rconn_unit`: When to perform the R connection cleanup. Possible values #' are `"test"` and `"testsuite"`, like for `proc_unit`. #' * `rconn_cleanup`: Logical scalar, whether to clean up leftover R #' connections. `TRUE` by default. #' * `rconn_fail`: Whether to fail for leftover R connections. `TRUE` by #' default. #' * `file_unit`: When to check for open files. Possible values are #' `"test"` and `"testsuite"`, like for `proc_unit`. #' * `file_fail`: Whether to fail for leftover open files. `TRUE` by #' default. #' * `conn_unit`: When to check for open network connections. #' Possible values are `"test"` and `"testsuite"`, like for `proc_unit`. #' * `conn_fail`: Whether to fail for leftover network connections. #' `TRUE` by default. #' #' @note Some IDEs, like RStudio, start child processes frequently, and #' sometimes crash when these are killed, only use this reporter in a #' terminal session. In particular, you can always use it in the #' idiomatic `testthat.R` file, that calls `test_check()` during #' `R CMD check`. #' #' @param reporter A testthat reporter to wrap into a new `CleanupReporter` #' class. #' @return New reporter class that behaves exactly like `reporter`, #' but it checks for, and optionally cleans up child processes, at the #' specified granularity. #' #' @section Examples: #' This is how to use this reporter in `testthat.R`: #' ``` #' library(testthat) #' library(mypackage) #' #' if (ps::ps_is_supported()) { #' reporter <- ps::CleanupReporter(testthat::ProgressReporter)$new( #' proc_unit = "test", proc_cleanup = TRUE) #' } else { #' ## ps does not support this platform #' reporter <- "progress" #' } #' #' test_check("mypackage", reporter = reporter) #' ``` #' #' @export CleanupReporter <- function(reporter = testthat::ProgressReporter) { R6::R6Class("CleanupReporter", inherit = reporter, public = list( initialize = function( file = getOption("testthat.output_file", stdout()), proc_unit = c("test", "testsuite"), proc_cleanup = TRUE, proc_fail = TRUE, proc_timeout = 1000, rconn_unit = c("test", "testsuite"), rconn_cleanup = TRUE, rconn_fail = TRUE, file_unit = c("test", "testsuite"), file_fail = TRUE, conn_unit = c("test", "testsuite"), conn_fail = TRUE) { if (!ps::ps_is_supported()) { stop("CleanupReporter is not supported on this platform") } super$initialize(file = file) private$proc_unit <- match.arg(proc_unit) private$proc_cleanup <- proc_cleanup private$proc_fail <- proc_fail private$proc_timeout <- proc_timeout private$rconn_unit <- match.arg(rconn_unit) private$rconn_cleanup <- rconn_cleanup private$rconn_fail <- rconn_fail private$file_unit <- match.arg(file_unit) private$file_fail <- file_fail private$conn_unit <- match.arg(conn_unit) private$conn_fail <- conn_fail invisible(self) }, start_test = function(context, test) { super$start_test(context, test) if (private$file_unit == "test") private$files <- ps_open_files(ps_handle()) if (private$rconn_unit == "test") private$rconns <- showConnections() if (private$proc_unit == "test") private$tree_id <- ps::ps_mark_tree() if (private$conn_unit == "test") private$conns <- ps_connections(ps_handle()) }, end_test = function(context, test) { if (private$proc_unit == "test") self$do_proc_cleanup(test) if (private$rconn_unit == "test") self$do_rconn_cleanup(test) if (private$file_unit == "test") self$do_file_cleanup(test) if (private$conn_unit == "test") self$do_conn_cleanup(test) super$end_test(context, test) }, start_reporter = function() { super$start_reporter() if (private$file_unit == "testsuite") private$files <- ps_open_files(ps_handle()) if (private$rconn_unit == "testsuite") private$rconns <- showConnections() if (private$proc_unit == "testsuite") private$tree_id <- ps::ps_mark_tree() if (private$conn_unit == "testsuite") private$conns <- ps_connections(ps_handle()) }, end_reporter = function() { super$end_reporter() if (private$proc_unit == "testsuite") { self$do_proc_cleanup("testsuite", quote = "") } if (private$rconn_unit == "testsuite") { self$do_rconn_cleanup("testsuite", quote = "") } if (private$file_unit == "testsuite") { self$do_file_cleanup("testsuite", quote = "") } if (private$conn_unit == "testsuite") { self$do_conn_cleanup("testsuite", quote = "") } }, do_proc_cleanup = function(test, quote = "'") { Sys.unsetenv(private$tree_id) deadline <- Sys.time() + private$proc_timeout / 1000 if (private$proc_fail) { while (length(ret <- ps::ps_find_tree(private$tree_id)) && Sys.time() < deadline) Sys.sleep(0.05) } if (private$proc_cleanup) { ret <- ps::ps_kill_tree(private$tree_id) } if (private$proc_fail) { testthat::with_reporter(self, start_end_reporter = FALSE, { self$expect_cleanup(test, ret, quote) }) } }, do_rconn_cleanup = function(test, quote = "'") { old <- private$rconns new <- showConnections() private$rconns <- NULL leftover <- ! new[, "description"] %in% old[, "description"] if (private$rconn_cleanup) { for (no in as.integer(rownames(new)[leftover])) { tryCatch(close(getConnection(no)), error = function(e) NULL) } } if (private$rconn_fail) { act <- testthat::quasi_label(rlang::enquo(test), test) testthat::expect( sum(leftover) == 0, sprintf( "%s did not close R connections: %s", encodeString(act$lab, quote = quote), paste0(encodeString(new[leftover, "description"], quote = "'"), " (", rownames(new)[leftover], ")", collapse = ", "))) } }, do_file_cleanup = function(test, quote = "'") { old <- private$files new <- ps_open_files(ps_handle()) private$files <- NULL leftover <- ! new$path %in% old$path ## Need to ignore some open files: ## * /dev/urandom might be opened internally by curl, openssl, etc. leftover <- leftover & new$path != "/dev/urandom" if (private$file_fail) { act <- testthat::quasi_label(rlang::enquo(test), test) testthat::expect( sum(leftover) == 0, sprintf( "%s did not close open files: %s", encodeString(act$lab, quote = quote), paste0(encodeString(new$path[leftover], quote = "'"), collapse = ", "))) } }, do_conn_cleanup = function(test, quote = "'") { old <- private$conns[, 1:6] private$conns <- NULL ## On windows, sometimes it takes time to remove the connection ## from the processes connection tables, so we try waiting a bit. ## We haven't seen issues with this on other OSes yet. deadline <- Sys.time() + as.difftime(0.5, units = "secs") repeat { new <- ps_connections(ps_handle())[, 1:6] ## This is a connection that is used internally on macOS, ## for DNS resolution. We'll just ignore it. Looks like this: ## # A tibble: 2 x 6 ## fd family type laddr lport raddr ## ## 7 AF_UNIX SOCK_STREAM NA /var/run/mDNSResponder ## 10 AF_UNIX SOCK_STREAM NA /var/run/mDNSResponder new <- new[ new$family != "AF_UNIX" | new$type != "SOCK_STREAM" | is.na(new$raddr) | paste(tolower(basename(new$raddr))) != "mdnsresponder", ] leftover <- ! apply(new, 1, paste, collapse = "&") %in% apply(old, 1, paste, collapse = "&") if (!ps_os_type()[["WINDOWS"]] || sum(leftover) == 0 || Sys.time() >= deadline) break; Sys.sleep(0.05) } if (private$conn_fail) { left <- new[leftover,] act <- testthat::quasi_label(rlang::enquo(test), test) testthat::expect( sum(leftover) == 0, sprintf( "%s did not close network connections: \n%s", encodeString(act$lab, quote = quote), paste(format(left), collapse = "\n"))) } }, expect_cleanup = function(test, pids, quote) { act <- testthat::quasi_label(rlang::enquo(test), test) act$pids <- length(pids) testthat::expect( length(pids) == 0, sprintf("%s did not clean up processes: %s", encodeString(act$lab, quote = quote), paste0(encodeString(names(pids), quote = "'"), " (", pids, ")", collapse = ", "))) invisible(act$val) } ), private = list( proc_unit = NULL, proc_cleanup = NULL, proc_fail = NULL, proc_timeout = NULL, rconn_unit = NULL, rconn_cleanup = NULL, rconn_fail = NULL, rconns = NULL, file_unit = NULL, file_fail = NULL, files = NULL, conn_unit = NULL, conn_fail = NULL, conns = NULL, tree_id = NULL ) ) } ps/R/os.R0000644000176200001440000000206013737122325011644 0ustar liggesusers #' Query the type of the OS #' #' @return `ps_os_type` returns a named logical vector. The rest of the #' functions return a logical scalar. #' #' `ps_is_supported()` returns `TRUE` if ps supports the current platform. #' #' @export #' @examples #' ps_os_type() #' ps_is_supported() ps_os_type <- function() { if (is.null(ps_env$os_type)) ps_env$os_type <- .Call(ps__os_type) ps_env$os_type } ps_os_name <- function() { os <- ps_os_type() os <- os[setdiff(names(os), c("BSD", "POSIX"))] names(os)[which(os)] } #' @rdname ps_os_type #' @export ps_is_supported <- function() { os <- ps_os_type() if (os[["LINUX"]]) { # On Linux we need to check if /proc is readable supported <- FALSE tryCatch({ readLines("/proc/stat", warn = FALSE, n = 1) supported <- TRUE }, error = function(e) e) supported } else { os <- os[setdiff(names(os), c("BSD", "POSIX"))] any(os) } } supported_str <- function() { os <- ps_os_type() os <- os[setdiff(names(os), c("BSD", "POSIX"))] paste(caps(names(os)), collapse = ", ") } ps/R/errno.R0000644000176200001440000000106513737300630012351 0ustar liggesusers #' List of 'errno' error codes #' #' For the errors that are not used on the current platform, `value` is #' `NA_integer_`. #' #' A data frame with columns: `name`, `value`, `description`. #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' errno() errno <- function() { err <- as.list(ps_env$constants$errno) err <- err[order(names(err))] data.frame( stringsAsFactors = FALSE, row.names = NULL, name = names(err), value = vapply(err, "[[", integer(1), 1), description = vapply(err, "[[", character(1), 2) ) } ps/R/glob.R0000644000176200001440000000335013742003274012146 0ustar liggesusers glob <- local({ to_regex <- function(glob) { restr <- new.env(parent = emptyenv(), size = 1003) idx <- 0L chr <- strsplit(glob, "", fixed = TRUE)[[1]] in_group <- FALSE for (c in chr) { if (c %in% c("/", "$", "^", "+", ".", "(", ")", "=", "!", "|")) { idx <- idx + 1L restr[[as.character(idx)]] <- paste0("\\", c) } else if (c == "?") { idx <- idx + 1L restr[[as.character(idx)]] <- "." } else if (c == "[" || c == "]") { idx <- idx + 1L restr[[as.character(idx)]] <- c } else if (c == "{") { idx <- idx + 1L restr[[as.character(idx)]] <- "(" in_group <- TRUE } else if (c == "}") { idx <- idx + 1L restr[[as.character(idx)]] <- ")" in_group <- FALSE } else if (c ==",") { idx <- idx + 1L restr[[as.character(idx)]] <- if (in_group) "|" else paste0("\\", c) } else if (c == "*") { idx <- idx + 1L restr[[as.character(idx)]] <- ".*" } else { idx <- idx + 1L restr[[as.character(idx)]] <- c } } paste0( "^", paste(mget(as.character(seq_len(idx)), restr), collapse = ""), "$") } test <- function(glob, paths) { re <- to_regex(glob) grepl(re, paths) } test_any <- function(globs, paths) { if (!length(paths)) return(logical()) res <- vapply(globs, to_regex, character(1)) m <- matrix( as.logical(unlist(lapply(res, grepl, x = paths))), nrow = length(paths)) apply(m, 1, any) } structure( list( .internal = environment(), to_regex = to_regex, test = test, test_any = test_any ), class = c("standalone_glob", "standalone") ) }) ps/R/memory.R0000644000176200001440000001327613737300630012543 0ustar liggesusers #' Statistics about system memory usage #' #' @return Named list. All numbers are in bytes: #' * `total`: total physical memory (exclusive swap). #' * `avail` the memory that can be given instantly to processes without #' the system going into swap. This is calculated by summing different #' memory values depending on the platform and it is supposed to be used #' to monitor actual memory usage in a cross platform fashion. #' * `percent`: Percentage of memory that is taken. #' * `used`: memory used, calculated differently depending on #' the platform and designed for informational purposes only. #' `total` - `free` does not necessarily match `used`. #' * `free`: memory not being used at all (zeroed) that is #' readily available; note that this doesn’t reflect the actual memory #' available (use `available` instead). `total` - `used` does not #' necessarily match `free`. #' * `active`: (Unix only) memory currently in use or very recently used, #' and so it is in RAM. #' * `inactive`: (Unix only) memory that is marked as not used. #' * `wired`: (macOS only) memory that is marked to always stay in RAM. It #' is never moved to disk. #' * `buffers`: (Linux only) cache for things like file system metadata. #' * `cached`: (Linux only) cache for various things. #' * `shared`: (Linux only) memory that may be simultaneously accessed by #' multiple processes. #' * `slab`: (Linux only) in-kernel data structures cache. #' #' @family memory functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' ps_system_memory() ps_system_memory <- function() { os <- ps_os_name() if (os == "MACOS") { l <- .Call(ps__system_memory) l$avail <- l$inactive + l$free l$used <- l$active + l$wired l$free <- l$free - l$speculative l$percent <- (l$total - l$avail) / l$total * 100 l[c("total", "avail", "percent", "used", "free", "active", "inactive", "wired")] } else if (os == "LINUX") { ps__system_memory_linux() } else if (os == "WINDOWS") { l <- .Call(ps__system_memory)[c("total", "avail")] l$free <- l$avail l$used <- l$total - l$avail l$percent <- (l$total - l$avail) / l$total l[c("total", "avail", "percent", "used", "free")] } else { stop("ps is not supported in this platform") } } ps__system_memory_linux <- function() { tab <- read.table("/proc/meminfo", header = FALSE, fill = TRUE) mems <- structure( as.list(tab[[2]] * 1024), names = tolower(sub(":$", "", tab[[1]])) ) total <- mems[["memtotal"]] free <- mems[["memfree"]] buffers <- mems[["buffers"]] %||% NA_real_ cached <- mems[["cached"]] %||% NA_real_ + mems[["sreclaimable"]] %||% 0 shared <- mems[["shmem"]] %||% mems[["memshared"]] %||% NA_real_ active <- mems[["active"]] %||% NA_real_ inactive <- mems[["inactive"]] if (is.null(inactive)) { inactive <- mems[["inact_dirty"]] + mems[["inact_clean"]] + mems[["inact_laundry"]] } if (length(inactive) == 0) inactive <- NA_real_ slab <- mems[["slab"]] %||% 0 used <- total - free - cached - buffers if (used < 0 || is.na(used)) { # May be symptomatic of running within a LCX container where such # values will be dramatically distorted over those of the host. used <- total - free } avail <- mems[["memavailable"]] %||% NA_real_ # If avail is greater than total or our calculation overflows, # that's symptomatic of running within a LCX container where such # values will be dramatically distorted over those of the host. # https://gitlab.com/procps-ng/procps/blob/ # 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764 if (!is.na(avail) && avail > total) avail <- free percent <- (total - avail) / total * 100 list(total = total, avail = avail, percent = percent, used = used, free = free, active = active, inactive = inactive, buffers = buffers, cached = cached, shared = shared, slab = slab) } #' System swap memory statistics #' #' @return Named list. All numbers are in bytes: #' * `total`: total swap memory. #' * `used`: used swap memory. #' * `free`: free swap memory. #' * `percent`: the percentage usage. #' * `sin`: the number of bytes the system has swapped in from disk #' (cumulative). This is `NA` on Windows. #' * `sout`: the number of bytes the system has swapped out from disk #' (cumulative). This is `NA` on Windows. #' #' @family memory functions #' @export #' @examplesIf ps::ps_is_supported() && ! ps:::is_cran_check() #' ps_system_swap() ps_system_swap <- function() { os <- ps_os_name() if (os == "MACOS") { l <- .Call(ps__system_swap) l$percent <- l$used / l$total * 100 l[c("total", "used", "free", "percent", "sin", "sout")] } else if (os == "LINUX") { ps__system_swap_linux() } else if (os == "WINDOWS") { l <- .Call(ps__system_memory) total <- l[[3]] free <- l[[4]] used <- total - free percent = used / total list(total = total, used = used, free = free, percent = percent, sin = NA_real_, sout = NA_real_) } else { stop("ps is not supported in this platform") } } ps__system_swap_linux <- function() { tab <- read.table("/proc/meminfo", header = FALSE, fill = TRUE) mems <- structure( as.list(tab[[2]] * 1024), names = tolower(sub(":$", "", tab[[1]])) ) total <- mems[["swaptotal"]] free <- mems[["swapfree"]] used <- total - free percent <- used / total * 100 sin <- sout <- 0 tryCatch({ tab2 <- read.table("/proc/vmstat", header = FALSE) vms <- structure(as.list(tab2[[2]] * 4 * 1024), names = tab2[[1]]) sin <- vms[["pswpin"]] %||% 0 sout <- vms[["pswpout"]] %||% 0 }, error = function(e) NULL ) list(total = total, used = used, free = free, percent = percent, sin = sin, sout = sout) } ps/R/ps.R0000644000176200001440000000553013357347612011660 0ustar liggesusers #' @useDynLib ps, .registration = TRUE NULL #' Process table #' #' @param user Username, to filter the results to matching processes. #' @param after Start time (`POSIXt`), to filter the results to processes #' that started after this. #' @return Data frame (tibble), see columns below. #' #' Columns: #' * `pid`: Process ID. #' * `ppid`: Process ID of parent process. #' * `name`: Process name. #' * `username`: Name of the user (real uid on POSIX). #' * `status`: I.e. *running*, *sleeping*, etc. #' * `user`: User CPU time. #' * `system`: System CPU time. #' * `rss`: Resident set size, the amount of memory the process currently #' uses. Does not include memory that is swapped out. It does include #' shared libraries. #' * `vms`: Virtual memory size. All memory the process has access to. #' * `created`: Time stamp when the process was created. #' * `ps_handle`: `ps_handle` objects, in a list column. #' #' @export ps <- function(user = NULL, after = NULL) { if (!is.null(user)) assert_string(user) if (!is.null(after)) assert_time(after) pids <- ps_pids() processes <- not_null(lapply(pids, function(p) { tryCatch(ps_handle(p), error = function(e) NULL) })) ct <- NULL if (!is.null(after)) { ct <- lapply(processes, ps_create_time) selected <- ct >= after processes <- processes[selected] ct <- ct[selected] } us <- NULL if (!is.null(user)) { us <- map_chr(processes, function(p) fallback(ps_username(p), NA_character_)) selected <- !is.na(us) & us == user processes <- processes[selected] us <- us[selected] } us <- us %||% map_chr(processes, function(p) fallback(ps_username(p), NA_character_)) ct <- ct %||% lapply(processes, function(p) fallback(ps_create_time(p), NA_time())) pd <- map_int(processes, function(p) fallback(ps_pid(p), NA_integer_)) pp <- map_int(processes, function(p) fallback(ps_ppid(p), NA_integer_)) nm <- map_chr(processes, function(p) fallback(ps_name(p), NA_character_)) st <- map_chr(processes, function(p) fallback(ps_status(p), NA_character_)) time <- lapply(processes, function(p) fallback(ps_cpu_times(p), NULL)) cpt <- map_dbl(time, function(x) x[["user"]] %||% NA_real_) cps <- map_dbl(time, function(x) x[["system"]] %||% NA_real_) mem <- lapply(processes, function(p) fallback(ps_memory_info(p), NULL)) rss <- map_dbl(mem, function(x) x[["rss"]] %||% NA_real_) vms <- map_dbl(mem, function(x) x[["vms"]] %||% NA_real_) pss <- data.frame( stringsAsFactors = FALSE, pid = pd, ppid = pp, name = nm, username = us, status = st, user = cpt, system = cps, rss = rss, vms = vms, created = format_unix_time(unlist(ct)), ps_handle = I(processes) ) pss <- pss[order(-as.numeric(pss$created)), ] requireNamespace("tibble", quietly = TRUE) class(pss) <- unique(c("tbl_df", "tbl", class(pss))) pss } ps/R/package.R0000644000176200001440000000207013406773313012621 0ustar liggesusers ps_env <- new.env(parent = emptyenv()) Internal <- NULL ## nocov start .onLoad <- function(libname, pkgname) { ps_env$constants <- new.env(parent = emptyenv()) .Call(ps__init, asNamespace("ps"), ps_env$constants) if (!is.null(ps_env$constants$signals)) { ps_env$constants$signals <- as.list(ps_env$constants$signals) } if (!is.null(ps_env$constants$errno)) { ps_env$constants$errno <- as.list(ps_env$constants$errno) } if (!is.null(ps_env$constants$address_families)) { ps_env$constants$address_families <- as.list(ps_env$constants$address_families) } if (!is.null(ps_env$constants$socket_types)) { ps_env$constants$socket_types <- as.list(ps_env$constants$socket_types) } Internal <<- get(".Internal", asNamespace("base")) ps_boot_time <<- memoize(ps_boot_time) ps_cpu_count_logical <<- memoize(ps_cpu_count_logical) ps_cpu_count_physical <<- memoize(ps_cpu_count_physical) get_terminal_map <<- memoize(get_terminal_map) NA_time <<- memoize(NA_time) } ## nocov end utils::globalVariables(c("self", "super")) ps/R/memoize.R0000644000176200001440000000073413330045252012666 0ustar liggesusers ## nocov start memoize <- function(fun) { fun cache <- NULL if (length(formals(fun)) > 0) { stop("Only memoizing functions without arguments") } dec <- function() { if (is.null(cache)) cache <<- fun() cache } attr(dec, "clear") <- function() cache <<- TRUE class(dec) <- c("memoize", class(dec)) dec } `$.memoize` <- function(x, name) { switch( name, "clear" = attr(x, "clear"), stop("unknown memoize method") ) } ## nocov end ps/R/linux.R0000644000176200001440000001325614016767153012400 0ustar liggesusers #' @importFrom utils read.table psl_connections <- function(p) { sock_raw <- not_null(.Call("psll_connections", p)) sock <- data.frame( fd = as.integer(vapply(sock_raw, "[[", character(1), 1)), id = vapply(sock_raw, "[[", character(1), 2) ) flt <- function(x, col, values) x[x[[col]] %in% values, ] unix <- flt(psl__read_table("/proc/net/unix"), "V7", sock$id) tcp <- flt(psl__read_table("/proc/net/tcp") [, 1:10], "V10", sock$id) tcp6 <- flt(psl__read_table("/proc/net/tcp6")[, 1:10], "V10", sock$id) udp <- flt(psl__read_table("/proc/net/udp") [, 1:10], "V10", sock$id) udp6 <- flt(psl__read_table("/proc/net/udp6")[, 1:10], "V10", sock$id) ## Sockets that still existed when we queried /proc/net, ## because some of them might be closed already... sockx <- flt(sock, "id", c(unix$V7, tcp$V10, tcp6$V10, udp$V10, udp6$V10)) if (length(tcp) && nrow(tcp)) { tcp$type <- "SOCK_STREAM" tcp$family <- "AF_INET" } if (length(tcp6) && nrow(tcp6)) { tcp6$type <- "SOCK_STREAM" tcp6$family <- "AF_INET6" } if (length(udp) && nrow(udp)) { udp$type <- "SOCK_DGRAM" udp$family <- "AF_INET" } if (length(udp6) && nrow(udp6)) { udp6$type <- "SOCK_DGRAM" udp6$family <- "AF_INET6" } net <- rbind(tcp, tcp6, udp, udp6) ## Unix socket might or might not have a path if (length(unix) && nrow(unix)) { unix$V8 <- unix$V8 %||% "" unix$V8[unix$V8 == ""] <- NA_character_ } ## The status column is 01...09, 0A, 0B, but R might parse it as integer if (length(unix) && nrow(unix)) { unix$V6 <- str_tail(paste0('0', as.character(unix$V6)), 2) } if (length(net) && nrow(net)) { net$V4 <- str_tail(paste0('0', as.character(net$V4)), 2) } d <- data.frame( stringsAsFactors = FALSE, fd = integer(), family = character(), type = character(), laddr = character(), lport = integer(), raddr = character(), rport = integer(), state = character() ) if (length(unix) && nrow(unix)) { d <- data.frame( stringsAsFactors = FALSE, fd = sockx$fd[match(unix$V7, sockx$id)], family = "AF_UNIX", type = match_names(ps_env$constants$socket_types, unix$V5), laddr = unix$V8, lport = NA_integer_, raddr = NA_character_, rport = NA_integer_, state = NA_character_) } if (!is.null(net)) { laddr <- mapply(psl__decode_address, net$V2, net$family, SIMPLIFY = FALSE, USE.NAMES = FALSE) raddr <- mapply(psl__decode_address, net$V3, net$family, SIMPLIFY = FALSE, USE.NAMES = FALSE) net_d <- data.frame( stringsAsFactors = FALSE, fd = sockx$fd[match(net$V10, sockx$id)], family = net$family, type = net$type, laddr = vapply(laddr, "[[", character(1), 1), lport = vapply(laddr, "[[", integer(1), 2), raddr = vapply(raddr, "[[", character(1), 1), rport = vapply(raddr, "[[", integer(1), 2), state = match_names(ps_env$constants$tcp_statuses, net$V4)) d <- rbind(d, net_d) } requireNamespace("tibble", quietly = TRUE) class(d) <- unique(c("tbl_df", "tbl", class(d))) d } psl__read_table <- function(file, stringsAsFactors = FALSE, header = FALSE, skip = 1, fill = TRUE, ...) { tryCatch( read.table(file, stringsAsFactors = stringsAsFactors, header = header, skip = skip, fill = fill, ...), error = function(e) NULL ) } psl__decode_address <- function(addr, family) { ipp <- strsplit(addr, ":")[[1]] if (length(ipp) != 2) return(list(NA_character_, NA_integer_)) addr <- str_strip(ipp[[1]]) port <- strtoi(ipp[[2]], 16) if (family == "AF_INET") { AF_INET <- ps_env$constants$address_families[["AF_INET"]] addrn <- strtoi(substring(addr, 1:4*2-1, 1:4*2), base = 16) if (.Platform$endian == "little") addrn <- rev(addrn) addrs <- .Call(ps__inet_ntop, as.raw(addrn), AF_INET) %||% NA_character_ list(addrs, port) } else { AF_INET6 <- ps_env$constants$address_families[["AF_INET6"]] addrn <- strtoi(substring(addr, 1:16*2-1, 1:16*2), base = 16) if (.Platform$endian == "little") { addrn[ 1: 4] <- rev(addrn[ 1: 4]) addrn[ 5: 8] <- rev(addrn[ 5: 8]) addrn[ 9:12] <- rev(addrn[ 9:12]) addrn[13:16] <- rev(addrn[13:16]) } addrs <- .Call(ps__inet_ntop, as.raw(addrn), AF_INET6) %||% NA_character_ list(addrs, port) } } psl__cpu_count_from_lscpu <- function() { tryCatch({ lines <- system("lscpu -p=core", intern = TRUE) cores <- unique(lines[!str_starts_with(lines, "#")]) length(cores) }, error = function(e) psl__cpu_count_from_cpuinfo()) } psl__cpu_count_from_cpuinfo <- function() { lines <- readLines("/proc/cpuinfo") mapping = list() current = list() for (l in lines) { l <- tolower(str_strip(l)) if (!nchar(l)) { if ("physical id" %in% names(current) && "cpu cores" %in% names(current)) { mapping[[ current[["physical id"]] ]] <- current[["cpu cores"]] } current <- list() } else { if (str_starts_with(l, "physical id") || str_starts_with(l, "cpu cores")) { kv <- strsplit(l, "\\t+:")[[1]] current[[ kv[[1]] ]] <- kv[[2]] } } } sum(as.integer(unlist(mapping))) } ps_cpu_count_physical_linux <- function() { if (Sys.which("lscpu") != "") { psl__cpu_count_from_lscpu() } else { psl__cpu_count_from_cpuinfo() } } ps__system_cpu_times_linux <- function() { stat <- readLines("/proc/stat", n = 1) tms <- as.double(strsplit(stat, "\\s+")[[1]][-1]) nms <- c( "user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice" ) names(tms) <- nms[1:length(tms)] tms } ps/NEWS.md0000644000176200001440000000602614016776101012001 0ustar liggesusers # ps 1.6.0 * New function `ps_system_cpu_times()` to calculate system CPU times. * New function `ps_loadavg()` to show the Unix style load average. # ps 1.5.0 * New function `ps_shared_libs()` to list the loaded shared libraries of a process, on Windows. * New function `ps_shared_lib_users()` to list all processes that loaded a certain shared library, on Windows. * New function `ps_descent()` to query the ancestry of a process. # ps 1.4.0 * ps is now under the MIT license. * Process functions now default to the calling R process. So e.g. you can write simply `ps_connections()` to list all network connections of the current process, instead of `ps_connections(ps_handle())`. * New `ps_get_nice()` and `ps_set_nice()` functions to get and set the priority of a process (#89). * New `ps_system_memory()` and `ps_system_swap()` functions, to return information about system memory and swap usage. * New `ps_disk_partitions()` and `ps_disk_usage()` functions, they return information about file systems, similarly to the `mount` and `df` Unix commands. * New `ps_tty_size()` function to query the size of the terminal. * Fixed an issue in `CLeanupReporter()` that triggered random failures on macOS. # ps 1.3.4 * `ps_cpu_count()` now reports the correct number on Windows, even if the package binary was built on a Windows version with a different API (#77). # ps 1.3.3 * New function `errno()` returns a table of `errno.h` error codes and their description. * ps now compiles again on Solaris. # ps 1.3.2 * ps now compiles again on unsupported platforms like Solaris. # ps 1.3.1 * Fixed an installation problem on some Windows versions, where the output of `cmd /c ver` looks different (#69). # ps 1.3.0 * New `ps_cpu_count()` function returns the number of logical or physical processors. # ps 1.2.1 * Fix a crash on Linux, that happened at load time (#50). # ps 1.2.0 * New `ps_connections()` to list network connections. The `CleanupReporter()` testthat reporter can check for leftover open network connections in test cases. * `ps_open_files()` does not include open sockets now on Linux, they are rather included in `ps_connections()`. * `CleanupReporter()` now ignores `/dev/urandom`, some packages (curl, openssl, etc.) keep this file open. * Fix `ps()` printing without the tibble package (#43). * Fix compilation with ICC (#39). * Fix a crash on Linux (#47). # ps 1.1.0 * New `ps_num_fds()` returns the number of open files/handles. * New `ps_open_files()` lists all open files of a process. * New `ps_interrupt()` interrupts a process. It sends a `SIGINT` signal on POSIX systems, and it can send CTRL+C or CTRL+BREAK events on Windows. * New `ps_users()` lists users connected to the system. * New `ps_mark_tree()`, `ps_find_tree()`, `ps_kill_tree()`, `with_process_cleanup()`: functions to mark and clean up child processes. * New `CleanupReporter`, to be used with testthat: it checks for leftover child processes and open files in `test_that()` blocks. # ps 1.0.0 First released version. ps/MD50000644000176200001440000001457014017000772011211 0ustar liggesuserscedb80f5140f69fc96a6d9b6352b7d53 *DESCRIPTION ed122a1c3e9892dd2b2d2e3518850898 *LICENSE 63e8ae8f640a1e2c28ae10746f3c09b1 *LICENSE.note 859bfe173af63701c04791516ad84423 *NAMESPACE 5fd2f3d4d500776443a91ab48f4dfc6e *NEWS.md 3fca1882bc35552ac8583e88d1d5ef56 *R/disk.R 1ca7271bf20813111f062ada3b09c23b *R/errno.R e9d5da70cc329bca4418e19af321c9b8 *R/error.R 03d55c8abff700b07215c68b8a944acf *R/glob.R 22cd9e0029929362c7fc30c705dd795c *R/kill-tree.R 9c9089c9ce78289180ce91f30adc96f5 *R/linux.R 7fa00524847f4dccf0e1fa88010735bd *R/low-level.R 389340f5fee7127eb63eee1361cdd84d *R/memoize.R 9e52cf471ad9568243d5f17938e947cd *R/memory.R cdfd829ac1c275055e65981db923c891 *R/os.R c9251bd3db34af080f5fe5efd4a5d350 *R/package.R c65da554810268ea68e5c5047218d181 *R/posix.R d5beb6502a773f4ddb3b754099d1fea1 *R/ps.R 8339cdf4cb794f63b62d4c9ee8662a50 *R/system.R 0f5dec6896a333b3808862c9e495aa0d *R/testthat-reporter.R 541e8e009677f5a26bd602e428351521 *R/utils.R f17fc724415779cad643b47024d35828 *README.md 673df0aae8fd90003843cc26867ad7fd *cleanup 838613c70aaecca31563507379991e23 *configure 757ef8281ef2a1ebcbcc8235aeee2d0e *configure.win f76a886fa32868f9eeaf9b0dfa9dc7ad *inst/WORDLIST 28613e2a5a14a4fe95f362359b2af465 *inst/internals.md 91ff027948dd8c251de37765e141070d *inst/tools/error-codes.R 94d5c399a5ddddea6407eb43cb5e6ef4 *inst/tools/winver.R 16b0b9b54676b263d8f5ca4a57454e3e *man/CleanupReporter.Rd 6bdd1f2ec6ac42218f8b5db8cac6bccb *man/errno.Rd fc5c1d924dc3cea8467b5d2e947e23af *man/ps.Rd d3f65d772b6a08285e726a16890bf99a *man/ps_boot_time.Rd a8d3ea8594c147a60d861a71d8c92478 *man/ps_children.Rd 7f040cd02944e0442f7308b407be7816 *man/ps_cmdline.Rd dd533e04e4bd42689fc5d086c1449823 *man/ps_connections.Rd c98816e750a20a8b942f063ab8dc894d *man/ps_cpu_count.Rd 5b055c0a275fcfe5fa26e1d7363533f0 *man/ps_cpu_times.Rd c8da91226f7ac08312a56fcf76bd4d9d *man/ps_create_time.Rd 103ccf825a1f7d6c86907127d08c62de *man/ps_cwd.Rd 62d4cf9824c7d89703953e429792d4fc *man/ps_descent.Rd 9439c6b3bff18edf846fe215a7571018 *man/ps_disk_partitions.Rd aa33665f00aa509562aa5f17407dca2a *man/ps_disk_usage.Rd 2c971927e57416627000cfdb752e22d8 *man/ps_environ.Rd 0b19e1b1afecbcfbeedf4ed9e0e800fc *man/ps_exe.Rd 5891e27e505e5535bbb69e888691c7cb *man/ps_get_nice.Rd 60159dcc7a1d2c34e6dcdb29a5e44794 *man/ps_handle.Rd 752ca5ce2040777d838f6a59a473d892 *man/ps_interrupt.Rd ff872e411570beb96fa31eeacc7a6d55 *man/ps_is_running.Rd 028e0b3ddf4c10bc85927624759bdca2 *man/ps_kill.Rd a88b3df63afe0e108380348bd1267598 *man/ps_kill_tree.Rd 1ae36dd30f7365a9d70e9420e71defa4 *man/ps_loadavg.Rd 946307891948f0ce5d3ea6466568af94 *man/ps_memory_info.Rd 230d807a91b7abcc131733e52051adb1 *man/ps_name.Rd a3471a66af79365819c3848d4422be65 *man/ps_num_fds.Rd 2040bca15a24a2a4008cef672618dcf6 *man/ps_num_threads.Rd e44b28044ff4c95e9688428dbba70cf9 *man/ps_open_files.Rd 3c9b99d9e87f47e1114bf901ddcfe6ed *man/ps_os_type.Rd 3e5c92b0e1df2a29f5dbb36467e6c802 *man/ps_pid.Rd f5dd035dbcc82569f667b2e252db9981 *man/ps_pids.Rd 149eb2521db62f4caf708ad893f4d5d5 *man/ps_ppid.Rd 796f6dccf2a80e21981e0293eb6110f1 *man/ps_resume.Rd 29e550425ff8c4b6a6247a22096cd130 *man/ps_send_signal.Rd 83bb2bb027e0ea44d13e0be7777ebad2 *man/ps_shared_lib_users.Rd e5d95dd765ebd7302c5d8bdf5ad7560d *man/ps_shared_libs.Rd f983a44844703823ab26e4596971d780 *man/ps_status.Rd b47c0c69c144fb6944d04b1f193e61ff *man/ps_suspend.Rd 4027e0bbd5ed0776e9c16689ba300f66 *man/ps_system_cpu_times.Rd d4bc41c560f094cc5e417854bf449a10 *man/ps_system_memory.Rd 0cd6c754ae8720e24c0a977ea8f249be *man/ps_system_swap.Rd 1f0d34f90723e7ef8532fa6f1d6a22bc *man/ps_terminal.Rd f67a83c2beb637704c9ef886468c7074 *man/ps_terminate.Rd bd554c0b5f1ef8391e409640a2f46607 *man/ps_tty_size.Rd ddb5d56cc23031a4faf33ed3e859587e *man/ps_uids.Rd 2ea465bbfbf5c5af18386e05a6299b4c *man/ps_username.Rd a30f6dea22b8d1a89787c8cf500b17b7 *man/ps_users.Rd b90f28ca6453b80a98510a27fd9f764b *man/signals.Rd 8b20a5ff62cc7919eb4a8ac938774b8e *src/Makevars.in 13125835fe5730bc21c5a572269b0ada *src/api-common.c 543d51e88e4835c534d383dcee8ca755 *src/api-linux.c 1b7adb48dd01f7145204d2021b7fb974 *src/api-macos.c 747409c3a75cc2b5bc2d6a7cb7aa2f13 *src/api-posix.c 2f66c9907cb31f96a470ddc5d9cdce0b *src/api-windows-conn.c a5eff007cc06cc18a26f86ee7d2e2035 *src/api-windows.c 3676a3ae0162ddfebf35a6646dc90c1b *src/arch/macos/process_info.c 1e38474d4963d003a9404e6895c745f6 *src/arch/macos/process_info.h 4cc297fd88db19c155a363abe85ac7e9 *src/arch/windows/ntextapi.h ed51ab1b3f99df7b58e5f00203614e58 *src/arch/windows/process_handles.c 9778eb5853e091f40e9fb7aa62ac3b09 *src/arch/windows/process_handles.h 2c0a5cb376ab6d0a1aac9cbdc2034607 *src/arch/windows/process_info.c 0b15bf50b4da657b87730ed80819b09c *src/arch/windows/process_info.h 707ee707cf063ee7425ffac6053aceeb *src/arch/windows/wmi.c 6675bf8f84a299db729356e9210c6163 *src/common.c ea8fe6cdf4b1b1095dc60101cbfec181 *src/common.h 045b222b175e68116c3557f58189579b *src/dummy.c d6b32a3299957efccddf2c37085cbecc *src/extra.c 4fb2102f7281cba7514a4719cb8b0f23 *src/init.c 25293087d29ce032315b03ca28b91cf7 *src/install.libs.R 3db35656c2495dee4cbe752196cb47b0 *src/interrupt.c 86ff1c57037b177626aabbe0cd9468df *src/linux.c 48ce22a7b5c6f09084d0a265616cbe4e *src/macos.c 6a50c5d34538325d1ff5da5159078dd8 *src/posix.c 2507b0980ff4bc172f06f04b9a37a935 *src/posix.h f959469346493710941ec8dda72c3f12 *src/ps-internal.h 668efb43850320e932987b7bb4b85e61 *src/ps.h ac0649b539050ee02a7e6d1cd1639d24 *src/px.c c317a2622a9f7e369ad47200c91c74f5 *src/windows.c 5795cd4a210d26008a52fbd7b624d87f *src/windows.h 41b67f188db189a58d2e3627e75c46cd *tests/testthat.R eebb6e5855f584ba2505b996d39e2664 *tests/testthat/helpers.R 0773c80eff63790f5bfb5315fdafa75a *tests/testthat/test-cleanup-reporter.R 93bf1c7f5da033440da7e0f6a0a2a0a0 *tests/testthat/test-common.R d44b4c2dcd8fbdbeae4136a5b8691f24 *tests/testthat/test-connections.R 640231323d4b9d2413f4454879c0a329 *tests/testthat/test-finished.R 35f49e0316e86b83858addbb3401b06f *tests/testthat/test-kill-tree.R 97e245023bee6a72970e6f54a973f1af *tests/testthat/test-linux.R ea8cbac8372243c712f2115f88ff5df4 *tests/testthat/test-macos.R 475b94523faffd01284aae14abed1492 *tests/testthat/test-pid-reuse.R 985f020240fc7b897ef0fe7b8ef4138e *tests/testthat/test-posix-zombie.R 33d341c2ab1ddeceeb8f3f1767376bb6 *tests/testthat/test-posix.R 8bf2aade7ae0ba33a5db859393ef1478 *tests/testthat/test-system.R bf549b0992380b0083013ec5b40a9472 *tests/testthat/test-utils.R 64704b900b93cad85ebb1d24deac7680 *tests/testthat/test-windows.R dbf065d9384b8c9bb73a5234c715a691 *tests/testthat/test-winver.R ps/inst/0000755000176200001440000000000013620625516011657 5ustar liggesusersps/inst/tools/0000755000176200001440000000000013655071345013022 5ustar liggesusersps/inst/tools/winver.R0000644000176200001440000000151713620625516014460 0ustar liggesusers winver_ver <- function(v = NULL) { if (is.null(v)) v <- system("cmd /c ver", intern = TRUE) v2 <- grep("\\[.*\\s.*\\]", v, value = TRUE)[1] v3 <- sub("^.*\\[[^ ]+\\s+", "", v2) v4 <- sub("\\]$", "", v3) if (is.na(v4)) stop("Failed to parse windows version") v4 } winver_wmic <- function(v = NULL) { cmd <- "wmic os get Version /value" if (is.null(v)) v <- system(cmd, intern = TRUE) v2 <- grep("=", v, value = TRUE) v3 <- strsplit(v2, "=", fixed = TRUE)[[1]][2] v4 <- sub("\\s*$", "", sub("^\\s*", "", v3)) if (is.na(v4)) stop("Failed to parse windows version") v4 } winver <- function() { ## First we try with `wmic` v <- if (Sys.which("wmic") != "") { tryCatch(winver_wmic(), error = function(e) NULL) } ## Otherwise `ver` if (is.null(v)) winver_ver() else v } if (is.null(sys.calls())) cat(winver()) ps/inst/tools/error-codes.R0000644000176200001440000001454013655027125015372 0ustar liggesusers code <- ' #include "common.h" #include SEXP ps__define_errno() { SEXP env = PROTECT(Rf_allocSExp(ENVSXP)); #define PS_ADD_ERRNO(err,str,val) \\ defineVar( \\ install(#err), \\ PROTECT(list2(PROTECT(ScalarInteger(val)), PROTECT(mkString(str)))), \\ env); \\ UNPROTECT(3) %s #undef PS_ADD_ERRNO #undef PS_ADD_ERRNOX UNPROTECT(1); return env; } ' data <- read.csv(stringsAsFactors = FALSE, textConnection(' name,txt EPERM,"Operation not permitted." ENOENT,"No such file or directory." ESRCH,"No such process." EINTR,"Interrupted function call." EIO,"Input/output error." ENXIO,"No such device or address." E2BIG,"Arg list too long." ENOEXEC,"Exec format error." EBADF,"Bad file descriptor." ECHILD,"No child processes." EDEADLK,"Resource deadlock avoided." ENOMEM,"Cannot allocate memory." EACCES,"Permission denied." EFAULT,"Bad address." ENOTBLK,"Not a block device." EBUSY,"Resource busy." EEXIST,"File exists." EXDEV,"Improper link." ENODEV,"Operation not supported by device." ENOTDIR,"Not a directory." EISDIR,"Is a directory." EINVAL,"Invalid argument." ENFILE,"Too many open files in system." EMFILE,"Too many open files." ENOTTY,"Inappropriate ioctl for device." ETXTBSY,"Text file busy." EFBIG,"File too large." ENOSPC,"Device out of space." ESPIPE,"Illegal seek." EROFS,"Read-only file system." EMLINK,"Too many links." EPIPE,"Broken pipe." EDOM,"Numerical argument out of domain." ERANGE,"Numerical result out of range." EAGAIN,"Resource temporarily unavailable." EINPROGRESS,"Operation now in progress." EALREADY,"Operation already in progress." ENOTSOCK,"Socket operation on non-socket." EDESTADDRREQ,"Destination address required." EMSGSIZE,"Message too long." EPROTOTYPE,"Protocol wrong type for socket." ENOPROTOOPT,"Protocol not available." EPROTONOSUPPORT,"Protocol not supported." ESOCKTNOSUPPORT,"Socket type not supported." ENOTSUP,"Not supported." EPFNOSUPPORT,"Protocol family not supported." EAFNOSUPPORT,"Address family not supported by protocol family." EADDRINUSE,"Address already in use." EADDRNOTAVAIL,"Cannot assign requested address." ENETDOWN,"Network is down." ENETUNREACH,"Network is unreachable." ENETRESET,"Network dropped connection on reset." ECONNABORTED,"Software caused connection abort." ECONNRESET,"Connection reset by peer." ENOBUFS,"No buffer space available." EISCONN,"Socket is already connected." ENOTCONN,"Socket is not connected." ESHUTDOWN,"Cannot send after socket shutdown." ETIMEDOUT,"Operation timed out." ECONNREFUSED,"Connection refused." ELOOP,"Too many levels of symbolic links." ENAMETOOLONG,"File name too long." EHOSTDOWN,"Host is down." EHOSTUNREACH,"No route to host." ENOTEMPTY,"Directory not empty." EPROCLIM,"Too many processes." EUSERS,"Too many users." EDQUOT,"Disc quota exceeded." ESTALE,"Stale NFS file handle." EBADRPC,"RPC struct is bad." ERPCMISMATCH,"RPC version wrong." EPROGUNAVAIL,"RPC prog. not avail." EPROGMISMATCH,"Program version wrong." EPROCUNAVAIL,"Bad procedure for program." ENOLCK,"No locks available." ENOSYS,"Function not implemented." EFTYPE,"Inappropriate file type or format." EAUTH,"Authentication error." ENEEDAUTH,"Need authenticator." EPWROFF,"Device power is off." EDEVERR,"Device error." EOVERFLOW,"Value too large to be stored in data type." EBADEXEC,"Bad executable (or shared library)." EBADARCH,"Bad CPU type in executable." ESHLIBVERS,"Shared library version mismatch." EBADMACHO,"Malformed Mach-o file." ECANCELED,"Operation canceled." EIDRM,"Identifier removed." ENOMSG,"No message of desired type." EILSEQ,"Illegal byte sequence." ENOATTR,"Attribute not found." EBADMSG,"Bad message." EMULTIHOP,"Multihop attempted." ENODATA,"No message available." ENOSTR,"Not a STREAM." EPROTO,"Protocol error." ETIME,"STREAM ioctl() timeout." EOPNOTSUPP,"Operation not supported on socket." EWOULDBLOCK,"Resource temporarily unavailable." ETOOMANYREFS,"Too many references: cannot splice." EREMOTE,"File is already NFS-mounted" EBACKGROUND,"Caller not in the foreground process group" EDIED,"Translator died" ED,"The experienced user will know what is wrong."#else EGREGIOUS,"You did *what*?" EIEIO,"Go home and have a glass of warm, dairy-fresh milk." EGRATUITOUS,"This error code has no purpose." ENOLINK,"Link has been severed." ENOSR,"Out of streams resources." ERESTART,"Interrupted system call should be restarted." ECHRNG,"Channel number out of range." EL2NSYNC,"Level 2 not synchronized." EL3HLT,"Level 3 halted." EL3RST,"Level 3 reset." ELNRNG,"Link number out of range." EUNATCH,"Protocol driver not attached." ENOCSI,"No CSI structure available." EL2HLT,"Level 2 halted." EBADE,"Invalid exchange." EBADR,"Invalid request descriptor." EXFULL,"Exchange full." ENOANO,"No anode." EBADRQC,"Invalid request code." EBADSLT,"Invalid slot." EDEADLOCK,"File locking deadlock error." EBFONT,"Bad font file format." ENONET,"Machine is not on the network." ENOPKG,"Package not installed." EADV,"Advertise error." ESRMNT,"Srmount error." ECOMM,"Communication error on send." EDOTDOT,"RFS specific error" ENOTUNIQ,"Name not unique on network." EBADFD,"File descriptor in bad state." EREMCHG,"Remote address changed." ELIBACC,"Can not access a needed shared library." ELIBBAD,"Accessing a corrupted shared library." ELIBSCN,".lib section in a.out corrupted." ELIBMAX,"Attempting to link in too many shared libraries." ELIBEXEC,"Cannot exec a shared library directly." ESTRPIPE,"Streams pipe error." EUCLEAN,"Structure needs cleaning." ENOTNAM,"Not a XENIX named type file." ENAVAIL,"No XENIX semaphores available." EISNAM,"Is a named type file." EREMOTEIO,"Remote I/O error." ENOMEDIUM,"No medium found." EMEDIUMTYPE,"Wrong medium type." ENOKEY,"Required key not available." EKEYEXPIRED,"Key has expired." EKEYREVOKED,"Key has been revoked." EKEYREJECTED,"Key was rejected by service." EOWNERDEAD,"Owner died." ENOTRECOVERABLE,"State not recoverable." ERFKILL,"Operation not possible due to RF-kill." EHWPOISON,"Memory page has hardware error." ')) defs <- sprintf(" #ifdef %s PS_ADD_ERRNO(%s,\"%s\",%s); #else PS_ADD_ERRNO(%s,\"%s\",NA_INTEGER); #endif ", data$name, data$name, data$txt, data$name, data$name, data$txt) txt <- paste0(sprintf(code, paste(defs, collapse = "\n")), collapse = "\n") writeBin(charToRaw(txt), con = "src/error-codes.c") ps/inst/internals.md0000644000176200001440000000231613356424172014203 0ustar liggesusers # `ps_handle` methods ``` method A C Z -------------- - - - ps_pid + . + ps_create_time + . + ps_is_running + . + ps_format + . + - ps_ppid . > + ps_parent . > + ps_name . > + ps_exe . > Z ps_cmdline . > Z ps_status . > + ps_username . > + ps_cwd . > Z ps_uids . > + ps_gids . > + ps_terminal . > + ps_environ . > Z ps_environ_raw . > Z ps_num_threads . > Z ps_cpu_times . > Z ps_memory_info . > Z ps_num_fds . > Z ps_open_files . > Z ps_connections . > Z ps_children . > + ps_send_signal . < + ps_suspend . < + ps_resume . < + ps_terminate . < + ps_kill . < + ps_interrupt . < + ``` ``` A: always works, even if the process has finished C: <: checks if process is running, before >: checks if process is running, after Z: +: works fine on a zombie Z: errors (zombie_process) on a zombie ``` # System API ## `ps()` ## `ps_pids()` ## `ps_boot_time()` ## Process cleanup `ps_kill_tree()`, `ps_mark_tree()`, `with_process_cleanup()`. ## `ps_os_type()` ## `signals()` ps/inst/WORDLIST0000644000176200001440000000020713357347755013065 0ustar liggesusersCTRL DRS IDEs macOS pageins pid Pid PID pids RStudio runnable SHR SIGCONT SIGKILL signalling testthat tibble TRS UDP uid unparsed VIRT ps/cleanup0000755000176200001440000000010714016776112012254 0ustar liggesusers#' !/usr/bin/env sh rm -f src/Makevars src/config.h src/error-codes.c ps/configure0000755000176200001440000001136014016776112012611 0ustar liggesusers#! /usr/bin/env sh # Check that this is not just ./configure. We need to run this # from R CMD INSTALL, to have the R env vars set. if [ -z "$R_HOME" ]; then echo >&2 R_HOME is not set, are you running R CMD INSTALL? exit 1 fi # Find the R binary we need to use. This is a bit trickier on # Windows, because it has two architectures. On windows R_ARCH_BIN # is set, so this should work everywhere. RBIN="${R_HOME}/bin${R_ARCH_BIN}/R" # ------------------------------------------------------------------------ # Detect system # ------------------------------------------------------------------------ unset POSIX if [ "$R_OSTYPE" = "unix" ]; then UNAME=`uname` else UNAME=Windows fi unset WINDOWS if [ "$R_OSTYPE" = "windows" ]; then WINDOWS=true; fi unset LINUX if [ "$UNAME" = "Linux" ]; then LINUX=true; POSIX=true; fi unset MACOS if [ "$UNAME" = "Darwin" ]; then MACOS=true; POSIX=true; fi unset FREEBSD ## if [ "UNAME" = "FreeBSD" ]; then FREEBSD=true; POSIX=true; fi unset OPENBSD ## if [ "UNAME" = "OpenBSD" ]; then OPENBSD=true; POSIX=true; fi unset NETBSD ## if [ "UNAME" = "NetBSD" ]; then NETBSD=true; POSIX=true; fi unset BSD if [ -n "$FREEBSD" ] || [ -n "$OPENBSD" ] || [ -n "$NETBSD" ]; then BSD=true fi unset SUNOS ## if [ "UNAME" = "SunOS" ]; then SUNOS=true; POSIX=true; fi unset AIX ## if [ "UNAME" = "AIX" ]; then AIX=true; POSIX=true; fi # ------------------------------------------------------------------------ # Set source files, macros, libs, compile flags # ------------------------------------------------------------------------ MACROS="" OBJECTS="init.o api-common.o common.o extra.o dummy.o error-codes.o" if [ -n "$POSIX" ]; then OBJECTS="${OBJECTS} posix.o api-posix.o"; MACROS="${MACROS} PS__POSIX" PS__POSIX=1 fi if [ -n "$BSD" ]; then MACROS="${MACROS} PS__BSD" PS__BSD=1 fi MACROS="$MACROS PS__VERSION" PS__VERSION=546 if [ -n "$WINDOWS" ]; then VER=`"$RBIN" --vanilla --slave -f inst/tools/winver.R` MAJOR=`echo $VER | cut -f1 -d.` MINOR=`echo $VER | cut -f2 -d.` WINVER=`"$RBIN" --vanilla --slave -e "cat(sprintf('0x0%s', $MAJOR*100 + $MINOR))"` MACROS="${MACROS} PS__WINDOWS _WIN32_WINNT _AVAIL_WINVER" MACROS="${MACROS} _CRT_SECURE_NO_WARNINGS PSAPI_VERSION" PS__WINDOWS=1 _WIN32_WINNT=$WINVER _AVAIL_WINVER=$WINVER _CT_SECURE_NO_WARNINGS="" PSAPI_VERSION=1 OBJECTS="${OBJECTS} windows.o api-windows.o api-windows-conn.o" OBJECTS="${OBJECTS} arch/windows/process_info.o" OBJECTS="${OBJECTS} arch/windows/process_handles.o" OBJECTS="${OBJECTS} arch/windows/wmi.o" LIBRARIES="psapi kernel32 advapi32 shell32 netapi32 iphlpapi wtsapi32" LIBRARIES="${LIBRARIES} ws2_32 PowrProf Pdh" TARGETS=interrupt elif [ -n "$MACOS" ]; then MACROS="${MACROS} PS__MACOS" PS__MACOS=1 OBJECTS="${OBJECTS} macos.o api-macos.o arch/macos/process_info.o" elif [ -n "$FREEBSD" ]; then MACROS="${MACROS} PS__FREEBSD" PS__FREEBSD=1 OBJECTS="${OBJECTS} bsd.o arch/freebsd/specific.o" OBJECTS="${OBJECTS} arch/freebsd/sys_socks.o" OBJECTS="${OBJECTS} arch/freebsd/proc_socks.o" LIBRARIES="devstat" elif [ -n "$OPENBSD" ]; then MACROS="${MACROS} PS__OPENBSD" PS__OPENBSD=1 OBJECTS="${OBJECTS} bsd.o arch/openbsd/specific.o" LIBRARIES="kvm" elif [ -n "$NETBSD" ]; then MACROS="${MACROS} PS__NETBSD" PS__NETBSD=1 OBJECTS="${OBJECTS} bsd.o arch/netbsd/specific.o" OBJECTS="${OBJECTS} arch/netbsd/sys_socks.o" OBJECTS="${OBJECTS} arch/netbsd/proc_socks.o" LIBRARIES="kvm" elif [ -n "$LINUX" ]; then MACROS="${MACROS} PS__LINUX" PS__LINUX=1 OBJECTS="${OBJECTS} linux.o api-linux.o" elif [ -n "$SUNOS" ]; then MACROS="${MACROS} PS__SUNOS" PS__SUNOS=1 OBJECTS="${OBJECTS} sunos.o arch/solaris/v10/ifaddrs.o" OBJECTS="${OBJECTS} arch/solaris/environ.o" elif [ -n "$AIX" ]; then MACROS="${MACROS} PS__AIX" PS__AIX=1 OBJECTS="${OBJECTS} aix.o" OBJECTS="${OBJECTS} arch/aix/net_connections.o" OBJECTS="${OBJECTS} arch/aix/common.o" OBJECTS="${OBJECTS} arch/aix/ifaddrs.o" fi # ------------------------------------------------------------------------ # Create Makevars file # ------------------------------------------------------------------------ "$RBIN" --vanilla --slave -f inst/tools/error-codes.R # OBJECTS (= source files) # LIBRARIES -> PKG_LIBS LIBS=`for l in $LIBRARIES; do echo "-l${l}"; done | tr "\n", " "` cat src/Makevars.in | \ sed "s|@OBJECTS@|${OBJECTS}|" | \ sed "s/@LIBS@/${LIBS}/" | \ sed "s/@TARGETS@/${TARGETS}/" > src/Makevars # MACROS will be set as preprocessor macros echo "/* Macros created by configure */" > src/config.h for m in $MACROS; do ind='$'$m echo \#undef $m eval echo '\#'define $m $ind done >> src/config.h