Refactor redirect_stdout_stderr into cliutil.
BUG=b:297288535
TEST=portage/tools/run_tests.sh
Change-Id: I7677f8528215b2a3cdca7aa671bf304d864d046f
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/bazel/+/4820053
Auto-Submit: Matt Stark <msta@google.com>
Tested-by: Matt Stark <msta@google.com>
Reviewed-by: Shuhei Takahashi <nya@chromium.org>
Commit-Queue: Matt Stark <msta@google.com>
diff --git a/portage/bin/action_wrapper/src/main.rs b/portage/bin/action_wrapper/src/main.rs
index 7b7e3b9..04c1bcc 100644
--- a/portage/bin/action_wrapper/src/main.rs
+++ b/portage/bin/action_wrapper/src/main.rs
@@ -12,7 +12,6 @@
use serde_json::json;
use std::collections::HashMap;
use std::fs::File;
-use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::os::unix::process::ExitStatusExt;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitCode, ExitStatus};
@@ -77,24 +76,6 @@
Ok(())
}
-/// Redirects stdout and stderr to the specified file, and returns the saved
-/// stdout/stderr file descriptors.
-fn redirect_stdout_stderr(output: &File) -> Result<(OwnedFd, OwnedFd)> {
- let stdout_fd = std::io::stdout().as_raw_fd();
- let saved_stdout_fd = nix::fcntl::fcntl(stdout_fd, nix::fcntl::F_DUPFD_CLOEXEC(3))?;
- let saved_stdout = unsafe { OwnedFd::from_raw_fd(saved_stdout_fd) };
-
- let stderr_fd = std::io::stderr().as_raw_fd();
- let saved_stderr_fd = nix::fcntl::fcntl(stderr_fd, nix::fcntl::F_DUPFD_CLOEXEC(3))?;
- let saved_stderr = unsafe { OwnedFd::from_raw_fd(saved_stderr_fd) };
-
- let output_fd = output.as_raw_fd();
- nix::unistd::dup2(output_fd, stdout_fd)?;
- nix::unistd::dup2(output_fd, stderr_fd)?;
-
- Ok((saved_stdout, saved_stderr))
-}
-
fn merge_profiles(input_profiles_dir: &Path, output_profile_file: &Path) -> Result<()> {
let mut merged_trace = Trace::new();
@@ -250,11 +231,8 @@
std::env::set_var("RUST_BACKTRACE", "1");
// Redirect stdout/stderr to a file if `--log` was specified.
- let mut saved_output: Option<(File, OwnedFd)> = if let Some(log_name) = &args.log {
- let file = File::create(log_name).expect("Failed to create the log file");
- let (_saved_stdout, saved_stderr) =
- redirect_stdout_stderr(&file).expect("Failed to redirect stdout/stderr");
- Some((file, saved_stderr))
+ let redirector = if let Some(log_name) = &args.log {
+ Some(cliutil::StdioRedirector::new(log_name).unwrap())
} else {
None
};
@@ -269,11 +247,8 @@
match status {
Ok(status) if status.code() == Some(0) => {}
_ => {
- if let Some((write_file, saved_stderr)) = saved_output.take() {
- // Reopen the file to get an independent seek position.
- let mut read_file = File::open(format!("/proc/self/fd/{}", write_file.as_raw_fd()))
- .expect("Failed to reopen the log file");
- std::io::copy(&mut read_file, &mut File::from(saved_stderr)).ok();
+ if let Some(redirector) = redirector {
+ redirector.flush_to_real_stderr().unwrap()
}
}
}
diff --git a/portage/bin/alchemist/Cargo.lock b/portage/bin/alchemist/Cargo.lock
index 4e56cd9..3634e0c 100644
--- a/portage/bin/alchemist/Cargo.lock
+++ b/portage/bin/alchemist/Cargo.lock
@@ -235,7 +235,9 @@
version = "0.1.0"
dependencies = [
"anyhow",
+ "fileutil",
"itertools",
+ "nix",
"shell-escape",
"tracing",
"tracing-chrome-trace",
@@ -378,6 +380,17 @@
]
[[package]]
+name = "fileutil"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "lazy_static",
+ "libc",
+ "tempfile",
+ "walkdir",
+]
+
+[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/portage/bin/alchemist/shared_crates.bzl b/portage/bin/alchemist/shared_crates.bzl
index e6663ee..6065be7 100644
--- a/portage/bin/alchemist/shared_crates.bzl
+++ b/portage/bin/alchemist/shared_crates.bzl
@@ -7,6 +7,7 @@
SHARED_CRATES = [
"//bazel/portage/common/chrome-trace:srcs",
"//bazel/portage/common/cliutil:srcs",
+ "//bazel/portage/common/fileutil:srcs",
"//bazel/portage/common/portage/version:srcs",
"//bazel/portage/common/testutil:srcs",
"//bazel/portage/common/tracing-chrome-trace:srcs",
diff --git a/portage/bin/alchemist/src.bzl b/portage/bin/alchemist/src.bzl
index d6a094a..a60b4c9 100644
--- a/portage/bin/alchemist/src.bzl
+++ b/portage/bin/alchemist/src.bzl
@@ -39,6 +39,9 @@
"//bazel/portage/bin/alchemist:src/bin/alchemist/generate_repo/internal/sources/mod.rs",
"//bazel/portage/bin/alchemist:src/bin/alchemist/generate_repo/internal/sources/templates/source.BUILD.bazel",
"//bazel/portage/bin/alchemist:src/bin/alchemist/generate_repo/internal/sources/testdata/.presubmitignore",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/generate_repo/internal/sources/testdata/empty_dirs.golden.BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/generate_repo/internal/sources/testdata/empty_dirs_git.llvm-project.golden.BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/generate_repo/internal/sources/testdata/empty_dirs_git.platform2.golden.BUILD.bazel",
"//bazel/portage/bin/alchemist:src/bin/alchemist/generate_repo/internal/sources/testdata/nested/golden/foo/BUILD.bazel",
"//bazel/portage/bin/alchemist:src/bin/alchemist/generate_repo/internal/sources/testdata/nested/golden/foo/bar/BUILD.bazel",
"//bazel/portage/bin/alchemist:src/bin/alchemist/generate_repo/internal/sources/testdata/nested/golden/foo/bar/baz/BUILD.bazel",
@@ -73,6 +76,108 @@
"//bazel/portage/bin/alchemist:src/bin/alchemist/generate_repo/templates/root.BUILD.bazel",
"//bazel/portage/bin/alchemist:src/bin/alchemist/generate_repo/templates/settings.bzl",
"//bazel/portage/bin/alchemist:src/bin/alchemist/main.rs",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/.presubmitignore",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/README.md",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/WORKSPACE.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/amd64-generic/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/amd64-generic/make.conf",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/amd64-generic/metadata/layout.conf",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/amd64-generic/profiles/base/make.defaults",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/amd64-generic/toolchain.conf",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/chromeos/config/make.conf.common",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/chromeos/config/make.conf.generic-target",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/eclass/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/eclass/myclass.eclass",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/eclass/mysuper.eclass",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/metadata/layout.conf",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/sys-kernel/linux-headers/linux-headers-4.14.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/sys-libs/gcc-libs/gcc-libs-10.2.0.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/sys-libs/glibc/glibc-2.35-r20.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/sys-libs/libcxx/libcxx-16.0_pre484197.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/sys-libs/llvm-libunwind/llvm-libunwind-16.0_pre484197.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/test-cases/distfiles/Manifest",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/test-cases/distfiles/distfiles-1.0.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/test-cases/failure/failure-1.0.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/chromiumos/test-cases/inherit/inherit-1.0.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/portage-stable/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/portage-stable/metadata/layout.conf",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/overlays/portage-stable/virtual/os-headers/os-headers-0-r2.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/sys-kernel/linux-headers/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/sys-kernel/linux-headers/linux-headers-4.14.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/sys-libs/gcc-libs/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/sys-libs/gcc-libs/gcc-libs-10.2.0.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/sys-libs/glibc/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/sys-libs/glibc/glibc-2.35-r20.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/sys-libs/libcxx/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/sys-libs/libcxx/libcxx-16.0_pre484197.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/sys-libs/llvm-libunwind/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/sys-libs/llvm-libunwind/llvm-libunwind-16.0_pre484197.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/test-cases/distfiles/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/test-cases/distfiles/distfiles-1.0.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/test-cases/failure/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/test-cases/failure/failure-1.0.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/test-cases/inherit/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/chromiumos/test-cases/inherit/inherit-1.0.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/portage-stable/virtual/os-headers/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/packages/stage1/target/board/portage-stable/virtual/os-headers/os-headers-0-r2.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/eclean",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/emaint",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/emerge",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/equery",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/make.conf.board",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/make.conf.board_setup",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/pkg-config",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/portageq",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/qcheck",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/qdepends",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/qfile",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/qlist",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/qmerge",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sdk/stage1/target/board/qsize",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sources/chromite/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sources/chromite/main.py",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sources/src/scripts/hooks/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/internal/sources/src/scripts/hooks/install/hello.sh",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/settings.bzl",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/sys-kernel/linux-headers/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/sys-libs/gcc-libs/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/sys-libs/glibc/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/sys-libs/libcxx/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/sys-libs/llvm-libunwind/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/test-cases/distfiles/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/test-cases/failure/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/test-cases/inherit/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/golden/virtual/os-headers/BUILD.bazel",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/chromite/__pycache__/README.md",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/chromite/main.py",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/overlays/overlay-amd64-generic/make.conf",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/overlays/overlay-amd64-generic/metadata/layout.conf",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/overlays/overlay-amd64-generic/profiles/base/make.defaults",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/overlays/overlay-amd64-generic/toolchain.conf",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/overlays/overlay-amd64-host/metadata/layout.conf",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/overlays/overlay-amd64-host/toolchain.conf",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/scripts/hooks/install/hello.sh",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/chromeos/config/make.conf.common",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/chromeos/config/make.conf.generic-target",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/eclass/myclass.eclass",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/eclass/mysuper.eclass",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/metadata/layout.conf",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/sys-kernel/linux-headers/linux-headers-4.14.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/sys-libs/gcc-libs/gcc-libs-10.2.0.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/sys-libs/glibc/glibc-2.35-r20.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/sys-libs/libcxx/libcxx-16.0_pre484197.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/sys-libs/llvm-libunwind/llvm-libunwind-16.0_pre484197.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/test-cases/distfiles/Manifest",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/test-cases/distfiles/distfiles-1.0.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/test-cases/failure/failure-1.0.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/chromiumos-overlay/test-cases/inherit/inherit-1.0.ebuild",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/portage-stable/metadata/layout.conf",
+ "//bazel/portage/bin/alchemist:src/bin/alchemist/testdata/input/src/third_party/portage-stable/virtual/os-headers/os-headers-0-r2.ebuild",
"//bazel/portage/bin/alchemist:src/bin/alchemist/ver_rs.rs",
"//bazel/portage/bin/alchemist:src/bin/alchemist/ver_test.rs",
"//bazel/portage/bin/alchemist:src/common.rs",
@@ -117,6 +222,14 @@
"//bazel/portage/common/chrome-trace:src/lib.rs",
"//bazel/portage/common/cliutil:Cargo.toml",
"//bazel/portage/common/cliutil:src/lib.rs",
+ "//bazel/portage/common/cliutil:src/stdio_redirector.rs",
+ "//bazel/portage/common/fileutil:Cargo.toml",
+ "//bazel/portage/common/fileutil:src/dualpath.rs",
+ "//bazel/portage/common/fileutil:src/lib.rs",
+ "//bazel/portage/common/fileutil:src/move.rs",
+ "//bazel/portage/common/fileutil:src/remove.rs",
+ "//bazel/portage/common/fileutil:src/symlink_forest.rs",
+ "//bazel/portage/common/fileutil:src/tempdir.rs",
"//bazel/portage/common/portage/version:Cargo.toml",
"//bazel/portage/common/portage/version:src/lib.rs",
"//bazel/portage/common/portage/version:src/version.rs",
diff --git a/portage/common/cliutil/BUILD.bazel b/portage/common/cliutil/BUILD.bazel
index 39dbdf2..311597e 100644
--- a/portage/common/cliutil/BUILD.bazel
+++ b/portage/common/cliutil/BUILD.bazel
@@ -19,11 +19,13 @@
srcs = glob(["src/*.rs"]),
crate_name = "cliutil",
rustc_flags = RUSTC_DEBUG_FLAGS,
- visibility = ["//bazel:internal"],
+ visibility = ["//visibility:public"],
deps = [
+ "//bazel/portage/common/fileutil",
"//bazel/portage/common/tracing-chrome-trace",
"@alchemy_crates//:anyhow",
"@alchemy_crates//:itertools",
+ "@alchemy_crates//:nix",
"@alchemy_crates//:shell-escape",
"@alchemy_crates//:tracing",
"@alchemy_crates//:tracing-subscriber",
diff --git a/portage/common/cliutil/Cargo.toml b/portage/common/cliutil/Cargo.toml
index 8d05760..abd67d9 100644
--- a/portage/common/cliutil/Cargo.toml
+++ b/portage/common/cliutil/Cargo.toml
@@ -7,9 +7,11 @@
[dependencies]
tracing-chrome-trace = { path = "../tracing-chrome-trace" }
+fileutil = { path = "../fileutil" }
anyhow.workspace = true
itertools.workspace = true
+nix.workspace = true
shell-escape.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
diff --git a/portage/common/cliutil/src/lib.rs b/portage/common/cliutil/src/lib.rs
index 41f5eb1..2de7e5c 100644
--- a/portage/common/cliutil/src/lib.rs
+++ b/portage/common/cliutil/src/lib.rs
@@ -18,6 +18,10 @@
use tracing_chrome_trace::ChromeTraceLayer;
use tracing_subscriber::prelude::*;
+mod stdio_redirector;
+
+pub use crate::stdio_redirector::StdioRedirector;
+
/// Wraps a CLI main function to provide the common startup/cleanup logic.
///
/// Most programs implementing Alchemy actions likely want to call this function
diff --git a/portage/common/cliutil/src/stdio_redirector.rs b/portage/common/cliutil/src/stdio_redirector.rs
new file mode 100644
index 0000000..5c75118
--- /dev/null
+++ b/portage/common/cliutil/src/stdio_redirector.rs
@@ -0,0 +1,61 @@
+// Copyright 2023 The ChromiumOS Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use anyhow::{Context, Result};
+use std::{fs::File, os::fd::AsRawFd, os::fd::FromRawFd, os::fd::OwnedFd, path::Path};
+
+/// Redirects stdout and stderr to the specified file, and returns the saved
+/// stdout/stderr file descriptors.
+fn redirect_stdout_stderr(output: &File) -> Result<(OwnedFd, OwnedFd)> {
+ let stdout_fd = std::io::stdout().as_raw_fd();
+ let saved_stdout_fd = nix::fcntl::fcntl(stdout_fd, nix::fcntl::F_DUPFD_CLOEXEC(3))?;
+ let saved_stdout = unsafe { OwnedFd::from_raw_fd(saved_stdout_fd) };
+
+ let stderr_fd = std::io::stderr().as_raw_fd();
+ let saved_stderr_fd = nix::fcntl::fcntl(stderr_fd, nix::fcntl::F_DUPFD_CLOEXEC(3))?;
+ let saved_stderr = unsafe { OwnedFd::from_raw_fd(saved_stderr_fd) };
+
+ let output_fd = output.as_raw_fd();
+ nix::unistd::dup2(output_fd, stdout_fd)?;
+ nix::unistd::dup2(output_fd, stderr_fd)?;
+
+ Ok((saved_stdout, saved_stderr))
+}
+
+pub struct StdioRedirector {
+ file: File,
+ saved_stdout: OwnedFd,
+ saved_stderr: File,
+}
+
+impl StdioRedirector {
+ /// Redirects stdout and stderr to the specified path.
+ pub fn new(path: &Path) -> Result<Self> {
+ let file = File::create(path).context("Failed to create the log file")?;
+ let (saved_stdout, saved_stderr) =
+ redirect_stdout_stderr(&file).context("Failed to redirect stdout/stderr")?;
+
+ Ok(Self {
+ file,
+ saved_stdout,
+ saved_stderr: saved_stderr.into(),
+ })
+ }
+
+ /// Prints the contents of the file to the real stderr.
+ /// Also consumes the redirector, which restores the original stdout/stderr.
+ pub fn flush_to_real_stderr(mut self) -> Result<()> {
+ // Reopen the file to get an independent seek position.
+ let read_file = File::open(format!("/proc/self/fd/{}", self.file.as_raw_fd()));
+ std::io::copy(&mut read_file?, &mut self.saved_stderr)?;
+ Ok(())
+ }
+}
+
+impl Drop for StdioRedirector {
+ fn drop(&mut self) {
+ nix::unistd::dup2(self.saved_stdout.as_raw_fd(), std::io::stdout().as_raw_fd()).unwrap();
+ nix::unistd::dup2(self.saved_stderr.as_raw_fd(), std::io::stderr().as_raw_fd()).unwrap();
+ }
+}
diff --git a/portage/common/fileutil/BUILD.bazel b/portage/common/fileutil/BUILD.bazel
index 10dac7c..e1cab64 100644
--- a/portage/common/fileutil/BUILD.bazel
+++ b/portage/common/fileutil/BUILD.bazel
@@ -5,6 +5,15 @@
load("//bazel/portage/build_defs:common.bzl", "RUSTC_DEBUG_FLAGS")
load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+filegroup(
+ name = "srcs",
+ srcs = glob(
+ ["**"],
+ exclude = ["BUILD.bazel"],
+ ),
+ visibility = ["//bazel/portage/bin/alchemist:__pkg__"],
+)
+
rust_library(
name = "fileutil",
srcs = glob(["src/*.rs"]),
diff --git a/workspace_root/alchemy/Cargo.lock b/workspace_root/alchemy/Cargo.lock
index e250d48..31f7d5f 100644
--- a/workspace_root/alchemy/Cargo.lock
+++ b/workspace_root/alchemy/Cargo.lock
@@ -236,7 +236,9 @@
version = "0.1.0"
dependencies = [
"anyhow",
+ "fileutil",
"itertools",
+ "nix",
"shell-escape",
"tempfile",
"tracing",