Skip to content

Commit

Permalink
Mark files as read-only when copying them into the archive.
Browse files Browse the repository at this point in the history
It is easy for a user to inspect a packet's result by opening it up in
their editor and accidentally edit and save the file. If they didn't
have a file store and no other packet contains a copy of the file then
the packet may be irremediably corrupted.

Marking the file as read-only is an easy way to prevent this. Of course
a user can always force their way around it, but this should at least
prevent most accidental occurrences.

A side-effect of this change is that file copied out of the archive will
now be read-only as well, since copying the files preserves the access
mode. This is desirable when copying files from a dependency, but may
not be when copying outside of a packet context. If this causes problems
we can introduce some option to `orderly_copy_files` to add the write
bit back on files it copies.

Files that were put into the file store were already being marked as
read-only, so this change makes the two implementations more consistent.
The executable bit was being cleared as well, which seems unnecessary to
me and in some contexts even wrong (a user may well want to include a
bash script in their report). I've changed the chmod operation from
"a-wx" to "a-w". This doesn't matter on Windows, where executable bits
don't exist.
  • Loading branch information
plietar committed May 30, 2024
1 parent 1a77601 commit 80a3a02
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 3 deletions.
3 changes: 3 additions & 0 deletions R/outpack_root.R
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ file_import_archive <- function(root, path, file_path, name, id) {
fs::dir_create(dirname(file_path_dest))
## overwrite = FALSE; see assertion above
fs::file_copy(file.path(path, file_path), file_path_dest, overwrite = FALSE)
if (length(file_path_dest) > 0) {
fs::file_chmod(file_path_dest, "a-w")
}
}


Expand Down
2 changes: 1 addition & 1 deletion R/outpack_store.R
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ file_store <- R6::R6Class(
} else {
fs::file_copy(src, dst, overwrite = FALSE)
}
fs::file_chmod(dst, "a-wx")
fs::file_chmod(dst, "a-w")
} else if (move) {
unlink(src)
}
Expand Down
35 changes: 33 additions & 2 deletions tests/testthat/test-outpack-packet.R
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,12 @@ test_that("validate dependencies from archive", {
outpack_packet_run(p1, "script.R")
outpack_packet_end_quietly(p1)

## Change the value here:
# Change the value here. The archive is intentionally read-only, so chmod it
# first.
data_path <- file.path(root$path, "archive", "a", id1, "data.csv")
fs::file_chmod(data_path, "a+w")
write.csv(data.frame(x = 1:10, y = runif(10)),
file.path(root$path, "archive", "a", id1, "data.csv"),
data_path,
row.names = FALSE)

p2 <- outpack_packet_start_quietly(path_src2, "b", root = root)
Expand Down Expand Up @@ -835,3 +838,31 @@ test_that("metadata files match their hash", {
path <- file.path(root$path, ".outpack", "metadata", id)
expect_no_error(hash_validate_file(path, expected_hash))
})


test_that("Files in the archive are read-only", {
src <- temp_file()
fs::dir_create(src)
writeLines(c(
"d <- read.csv('data.csv')",
"png('myplot.png')",
"plot(d)",
"dev.off()"),
file.path(src, "script.R"))
write.csv(data.frame(x = 1:10, y = runif(10)),
file.path(src, "data.csv"),
row.names = FALSE)

root <- create_temporary_root(path_archive = "archive",
use_file_store = FALSE)
path <- root$path

p <- outpack_packet_start_quietly(src, "a", root = root)
outpack_packet_run(p, "script.R")
outpack_packet_end_quietly(p)

files <- c("data.csv", "script.R", "myplot.png")
files_path <- file.path(path, "archive", "a", p$id, files)
expect_true(all(fs::file_access(files_path, "read")))
expect_true(all(!fs::file_access(files_path, "write")))
})

0 comments on commit 80a3a02

Please sign in to comment.