501 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			501 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
use std::path::{Path, PathBuf};
 | 
						|
use std::sync::{Arc, OnceLock, Mutex, RwLock};
 | 
						|
use std::collections::HashMap;
 | 
						|
 | 
						|
use crate::error::DBError;
 | 
						|
use crate::options;
 | 
						|
use crate::rpc::Permissions;
 | 
						|
use crate::storage::Storage;
 | 
						|
use crate::storage_sled::SledStorage;
 | 
						|
use crate::storage_trait::StorageBackend;
 | 
						|
 | 
						|
// Key builders
 | 
						|
fn k_admin_next_id() -> &'static str {
 | 
						|
    "admin:next_id"
 | 
						|
}
 | 
						|
fn k_admin_dbs() -> &'static str {
 | 
						|
    "admin:dbs"
 | 
						|
}
 | 
						|
fn k_meta_db(id: u64) -> String {
 | 
						|
    format!("meta:db:{}", id)
 | 
						|
}
 | 
						|
fn k_meta_db_keys(id: u64) -> String {
 | 
						|
    format!("meta:db:{}:keys", id)
 | 
						|
}
 | 
						|
fn k_meta_db_enc(id: u64) -> String {
 | 
						|
    format!("meta:db:{}:enc", id)
 | 
						|
}
 | 
						|
 | 
						|
// Global cache of admin DB 0 handles per base_dir to avoid sled/reDB file-lock contention
 | 
						|
// and to correctly isolate different test instances with distinct directories.
 | 
						|
static ADMIN_STORAGES: OnceLock<RwLock<HashMap<String, Arc<dyn StorageBackend>>>> = OnceLock::new();
 | 
						|
 | 
						|
// Global registry for data DB storages to avoid double-open across process.
 | 
						|
static DATA_STORAGES: OnceLock<RwLock<HashMap<u64, Arc<dyn StorageBackend>>>> = OnceLock::new();
 | 
						|
static DATA_INIT_LOCK: Mutex<()> = Mutex::new(());
 | 
						|
 | 
						|
fn init_admin_storage(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
) -> Result<Arc<dyn StorageBackend>, DBError> {
 | 
						|
    let db_file = base_dir.join("0.db");
 | 
						|
    if let Some(parent_dir) = db_file.parent() {
 | 
						|
        std::fs::create_dir_all(parent_dir).map_err(|e| {
 | 
						|
            DBError(format!("Failed to create directory {}: {}", parent_dir.display(), e))
 | 
						|
        })?;
 | 
						|
    }
 | 
						|
    let storage: Arc<dyn StorageBackend> = match backend {
 | 
						|
        options::BackendType::Redb => Arc::new(Storage::new(&db_file, true, Some(admin_secret))?),
 | 
						|
        options::BackendType::Sled => Arc::new(SledStorage::new(&db_file, true, Some(admin_secret))?),
 | 
						|
        options::BackendType::Tantivy | options::BackendType::Lance => {
 | 
						|
            return Err(DBError("Admin DB 0 cannot use search-only backends (Tantivy/Lance)".to_string()))
 | 
						|
        }
 | 
						|
    };
 | 
						|
    Ok(storage)
 | 
						|
}
 | 
						|
 | 
						|
// Get or initialize a cached handle to admin DB 0 per base_dir (thread-safe, no double-open race)
 | 
						|
pub fn open_admin_storage(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
) -> Result<Arc<dyn StorageBackend>, DBError> {
 | 
						|
    let map = ADMIN_STORAGES.get_or_init(|| RwLock::new(HashMap::new()));
 | 
						|
    let key = base_dir.display().to_string();
 | 
						|
    // Fast path
 | 
						|
    if let Some(st) = map.read().unwrap().get(&key) {
 | 
						|
        return Ok(st.clone());
 | 
						|
    }
 | 
						|
    // Slow path with write lock
 | 
						|
    {
 | 
						|
        let mut w = map.write().unwrap();
 | 
						|
        if let Some(st) = w.get(&key) {
 | 
						|
            return Ok(st.clone());
 | 
						|
        }
 | 
						|
 | 
						|
        // Detect existing 0.db backend by filesystem, if present.
 | 
						|
        let admin_path = base_dir.join("0.db");
 | 
						|
        let detected = if admin_path.exists() {
 | 
						|
            if admin_path.is_file() {
 | 
						|
                Some(options::BackendType::Redb)
 | 
						|
            } else if admin_path.is_dir() {
 | 
						|
                Some(options::BackendType::Sled)
 | 
						|
            } else {
 | 
						|
                None
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            None
 | 
						|
        };
 | 
						|
 | 
						|
        let effective_backend = match detected {
 | 
						|
            Some(d) if d != backend => {
 | 
						|
                eprintln!(
 | 
						|
                    "warning: Admin DB 0 at {} appears to be {:?}, but process default is {:?}. Using detected backend.",
 | 
						|
                    admin_path.display(),
 | 
						|
                    d,
 | 
						|
                    backend
 | 
						|
                );
 | 
						|
                d
 | 
						|
            }
 | 
						|
            Some(d) => d,
 | 
						|
            None => backend, // First boot: use requested backend to initialize 0.db
 | 
						|
        };
 | 
						|
 | 
						|
        let st = init_admin_storage(base_dir, effective_backend, admin_secret)?;
 | 
						|
        w.insert(key, st.clone());
 | 
						|
        Ok(st)
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// Ensure admin structures exist in encrypted DB 0
 | 
						|
pub fn ensure_bootstrap(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
) -> Result<(), DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
 | 
						|
    // Initialize next id if missing
 | 
						|
    if !admin.exists(k_admin_next_id())? {
 | 
						|
        admin.set(k_admin_next_id().to_string(), "1".to_string())?;
 | 
						|
    }
 | 
						|
    // admin:dbs is a hash; it's fine if it doesn't exist (hlen -> 0)
 | 
						|
    Ok(())
 | 
						|
}
 | 
						|
 | 
						|
// Get or initialize a shared handle to a data DB (> 0), avoiding double-open across subsystems
 | 
						|
pub fn open_data_storage(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
    id: u64,
 | 
						|
) -> Result<Arc<dyn StorageBackend>, DBError> {
 | 
						|
    if id == 0 {
 | 
						|
        return open_admin_storage(base_dir, backend, admin_secret);
 | 
						|
    }
 | 
						|
 | 
						|
    // Validate existence in admin metadata
 | 
						|
    if !db_exists(base_dir, backend.clone(), admin_secret, id)? {
 | 
						|
        return Err(DBError(format!(
 | 
						|
            "Cannot open database instance {}, as that database instance does not exist.",
 | 
						|
            id
 | 
						|
        )));
 | 
						|
    }
 | 
						|
 | 
						|
    let map = DATA_STORAGES.get_or_init(|| RwLock::new(HashMap::new()));
 | 
						|
    // Fast path
 | 
						|
    if let Some(st) = map.read().unwrap().get(&id) {
 | 
						|
        return Ok(st.clone());
 | 
						|
    }
 | 
						|
 | 
						|
    // Slow path with init lock
 | 
						|
    let _guard = DATA_INIT_LOCK.lock().unwrap();
 | 
						|
    if let Some(st) = map.read().unwrap().get(&id) {
 | 
						|
        return Ok(st.clone());
 | 
						|
    }
 | 
						|
 | 
						|
    // Resolve effective backend for this db id:
 | 
						|
    // 1) Try admin meta "backend" field
 | 
						|
    // 2) If missing, sniff filesystem (file => Redb, dir => Sled), then persist into admin meta
 | 
						|
    // 3) Fallback to requested 'backend' (startup default) if nothing else is known
 | 
						|
    let meta_backend = get_database_backend(base_dir, backend.clone(), admin_secret, id).ok().flatten();
 | 
						|
    let db_path = base_dir.join(format!("{}.db", id));
 | 
						|
    let sniffed_backend = if db_path.exists() {
 | 
						|
        if db_path.is_file() {
 | 
						|
            Some(options::BackendType::Redb)
 | 
						|
        } else if db_path.is_dir() {
 | 
						|
            Some(options::BackendType::Sled)
 | 
						|
        } else {
 | 
						|
            None
 | 
						|
        }
 | 
						|
    } else {
 | 
						|
        None
 | 
						|
    };
 | 
						|
    let effective_backend = meta_backend.clone().or(sniffed_backend).unwrap_or(backend.clone());
 | 
						|
 | 
						|
    // If we had to sniff (i.e., meta missing), persist it for future robustness
 | 
						|
    if meta_backend.is_none() {
 | 
						|
        let _ = set_database_backend(base_dir, backend.clone(), admin_secret, id, effective_backend.clone());
 | 
						|
    }
 | 
						|
 | 
						|
    // Warn if caller-provided backend differs from effective
 | 
						|
    if effective_backend != backend {
 | 
						|
        eprintln!(
 | 
						|
            "notice: Database {} backend resolved to {:?} (caller requested {:?}). Using resolved backend.",
 | 
						|
            id, effective_backend, backend
 | 
						|
        );
 | 
						|
    }
 | 
						|
 | 
						|
    // Determine per-db encryption (from admin meta)
 | 
						|
    let enc = get_enc_key(base_dir, backend.clone(), admin_secret, id)?;
 | 
						|
    let should_encrypt = enc.is_some();
 | 
						|
 | 
						|
    // Build database file path and ensure parent dir exists
 | 
						|
    let db_file = PathBuf::from(base_dir).join(format!("{}.db", id));
 | 
						|
    if let Some(parent_dir) = db_file.parent() {
 | 
						|
        std::fs::create_dir_all(parent_dir).map_err(|e| {
 | 
						|
            DBError(format!("Failed to create directory {}: {}", parent_dir.display(), e))
 | 
						|
        })?;
 | 
						|
    }
 | 
						|
 | 
						|
    // Open storage using the effective backend
 | 
						|
    let storage: Arc<dyn StorageBackend> = match effective_backend {
 | 
						|
        options::BackendType::Redb => Arc::new(Storage::new(&db_file, should_encrypt, enc.as_deref())?),
 | 
						|
        options::BackendType::Sled => Arc::new(SledStorage::new(&db_file, should_encrypt, enc.as_deref())?),
 | 
						|
        options::BackendType::Tantivy => {
 | 
						|
            return Err(DBError("Tantivy backend has no KV storage; use FT.* commands only".to_string()))
 | 
						|
        }
 | 
						|
        options::BackendType::Lance => {
 | 
						|
            return Err(DBError("Lance backend has no KV storage; use LANCE.* commands only".to_string()))
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    // Publish to registry
 | 
						|
    map.write().unwrap().insert(id, storage.clone());
 | 
						|
    Ok(storage)
 | 
						|
}
 | 
						|
 | 
						|
// Allocate the next DB id and persist new pointer
 | 
						|
pub fn allocate_next_id(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
) -> Result<u64, DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    let cur = admin
 | 
						|
        .get(k_admin_next_id())?
 | 
						|
        .unwrap_or_else(|| "1".to_string());
 | 
						|
    let id: u64 = cur.parse().unwrap_or(1);
 | 
						|
    let next = id.checked_add(1).ok_or_else(|| DBError("next_id overflow".into()))?;
 | 
						|
    admin.set(k_admin_next_id().to_string(), next.to_string())?;
 | 
						|
 | 
						|
    // Register into admin:dbs set/hash
 | 
						|
    let _ = admin.hset(k_admin_dbs(), vec![(id.to_string(), "1".to_string())])?;
 | 
						|
 | 
						|
    // Default meta for the new db: public true
 | 
						|
    let meta_key = k_meta_db(id);
 | 
						|
    let _ = admin.hset(&meta_key, vec![("public".to_string(), "true".to_string())])?;
 | 
						|
 | 
						|
    Ok(id)
 | 
						|
}
 | 
						|
 | 
						|
// Check existence of a db id in admin:dbs
 | 
						|
pub fn db_exists(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
    id: u64,
 | 
						|
) -> Result<bool, DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    Ok(admin.hexists(k_admin_dbs(), &id.to_string())?)
 | 
						|
}
 | 
						|
 | 
						|
// Get per-db encryption key, if any
 | 
						|
pub fn get_enc_key(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
    id: u64,
 | 
						|
) -> Result<Option<String>, DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    admin.get(&k_meta_db_enc(id))
 | 
						|
}
 | 
						|
 | 
						|
// Set per-db encryption key (called during create)
 | 
						|
pub fn set_enc_key(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
    id: u64,
 | 
						|
    key: &str,
 | 
						|
) -> Result<(), DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    admin.set(k_meta_db_enc(id), key.to_string())
 | 
						|
}
 | 
						|
 | 
						|
// Set database public flag
 | 
						|
pub fn set_database_public(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
    id: u64,
 | 
						|
    public: bool,
 | 
						|
) -> Result<(), DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    let mk = k_meta_db(id);
 | 
						|
    let _ = admin.hset(&mk, vec![("public".to_string(), public.to_string())])?;
 | 
						|
    Ok(())
 | 
						|
}
 | 
						|
 | 
						|
// Persist per-db backend type in admin metadata (module-scope)
 | 
						|
pub fn set_database_backend(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
    id: u64,
 | 
						|
    db_backend: options::BackendType,
 | 
						|
) -> Result<(), DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    let mk = k_meta_db(id);
 | 
						|
    let val = match db_backend {
 | 
						|
        options::BackendType::Redb => "Redb",
 | 
						|
        options::BackendType::Sled => "Sled",
 | 
						|
        options::BackendType::Tantivy => "Tantivy",
 | 
						|
        options::BackendType::Lance => "Lance",
 | 
						|
    };
 | 
						|
    let _ = admin.hset(&mk, vec![("backend".to_string(), val.to_string())])?;
 | 
						|
    Ok(())
 | 
						|
}
 | 
						|
 | 
						|
pub fn get_database_backend(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
    id: u64,
 | 
						|
) -> Result<Option<options::BackendType>, DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    let mk = k_meta_db(id);
 | 
						|
    match admin.hget(&mk, "backend")? {
 | 
						|
        Some(s) if s == "Redb" => Ok(Some(options::BackendType::Redb)),
 | 
						|
        Some(s) if s == "Sled" => Ok(Some(options::BackendType::Sled)),
 | 
						|
        Some(s) if s == "Tantivy" => Ok(Some(options::BackendType::Tantivy)),
 | 
						|
        Some(s) if s == "Lance" => Ok(Some(options::BackendType::Lance)),
 | 
						|
        _ => Ok(None),
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// Set database name
 | 
						|
pub fn set_database_name(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
    id: u64,
 | 
						|
    name: &str,
 | 
						|
) -> Result<(), DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    let mk = k_meta_db(id);
 | 
						|
    let _ = admin.hset(&mk, vec![("name".to_string(), name.to_string())])?;
 | 
						|
    Ok(())
 | 
						|
}
 | 
						|
 | 
						|
// Get database name
 | 
						|
pub fn get_database_name(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
    id: u64,
 | 
						|
) -> Result<Option<String>, DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    let mk = k_meta_db(id);
 | 
						|
    admin.hget(&mk, "name")
 | 
						|
}
 | 
						|
 | 
						|
// Internal: load public flag; default to true when meta missing
 | 
						|
fn load_public(
 | 
						|
    admin: &Arc<dyn StorageBackend>,
 | 
						|
    id: u64,
 | 
						|
) -> Result<bool, DBError> {
 | 
						|
    let mk = k_meta_db(id);
 | 
						|
    match admin.hget(&mk, "public")? {
 | 
						|
        Some(v) => Ok(v == "true"),
 | 
						|
        None => Ok(true),
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// Add access key for db (value format: "Read:ts" or "ReadWrite:ts")
 | 
						|
pub fn add_access_key(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
    id: u64,
 | 
						|
    key_plain: &str,
 | 
						|
    perms: Permissions,
 | 
						|
) -> Result<(), DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    let hash = crate::rpc::hash_key(key_plain);
 | 
						|
    let v = match perms {
 | 
						|
        Permissions::Read => format!("Read:{}", now_secs()),
 | 
						|
        Permissions::ReadWrite => format!("ReadWrite:{}", now_secs()),
 | 
						|
    };
 | 
						|
    let _ = admin.hset(&k_meta_db_keys(id), vec![(hash, v)])?;
 | 
						|
    Ok(())
 | 
						|
}
 | 
						|
 | 
						|
// Delete access key by hash
 | 
						|
pub fn delete_access_key(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
    id: u64,
 | 
						|
    key_hash: &str,
 | 
						|
) -> Result<bool, DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    let n = admin.hdel(&k_meta_db_keys(id), vec![key_hash.to_string()])?;
 | 
						|
    Ok(n > 0)
 | 
						|
}
 | 
						|
 | 
						|
// List access keys, returning (hash, perms, created_at_secs)
 | 
						|
pub fn list_access_keys(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
    id: u64,
 | 
						|
) -> Result<Vec<(String, Permissions, u64)>, DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    let pairs = admin.hgetall(&k_meta_db_keys(id))?;
 | 
						|
    let mut out = Vec::new();
 | 
						|
    for (hash, val) in pairs {
 | 
						|
        let (perm, ts) = parse_perm_value(&val);
 | 
						|
        out.push((hash, perm, ts));
 | 
						|
    }
 | 
						|
    Ok(out)
 | 
						|
}
 | 
						|
 | 
						|
// Verify access permission for db id with optional key
 | 
						|
// Returns:
 | 
						|
// - Ok(Some(Permissions)) when access is allowed
 | 
						|
// - Ok(None) when not allowed or db missing (caller can distinguish by calling db_exists)
 | 
						|
pub fn verify_access(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
    id: u64,
 | 
						|
    key_opt: Option<&str>,
 | 
						|
) -> Result<Option<Permissions>, DBError> {
 | 
						|
    // Admin DB 0: require exact admin_secret
 | 
						|
    if id == 0 {
 | 
						|
        if let Some(k) = key_opt {
 | 
						|
            if k == admin_secret {
 | 
						|
                return Ok(Some(Permissions::ReadWrite));
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return Ok(None);
 | 
						|
    }
 | 
						|
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    if !admin.hexists(k_admin_dbs(), &id.to_string())? {
 | 
						|
        return Ok(None);
 | 
						|
    }
 | 
						|
 | 
						|
    let is_public = load_public(&admin, id)?;
 | 
						|
 | 
						|
    // If a key is explicitly provided, enforce its validity strictly.
 | 
						|
    // Do NOT fall back to public when an invalid key is supplied.
 | 
						|
    if let Some(k) = key_opt {
 | 
						|
        let hash = crate::rpc::hash_key(k);
 | 
						|
        if let Some(v) = admin.hget(&k_meta_db_keys(id), &hash)? {
 | 
						|
            let (perm, _ts) = parse_perm_value(&v);
 | 
						|
            return Ok(Some(perm));
 | 
						|
        }
 | 
						|
        // Invalid key
 | 
						|
        return Ok(None);
 | 
						|
    }
 | 
						|
 | 
						|
    // No key provided: allow access if DB is public, otherwise deny
 | 
						|
    if is_public {
 | 
						|
        Ok(Some(Permissions::ReadWrite))
 | 
						|
    } else {
 | 
						|
        Ok(None)
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// Enumerate all db ids
 | 
						|
pub fn list_dbs(
 | 
						|
    base_dir: &Path,
 | 
						|
    backend: options::BackendType,
 | 
						|
    admin_secret: &str,
 | 
						|
) -> Result<Vec<u64>, DBError> {
 | 
						|
    let admin = open_admin_storage(base_dir, backend, admin_secret)?;
 | 
						|
    let ids = admin.hkeys(k_admin_dbs())?;
 | 
						|
    let mut out = Vec::new();
 | 
						|
    for s in ids {
 | 
						|
        if let Ok(v) = s.parse() {
 | 
						|
            out.push(v);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    Ok(out)
 | 
						|
}
 | 
						|
 | 
						|
// Helper: parse permission value "Read:ts" or "ReadWrite:ts"
 | 
						|
fn parse_perm_value(v: &str) -> (Permissions, u64) {
 | 
						|
    let mut parts = v.split(':');
 | 
						|
    let p = parts.next().unwrap_or("Read");
 | 
						|
    let ts = parts
 | 
						|
        .next()
 | 
						|
        .and_then(|s| s.parse().ok())
 | 
						|
        .unwrap_or(0u64);
 | 
						|
    let perm = match p {
 | 
						|
        "ReadWrite" => Permissions::ReadWrite,
 | 
						|
        _ => Permissions::Read,
 | 
						|
    };
 | 
						|
    (perm, ts)
 | 
						|
}
 | 
						|
 | 
						|
fn now_secs() -> u64 {
 | 
						|
    use std::time::{SystemTime, UNIX_EPOCH};
 | 
						|
    SystemTime::now()
 | 
						|
        .duration_since(UNIX_EPOCH)
 | 
						|
        .unwrap_or_default()
 | 
						|
        .as_secs()
 | 
						|
} |