use std::{ffi::OsStr, os::unix::ffi::OsStrExt, path::Path};
use rusqlite::{ToSql, types::FromSql};
use super::{path::PathInsertResult, store::SqlStore};
use crate::{
FileLocator, FileMode, FileOrigin, Gid, RootId, Uid,
block::FileId as StoreFileId,
errors::InternalError,
file_metadata::{Device, FileKind, FileMetadata, RawMetadata},
path::NormalizedPath,
util::system_time_to_nanos,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileDiscriminant {
Regular,
Dir,
Symlink,
Block,
Char,
Pipe,
}
impl FileKind {
pub fn discriminant(&self) -> FileDiscriminant {
match self {
FileKind::Regular => FileDiscriminant::Regular,
FileKind::Dir => FileDiscriminant::Dir,
FileKind::Symlink { .. } => FileDiscriminant::Symlink,
FileKind::Block { .. } => FileDiscriminant::Block,
FileKind::Char { .. } => FileDiscriminant::Char,
FileKind::Pipe => FileDiscriminant::Pipe,
}
}
}
#[doc(hidden)]
impl FromSql for FileDiscriminant {
fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
match value.as_i64()? {
1 => Ok(FileDiscriminant::Regular),
2 => Ok(FileDiscriminant::Dir),
3 => Ok(FileDiscriminant::Symlink),
4 => Ok(FileDiscriminant::Block),
5 => Ok(FileDiscriminant::Char),
6 => Ok(FileDiscriminant::Pipe),
other => Err(rusqlite::types::FromSqlError::Other(Box::new(
InternalError::InvalidFileKind {
kind: other.to_string(),
},
))),
}
}
}
#[doc(hidden)]
impl ToSql for FileDiscriminant {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
let s = match self {
FileDiscriminant::Regular => 1,
FileDiscriminant::Dir => 2,
FileDiscriminant::Symlink => 3,
FileDiscriminant::Block => 4,
FileDiscriminant::Char => 5,
FileDiscriminant::Pipe => 6,
};
Ok(rusqlite::types::ToSqlOutput::from(s))
}
}
impl<'conn> SqlStore<'conn> {
#[cfg(all(feature = "fs", target_os = "linux"))]
pub fn create_temp_file(&self, metadata: FileMetadata) -> crate::Result<StoreFileId> {
let mut stmt = self.db.prepare_cached(
r#"
INSERT INTO
liteboxfs_files (kind, mode, atime, mtime, ctime, btime, uid, gid)
VALUES
(
?,
?,
?,
?,
?,
?,
?,
?
)
RETURNING
id;
"#,
)?;
let params = rusqlite::params![
FileDiscriminant::Regular,
metadata.mode().bits(),
system_time_to_nanos(metadata.accessed())?,
system_time_to_nanos(metadata.modified())?,
system_time_to_nanos(metadata.changed())?,
metadata.created().map(system_time_to_nanos).transpose()?,
metadata.user().as_raw(),
metadata.group().as_raw(),
];
Ok(stmt.query_one(params, |row| Ok(StoreFileId::from(row.get::<_, i64>(0)?)))?)
}
pub fn create_file(
&self,
root: RootId,
path: &NormalizedPath,
kind: &FileKind,
metadata: FileMetadata,
) -> crate::Result<StoreFileId> {
let file_id = {
let mut stmt = self.db.prepare_cached(
r#"
INSERT INTO
liteboxfs_files (kind, mode, atime, mtime, ctime, btime, uid, gid, major, minor, target)
VALUES
(
?,
?,
?,
?,
?,
?,
?,
?,
?,
?,
?
)
RETURNING
id;
"#,
)?;
let params = rusqlite::params![
kind.discriminant(),
metadata.mode().bits(),
system_time_to_nanos(metadata.accessed())?,
system_time_to_nanos(metadata.modified())?,
system_time_to_nanos(metadata.changed())?,
metadata.created().map(system_time_to_nanos).transpose()?,
metadata.user().as_raw(),
metadata.group().as_raw(),
match kind {
FileKind::Block { dev } | FileKind::Char { dev } => Some(dev.major()),
_ => None,
},
match kind {
FileKind::Block { dev } | FileKind::Char { dev } => Some(dev.minor()),
_ => None,
},
match kind {
FileKind::Symlink { target } => Some(target.as_os_str().as_encoded_bytes()),
_ => None,
},
];
stmt.query_one(params, |row| Ok(StoreFileId::from(row.get::<_, i64>(0)?)))?
};
if self.insert_path(root, path, file_id)? == PathInsertResult::AlreadyExists {
return Err(crate::Error::FileAlreadyExists {
path: FileOrigin::Litebox {
root,
locator: path.to_path_buf(),
},
});
}
Ok(file_id)
}
pub fn get_file(
&self,
locator: FileLocator,
root_id: RootId,
) -> crate::Result<(StoreFileId, FileKind, RawMetadata)> {
let map_row = |row: &rusqlite::Row| {
let file_id = StoreFileId::from(row.get::<_, i64>(0)?);
let discriminant: FileDiscriminant = row.get(1)?;
let metadata = RawMetadata {
discriminant,
mode: FileMode::from_bits_truncate(row.get(2)?),
atime: row.get(3)?,
mtime: row.get(4)?,
ctime: row.get(5)?,
btime: row.get(6)?,
uid: Uid::from_raw(row.get(7)?),
gid: Gid::from_raw(row.get(8)?),
};
match discriminant {
FileDiscriminant::Regular => Ok((file_id, FileKind::Regular, metadata)),
FileDiscriminant::Dir => Ok((file_id, FileKind::Dir, metadata)),
FileDiscriminant::Pipe => Ok((file_id, FileKind::Pipe, metadata)),
FileDiscriminant::Symlink => {
let target: Vec<u8> = row.get::<_, Vec<u8>>(11)?;
Ok((
file_id,
FileKind::Symlink {
target: Path::new(OsStr::from_bytes(&target)).to_path_buf(),
},
metadata,
))
}
FileDiscriminant::Block | FileDiscriminant::Char => {
let major: u64 = row.get(9)?;
let minor: u64 = row.get(10)?;
let device = Device::new(major, minor);
Ok((
file_id,
match discriminant {
FileDiscriminant::Block => FileKind::Block { dev: device },
FileDiscriminant::Char => FileKind::Char { dev: device },
_ => unreachable!(),
},
metadata,
))
}
}
};
let query_result = match &locator {
FileLocator::Path(path) => {
let normalized = NormalizedPath::new(path.as_ref());
let root_row_id: i64 = self.db.query_row(
r#"
SELECT
id
FROM
liteboxfs_roots
WHERE
uuid = ?;
"#,
rusqlite::params![root_id.to_string()],
|row| row.get(0),
)?;
let path_row_id = self.resolve_path_id(root_row_id, root_id, &normalized)?;
self.db.query_one(
r#"
SELECT
liteboxfs_files.id,
liteboxfs_files.kind,
liteboxfs_files.mode,
liteboxfs_files.atime,
liteboxfs_files.mtime,
liteboxfs_files.ctime,
liteboxfs_files.btime,
liteboxfs_files.uid,
liteboxfs_files.gid,
liteboxfs_files.major,
liteboxfs_files.minor,
liteboxfs_files.target
FROM
liteboxfs_files
JOIN
liteboxfs_paths ON liteboxfs_files.id = liteboxfs_paths.file
WHERE
liteboxfs_paths.id = ?;
"#,
rusqlite::params![path_row_id],
map_row,
)
}
FileLocator::Id(id) => self.db.query_one(
r#"
SELECT
liteboxfs_files.id,
liteboxfs_files.kind,
liteboxfs_files.mode,
liteboxfs_files.atime,
liteboxfs_files.mtime,
liteboxfs_files.ctime,
liteboxfs_files.btime,
liteboxfs_files.uid,
liteboxfs_files.gid,
liteboxfs_files.major,
liteboxfs_files.minor,
liteboxfs_files.target
FROM
liteboxfs_files
WHERE
liteboxfs_files.id = ?;
"#,
rusqlite::params![*id],
map_row,
),
};
match query_result {
Err(rusqlite::Error::QueryReturnedNoRows) => Err(crate::Error::FileNotFound {
file: FileOrigin::Litebox {
root: root_id,
locator: locator.into_normalized(),
},
}),
Err(err) => Err(err.into()),
Ok(tuple) => Ok(tuple),
}
}
#[cfg(all(feature = "fs", target_family = "unix"))]
pub fn get_file_discriminant(
&self,
path: &NormalizedPath,
root_id: RootId,
) -> crate::Result<FileDiscriminant> {
let root_row_id: i64 = self.db.query_row(
r#"
SELECT
id
FROM
liteboxfs_roots
WHERE
uuid = ?;
"#,
rusqlite::params![root_id.to_string()],
|row| row.get(0),
)?;
let path_row_id = self.resolve_path_id(root_row_id, root_id, path)?;
Ok(self.db.query_row(
r#"
SELECT
liteboxfs_files.kind
FROM
liteboxfs_files
JOIN
liteboxfs_paths ON liteboxfs_files.id = liteboxfs_paths.file
WHERE
liteboxfs_paths.id = ?;
"#,
rusqlite::params![path_row_id],
|row| row.get::<_, FileDiscriminant>(0),
)?)
}
pub fn get_file_count(&self, root_id: RootId) -> crate::Result<u64> {
let mut stmt = self.db.prepare_cached(
r#"
SELECT
COUNT(DISTINCT liteboxfs_paths.file)
FROM
liteboxfs_paths
JOIN
liteboxfs_roots ON liteboxfs_paths.root = liteboxfs_roots.id
WHERE
liteboxfs_roots.uuid = ?;
"#,
)?;
Ok(stmt.query_one(rusqlite::params![root_id], |row| row.get(0))?)
}
}