1 ///! Daemon binary to run inside a micro-VM for secure single file restore of disk images
3 use std
::io
::prelude
::*;
5 io
::{FromRawFd, RawFd}
,
9 use std
::sync
::{Arc, Mutex}
;
11 use anyhow
::{bail, format_err, Error}
;
12 use lazy_static
::lazy_static
;
13 use log
::{error, info}
;
14 use tokio
::sync
::mpsc
;
15 use tokio_stream
::wrappers
::ReceiverStream
;
17 use proxmox_router
::RpcEnvironmentType
;
19 use pbs_client
::DEFAULT_VSOCK_PORT
;
20 use proxmox_rest_server
::{ApiConfig, RestServer}
;
22 mod proxmox_restore_daemon
;
23 use proxmox_restore_daemon
::*;
25 /// Maximum amount of pending requests. If saturated, virtio-vsock returns ETIMEDOUT immediately.
26 /// We should never have more than a few requests in queue, so use a low number.
27 pub const MAX_PENDING
: usize = 32;
29 /// Will be present in base initramfs
30 pub const VM_DETECT_FILE
: &str = "/restore-vm-marker";
33 /// The current disks state. Use for accessing data on the attached snapshots.
34 pub static ref DISK_STATE
: Arc
<Mutex
<DiskState
>> = {
35 Arc
::new(Mutex
::new(DiskState
::scan().unwrap()))
39 /// This is expected to be run by 'proxmox-file-restore' within a mini-VM
40 fn main() -> Result
<(), Error
> {
41 pbs_tools
::setup_libc_malloc_opts();
43 if !Path
::new(VM_DETECT_FILE
).exists() {
45 "This binary is not supposed to be run manually, use 'proxmox-file-restore' instead."
49 // don't have a real syslog (and no persistance), so use env_logger to print to a log file (via
50 // stdout to a serial terminal attached by QEMU)
51 env_logger
::Builder
::from_env(env_logger
::Env
::default().default_filter_or("info"))
52 .write_style(env_logger
::WriteStyle
::Never
)
53 .format_timestamp_millis()
56 info
!("setup basic system environment...");
57 setup_system_env().map_err(|err
| format_err
!("system environment setup failed: {}", err
))?
;
59 // scan all attached disks now, before starting the API
60 // this will panic and stop the VM if anything goes wrong
61 info
!("scanning all disks...");
63 let _disk_state
= DISK_STATE
.lock().unwrap();
66 info
!("disk scan complete, starting main runtime...");
68 proxmox_async
::runtime
::main(run())
71 /// ensure we have our /run dirs, system users and stuff like that setup
72 fn setup_system_env() -> Result
<(), Error
> {
73 // the API may save some stuff there, e.g., the memcon tracking file
74 // we do not care much, but it's way less headache to just create it
75 std
::fs
::create_dir_all("/run/proxmox-backup")?
;
77 // we now ensure that all lock files are owned by the backup user, and as we reuse the
78 // specialized REST module from pbs api/daemon we have some checks there for user/acl stuff
79 // that gets locked, and thus needs the backup system user to work.
80 std
::fs
::create_dir_all("/etc")?
;
81 let mut passwd
= File
::create("/etc/passwd")?
;
82 writeln
!(passwd
, "root:x:0:0:root:/root:/bin/sh")?
;
83 writeln
!(passwd
, "backup:x:34:34:backup:/var/backups:/usr/sbin/nologin")?
;
85 let mut group
= File
::create("/etc/group")?
;
86 writeln
!(group
, "root:x:0:")?
;
87 writeln
!(group
, "backup:x:34:")?
;
93 async
fn run() -> Result
<(), Error
> {
96 let adaptor
= StaticAuthAdapter
::new()
97 .map_err(|err
| format_err
!("reading ticket file failed: {}", err
))?
;
99 let config
= ApiConfig
::new("", &ROUTER
, RpcEnvironmentType
::PUBLIC
, adaptor
)?
;
100 let rest_server
= RestServer
::new(config
);
102 let vsock_fd
= get_vsock_fd()?
;
103 let connections
= accept_vsock_connections(vsock_fd
);
104 let receiver_stream
= ReceiverStream
::new(connections
);
105 let acceptor
= hyper
::server
::accept
::from_stream(receiver_stream
);
107 hyper
::Server
::builder(acceptor
).serve(rest_server
).await?
;
109 bail
!("hyper server exited");
112 fn accept_vsock_connections(
114 ) -> mpsc
::Receiver
<Result
<tokio
::net
::UnixStream
, Error
>> {
115 use nix
::sys
::socket
::*;
116 let (sender
, receiver
) = mpsc
::channel(MAX_PENDING
);
118 tokio
::spawn(async
move {
120 let stream
: Result
<tokio
::net
::UnixStream
, Error
> = tokio
::task
::block_in_place(|| {
121 // we need to accept manually, as UnixListener aborts if socket type != AF_UNIX ...
122 let client_fd
= accept(vsock_fd
)?
;
123 let stream
= unsafe { net::UnixStream::from_raw_fd(client_fd) }
;
124 stream
.set_nonblocking(true)?
;
125 tokio
::net
::UnixStream
::from_std(stream
).map_err(|err
| err
.into())
130 if sender
.send(Ok(stream
)).await
.is_err() {
131 error
!("connection accept channel was closed");
135 error
!("error accepting vsock connetion: {}", err
);
144 fn get_vsock_fd() -> Result
<RawFd
, Error
> {
145 use nix
::sys
::socket
::*;
146 let sock_fd
= socket(
147 AddressFamily
::Vsock
,
152 let sock_addr
= VsockAddr
::new(libc
::VMADDR_CID_ANY
, DEFAULT_VSOCK_PORT
as u32);
153 bind(sock_fd
, &SockAddr
::Vsock(sock_addr
))?
;
154 listen(sock_fd
, MAX_PENDING
)?
;