Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions crates/kit/src/qemu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,8 @@ pub struct VirtiofsConfig {
pub debug: bool,
/// Mount as read-only
pub readonly: bool,
/// Optional log file path for virtiofsd output
pub log_file: Option<Utf8PathBuf>,
}

impl Default for VirtiofsConfig {
Expand All @@ -917,6 +919,7 @@ impl Default for VirtiofsConfig {
debug: false,
// We don't need to write to this, there's a transient overlay
readonly: true,
log_file: None,
}
}
}
Expand Down Expand Up @@ -998,8 +1001,31 @@ pub async fn spawn_virtiofsd_async(config: &VirtiofsConfig) -> Result<tokio::pro
// but we want to be compatible with older virtiofsd too.
cmd.arg("--inode-file-handles=fallback");

cmd.stdout(std::process::Stdio::piped());
cmd.stderr(std::process::Stdio::piped());
// Configure output redirection
if let Some(log_file) = &config.log_file {
// Create/open log file for both stdout and stderr
let tokio_file = tokio::fs::OpenOptions::new()
.create(true)
.append(true)
.open(log_file)
.await
.with_context(|| format!("Failed to open virtiofsd log file: {}", log_file))?;

let log_file_handle = tokio_file.into_std().await;

// Clone for stderr
let stderr_handle = log_file_handle
.try_clone()
.with_context(|| "Failed to clone log file handle for stderr")?;

cmd.stdout(std::process::Stdio::from(log_file_handle));
cmd.stderr(std::process::Stdio::from(stderr_handle));

debug!("virtiofsd output will be logged to: {}", log_file);
} else {
cmd.stdout(std::process::Stdio::piped());
cmd.stderr(std::process::Stdio::piped());
}

let child = cmd.spawn().with_context(|| {
format!(
Expand All @@ -1009,8 +1035,8 @@ pub async fn spawn_virtiofsd_async(config: &VirtiofsConfig) -> Result<tokio::pro
})?;

debug!(
"Spawned virtiofsd: binary={}, socket={}, shared_dir={}, debug={}",
virtiofsd_binary, config.socket_path, config.shared_dir, config.debug
"Spawned virtiofsd: binary={}, socket={}, shared_dir={}, debug={}, log_file={:?}",
virtiofsd_binary, config.socket_path, config.shared_dir, config.debug, config.log_file
);

Ok(child)
Expand Down
36 changes: 36 additions & 0 deletions crates/kit/src/run_ephemeral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,7 @@ pub(crate) async fn run_impl(opts: RunEphemeralOpts) -> Result<()> {
shared_dir: source_path,
debug: false,
readonly: is_readonly,
log_file: Some(format!("/run/virtiofsd-{}.log", mount_name_str).into()),
};
additional_mounts.push((virtiofsd_config, tag.clone()));

Expand Down Expand Up @@ -892,6 +893,35 @@ WantedBy=local-fs.target
let default_wantsdir = format!("{target_unitdir}/default.target.wants");
fs::create_dir_all(&default_wantsdir)?;

// Create systemd unit to stream journal to virtio-serial device
let journal_stream_unit = r#"[Unit]
Description=Stream systemd journal to host via virtio-serial
DefaultDependencies=no
After=systemd-journald.service dev-virtio\x2dports-org.bcvk.journal.device
Requires=systemd-journald.service dev-virtio\x2dports-org.bcvk.journal.device

[Service]
Type=simple
ExecStart=/usr/bin/journalctl -f -o short-precise --no-pager
StandardOutput=file:/dev/virtio-ports/org.bcvk.journal
StandardError=file:/dev/virtio-ports/org.bcvk.journal
Restart=always
RestartSec=1s

[Install]
WantedBy=sysinit.target
"#;
let journal_unit_path = format!("{target_unitdir}/bcvk-journal-stream.service");
tokio::fs::write(&journal_unit_path, journal_stream_unit).await?;
debug!("Created journal streaming unit at {journal_unit_path}");

// Enable the journal streaming unit
let sysinit_wantsdir = format!("{target_unitdir}/sysinit.target.wants");
tokio::fs::create_dir_all(&sysinit_wantsdir).await?;
let journal_wants_link = format!("{sysinit_wantsdir}/bcvk-journal-stream.service");
tokio::fs::symlink("../bcvk-journal-stream.service", &journal_wants_link).await?;
debug!("Enabled journal streaming unit in sysinit.target.wants");

match opts.common.execute.as_slice() {
[] => {}
elts => {
Expand Down Expand Up @@ -945,6 +975,8 @@ StandardOutput=file:/dev/virtio-ports/executestatus
// Prepare main virtiofsd config for the source image (will be spawned by QEMU)
let mut main_virtiofsd_config = qemu::VirtiofsConfig::default();
main_virtiofsd_config.debug = std::env::var("DEBUG_MODE").is_ok();
// Always log virtiofsd output for debugging
main_virtiofsd_config.log_file = Some("/run/virtiofsd.log".into());

std::fs::create_dir_all(CONTAINER_STATEDIR)?;

Expand Down Expand Up @@ -1092,6 +1124,10 @@ Options=
.set_kernel_cmdline(kernel_cmdline)
.set_console(opts.common.console);

// Add virtio-serial device for journal streaming
qemu_config.add_virtio_serial_out("org.bcvk.journal", "/run/journal.log".to_string(), false);
debug!("Added virtio-serial device for journal streaming to /run/journal.log");

if opts.common.ssh_keygen {
qemu_config.enable_ssh_access(None); // Use default port 2222
debug!("Enabled SSH port forwarding: host port 2222 -> guest port 22");
Expand Down
42 changes: 42 additions & 0 deletions docs/src/man/bcvk-ephemeral-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,48 @@ Development workflow example:

# VM automatically cleans up when stopped due to --rm flag

# DEBUGGING

When troubleshooting ephemeral VM issues, bcvk provides several debugging logs that can be accessed from within the container.

## Guest Journal Log

The systemd journal from the guest VM is automatically streamed to `/run/journal.log` inside the container. This log captures all boot messages, service startup events, and system errors from the VM's perspective.

To view the journal log:

# For a running detached VM
podman exec <container-id> tail -f /run/journal.log

# View specific systemd service messages
podman exec <container-id> grep "dbus-broker" /run/journal.log

# Save journal for offline analysis
podman exec <container-id> cat /run/journal.log > guest-journal.log

The journal log is particularly useful for:
- Diagnosing boot failures and systemd service issues
- Investigating permission denied errors
- Understanding VM initialization problems
- Debugging network and device configuration

## Virtiofsd Logs

The virtiofsd daemon logs are written to `/run/virtiofsd.log` and `/run/virtiofsd-<mount-name>.log` for each filesystem mount. These logs show filesystem sharing operations between the container and VM.

To view virtiofsd logs:

# Main virtiofsd log
podman exec <container-id> cat /run/virtiofsd.log

# Logs for additional bind mounts
podman exec <container-id> cat /run/virtiofsd-workspace.log

Virtiofsd logs are helpful for:
- Debugging filesystem access issues
- Understanding file handle support warnings
- Investigating mount-related errors

# SEE ALSO

**bcvk**(8)
Expand Down