Skip to content

Commit 0c7c137

Browse files
liubogithubjiangliu
authored andcommitted
passthrough: reduce the memory footprint of file handle
File handles used by name_to_handle_at(2) are allocated with fixed max bytes, which results in unnecessary memory pressure. Per the manual of open_by_handle_at(2), "The caller can discover the required size for the file_handle structure by making a call in which handle->handle_bytes is zero; in this case, the call fails with the error EOVERFLOW and handle->handle_bytes is set to indicate the required size; the caller can then use this information to allocate a structure of the correct size (see EXAMPLES below)." This follows the description and allocates the needed bytes. With this, sizeof::<CFileHandle> drops from 136 bytes to 16 bytes. sizeof::<InodeData> drops from 328 bytes to 120 bytes. Also there is no need to make CFileHandle public. Signed-off-by: Liu Bo <bo.liu@linux.alibaba.com>
1 parent 4f3dd21 commit 0c7c137

File tree

2 files changed

+120
-39
lines changed

2 files changed

+120
-39
lines changed

src/passthrough/file_handle.rs

Lines changed: 108 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::fmt::{Debug, Formatter};
99
use std::fs::File;
1010
use std::io;
1111
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
12+
use std::ptr;
1213
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
1314

1415
use crate::passthrough::PassthroughFs;
@@ -17,29 +18,33 @@ use crate::passthrough::PassthroughFs;
1718
///
1819
/// According to Linux ABI, struct file_handle has a flexible array member 'f_handle', but it's
1920
/// hard-coded here for simplicity.
20-
pub const MAX_HANDLE_SZ: usize = 128;
21+
//pub const MAX_HANDLE_SZ: usize = 128;
2122

2223
#[derive(Clone, Copy)]
2324
#[repr(C)]
24-
pub(crate) struct CFileHandle {
25+
struct CFileHandle {
2526
// Size of f_handle [in, out]
26-
pub(crate) handle_bytes: libc::c_uint,
27+
handle_bytes: libc::c_uint,
2728
// Handle type [out]
28-
pub(crate) handle_type: libc::c_int,
29+
handle_type: libc::c_int,
2930
// File identifier (sized by caller) [out]
30-
pub(crate) f_handle: [libc::c_char; MAX_HANDLE_SZ],
31+
f_handle: *mut libc::c_char,
3132
}
3233

3334
impl CFileHandle {
3435
fn new() -> Self {
3536
CFileHandle {
36-
handle_bytes: MAX_HANDLE_SZ as libc::c_uint,
37+
handle_bytes: 0,
3738
handle_type: 0,
38-
f_handle: [0; MAX_HANDLE_SZ],
39+
f_handle: ptr::null_mut(),
3940
}
4041
}
4142
}
4243

44+
// Safe because f_handle is readonly once FileHandle is initialized.
45+
unsafe impl Send for CFileHandle {}
46+
unsafe impl Sync for CFileHandle {}
47+
4348
impl Ord for CFileHandle {
4449
fn cmp(&self, other: &Self) -> Ordering {
4550
if self.handle_bytes != other.handle_bytes {
@@ -49,12 +54,8 @@ impl Ord for CFileHandle {
4954
return self.handle_type.cmp(&other.handle_type);
5055
}
5156

52-
self.f_handle
53-
.iter()
54-
.zip(other.f_handle.iter())
55-
.map(|(x, y)| x.cmp(y))
56-
.find(|&ord| ord != std::cmp::Ordering::Equal)
57-
.unwrap_or(Ordering::Equal)
57+
// f_handle is left to be compared by FileHandle's buf.
58+
Ordering::Equal
5859
}
5960
}
6061

@@ -83,10 +84,12 @@ impl Debug for CFileHandle {
8384
}
8485

8586
/// Struct to maintain information for a file handle.
86-
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug)]
87+
#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)]
8788
pub struct FileHandle {
8889
pub(crate) mnt_id: u64,
89-
pub(crate) handle: CFileHandle,
90+
handle: CFileHandle,
91+
// internal buffer for handle.f_handle
92+
buf: Vec<libc::c_char>,
9093
}
9194

9295
extern "C" {
@@ -113,6 +116,38 @@ impl FileHandle {
113116
let mut mount_id: libc::c_int = 0;
114117
let mut c_fh = CFileHandle::new();
115118

119+
// Per name_to_handle_at(2), the caller can discover the required size
120+
// for the file_handle structure by making a call in which
121+
// handle->handle_bytes is zero. In this case, the call fails with the
122+
// error EOVERFLOW and handle->handle_bytes is set to indicate the
123+
// required size; the caller can then use this information to allocate a
124+
// structure of the correct size.
125+
let ret = unsafe {
126+
name_to_handle_at(
127+
dir_fd,
128+
path.as_ptr(),
129+
&mut c_fh,
130+
&mut mount_id,
131+
libc::AT_EMPTY_PATH,
132+
)
133+
};
134+
if ret == -1 {
135+
let e = io::Error::last_os_error();
136+
// unwrap is safe as e is obtained from last_os_error().
137+
if e.raw_os_error().unwrap() != libc::EOVERFLOW {
138+
return Err(e);
139+
}
140+
} else {
141+
return Err(io::Error::from(io::ErrorKind::InvalidData));
142+
}
143+
144+
let needed = c_fh.handle_bytes as usize;
145+
let mut buf = vec![0; needed];
146+
147+
// get a unsafe pointer, FileHandle takes care of freeing the memory
148+
// that 'f_handle' points to.
149+
c_fh.f_handle = buf.as_mut_ptr();
150+
116151
let ret = unsafe {
117152
name_to_handle_at(
118153
dir_fd,
@@ -126,6 +161,7 @@ impl FileHandle {
126161
Ok(FileHandle {
127162
mnt_id: mount_id as u64,
128163
handle: c_fh,
164+
buf,
129165
})
130166
} else {
131167
let e = io::Error::last_os_error();
@@ -296,43 +332,80 @@ mod tests {
296332

297333
#[test]
298334
fn test_file_handle_derives() {
299-
let mut h1 = CFileHandle {
335+
let mut buf1 = vec![0; 128];
336+
let h1 = CFileHandle {
300337
handle_bytes: 128,
301338
handle_type: 3,
302-
f_handle: [0; MAX_HANDLE_SZ],
339+
f_handle: buf1.as_mut_ptr(),
303340
};
341+
let mut fh1 = FileHandle {
342+
mnt_id: 0,
343+
handle: h1,
344+
buf: buf1,
345+
};
346+
347+
let mut buf2 = vec![0; 129];
304348
let h2 = CFileHandle {
305349
handle_bytes: 129,
306350
handle_type: 3,
307-
f_handle: [0; MAX_HANDLE_SZ],
351+
f_handle: buf2.as_mut_ptr(),
308352
};
353+
let fh2 = FileHandle {
354+
mnt_id: 0,
355+
handle: h2,
356+
buf: buf2,
357+
};
358+
359+
let mut buf3 = vec![0; 128];
309360
let h3 = CFileHandle {
310361
handle_bytes: 128,
311362
handle_type: 4,
312-
f_handle: [0; MAX_HANDLE_SZ],
363+
f_handle: buf3.as_mut_ptr(),
364+
};
365+
let fh3 = FileHandle {
366+
mnt_id: 0,
367+
handle: h3,
368+
buf: buf3,
313369
};
370+
371+
let mut buf4 = vec![1; 128];
314372
let h4 = CFileHandle {
315373
handle_bytes: 128,
316-
handle_type: 4,
317-
f_handle: [1; MAX_HANDLE_SZ],
374+
handle_type: 3,
375+
f_handle: buf4.as_mut_ptr(),
318376
};
319-
let mut h5 = CFileHandle {
377+
let fh4 = FileHandle {
378+
mnt_id: 0,
379+
handle: h4,
380+
buf: buf4,
381+
};
382+
383+
let mut buf5 = vec![0; 128];
384+
let h5 = CFileHandle {
320385
handle_bytes: 128,
321386
handle_type: 3,
322-
f_handle: [0; MAX_HANDLE_SZ],
387+
f_handle: buf5.as_mut_ptr(),
388+
};
389+
let mut fh5 = FileHandle {
390+
mnt_id: 0,
391+
handle: h5,
392+
buf: buf5,
323393
};
324394

325-
assert!(h1 < h2);
326-
assert!(h1 != h2);
327-
assert!(h1 < h3);
328-
assert!(h1 != h3);
329-
assert!(h1 < h4);
330-
assert!(h1 != h4);
331-
332-
assert!(h1 == h5);
333-
h1.f_handle[0] = 1;
334-
assert!(h1 > h5);
335-
h5.f_handle[0] = 1;
336-
assert!(h1 == h5);
395+
assert!(fh1 < fh2);
396+
assert!(fh1 != fh2);
397+
assert!(fh1 < fh3);
398+
assert!(fh1 != fh3);
399+
assert!(fh1 < fh4);
400+
assert!(fh1 != fh4);
401+
402+
assert!(fh1 == fh5);
403+
fh1.buf[0] = 1;
404+
fh1.handle.f_handle = fh1.buf.as_mut_ptr();
405+
assert!(fh1 > fh5);
406+
407+
fh5.buf[0] = 1;
408+
fh5.handle.f_handle = fh5.buf.as_mut_ptr();
409+
assert!(fh1 == fh5);
337410
}
338411
}

src/passthrough/mod.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ impl InodeStat {
6767
}
6868
}
6969

70-
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug)]
70+
#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)]
7171
enum InodeAltKey {
7272
Ids {
7373
ino: libc::ino64_t,
@@ -622,7 +622,7 @@ impl<S: BitmapSlice + Send + Sync> PassthroughFs<S> {
622622
fuse::ROOT_ID,
623623
file_or_handle,
624624
2,
625-
ids_altkey,
625+
ids_altkey.clone(),
626626
st.get_stat().st_mode,
627627
),
628628
ids_altkey,
@@ -834,7 +834,9 @@ impl<S: BitmapSlice + Send + Sync> PassthroughFs<S> {
834834
// Note that this will always be `None` if `cfg.inode_file_handles` is false, but we only
835835
// really need this alt key when we do not have an `O_PATH` fd open for every inode. So if
836836
// `cfg.inode_file_handles` is false, we do not need this key anyway.
837-
let handle_altkey = file_or_handle.handle().map(|h| InodeAltKey::Handle(*h));
837+
let handle_altkey = file_or_handle
838+
.handle()
839+
.map(|h| InodeAltKey::Handle((*h).clone()));
838840

839841
Ok((file_or_handle, inode_stat, ids_altkey, handle_altkey))
840842
}
@@ -937,7 +939,13 @@ impl<S: BitmapSlice + Send + Sync> PassthroughFs<S> {
937939
InodeMap::insert_locked(
938940
inodes.deref_mut(),
939941
inode,
940-
InodeData::new(inode, file_or_handle, 1, ids_altkey, st.get_stat().st_mode),
942+
InodeData::new(
943+
inode,
944+
file_or_handle,
945+
1,
946+
ids_altkey.clone(),
947+
st.get_stat().st_mode,
948+
),
941949
ids_altkey,
942950
handle_altkey,
943951
);

0 commit comments

Comments
 (0)