ps/0000755000176200001440000000000013621233753010701 5ustar liggesusersps/NAMESPACE0000644000176200001440000000213713620637301012117 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(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_environ) export(ps_environ_raw) export(ps_exe) export(ps_find_tree) 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_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_status) export(ps_suspend) export(ps_terminal) export(ps_terminate) export(ps_uids) export(ps_username) export(ps_users) export(signals) export(with_process_cleanup) importFrom(utils,head) importFrom(utils,read.table) importFrom(utils,tail) useDynLib(ps, .registration = TRUE) ps/LICENSE0000644000176200001440000000013013621001756011674 0ustar liggesusersYEAR: 2009-2020 COPYRIGHT HOLDER: Jay Loden, Dave Daeschler, Giampaolo Rodola', RStudio ps/README.md0000644000176200001440000005534213620744006012166 0ustar liggesusers # ps > List, Query, Manipulate System Processes [![lifecycle](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://www.tidyverse.org/lifecycle/#maturing) [![Travis build status](https://travis-ci.org/r-lib/ps.svg?branch=master)](https://travis-ci.org/r-lib/ps) [![AppVeyor build status](https://ci.appveyor.com/api/projects/status/github/r-lib/ps?branch=master&svg=true)](https://ci.appveyor.com/project/gaborcsardi/ps) [![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 BSD © RStudio ps/man/0000755000176200001440000000000013620625516011455 5ustar liggesusersps/man/ps_create_time.Rd0000644000176200001440000000306413620634430014725 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) } \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()) (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_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}()} } \concept{process handle functions} ps/man/ps_connections.Rd0000644000176200001440000000432013620634430014762 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) } \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()) (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_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}()} } \concept{process handle functions} ps/man/ps_terminate.Rd0000644000176200001440000000275713620634430014444 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) } \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"]) (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_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_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.Rd0000644000176200001440000000266313620634430013370 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) } \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()) (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_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_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_terminal.Rd0000644000176200001440000000272113620634430014256 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) } \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()) (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_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_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_cpu_times.Rd0000644000176200001440000000366713620634430014445 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) } \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()) (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_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}()} } \concept{process handle functions} ps/man/ps_num_fds.Rd0000644000176200001440000000306113620634430014074 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) } \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()) (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_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_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.Rd0000644000176200001440000000246713620634430013226 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) } \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()) (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_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_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.Rd0000644000176200001440000000547113620634430014773 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) } \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{http://msdn.microsoft.com/en-us/library/windows/desktop/ms684874(v=vs.85).aspx}{PROCESS_MEMORY_COUNTERS_EX} structure. Throws a \code{zombie_process()} error for zombie processes. } \examples{ \dontshow{if (ps::ps_is_supported()) (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_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_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.Rd0000644000176200001440000000124313620634430014440 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()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} ps_cpu_count(logical = TRUE) ps_cpu_count(logical = FALSE) \dontshow{\}) # examplesIf} } ps/man/ps_cmdline.Rd0000644000176200001440000000302113620634430014050 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) } \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()) (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_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}()} } \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.Rd0000644000176200001440000000277613620634430013755 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) } \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"]) (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_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_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_exe.Rd0000644000176200001440000000270513620634430013226 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) } \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()) (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_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_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.Rd0000644000176200001440000000274413620634430014267 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) } \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()) (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_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}()} } \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.Rd0000644000176200001440000000332613620634430013700 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()) (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_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_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.Rd0000644000176200001440000000334413620635217013415 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_gids(p) } \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"]) (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_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_username}()} } \concept{process handle functions} ps/man/ps_cwd.Rd0000644000176200001440000000247713620634430013230 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) } \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()) (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_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}()} } \concept{process handle functions} ps/man/ps_status.Rd0000644000176200001440000000402513620634430013765 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) } \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()) (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_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_suspend}()}, \code{\link{ps_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_environ.Rd0000644000176200001440000000365613620635217014137 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_environ_raw(p) } \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()) (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_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}()} } \concept{process handle functions} ps/man/ps_ppid.Rd0000644000176200001440000000341513620635217013404 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_parent(p) } \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()) (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_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_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.Rd0000644000176200001440000000333013620634430014726 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, 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"]) (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_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_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.Rd0000644000176200001440000000341413620634430014566 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) } \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()) (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_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_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_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.Rd0000644000176200001440000000300113620634430014114 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) } \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"]) (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_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_terminal}()}, \code{\link{ps_terminate}()}, \code{\link{ps_uids}()}, \code{\link{ps_username}()} } \concept{process handle functions} ps/man/ps_kill.Rd0000644000176200001440000000270613620634430013401 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) } \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"]) (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_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_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.Rd0000644000176200001440000000262213620634430014616 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) } \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()) (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_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_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.Rd0000644000176200001440000000322513620634430014233 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, 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()) (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_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}()} } \concept{process handle functions} ps/man/ps_num_threads.Rd0000644000176200001440000000246213620634430014756 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) } \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()) (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_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_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_interrupt.Rd0000644000176200001440000000237013620625516014504 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, 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_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_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/DESCRIPTION0000644000176200001440000000203213621233753012404 0ustar liggesusersPackage: ps Version: 1.3.2 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: BSD_3_clause + 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.0.2.9000 Biarch: true NeedsCompilation: yes Packaged: 2020-02-13 11:07:05 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: 2020-02-13 11:50:03 UTC ps/tests/0000755000176200001440000000000013620625516012044 5ustar liggesusersps/tests/testthat/0000755000176200001440000000000013621233753013703 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.R0000644000176200001440000000317513356424172017440 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)) }) 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.R0000644000176200001440000002574313356424172020307 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), { 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)) }) test_that("Network cleanup, test, fail", { skip_if_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", { skip_if_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", { skip_if_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.R0000644000176200001440000000154013330045252016133 0ustar liggesusers context("utils") test_that("errno", { err <- errno() expect_true(is.list(err)) if (ps_os_type()[["POSIX"]]) { expect_true("EINVAL" %in% names(err)) expect_true("EBADF" %in% names(err)) } }) 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.R0000644000176200001440000000264413356424172016604 0ustar liggesusers context("process finished") test_that("process already finished", { 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.R0000644000176200001440000001460513350016471016674 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) 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_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.05) 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) { 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) timeout <- Sys.time() + 10 while (length(dir(tmp)) < N && Sys.time() < timeout) Sys.sleep(0.1) 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")] expect_equal(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)) expect_true(all(ccpids %in% res) ) }) 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.R0000644000176200001440000000346213333136066016151 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") 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/0000755000176200001440000000000013621226731011466 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.c0000644000176200001440000005422313620625516013005 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 specific to all POSIX compliant platforms. */ #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_errno() { SEXP env = PROTECT(Rf_allocSExp(ENVSXP)); #define PS_ADD_ERRNO(err,str) \ defineVar(install(#err), PROTECT(ScalarInteger(err)), env); \ UNPROTECT(1) /* MACOS */ #ifdef EPERM PS_ADD_ERRNO(EPERM, "Operation not permitted."); #endif #ifdef ENOENT PS_ADD_ERRNO(ENOENT, "No such file or directory."); #endif #ifdef ESRCH PS_ADD_ERRNO(ESRCH, "No such process."); #endif #ifdef EINTR PS_ADD_ERRNO(EINTR, "Interrupted function call."); #endif #ifdef EIO PS_ADD_ERRNO(EIO, "Input/output error."); #endif #ifdef ENXIO PS_ADD_ERRNO(ENXIO, "No such device or address."); #endif #ifdef E2BIG PS_ADD_ERRNO(E2BIG, "Arg list too long."); #endif #ifdef ENOEXEC PS_ADD_ERRNO(ENOEXEC, "Exec format error."); #endif #ifdef EBADF PS_ADD_ERRNO(EBADF, "Bad file descriptor."); #endif #ifdef ECHILD PS_ADD_ERRNO(ECHILD, "No child processes."); #endif #ifdef EDEADLK PS_ADD_ERRNO(EDEADLK, "Resource deadlock avoided."); #endif #ifdef ENOMEM PS_ADD_ERRNO(ENOMEM, "Cannot allocate memory."); #endif #ifdef EACCES PS_ADD_ERRNO(EACCES, "Permission denied."); #endif #ifdef EFAULT PS_ADD_ERRNO(EFAULT, "Bad address."); #endif #ifdef ENOTBLK PS_ADD_ERRNO(ENOTBLK, "Not a block device."); #endif #ifdef EBUSY PS_ADD_ERRNO(EBUSY, "Resource busy."); #endif #ifdef EEXIST PS_ADD_ERRNO(EEXIST, "File exists."); #endif #ifdef EXDEV PS_ADD_ERRNO(EXDEV, "Improper link."); #endif #ifdef ENODEV PS_ADD_ERRNO(ENODEV, "Operation not supported by device."); #endif #ifdef ENOTDIR PS_ADD_ERRNO(ENOTDIR, "Not a directory."); #endif #ifdef EISDIR PS_ADD_ERRNO(EISDIR, "Is a directory."); #endif #ifdef EINVAL PS_ADD_ERRNO(EINVAL, "Invalid argument."); #endif #ifdef ENFILE PS_ADD_ERRNO(ENFILE, "Too many open files in system."); #endif #ifdef EMFILE PS_ADD_ERRNO(EMFILE, "Too many open files."); #endif #ifdef ENOTTY PS_ADD_ERRNO(ENOTTY, "Inappropriate ioctl for device."); #endif #ifdef ETXTBSY PS_ADD_ERRNO(ETXTBSY, "Text file busy."); #endif #ifdef EFBIG PS_ADD_ERRNO(EFBIG, "File too large."); #endif #ifdef ENOSPC PS_ADD_ERRNO(ENOSPC, "Device out of space."); #endif #ifdef ESPIPE PS_ADD_ERRNO(ESPIPE, "Illegal seek."); #endif #ifdef EROFS PS_ADD_ERRNO(EROFS, "Read-only file system."); #endif #ifdef EMLINK PS_ADD_ERRNO(EMLINK, "Too many links."); #endif #ifdef EPIPE PS_ADD_ERRNO(EPIPE, "Broken pipe."); #endif #ifdef EDOM PS_ADD_ERRNO(EDOM, "Numerical argument out of domain."); #endif #ifdef ERANGE PS_ADD_ERRNO(ERANGE, "Numerical result out of range."); #endif #ifdef EAGAIN PS_ADD_ERRNO(EAGAIN, "Resource temporarily unavailable."); #endif #ifdef EINPROGRESS PS_ADD_ERRNO(EINPROGRESS, "Operation now in progress."); #endif #ifdef EALREADY PS_ADD_ERRNO(EALREADY, "Operation already in progress."); #endif #ifdef ENOTSOCK PS_ADD_ERRNO(ENOTSOCK, "Socket operation on non-socket."); #endif #ifdef EDESTADDRREQ PS_ADD_ERRNO(EDESTADDRREQ, "Destination address required."); #endif #ifdef EMSGSIZE PS_ADD_ERRNO(EMSGSIZE, "Message too long."); #endif #ifdef EPROTOTYPE PS_ADD_ERRNO(EPROTOTYPE, "Protocol wrong type for socket."); #endif #ifdef ENOPROTOOPT PS_ADD_ERRNO(ENOPROTOOPT, "Protocol not available."); #endif #ifdef EPROTONOSUPPORT PS_ADD_ERRNO(EPROTONOSUPPORT, "Protocol not supported."); #endif #ifdef ESOCKTNOSUPPORT PS_ADD_ERRNO(ESOCKTNOSUPPORT, "Socket type not supported."); #endif #ifdef ENOTSUP PS_ADD_ERRNO(ENOTSUP, "Not supported."); #endif #ifdef EPFNOSUPPORT PS_ADD_ERRNO(EPFNOSUPPORT, "Protocol family not supported."); #endif #ifdef EAFNOSUPPORT PS_ADD_ERRNO(EAFNOSUPPORT, "Address family not supported by protocol family."); #endif #ifdef EADDRINUSE PS_ADD_ERRNO(EADDRINUSE, "Address already in use."); #endif #ifdef EADDRNOTAVAIL PS_ADD_ERRNO(EADDRNOTAVAIL, "Cannot assign requested address."); #endif #ifdef ENETDOWN PS_ADD_ERRNO(ENETDOWN, "Network is down."); #endif #ifdef ENETUNREACH PS_ADD_ERRNO(ENETUNREACH, "Network is unreachable."); #endif #ifdef ENETRESET PS_ADD_ERRNO(ENETRESET, "Network dropped connection on reset."); #endif #ifdef ECONNABORTED PS_ADD_ERRNO(ECONNABORTED, "Software caused connection abort."); #endif #ifdef ECONNRESET PS_ADD_ERRNO(ECONNRESET, "Connection reset by peer."); #endif #ifdef ENOBUFS PS_ADD_ERRNO(ENOBUFS, "No buffer space available."); #endif #ifdef EISCONN PS_ADD_ERRNO(EISCONN, "Socket is already connected."); #endif #ifdef ENOTCONN PS_ADD_ERRNO(ENOTCONN, "Socket is not connected."); #endif #ifdef ESHUTDOWN PS_ADD_ERRNO(ESHUTDOWN, "Cannot send after socket shutdown."); #endif #ifdef ETIMEDOUT PS_ADD_ERRNO(ETIMEDOUT, "Operation timed out."); #endif #ifdef ECONNREFUSED PS_ADD_ERRNO(ECONNREFUSED, "Connection refused."); #endif #ifdef ELOOP PS_ADD_ERRNO(ELOOP, "Too many levels of symbolic links."); #endif #ifdef ENAMETOOLONG PS_ADD_ERRNO(ENAMETOOLONG, "File name too long."); #endif #ifdef EHOSTDOWN PS_ADD_ERRNO(EHOSTDOWN, "Host is down."); #endif #ifdef EHOSTUNREACH PS_ADD_ERRNO(EHOSTUNREACH, "No route to host."); #endif #ifdef ENOTEMPTY PS_ADD_ERRNO(ENOTEMPTY, "Directory not empty."); #endif #ifdef EPROCLIM PS_ADD_ERRNO(EPROCLIM, "Too many processes."); #endif #ifdef EUSERS PS_ADD_ERRNO(EUSERS, "Too many users."); #endif #ifdef EDQUOT PS_ADD_ERRNO(EDQUOT, "Disc quota exceeded."); #endif #ifdef ESTALE PS_ADD_ERRNO(ESTALE, "Stale NFS file handle."); #endif #ifdef EBADRPC PS_ADD_ERRNO(EBADRPC, "RPC struct is bad."); #endif #ifdef ERPCMISMATCH PS_ADD_ERRNO(ERPCMISMATCH, "RPC version wrong."); #endif #ifdef EPROGUNAVAIL PS_ADD_ERRNO(EPROGUNAVAIL, "RPC prog. not avail."); #endif #ifdef EPROGMISMATCH PS_ADD_ERRNO(EPROGMISMATCH, "Program version wrong."); #endif #ifdef EPROCUNAVAIL PS_ADD_ERRNO(EPROCUNAVAIL, "Bad procedure for program."); #endif #ifdef ENOLCK PS_ADD_ERRNO(ENOLCK, "No locks available."); #endif #ifdef ENOSYS PS_ADD_ERRNO(ENOSYS, "Function not implemented."); #endif #ifdef EFTYPE PS_ADD_ERRNO(EFTYPE, "Inappropriate file type or format."); #endif #ifdef EAUTH PS_ADD_ERRNO(EAUTH, "Authentication error."); #endif #ifdef ENEEDAUTH PS_ADD_ERRNO(ENEEDAUTH, "Need authenticator."); #endif #ifdef EPWROFF PS_ADD_ERRNO(EPWROFF, "Device power is off."); #endif #ifdef EDEVERR PS_ADD_ERRNO(EDEVERR, "Device error."); #endif #ifdef EOVERFLOW PS_ADD_ERRNO(EOVERFLOW, "Value too large to be stored in data type."); #endif #ifdef EBADEXEC PS_ADD_ERRNO(EBADEXEC, "Bad executable (or shared library)."); #endif #ifdef EBADARCH PS_ADD_ERRNO(EBADARCH, "Bad CPU type in executable."); #endif #ifdef ESHLIBVERS PS_ADD_ERRNO(ESHLIBVERS, "Shared library version mismatch."); #endif #ifdef EBADMACHO PS_ADD_ERRNO(EBADMACHO, "Malformed Mach-o file."); #endif #ifdef ECANCELED PS_ADD_ERRNO(ECANCELED, "Operation canceled."); #endif #ifdef EIDRM PS_ADD_ERRNO(EIDRM, "Identifier removed."); #endif #ifdef ENOMSG PS_ADD_ERRNO(ENOMSG, "No message of desired type."); #endif #ifdef EILSEQ PS_ADD_ERRNO(EILSEQ, "Illegal byte sequence."); #endif #ifdef ENOATTR PS_ADD_ERRNO(ENOATTR, "Attribute not found."); #endif #ifdef EBADMSG PS_ADD_ERRNO(EBADMSG, "Bad message."); #endif #ifdef EMULTIHOP PS_ADD_ERRNO(EMULTIHOP, "Multihop attempted."); #endif #ifdef ENODATA PS_ADD_ERRNO(ENODATA, "No message available."); #endif #ifdef ENOSTR PS_ADD_ERRNO(ENOSTR, "Not a STREAM."); #endif #ifdef EPROTO PS_ADD_ERRNO(EPROTO, "Protocol error."); #endif #ifdef ETIME PS_ADD_ERRNO(ETIME, "STREAM ioctl() timeout."); #endif #ifdef EOPNOTSUPP PS_ADD_ERRNO(EOPNOTSUPP, "Operation not supported on socket."); #endif /* Linux */ #ifdef EWOULDBLOCK PS_ADD_ERRNO(EWOULDBLOCK, "Resource temporarily unavailable."); #endif #ifdef ETOOMANYREFS PS_ADD_ERRNO(ETOOMANYREFS, "Too many references: cannot splice."); #endif #ifdef EREMOTE PS_ADD_ERRNO(EREMOTE, "File is already NFS-mounted"); #endif #ifdef EBACKGROUND PS_ADD_ERRNO(EBACKGROUND, "Caller not in the foreground process group"); #endif #ifdef EDIED PS_ADD_ERRNO(EDIED, "Translator died"); #endif #ifdef ED PS_ADD_ERRNO(ED, "The experienced user will know what is wrong."); #endif #ifdef EGREGIOUS PS_ADD_ERRNO(EGREGIOUS, "You did *what*?"); #endif #ifdef EIEIO PS_ADD_ERRNO(EIEIO, "Go home and have a glass of warm, dairy-fresh milk."); #endif #ifdef EGRATUITOUS PS_ADD_ERRNO(EGRATUITOUS, "This error code has no purpose."); #endif #ifdef ENOLINK PS_ADD_ERRNO(ENOLINK, "Link has been severed."); #endif #ifdef ENOSR PS_ADD_ERRNO(ENOSR, "Out of streams resources."); #endif #ifdef ERESTART PS_ADD_ERRNO(ERESTART, "Interrupted system call should be restarted."); #endif #ifdef ECHRNG PS_ADD_ERRNO(ECHRNG, "Channel number out of range."); #endif #ifdef EL2NSYNC PS_ADD_ERRNO(EL2NSYNC, "Level 2 not synchronized."); #endif #ifdef EL3HLT PS_ADD_ERRNO(EL3HLT, "Level 3 halted."); #endif #ifdef EL3RST PS_ADD_ERRNO(EL3RST, "Level 3 reset."); #endif #ifdef ELNRNG PS_ADD_ERRNO(ELNRNG, "Link number out of range."); #endif #ifdef EUNATCH PS_ADD_ERRNO(EUNATCH, "Protocol driver not attached."); #endif #ifdef ENOCSI PS_ADD_ERRNO(ENOCSI, "No CSI structure available."); #endif #ifdef EL2HLT PS_ADD_ERRNO(EL2HLT, "Level 2 halted."); #endif #ifdef EBADE PS_ADD_ERRNO(EBADE, "Invalid exchange."); #endif #ifdef EBADR PS_ADD_ERRNO(EBADR, "Invalid request descriptor."); #endif #ifdef EXFULL PS_ADD_ERRNO(EXFULL, "Exchange full."); #endif #ifdef ENOANO PS_ADD_ERRNO(ENOANO, "No anode."); #endif #ifdef EBADRQC PS_ADD_ERRNO(EBADRQC, "Invalid request code."); #endif #ifdef EBADSLT PS_ADD_ERRNO(EBADSLT, "Invalid slot."); #endif #ifdef EDEADLOCK PS_ADD_ERRNO(EDEADLOCK, "File locking deadlock error."); #endif #ifdef EBFONT PS_ADD_ERRNO(EBFONT, "Bad font file format."); #endif #ifdef ENONET PS_ADD_ERRNO(ENONET, "Machine is not on the network."); #endif #ifdef ENOPKG PS_ADD_ERRNO(ENOPKG, "Package not installed."); #endif #ifdef EADV PS_ADD_ERRNO(EADV, "Advertise error."); #endif #ifdef ESRMNT PS_ADD_ERRNO(ESRMNT, "Srmount error."); #endif #ifdef ECOMM PS_ADD_ERRNO(ECOMM, "Communication error on send."); #endif #ifdef EDOTDOT PS_ADD_ERRNO(EDOTDOT, "RFS specific error"); #endif #ifdef ENOTUNIQ PS_ADD_ERRNO(ENOTUNIQ, "Name not unique on network."); #endif #ifdef EBADFD PS_ADD_ERRNO(EBADFD, "File descriptor in bad state."); #endif #ifdef EREMCHG PS_ADD_ERRNO(EREMCHG, "Remote address changed."); #endif #ifdef ELIBACC PS_ADD_ERRNO(ELIBACC, "Can not access a needed shared library."); #endif #ifdef ELIBBAD PS_ADD_ERRNO(ELIBBAD, "Accessing a corrupted shared library."); #endif #ifdef ELIBSCN PS_ADD_ERRNO(ELIBSCN, ".lib section in a.out corrupted."); #endif #ifdef ELIBMAX PS_ADD_ERRNO(ELIBMAX, "Attempting to link in too many shared libraries."); #endif #ifdef ELIBEXEC PS_ADD_ERRNO(ELIBEXEC, "Cannot exec a shared library directly."); #endif #ifdef ESTRPIPE PS_ADD_ERRNO(ESTRPIPE, "Streams pipe error."); #endif #ifdef EUCLEAN PS_ADD_ERRNO(EUCLEAN, "Structure needs cleaning."); #endif #ifdef ENOTNAM PS_ADD_ERRNO(ENOTNAM, "Not a XENIX named type file."); #endif #ifdef ENAVAIL PS_ADD_ERRNO(ENAVAIL, "No XENIX semaphores available."); #endif #ifdef EISNAM PS_ADD_ERRNO(EISNAM, "Is a named type file."); #endif #ifdef EREMOTEIO PS_ADD_ERRNO(EREMOTEIO, "Remote I/O error."); #endif #ifdef ENOMEDIUM PS_ADD_ERRNO(ENOMEDIUM, "No medium found."); #endif #ifdef EMEDIUMTYPE PS_ADD_ERRNO(EMEDIUMTYPE, "Wrong medium type."); #endif #ifdef ENOKEY PS_ADD_ERRNO(ENOKEY, "Required key not available."); #endif #ifdef EKEYEXPIRED PS_ADD_ERRNO(EKEYEXPIRED, "Key has expired."); #endif #ifdef EKEYREVOKED PS_ADD_ERRNO(EKEYREVOKED, "Key has been revoked."); #endif #ifdef EKEYREJECTED PS_ADD_ERRNO(EKEYREJECTED, "Key was rejected by service."); #endif #ifdef EOWNERDEAD PS_ADD_ERRNO(EOWNERDEAD, "Owner died."); #endif #ifdef ENOTRECOVERABLE PS_ADD_ERRNO(ENOTRECOVERABLE, "State not recoverable."); #endif #ifdef ERFKILL PS_ADD_ERRNO(ERFKILL, "Operation not possible due to RF-kill."); #endif #ifdef EHWPOISON PS_ADD_ERRNO(EHWPOISON, "Memory page has hardware error."); #endif #undef PS_ADD_ERRNO UNPROTECT(1); return env; } 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.c0000644000176200001440000000364313620625516013554 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #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; } ps/src/linux.c0000644000176200001440000000600413620625516012774 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. * * Linux-specific functions. */ #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.c0000644000176200001440000006534013620625516013553 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #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 + \ (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 + 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 + 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; } ps/src/macos.c0000644000176200001440000000770213620625516012745 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. * * OS X platform-specific module methods for macos */ #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.c0000644000176200001440000005050413620625516013333 0ustar liggesusers/* * Copyright (c) 2009, Jay Loden, 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. * * Windows platform-specific module methods for _psutil_windows */ // 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" #include "arch/windows/process_handles.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 PROTECT(retlist = ps__build_named_list( #if defined(_WIN64) "kkdddiKKKKKK" "kKKKKKKKKK", #else "kkdddiKKKKKK" "kIIIIIIIII", #endif "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); return R_NilValue; } ps/src/init.c0000644000176200001440000000655413620625516012612 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__users", (DL_FUNC) ps__users, 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 }, /* 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/0000755000176200001440000000000013621226731013505 5ustar liggesusersps/src/arch/macos/process_info.c0000644000176200001440000001743013620625516016352 0ustar liggesusers/* * Copyright (c) 2009, Jay Loden, 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. * * Helper functions related to fetching process information. * Used by _psutil_macos module methods. */ #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.h0000644000176200001440000000104413311730044016337 0ustar liggesusers/* * Copyright (c) 2009, Jay Loden, 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. */ #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/0000755000176200001440000000000013330045252014067 5ustar liggesusersps/src/arch/windows/process_info.c0000644000176200001440000005467113330045252016741 0ustar liggesusers/* * Copyright (c) 2009, Jay Loden, 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. * * Helper functions related to fetching process information. Used by * _psutil_windows module methods. */ #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.h0000644000176200001440000002223113313262223016074 0ustar liggesusers/* * Copyright (c) 2009, Jay Loden, 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. */ #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.c0000644000176200001440000001464213330045252017416 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. * */ #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); } } ps/src/arch/windows/process_info.h0000644000176200001440000000141313330045252016730 0ustar liggesusers/* * Copyright (c) 2009, Jay Loden, 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. */ #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/process_handles.h0000644000176200001440000000577613330045252017433 0ustar liggesusers/* * Copyright (c) 2009, Jay Loden, 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. */ #ifndef __PROCESS_HANDLES_H__ #define __PROCESS_HANDLES_H__ #ifndef UNICODE #define UNICODE #endif #include #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); #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.h0000644000176200001440000000277713452604204012273 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); /* 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__users(); /* 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.c0000644000176200001440000004721413620625516013516 0ustar liggesusers #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #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) { struct kinfo_proc kp; int ret; if (handle->pid == 0) { ps__access_denied(""); } 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); } else if (kp.kp_proc.p_stat == SZOMB) { ps__zombie_process(handle->pid); } else { ps__access_denied(""); } } else { ps__set_error_from_errno(); } 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); 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); 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); } 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); 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); } 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); } 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); } 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); 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); } 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); 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); 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; } 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.h0000644000176200001440000000406513357340427014105 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__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.c0000644000176200001440000000162613620625516013132 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. * * Routines common to all platforms. */ #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.c0000644000176200001440000000751113621223453012767 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"); } #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__users("ps_users"); } 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 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 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.h0000644000176200001440000000146613330045252013130 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. */ #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.c0000644000176200001440000006253613620625516014112 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 */ #if (_WIN32_WINNT < 0x0601) // < Windows 7 (namely Vista and XP) SEXP ps__cpu_count_logical() { return ScalarInteger(NA_INTEGER); } SEXP ps__cpu_count_physical() { return ScalarInteger(NA_INTEGER); } #else // Windows >= 7 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); } } #endif 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_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; } ps/src/posix.h0000644000176200001440000000037213330045252012775 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. */ 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/0000755000176200001440000000000013620635245011103 5ustar liggesusersps/R/utils.R0000644000176200001440000001040313620634537012367 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())")) } 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)] } ps/R/system.R0000644000176200001440000000464313620633226012556 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_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) } } 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.R0000644000176200001440000005460213620635166013145 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() #' 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() #' p <- ps_handle() #' p #' ps_pid(p) #' ps_pid(p) == Sys.getpid() ps_pid <- function(p) { 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() #' p <- ps_handle() #' p #' ps_create_time(p) ps_create_time <- function(p) { 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() #' p <- ps_handle() #' p #' ps_is_running(p) ps_is_running <- function(p) { 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() #' p <- ps_handle() #' p #' ps_ppid(p) #' ps_parent(p) ps_ppid <- function(p) { assert_ps_handle(p) .Call(psll_ppid, p) } #' @rdname ps_ppid #' @export ps_parent <- function(p) { 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() #' p <- ps_handle() #' p #' ps_name(p) #' ps_exe(p) #' ps_cmdline(p) ps_name <- function(p) { 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() #' p <- ps_handle() #' p #' ps_name(p) #' ps_exe(p) #' ps_cmdline(p) ps_exe <- function(p) { 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() #' p <- ps_handle() #' p #' ps_name(p) #' ps_exe(p) #' ps_cmdline(p) ps_cmdline <- function(p) { 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() #' p <- ps_handle() #' p #' ps_status(p) ps_status <- function(p) { 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() #' p <- ps_handle() #' p #' ps_username(p) ps_username <- function(p) { 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() #' p <- ps_handle() #' p #' ps_cwd(p) ps_cwd <- function(p) { 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"] #' p <- ps_handle() #' p #' ps_uids(p) #' ps_gids(p) ps_uids <- function(p) { assert_ps_handle(p) .Call(psll_uids, p) } #' @rdname ps_uids #' @export ps_gids <- function(p) { 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() #' p <- ps_handle() #' p #' ps_terminal(p) ps_terminal <- function(p) { 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() #' p <- ps_handle() #' p #' env <- ps_environ(p) #' env[["R_HOME"]] ps_environ <- function(p) { assert_ps_handle(p) parse_envs(.Call(psll_environ, p)) } #' @rdname ps_environ #' @export ps_environ_raw <- function(p) { 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() #' p <- ps_handle() #' p #' ps_num_threads(p) ps_num_threads <- function(p) { 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() #' p <- ps_handle() #' p #' ps_cpu_times(p) #' proc.time() ps_cpu_times <- function(p) { 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](http://msdn.microsoft.com/en-us/library/windows/desktop/ms684874(v=vs.85).aspx) #' 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() #' p <- ps_handle() #' p #' ps_memory_info(p) ps_memory_info <- function(p) { 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"] #' 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, 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"] #' 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) { 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"] #' 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) { 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"] #' 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) { 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"] #' 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) { 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() #' p <- ps_parent(ps_handle()) #' ps_children(p) ps_children <- function(p, 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 } 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() #' 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) { 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() #' 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) { 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() #' p <- ps_handle() #' ps_connections(p) #' sc <- socketConnection("httpbin.org", port = 80) #' ps_connections(p) #' close(sc) #' ps_connections(p) ps_connections <- function(p) { 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, 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) } } 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.R0000644000176200001440000000103113341037543012360 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) } errno <- function() { as.list(ps_env$constants$errno) } 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.R0000644000176200001440000002523613357347700014741 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" | 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.R0000644000176200001440000000146313330045252011642 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() 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/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.R0000644000176200001440000001260513620625516012371 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/NEWS.md0000644000176200001440000000302013621224344011766 0ustar liggesusers # 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/MD50000644000176200001440000001261613621233753011217 0ustar liggesusers14c5457291899584668546edaec17281 *DESCRIPTION ed122a1c3e9892dd2b2d2e3518850898 *LICENSE cb5cd2ed8bacfa0cb4584b8f341cea7f *NAMESPACE 683880a4bea17fa8825e1cf7e898bc1f *NEWS.md e9d5da70cc329bca4418e19af321c9b8 *R/error.R 22cd9e0029929362c7fc30c705dd795c *R/kill-tree.R 113eee7fd5be6631af582a4b515af4ab *R/linux.R 5ca48c9faa1910175afeef0edc7aed7e *R/low-level.R 389340f5fee7127eb63eee1361cdd84d *R/memoize.R 133103dc881fc2cb34ff7b3328c0d03e *R/os.R c9251bd3db34af080f5fe5efd4a5d350 *R/package.R b30caf5fdcffcc02c0d35ef4fb72210c *R/posix.R d5beb6502a773f4ddb3b754099d1fea1 *R/ps.R 7d3e132fe65a4fe01b5f4e0c5ee93654 *R/system.R 6da412a264dbd60023d5cce65f45eb15 *R/testthat-reporter.R 21796760bfe5812a0069afe85a30266c *R/utils.R fcf2880d76cb3fac5db05296bef8602b *README.md d8d31b225a4792ba5158a39713b5b269 *cleanup 269b4781b4f73af3e6512bd41e1c8924 *configure 757ef8281ef2a1ebcbcc8235aeee2d0e *configure.win f76a886fa32868f9eeaf9b0dfa9dc7ad *inst/WORDLIST 28613e2a5a14a4fe95f362359b2af465 *inst/internals.md 94d5c399a5ddddea6407eb43cb5e6ef4 *inst/tools/winver.R 16b0b9b54676b263d8f5ca4a57454e3e *man/CleanupReporter.Rd fc5c1d924dc3cea8467b5d2e947e23af *man/ps.Rd d3f65d772b6a08285e726a16890bf99a *man/ps_boot_time.Rd fdccdf16aea27cc66d1b853abd14a70a *man/ps_children.Rd b29b69ebd471b199706ddc8cff98b08a *man/ps_cmdline.Rd e09ba186c0ca859087439c0c7d5b8791 *man/ps_connections.Rd beffc8b724efc8dbd7027d68d280f62a *man/ps_cpu_count.Rd 04788fc5292bcd919455d07d2e216949 *man/ps_cpu_times.Rd b0879dda61c0d748931bfcb5dfe28c80 *man/ps_create_time.Rd 52825a682e53d6175ed0ea7109b0fe7b *man/ps_cwd.Rd f923701b29ff2608f85377ab59f9ff5b *man/ps_environ.Rd 5ae0d14edb1d8ee617a39654fcd34cff *man/ps_exe.Rd 580233466e463b191f4e10e5f774377f *man/ps_handle.Rd 5f8ec45d1ed10b7eb178634f28eaf073 *man/ps_interrupt.Rd 32755db8485b5bab664d7d3a2463d654 *man/ps_is_running.Rd 8c91eba7bc76ba16308abcc567351303 *man/ps_kill.Rd a88b3df63afe0e108380348bd1267598 *man/ps_kill_tree.Rd 2fb1a6c38e59989056f5e3d977ae087d *man/ps_memory_info.Rd 1398acb9b17317bd35dad2f94df37b5b *man/ps_name.Rd 6f855562e2ad9bf520ebee241fc636e7 *man/ps_num_fds.Rd 73c949811a77ef63e8c6a85624510135 *man/ps_num_threads.Rd c988672a889ad6325aeb42c21fc6ba61 *man/ps_open_files.Rd 3c9b99d9e87f47e1114bf901ddcfe6ed *man/ps_os_type.Rd 7f2ac2b817094ce60c7434b9b1cd18ea *man/ps_pid.Rd f5dd035dbcc82569f667b2e252db9981 *man/ps_pids.Rd 890fcc04e77ac9f402bcd322d927e1a5 *man/ps_ppid.Rd 73b7292f22c51239f32e664d4af42f3e *man/ps_resume.Rd 56eccae47da90be958e3be874db3271c *man/ps_send_signal.Rd ae67c70324324fac8f3f4a365be67886 *man/ps_status.Rd e74d4a04e33592cfb596bbc9b0b559e1 *man/ps_suspend.Rd fee824beacff85bb775949df4caffa4f *man/ps_terminal.Rd 78374465e7dbabea932ca4583a45ec16 *man/ps_terminate.Rd f82bf428a2bcf6af3e5eb799fa403452 *man/ps_uids.Rd 315ace9d8af5137c08c77f378de7613f *man/ps_username.Rd a30f6dea22b8d1a89787c8cf500b17b7 *man/ps_users.Rd b90f28ca6453b80a98510a27fd9f764b *man/signals.Rd 8b20a5ff62cc7919eb4a8ac938774b8e *src/Makevars.in 13125835fe5730bc21c5a572269b0ada *src/api-common.c 298ef941d8f8eb15c6ecda7e792dfd16 *src/api-linux.c f24316472fbbf3b4a3d1152b634b218f *src/api-macos.c 3ef90d3f905a5be8174a2b1f11d443e0 *src/api-posix.c 2f66c9907cb31f96a470ddc5d9cdce0b *src/api-windows-conn.c cd1fd3a9e9fe6d6d73f00c414a42c8a5 *src/api-windows.c 8ca6f5c9ed16d5d6e32b160f1e44ba7b *src/arch/macos/process_info.c 04622dcb75db2e95b3ae1419ef31499c *src/arch/macos/process_info.h 54db4d328cd7d820788ba526734022e3 *src/arch/windows/ntextapi.h 420aa5a215ab6868e278c024f4a01c19 *src/arch/windows/process_handles.c d6233fb7354ec73a7604e55d304509be *src/arch/windows/process_handles.h f5bc9064a4b629c53014ac1e32d2cf08 *src/arch/windows/process_info.c 4034d71409076ddf215011db0fd555c1 *src/arch/windows/process_info.h 9de4569887008827a4ccafb2167f8454 *src/common.c e1046ca769d4da994b1a60825b7b1855 *src/common.h c463bef2f8b9b4716d35b529162d0426 *src/dummy.c d6b32a3299957efccddf2c37085cbecc *src/extra.c e6abc42b1887e887ae84f4d6525fea40 *src/init.c 25293087d29ce032315b03ca28b91cf7 *src/install.libs.R 3db35656c2495dee4cbe752196cb47b0 *src/interrupt.c 267bddb3aa0b144ac93f15fe5bc82519 *src/linux.c 358fb223a51ec7bc1db17f4e8e11c0bc *src/macos.c dd3a96df85276799ff78ccb794b7dae2 *src/posix.c 0ae15b91452de2b2193049c64a162977 *src/posix.h 11d3a20cd8f247217241a6f80c2948f0 *src/ps-internal.h af95928c9aa9ddd11d408a8b00705d86 *src/ps.h ac0649b539050ee02a7e6d1cd1639d24 *src/px.c ca60b0f05a00d492cdab9d8d032d34de *src/windows.c 5795cd4a210d26008a52fbd7b624d87f *src/windows.h 41b67f188db189a58d2e3627e75c46cd *tests/testthat.R eebb6e5855f584ba2505b996d39e2664 *tests/testthat/helpers.R b7ca53c98022806ee4e1d3191c3df46f *tests/testthat/test-cleanup-reporter.R 93bf1c7f5da033440da7e0f6a0a2a0a0 *tests/testthat/test-common.R d44b4c2dcd8fbdbeae4136a5b8691f24 *tests/testthat/test-connections.R 20422c425958281421b0c458d71ccd5c *tests/testthat/test-finished.R a1703259b0fee8e9c52ef3eff2d32970 *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 075a9193d3a2ec8c9bc380d4928f5c58 *tests/testthat/test-posix-zombie.R fa94aa2a182b4fb13d2aaa0554befc8e *tests/testthat/test-posix.R 8bf2aade7ae0ba33a5db859393ef1478 *tests/testthat/test-system.R 230a86e4f3627388c2666c543b8d2631 *tests/testthat/test-utils.R 64704b900b93cad85ebb1d24deac7680 *tests/testthat/test-windows.R dbf065d9384b8c9bb73a5234c715a691 *tests/testthat/test-winver.R ps/inst/0000755000176200001440000000000013620625516011657 5ustar liggesusersps/inst/tools/0000755000176200001440000000000013620625516013017 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/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/cleanup0000755000176200001440000000006513621226731012255 0ustar liggesusers#' !/usr/bin/env sh rm -f src/Makevars src/config.h ps/configure0000755000176200001440000001117013621226731012606 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" 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" LIBRARIES="psapi kernel32 advapi32 shell32 netapi32 iphlpapi wtsapi32" LIBRARIES="${LIBRARIES} ws2_32 PowrProf" 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 # ------------------------------------------------------------------------ # 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