From the
R and RStudio in a Secured Environment on CentOS 7 and
Improving the secured computer-based testing environment for R on CentOS 7 using Rprofile and Renviron
posts, there was a need to override commands that required internet access
in an internetless environment. One huge part of the puzzle that wasn’t quite
solved was the ability to disable the install.packages()
command
within RStudio. In particular, the outcome from running install.packages()
looked like:
In reality, the desired outcome was to provide a custom message:
This resulted in an issue (4498)
being filed that asked for a specific point during RStudio’s startup of R.
Turns out that Jonathan McPherson already added it ~4+ years ago in commit fc86699#diff-9825eb7f38c890c5ed3121779b9f993e
.
He provided a sample use case of it in an earlier ticket requesting the feature.
Therefore, the hook will work with RStudio 0.99.* and later versions.
With this being said, the post is broken down into four sections that aim to
cover RStudio’s hook process, overriding a base function, establishing a hook,
and setting up hooks to run automatically in startup with .Rprofile
.
RStudio Hook
The injection point uses R’s setHook()
function to register a value for the session initialization in RStudio.
The example below can be found on RStudio’s rstudioapi
R package website.
setHook("rstudio.sessionInit", function(newSession) {
if (newSession)
message("Welcome to RStudio! ", rstudioapi::getVersion())
}, action = "append")
Overriding a base command
Next, we need to be able to override or replace an existing function in an R
package. Functions are able to be overriden by modifying the R package
environment they reside in. Overwriting a function within a package requires unlocking
the environment (“bind”), modifying the functions, and then relocking the
environment. The environment write status can be modified with
base::unlockBinding()
and base::lockBinding()
while the assignment is handled by base::assign()
.
Note: The approach is only for private use. CRAN will not permit any package modifying content in this way.
## Override an R function in a package.
shim_pkg_func = function(name, pkgname, value) {
## Ensure package is loaded.
## If the package is not on the search path, we cannot modify it!
pkg_env_name = paste0("package:", pkgname)
if (!pkg_env_name %in% search()) {
# Load the library
library(pkgname, character.only = TRUE)
}
# Retrieve package environment
env = as.environment(pkg_env_name)
# Unlock environment where the function/variable is found.
base::unlockBinding(name, env)
# Make the assignment into the environment with the new value
base::assign(name, value, envir = env)
# Close the environment
base::lockBinding(name, env)
}
Establishing a Hook
The next part is to establish the hook regardless of whether R was being
used in RStudio, RGUI, or in shell. Differentiating between applications is
key. One way to do so is to check whether the RSTUDIO
environment variable is
set. Environment variable can be searched for using Sys.getenv()
function.
## Provide an alternative install.packages(...) routine
install_packages_shim = function(...) { message("Done!") }
## Setup a shim to override the install.packages() function
if(Sys.getenv("RSTUDIO") == "1") {
## Establishes a delayed registration of the shim after
## RStudio's startup procedure occurs. This procedure overwrites settings
## sometimes established in .Rprofile. Thus, we need a delayed
## component to ensure we're preventing
## install.packages() from being delayed
## c.f. https://github.com/rstudio/rstudio/issues/1579#issuecomment-495706255
setHook("rstudio.sessionInit", function(newSession) {
if (newSession)
shim_pkg_func("install.packages", "utils", install_packages_shim)
}, action = "append")
} else {
## Establish the shim for _R_ terminal sessions or R GUI.
## Attempting to establish the hook with RStudio would result
## in its initialization procedure overwrite this shim.
shim_pkg_func("install.packages", "utils", install_packages_shim)
}
Full Code
Combining each portion of the above code inside the .Rprofile
gives the ideal
startup override. That said, the code should be placed inside the
.First()
function, which runs when R is first loaded. For the full context, consider
checking out the .Rprofile
on GitHub.
To create an .Rprofile
file, use:
touch ~/.Rprofile
vi ~/.Rprofile
Full code discussed previously with the .First()
modification to be placed
inside ~/.Rprofile
.
.First = function() {
## Disable install.packages ----
## Override an R function in a package.
shim_pkg_func = function(name, pkgname, value) {
## Ensure package is loaded.
## If the package is not on the search path, we cannot modify it!
pkg_env_name = paste0("package:", pkgname)
if (!pkg_env_name %in% search()) {
# Load the library
library(pkgname, character.only = TRUE)
}
# Retrieve package environment
env = as.environment(pkg_env_name)
#pkg_ns_env = asNamespace(pkgname)
# Unlock environment where the function/variable is found.
base::unlockBinding(name, env)
# Make the assignment into the environment with the new value
base::assign(name, value, envir = env)
# Close the environment
base::lockBinding(name, env)
}
## Provide an alternative install.packages(...) routine
install_packages_shim = function(...) { message("Done!") }
## Setup a shim to override the install.packages() function
if(Sys.getenv("RSTUDIO") == "1") {
setHook("rstudio.sessionInit", function(newSession) {
if (newSession)
shim_pkg_func("install.packages", "utils", install_packages_shim)
}, action = "append")
} else {
## Establish the shim for _R_ terminal sessions or R GUI.
shim_pkg_func("install.packages", "utils", install_packages_shim)
}
}
Fin
In conclusion, the hooks RStudio setups can be overriden with just a bit more code. Hopefully, this helps out either with auto-launching content, setting up new pranks, or just trying to avoid internet-facing functions from triggering.