Skip to content

Commit 567dc35

Browse files
authored
Bugfix: set/query FILE_QUOTA_INFORMATION (#137)
- Add CLI `info --show-quota` - Remove test (samba requires too much setup for now :() misc: - Remove `unwrap_*` methods in query/set cast types - Add some doc - Add `Boolean::Into<bool>` - Add macro for generating `Resource` casts
1 parent 84f9da7 commit 567dc35

File tree

10 files changed

+205
-108
lines changed

10 files changed

+205
-108
lines changed

crates/smb-cli/src/info.rs

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use clap::{Parser, ValueEnum};
44
use futures_util::StreamExt;
55
use maybe_async::*;
66
use smb::{
7-
Client, FileAccessMask, FileBasicInformation, FileIdBothDirectoryInformation, UncPath,
8-
resource::*,
7+
Client, FileAccessMask, FileBasicInformation, FileIdBothDirectoryInformation, QueryQuotaInfo,
8+
UncPath, resource::*,
99
};
1010
use std::collections::VecDeque;
1111
use std::fmt::Display;
@@ -39,6 +39,11 @@ pub struct InfoCmd {
3939
#[arg(short, long)]
4040
#[clap(default_value_t = RecursiveMode::NonRecursive)]
4141
pub recursive: RecursiveMode,
42+
43+
/// Whether to display quota information on the directory being queried.
44+
#[arg(long)]
45+
#[clap(default_value_t = false)]
46+
pub show_quota: bool,
4247
}
4348

4449
#[maybe_async]
@@ -80,14 +85,17 @@ pub async fn info(cmd: &InfoCmd, cli: &Cli) -> Result<(), Box<dyn Error>> {
8085
}
8186
Resource::Directory(dir) => {
8287
let dir = Arc::new(dir);
88+
if cmd.show_quota {
89+
try_query_and_show_quota(&dir).await;
90+
}
8391
iterate_directory(
8492
&dir,
8593
&cmd.path,
8694
"*",
8795
&IterateParams {
88-
display_func: &display_item_info,
8996
client: &client,
9097
recursive: cmd.recursive,
98+
show_quota: cmd.show_quota,
9199
},
92100
)
93101
.await?;
@@ -104,8 +112,6 @@ pub async fn info(cmd: &InfoCmd, cli: &Cli) -> Result<(), Box<dyn Error>> {
104112
Ok(())
105113
}
106114

107-
type DisplayFunc = dyn Fn(&FileIdBothDirectoryInformation, &UncPath);
108-
109115
fn display_item_info(info: &FileIdBothDirectoryInformation, dir_path: &UncPath) {
110116
if info.file_name == "." || info.file_name == ".." {
111117
return; // Skip current and parent directory entries
@@ -122,26 +128,40 @@ fn display_item_info(info: &FileIdBothDirectoryInformation, dir_path: &UncPath)
122128
}
123129
}
124130

131+
fn display_quota_info(info: &Vec<smb::FileQuotaInformation>) {
132+
for quota in info {
133+
if quota.quota_limit == u64::MAX && quota.quota_threshold == u64::MAX {
134+
log::trace!("Skipping quota for SID {} with no limit", quota.sid);
135+
continue; // No quota set
136+
}
137+
log::info!(
138+
"Quota for SID {}: used {}, threshold {}, limit {}",
139+
quota.sid,
140+
get_size_string(quota.quota_used),
141+
get_size_string(quota.quota_threshold),
142+
get_size_string(quota.quota_limit)
143+
);
144+
}
145+
}
146+
125147
fn get_size_string(size_bytes: u64) -> String {
126148
const KB: u64 = 1024;
127149
const MB: u64 = 1024 * KB;
128150
const GB: u64 = 1024 * MB;
129151

130-
if size_bytes >= GB {
131-
format!("{:.2} GB", size_bytes as f64 / GB as f64)
132-
} else if size_bytes >= MB {
133-
format!("{:.2} MB", size_bytes as f64 / MB as f64)
134-
} else if size_bytes >= KB {
135-
format!("{:.2} kB", size_bytes as f64 / KB as f64)
136-
} else {
137-
format!("{} bytes", size_bytes)
152+
match size_bytes {
153+
x if x == u64::MAX => "∞".to_string(),
154+
x if x >= GB => format!("{:.2} GB", x as f64 / GB as f64),
155+
x if x >= MB => format!("{:.2} MB", x as f64 / MB as f64),
156+
x if x >= KB => format!("{:.2} kB", x as f64 / KB as f64),
157+
x => format!("{} B", x),
138158
}
139159
}
140160

141161
struct IterateParams<'a> {
142-
display_func: &'a DisplayFunc,
143162
client: &'a Client,
144163
recursive: RecursiveMode,
164+
show_quota: bool,
145165
}
146166
struct IteratedItem {
147167
dir: Arc<Directory>,
@@ -207,9 +227,9 @@ async fn handle_iteration_item(
207227
dir_path: &UncPath,
208228
params: &IterateParams<'_>,
209229
) -> Option<IteratedItem> {
210-
(params.display_func)(info, dir_path);
230+
display_item_info(info, dir_path);
211231

212-
if params.recursive < RecursiveMode::List {
232+
if params.recursive < RecursiveMode::List && !params.show_quota {
213233
return None;
214234
}
215235

@@ -231,18 +251,44 @@ async fn handle_iteration_item(
231251
return None;
232252
}
233253

234-
let dir: Result<Directory, _> = dir_result.unwrap().try_into();
235-
match dir {
236-
Ok(dir) => Some(IteratedItem {
237-
dir: Arc::new(dir),
238-
path: path_of_subdir,
239-
}),
240-
_ => {
254+
let dir = dir_result.unwrap();
255+
256+
let dir: Directory = match dir.try_into() {
257+
Ok(dir) => dir,
258+
Err(e) => {
241259
log::warn!(
242-
"Failed to convert resource to directory for {}",
260+
"Failed to convert resource to directory {}: {}",
243261
path_of_subdir,
262+
e.0
244263
);
245-
None
264+
return None;
246265
}
266+
};
267+
268+
// Quota information
269+
if params.show_quota {
270+
try_query_and_show_quota(&dir).await;
271+
}
272+
273+
// Recursion
274+
if params.recursive >= RecursiveMode::List {
275+
Some(IteratedItem {
276+
dir: Arc::new(dir),
277+
path: path_of_subdir,
278+
})
279+
} else {
280+
dir.close().await.ok()?;
281+
None
282+
}
283+
}
284+
285+
#[maybe_async]
286+
async fn try_query_and_show_quota(dir: &Directory) {
287+
match dir
288+
.query_quota_info(QueryQuotaInfo::new(false, true, vec![]))
289+
.await
290+
{
291+
Ok(qi) => display_quota_info(&qi),
292+
Err(e) => log::warn!("Failed to query quota info: {}", e),
247293
}
248294
}

crates/smb-dtyp/src/binrw_util/helpers.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ impl From<bool> for Boolean {
125125
}
126126
}
127127

128+
impl From<Boolean> for bool {
129+
fn from(val: Boolean) -> Self {
130+
val.0
131+
}
132+
}
133+
128134
/// A MultiSz (Multiple Null-terminated Wide Strings) type that reads and writes a sequence of
129135
/// null-terminated wide strings, ending with an additional null string.
130136
///

crates/smb-fscc/src/lib.rs

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@
1515
1616
#![allow(unused_parens)]
1717

18-
use binrw::{io::TakeSeekExt, meta::ReadEndian, prelude::*};
18+
use binrw::{meta::ReadEndian, prelude::*};
1919
use modular_bitfield::prelude::*;
2020
use smb_dtyp::access_mask;
2121

22-
use smb_dtyp::SID;
2322
use smb_dtyp::binrw_util::prelude::*;
2423
pub mod chained_item;
2524
pub mod common_info;
2625
pub mod directory_info;
2726
pub mod error;
2827
pub mod filesystem_info;
2928
pub mod query_file_info;
29+
pub mod quota;
3030
pub mod set_file_info;
3131

3232
pub use chained_item::{ChainedItem, ChainedItemList};
@@ -35,6 +35,7 @@ pub use directory_info::*;
3535
pub use error::SmbFsccError;
3636
pub use filesystem_info::*;
3737
pub use query_file_info::*;
38+
pub use quota::*;
3839
pub use set_file_info::*;
3940

4041
/// MS-FSCC 2.6
@@ -261,31 +262,3 @@ macro_rules! file_info_classes {
261262
}
262263
}
263264
}
264-
265-
#[binrw::binrw]
266-
#[derive(Debug)]
267-
pub struct FileQuotaInformationInner {
268-
#[bw(calc = PosMarker::default())]
269-
sid_length: PosMarker<u32>,
270-
pub change_time: FileTime,
271-
pub quota_used: u64,
272-
pub quota_threshold: u64,
273-
pub quota_limit: u64,
274-
#[br(map_stream = |s| s.take_seek(sid_length.value as u64))]
275-
#[bw(write_with = PosMarker::write_size, args(&sid_length))]
276-
pub sid: SID,
277-
}
278-
279-
pub type FileQuotaInformation = ChainedItem<FileQuotaInformationInner>;
280-
281-
#[binrw::binrw]
282-
#[derive(Debug)]
283-
pub struct FileGetQuotaInformationInner {
284-
#[bw(calc = PosMarker::default())]
285-
sid_length: PosMarker<u32>,
286-
#[br(map_stream = |s| s.take_seek(sid_length.value as u64))]
287-
#[bw(write_with = PosMarker::write_size, args(&sid_length))]
288-
pub sid: SID,
289-
}
290-
291-
pub type FileGetQuotaInformation = ChainedItem<FileGetQuotaInformationInner>;

crates/smb-fscc/src/quota.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use crate::ChainedItem;
2+
use binrw::{io::TakeSeekExt, prelude::*};
3+
use smb_dtyp::SID;
4+
use smb_dtyp::binrw_util::prelude::*;
5+
6+
/// This information class is used to query or to set file quota information for a volume.
7+
/// For queries, an optional buffer of FILE_GET_QUOTA_INFORMATION (section 2.4.41.1) data elements is provided by the client to specify the SIDs for which quota information is requested.
8+
/// If the FILE_GET_QUOTA_INFORMATION buffer is not specified, information for all quotas is returned.
9+
/// A buffer of FILE_QUOTA_INFORMATION data elements is returned by the server.
10+
/// For sets, FILE_QUOTA_INFORMATION data elements are populated and sent by the client,
11+
/// as specified in [MS-SMB] section 2.2.7.6.1 and [MS-SMB2] section 3.2.4.15.<145>
12+
///
13+
/// [MS-FSCC 2.4.41](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/acdc0738-ba3c-47a1-b11a-72e22d831c57>)
14+
///
15+
/// _Note_: This structure is partial: it does not contain the NextEntryOffset field, as it is intended to be used
16+
/// in a chained list, see [`ChainedItem<T>`] and [`ChainedItemList<T>`].
17+
#[binrw::binrw]
18+
#[derive(Debug)]
19+
pub struct FileQuotaInformation {
20+
#[bw(calc = PosMarker::default())]
21+
sid_length: PosMarker<u32>,
22+
pub change_time: FileTime,
23+
pub quota_used: u64,
24+
pub quota_threshold: u64,
25+
pub quota_limit: u64,
26+
#[br(map_stream = |s| s.take_seek(sid_length.value as u64))]
27+
#[bw(write_with = PosMarker::write_size, args(&sid_length))]
28+
pub sid: SID,
29+
}
30+
31+
/// See [`FileGetQuotaInformation`]; use [`FileGetQuotaInformationInner::into`] to convert.
32+
#[binrw::binrw]
33+
#[derive(Debug)]
34+
pub struct FileGetQuotaInformationInner {
35+
#[bw(calc = PosMarker::default())]
36+
sid_length: PosMarker<u32>,
37+
#[br(map_stream = |s| s.take_seek(sid_length.value as u64))]
38+
#[bw(write_with = PosMarker::write_size, args(&sid_length))]
39+
pub sid: SID,
40+
}
41+
42+
/// This structure is used to provide the list of SIDs for which quota query information is requested.
43+
///
44+
/// [MS-FSCC 2.4.41.1](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/56adae21-add4-4434-97ec-e40e87739d52>)
45+
pub type FileGetQuotaInformation = ChainedItem<FileGetQuotaInformationInner>;

crates/smb-msg/src/info/common.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ pub struct AdditionalInfo {
3737
/// Internal helper macro to easily generate fields & methods for [QueryInfoData](super::query::QueryInfoData).
3838
///
3939
/// Builds:
40-
/// 1. The enum with the specified name, with variants for each info type specified.
40+
/// 1. The enum with the specified name, with variants for each info type specified. This includes:
41+
/// - A method to get the info type of the enum variant - `info_type()`.
42+
/// - A method to get the name of the enum variant - `name()`.
43+
/// - implementations of `From<ContentType>` for each content type.
44+
/// - Methods to unwrap the content type, named `as_<variant_name_in_snake_case>()`.
4145
/// 2. A generic struct names `Raw<name>` to hold the raw data, with a method to convert it to the actual data.
4246
#[macro_export]
4347
macro_rules! query_info_data {
@@ -47,9 +51,9 @@ macro_rules! query_info_data {
4751
use binrw::prelude::*;
4852
#[allow(unused_imports)]
4953
use binrw::meta::WriteEndian;
50-
/// Represents information passed in get/set info requests.
51-
/// This is the information matching [InfoType], and should be used
52-
/// in the get info response and in the set info request.
54+
55+
#[doc = concat!("Enum to hold the different info types for ", stringify!($name),
56+
", that are used within SMB requests for querying or setting information.")]
5357
#[binrw::binrw]
5458
#[derive(Debug)]
5559
#[brw(little)]
@@ -62,14 +66,9 @@ macro_rules! query_info_data {
6266
}
6367

6468
impl $name {
65-
// unwrap_ methods to easily get the inner content.
69+
// as_ methods to easily get the inner content.
6670
$(
67-
pub fn [<unwrap_ $info_type:lower>](self) -> $content {
68-
match self {
69-
$name::$info_type(data) => data,
70-
_ => panic!("Expected $info_type, got {:?}", self),
71-
}
72-
}
71+
#[doc = concat!("Get the inner content as [`", stringify!($content), "`].")]
7372
pub fn [<as_ $info_type:lower>](self) -> Result<$content, $crate::SmbMsgError> {
7473
match self {
7574
$name::$info_type(data) => Ok(data),

0 commit comments

Comments
 (0)