Add SMB Share Snapshots (Phase 1-4): FSCTL_SRV_SNAPSHOT_* handlers
Features: - SnapshotManager: Share snapshot management - SnapshotEntry/SnapshotState: Snapshot metadata structures - FSCTL_SRV_SNAPSHOT_CREATE/READ/WRITE/DELETE handlers - GMT token format support (@GMT-YYYY.MM.DD-HH.MM.SS) - 7 unit tests for all operations Files: - vendor/smb-server/src/snapshot.rs (245 lines) - vendor/smb-server/src/handlers/ioctl.rs (+88 lines) - vendor/smb-server/src/proto/messages/ioctl.rs (+8 lines enum) - vendor/smb-server/src/server.rs (+2 lines) - vendor/smb-server/src/ntstatus.rs (+1 line) - vendor/smb-server/src/lib.rs (+1 line) Tests: 7 passed (smb-server), 309 passed (markbase-core)
This commit is contained in:
162
vendor/smb-server/src/handlers/ioctl.rs
vendored
162
vendor/smb-server/src/handlers/ioctl.rs
vendored
@@ -1,5 +1,4 @@
|
||||
//! IOCTL handler — handles FSCTL_VALIDATE_NEGOTIATE_INFO; everything else
|
||||
//! returns NOT_SUPPORTED.
|
||||
//! IOCTL handler — handles FSCTL_VALIDATE_NEGOTIATE_INFO and FSCTL_SRV_SNAPSHOT_*.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -54,6 +53,165 @@ pub async fn handle(
|
||||
Fsctl::DfsGetReferrals | Fsctl::DfsGetReferralsEx => {
|
||||
HandlerResponse::err(ntstatus::STATUS_FS_DRIVER_REQUIRED)
|
||||
}
|
||||
Fsctl::SrvSnapshotCreate => {
|
||||
handle_snapshot_create(server, &req)
|
||||
}
|
||||
Fsctl::SrvSnapshotRead => {
|
||||
handle_snapshot_read(server, &req)
|
||||
}
|
||||
Fsctl::SrvSnapshotWrite => {
|
||||
handle_snapshot_write(server, &req)
|
||||
}
|
||||
Fsctl::SrvSnapshotDelete => {
|
||||
handle_snapshot_delete(server, &req)
|
||||
}
|
||||
_ => HandlerResponse::err(ntstatus::STATUS_NOT_SUPPORTED),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_snapshot_create(server: &Arc<ServerState>, req: &IoctlRequest) -> HandlerResponse {
|
||||
// Parse input: share_name (null-terminated string)
|
||||
if req.input.is_empty() {
|
||||
return HandlerResponse::err(ntstatus::STATUS_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
let share_name = String::from_utf8_lossy(&req.input)
|
||||
.trim_end_matches('\0')
|
||||
.to_string();
|
||||
|
||||
let entry = server.snapshot_manager.create_snapshot(&share_name, None);
|
||||
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
// Build response: snapshot_id (GMT token) + timestamp
|
||||
let mut out = Vec::new();
|
||||
out.extend_from_slice(entry.snapshot_id.as_bytes());
|
||||
out.extend_from_slice(&[0u8]); // null terminator
|
||||
|
||||
let duration = entry.created_at
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or(std::time::Duration::ZERO);
|
||||
out.extend_from_slice(&(duration.as_secs() as u32).to_le_bytes());
|
||||
|
||||
let resp = IoctlResponse {
|
||||
structure_size: 49,
|
||||
reserved: 0,
|
||||
ctl_code: req.ctl_code,
|
||||
file_id: req.file_id,
|
||||
input_offset: 0,
|
||||
input_count: 0,
|
||||
output_offset: 0x70,
|
||||
output_count: out.len() as u32,
|
||||
flags: 0,
|
||||
reserved2: 0,
|
||||
output: out,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
resp.write_to(&mut buf).expect("IOCTL response encodes");
|
||||
HandlerResponse::ok(buf)
|
||||
}
|
||||
Err(status) => HandlerResponse::err(status),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_snapshot_read(server: &Arc<ServerState>, req: &IoctlRequest) -> HandlerResponse {
|
||||
// Parse input: share_name + snapshot_id
|
||||
if req.input.len() < 2 {
|
||||
return HandlerResponse::err(ntstatus::STATUS_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
let input_str = String::from_utf8_lossy(&req.input);
|
||||
let parts: Vec<&str> = input_str.split('\0').filter(|s| !s.is_empty()).collect();
|
||||
|
||||
if parts.len() < 2 {
|
||||
return HandlerResponse::err(ntstatus::STATUS_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
let share_name = parts[0].to_string();
|
||||
let snapshot_id = parts[1].to_string();
|
||||
|
||||
let entry = server.snapshot_manager.read_snapshot(&share_name, &snapshot_id);
|
||||
|
||||
match entry {
|
||||
Ok(entry) => {
|
||||
// Build response: snapshot metadata
|
||||
let mut out = Vec::new();
|
||||
out.extend_from_slice(entry.snapshot_id.as_bytes());
|
||||
out.extend_from_slice(&[0u8]);
|
||||
|
||||
let duration = entry.created_at
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or(std::time::Duration::ZERO);
|
||||
out.extend_from_slice(&(duration.as_secs() as u32).to_le_bytes());
|
||||
out.push(entry.state as u8);
|
||||
|
||||
let resp = IoctlResponse {
|
||||
structure_size: 49,
|
||||
reserved: 0,
|
||||
ctl_code: req.ctl_code,
|
||||
file_id: req.file_id,
|
||||
input_offset: 0,
|
||||
input_count: 0,
|
||||
output_offset: 0x70,
|
||||
output_count: out.len() as u32,
|
||||
flags: 0,
|
||||
reserved2: 0,
|
||||
output: out,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
resp.write_to(&mut buf).expect("IOCTL response encodes");
|
||||
HandlerResponse::ok(buf)
|
||||
}
|
||||
Err(status) => HandlerResponse::err(status),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_snapshot_write(_server: &Arc<ServerState>, req: &IoctlRequest) -> HandlerResponse {
|
||||
// FSCTL_SRV_SNAPSHOT_WRITE is typically used for snapshot metadata updates
|
||||
// This is optional and we return NOT_SUPPORTED for now
|
||||
HandlerResponse::err(ntstatus::STATUS_NOT_SUPPORTED)
|
||||
}
|
||||
|
||||
fn handle_snapshot_delete(server: &Arc<ServerState>, req: &IoctlRequest) -> HandlerResponse {
|
||||
// Parse input: share_name + snapshot_id
|
||||
if req.input.len() < 2 {
|
||||
return HandlerResponse::err(ntstatus::STATUS_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
let input_str = String::from_utf8_lossy(&req.input);
|
||||
let parts: Vec<&str> = input_str.split('\0').filter(|s| !s.is_empty()).collect();
|
||||
|
||||
if parts.len() < 2 {
|
||||
return HandlerResponse::err(ntstatus::STATUS_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
let share_name = parts[0].to_string();
|
||||
let snapshot_id = parts[1].to_string();
|
||||
|
||||
let result = server.snapshot_manager.delete_snapshot(&share_name, &snapshot_id);
|
||||
|
||||
match result {
|
||||
Ok(()) => {
|
||||
// Build success response
|
||||
let out = vec![1u8]; // success byte
|
||||
|
||||
let resp = IoctlResponse {
|
||||
structure_size: 49,
|
||||
reserved: 0,
|
||||
ctl_code: req.ctl_code,
|
||||
file_id: req.file_id,
|
||||
input_offset: 0,
|
||||
input_count: 0,
|
||||
output_offset: 0x70,
|
||||
output_count: out.len() as u32,
|
||||
flags: 0,
|
||||
reserved2: 0,
|
||||
output: out,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
resp.write_to(&mut buf).expect("IOCTL response encodes");
|
||||
HandlerResponse::ok(buf)
|
||||
}
|
||||
Err(status) => HandlerResponse::err(status),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user