1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
// Copyright 2021 Ant Group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0

//! Nydus Image Service Management Framework
//!
//! The `nydus-service` crate provides facilities to manage Nydus services, such as:
//! - `blobfs`: share processed RAFS metadata/data blobs to guest by virtio-fs, so the RAFS
//!    filesystem can be mounted by EROFS inside guest.
//! - `blockdev`: compose processed RAFS metadata/data as a block device, so it can be used as
//!   backend for virtio-blk.
//! - `fscache`: cooperate Linux fscache subsystem to mount RAFS filesystems by EROFS.
//! - `fuse`: mount RAFS filesystems as FUSE filesystems.

#[macro_use]
extern crate log;
#[macro_use]
extern crate nydus_api;

use std::fmt::{self, Display};
use std::io;
use std::str::FromStr;
use std::sync::mpsc::{RecvError, SendError};

use fuse_backend_rs::api::vfs::VfsError;
use fuse_backend_rs::transport::Error as FuseTransportError;
use fuse_backend_rs::Error as FuseError;
use nydus_api::{ConfigV2, DaemonErrorKind};
use nydus_rafs::RafsError;
use serde::{Deserialize, Serialize};
use serde_json::Error as SerdeError;

pub mod daemon;
mod fs_service;
mod fusedev;
mod singleton;
pub mod upgrade;

pub use blob_cache::BlobCacheMgr;
pub use fs_service::{FsBackendCollection, FsBackendMountCmd, FsBackendUmountCmd, FsService};
pub use fusedev::{create_fuse_daemon, create_vfs_backend, FusedevDaemon};
pub use singleton::create_daemon;

#[cfg(target_os = "linux")]
pub mod blob_cache;
#[cfg(all(target_os = "linux", feature = "block-device"))]
pub mod block_device;
#[cfg(all(target_os = "linux", feature = "block-nbd"))]
pub mod block_nbd;
#[cfg(target_os = "linux")]
mod fs_cache;

#[cfg(target_os = "linux")]
pub use fs_cache::FsCacheHandler;

/// Error code related to Nydus library.
#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("object or filesystem already exists")]
    AlreadyExists,
    /// Invalid arguments provided.
    #[error("invalid argument `{0}`")]
    InvalidArguments(String),
    #[error("invalid configuration, {0}")]
    InvalidConfig(String),
    #[error("invalid prefetch file list")]
    InvalidPrefetchList,
    #[error("object or filesystem doesn't exist")]
    NotFound,
    #[error("daemon is not ready yet")]
    NotReady,
    #[error("unsupported request or operation")]
    Unsupported,
    #[error("failed to serialize/deserialize message, {0}")]
    Serde(SerdeError),
    #[error("failed to spawn thread, {0}")]
    ThreadSpawn(io::Error),
    #[error("failed to send message to channel, {0}")]
    ChannelSend(#[from] SendError<crate::daemon::DaemonStateMachineInput>),
    #[error("failed to receive message from channel, {0}")]
    ChannelReceive(#[from] RecvError),
    #[error(transparent)]
    UpgradeManager(upgrade::UpgradeMgrError),
    #[error("failed to start service, {0}")]
    StartService(String),
    /// Input event to stat-machine is not expected.
    #[error("unexpect state machine transition event `{0:?}`")]
    UnexpectedEvent(crate::daemon::DaemonStateMachineInput),
    #[error("failed to wait daemon, {0}")]
    WaitDaemon(#[source] io::Error),

    #[error("filesystem type mismatch, expect {0}")]
    FsTypeMismatch(String),
    #[error("passthroughfs failed to handle request, {0}")]
    PassthroughFs(#[source] io::Error),
    #[error("RAFS failed to handle request, {0}")]
    Rafs(#[from] RafsError),
    #[error("VFS failed to handle request, {0:?}")]
    Vfs(VfsError),

    // fusedev
    #[error("failed to create FUSE server, {0}")]
    CreateFuseServer(io::Error),
    // Fuse session has been shutdown.
    #[error("FUSE session has been shut down, {0}")]
    SessionShutdown(FuseTransportError),

    // virtio-fs
    #[error("failed to handle event other than input event")]
    HandleEventNotEpollIn,
    #[error("failed to handle unknown event")]
    HandleEventUnknownEvent,
    #[error("fail to walk descriptor chain")]
    IterateQueue,
    #[error("invalid Virtio descriptor chain, {0}")]
    InvalidDescriptorChain(#[from] FuseTransportError),
    #[error("failed to process FUSE request, {0}")]
    ProcessQueue(#[from] FuseError),
    #[error("failed to create epoll context, {0}")]
    Epoll(#[source] io::Error),
    #[error("vhost-user failed to process request, {0}")]
    VhostUser(String),
    #[error("missing memory configuration for virtio queue")]
    QueueMemoryUnset,
}

impl From<Error> for io::Error {
    fn from(e: Error) -> Self {
        einval!(e)
    }
}

impl From<VfsError> for Error {
    fn from(e: VfsError) -> Self {
        Error::Vfs(e)
    }
}

impl From<Error> for DaemonErrorKind {
    fn from(e: Error) -> Self {
        use Error::*;
        match e {
            UpgradeManager(_) => DaemonErrorKind::UpgradeManager,
            NotReady => DaemonErrorKind::NotReady,
            Unsupported => DaemonErrorKind::Unsupported,
            Serde(e) => DaemonErrorKind::Serde(e),
            UnexpectedEvent(e) => DaemonErrorKind::UnexpectedEvent(format!("{:?}", e)),
            o => DaemonErrorKind::Other(o.to_string()),
        }
    }
}

/// Specialized `Result` for Nydus library.
pub type Result<T> = std::result::Result<T, Error>;

/// Type of supported backend filesystems.
#[derive(Clone, Debug, Serialize, PartialEq, Deserialize)]
pub enum FsBackendType {
    /// Registry Accelerated File System
    Rafs,
    /// Share an underlying directory as a FUSE filesystem.
    PassthroughFs,
}

impl FromStr for FsBackendType {
    type Err = Error;

    fn from_str(s: &str) -> Result<FsBackendType> {
        match s {
            "rafs" => Ok(FsBackendType::Rafs),
            "passthrough" => Ok(FsBackendType::PassthroughFs),
            "passthroughfs" => Ok(FsBackendType::PassthroughFs),
            "passthrough_fs" => Ok(FsBackendType::PassthroughFs),
            o => Err(Error::InvalidArguments(format!(
                "only 'rafs' and 'passthrough_fs' are supported, but {} was specified",
                o
            ))),
        }
    }
}

impl Display for FsBackendType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

/// Backend filesystem descriptor.
#[derive(Serialize, Clone, Deserialize)]
pub struct FsBackendDescriptor {
    /// Type of backend filesystem.
    pub backend_type: FsBackendType,
    /// Mount point for the filesystem.
    pub mountpoint: String,
    /// Timestamp for the mount operation.
    pub mounted_time: time::OffsetDateTime,
    /// Optional configuration information for the backend filesystem.
    pub config: Option<ConfigV2>,
}

/// Validate thread number configuration, valid range is `[1-1024]`.
pub fn validate_threads_configuration<V: AsRef<str>>(v: V) -> std::result::Result<usize, String> {
    if let Ok(t) = v.as_ref().parse::<usize>() {
        if t > 0 && t <= 1024 {
            Ok(t)
        } else {
            Err(format!(
                "invalid thread number {}, valid range: [1-1024]",
                t
            ))
        }
    } else {
        Err(format!(
            "invalid thread number configuration: {}",
            v.as_ref()
        ))
    }
}

/// Trait to get configuration options for services.
pub trait ServiceArgs {
    /// Get value of commandline option `key`.
    fn value_of(&self, key: &str) -> Option<&String>;

    /// Check whether commandline optio `key` is present.
    fn is_present(&self, key: &str) -> bool;
}

#[cfg(not(target_os = "linux"))]
mod blob_cache {
    use super::*;

    pub struct BlobCacheMgr {}

    impl Default for BlobCacheMgr {
        fn default() -> Self {
            Self::new()
        }
    }

    impl BlobCacheMgr {
        pub fn new() -> Self {
            BlobCacheMgr {}
        }

        pub fn add_blob_list(&self, _blobs: &nydus_api::BlobCacheList) -> io::Result<()> {
            unimplemented!()
        }

        pub fn add_blob_entry(&self, _entry: &nydus_api::BlobCacheEntry) -> Result<()> {
            unimplemented!()
        }

        pub fn remove_blob_entry(&self, _param: &nydus_api::BlobCacheObjectId) -> Result<()> {
            unimplemented!()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_backend_fs_type() {
        assert_eq!(
            FsBackendType::from_str("rafs").unwrap(),
            FsBackendType::Rafs
        );
        assert_eq!(
            FsBackendType::from_str("passthrough").unwrap(),
            FsBackendType::PassthroughFs
        );
        assert_eq!(
            FsBackendType::from_str("passthroughfs").unwrap(),
            FsBackendType::PassthroughFs
        );
        assert_eq!(
            FsBackendType::from_str("passthrough_fs").unwrap(),
            FsBackendType::PassthroughFs
        );
        assert!(FsBackendType::from_str("passthroug").is_err());

        assert_eq!(format!("{}", FsBackendType::Rafs), "Rafs");
        assert_eq!(format!("{}", FsBackendType::PassthroughFs), "PassthroughFs");
    }

    #[test]
    fn test_validate_thread_configuration() {
        assert_eq!(validate_threads_configuration("1").unwrap(), 1);
        assert_eq!(validate_threads_configuration("1024").unwrap(), 1024);
        assert!(validate_threads_configuration("0").is_err());
        assert!(validate_threads_configuration("-1").is_err());
        assert!(validate_threads_configuration("1.0").is_err());
        assert!(validate_threads_configuration("1025").is_err());
        assert!(validate_threads_configuration("test").is_err());
    }
}