1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
// InodeTable :: a bi-directional map of paths to inodes.
//
// Copyright (c) 2016-2022 by William R. Fraser
//
use std::borrow::Borrow;
use std::cmp::{Eq, PartialEq};
use std::collections::{HashMap, VecDeque};
use std::collections::hash_map::Entry::*;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use std::sync::Arc;
pub type Inode = u64;
pub type Generation = u64;
pub type LookupCount = u64;
#[derive(Debug)]
struct InodeTableEntry {
path: Option<Arc<PathBuf>>,
lookups: LookupCount,
generation: Generation,
}
/// A data structure for mapping paths to inodes and vice versa.
#[derive(Debug)]
pub struct InodeTable {
table: Vec<InodeTableEntry>,
free_list: VecDeque<usize>,
by_path: HashMap<Arc<PathBuf>, usize>,
}
impl InodeTable {
/// Create a new inode table.
///
/// inode table entries have a limited lifetime, controlled by a 'lookup count', which is
/// manipulated with the `lookup` and `forget` functions.
///
/// The table initially contains just the root directory ("/"), mapped to inode 1.
/// inode 1 is special: it cannot be forgotten.
pub fn new() -> InodeTable {
let mut inode_table = InodeTable {
table: Vec::new(),
free_list: VecDeque::new(),
by_path: HashMap::new()
};
let root = Arc::new(PathBuf::from("/"));
inode_table.table.push(InodeTableEntry {
path: Some(root.clone()),
lookups: 0, // not used for this entry; root is always present.
generation: 0,
});
inode_table.by_path.insert(root, 0);
inode_table
}
/// Add a path to the inode table.
///
/// Returns the inode number the path is now mapped to.
/// The returned inode number may be a re-used number formerly assigned to a now-forgotten
/// path.
///
/// The path is added with an initial lookup count of 1.
///
/// This operation runs in O(log n) time.
pub fn add(&mut self, path: Arc<PathBuf>) -> (Inode, Generation) {
let (inode, generation) = {
let (inode, entry) = Self::get_inode_entry(&mut self.free_list, &mut self.table);
entry.path = Some(path.clone());
entry.lookups = 1;
(inode, entry.generation)
};
debug!("explicitly adding {} -> {:?} with 1 lookups", inode, path);
let previous = self.by_path.insert(path, inode as usize - 1);
if let Some(previous) = previous {
error!("inode table buggered: {:?}", self);
panic!("attempted to insert duplicate path into inode table: {:?}", previous);
}
(inode, generation)
}
/// Add a path to the inode table if it does not yet exist.
///
/// Returns the inode number the path is now mapped to.
///
/// If the path was not in the table, it is added with an initial lookup count of 0.
///
/// This operation runs in O(log n) time.
pub fn add_or_get(&mut self, path: Arc<PathBuf>) -> (Inode, Generation) {
match self.by_path.entry(path.clone()) {
Vacant(path_entry) => {
let (inode, entry) = Self::get_inode_entry(&mut self.free_list, &mut self.table);
debug!("adding {} -> {:?} with 0 lookups", inode, path);
entry.path = Some(path);
path_entry.insert(inode as usize - 1);
(inode, entry.generation)
},
Occupied(path_entry) => {
let idx = path_entry.get();
((idx + 1) as Inode, self.table[*idx].generation)
}
}
}
/// Get the path that corresponds to an inode, if there is one, or None, if it is not in the
/// table.
/// Note that the file could be unlinked but still open, in which case it's not actually
/// reachable from the path returned.
///
/// This operation runs in O(1) time.
pub fn get_path(&self, inode: Inode) -> Option<Arc<PathBuf>> {
self.table[inode as usize - 1].path.clone()
}
/// Get the inode that corresponds to a path, if there is one, or None, if it is not in the
/// table.
///
/// This operation runs in O(log n) time.
pub fn get_inode(&mut self, path: &Path) -> Option<Inode> {
self.by_path
.get(Pathish::new(path))
.map(|idx| (idx + 1) as Inode)
}
/// Increment the lookup count on a given inode.
///
/// Calling this on an invalid inode will result in a panic.
///
/// This operation runs in O(1) time.
pub fn lookup(&mut self, inode: Inode) {
if inode == 1 {
return;
}
let entry = &mut self.table[inode as usize - 1];
entry.lookups += 1;
debug!("lookups on {} -> {:?} now {}", inode, entry.path, entry.lookups);
}
/// Decrement the lookup count on a given inode by the given number.
///
/// If the lookup count reaches 0, the path is removed from the table, and the inode number
/// is eligible to be re-used.
///
/// Returns the new lookup count of the inode. (If it returns 0, that means the inode was
/// deleted.)
///
/// Calling this on an invalid inode will result in a panic.
///
/// This operation runs in O(1) time normally, or O(log n) time if the inode is deleted.
pub fn forget(&mut self, inode: Inode, n: LookupCount) -> LookupCount {
if inode == 1 {
return 1;
}
let mut delete = false;
let lookups: LookupCount;
let idx = inode as usize - 1;
{
let entry = &mut self.table[idx];
debug!("forget entry {:?}", entry);
assert!(n <= entry.lookups);
entry.lookups -= n;
lookups = entry.lookups;
if lookups == 0 {
delete = true;
self.by_path.remove(entry.path.as_ref().unwrap());
}
}
if delete {
self.table[idx].path = None;
self.free_list.push_back(idx);
}
lookups
}
/// Change an inode's path to a different one, without changing the inode number.
/// Lookup counts remain unchanged, even if this is replacing another file.
pub fn rename(&mut self, oldpath: &Path, newpath: Arc<PathBuf>) {
let idx = self.by_path.remove(Pathish::new(oldpath)).unwrap();
self.table[idx].path = Some(newpath.clone());
self.by_path.insert(newpath, idx); // this can replace a path with a new inode
}
/// Remove the path->inode mapping for a given path, but keep the inode around.
pub fn unlink(&mut self, path: &Path) {
self.by_path.remove(Pathish::new(path));
// Note that the inode->path mapping remains.
}
/// Get a free indode table entry and its number, either by allocating a new one, or re-using
/// one that had its lookup count previously go to zero.
///
/// 1st arg should be `&mut self.free_list`; 2nd arg should be `&mut self.table`.
/// This function's signature is like this instead of taking &mut self so that it can avoid
/// mutably borrowing *all* fields of self when we only need those two.
fn get_inode_entry<'a>(free_list: &mut VecDeque<usize>, table: &'a mut Vec<InodeTableEntry>)
-> (Inode, &'a mut InodeTableEntry) {
let idx = match free_list.pop_front() {
Some(idx) => {
debug!("re-using inode {}", idx + 1);
table[idx].generation += 1;
idx
},
None => {
table.push(InodeTableEntry {
path: None,
lookups: 0,
generation: 0,
});
table.len() - 1
}
};
((idx + 1) as Inode, &mut table[idx])
}
}
/// Facilitates comparing Rc<PathBuf> and &Path
#[derive(Debug)]
struct Pathish {
inner: Path,
}
impl Pathish {
pub fn new(p: &Path) -> &Pathish {
unsafe { &*(p as *const _ as *const Pathish) }
}
}
impl Borrow<Pathish> for Arc<PathBuf> {
fn borrow(&self) -> &Pathish {
Pathish::new(self.as_path())
}
}
impl Hash for Pathish {
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.hash(state);
}
}
impl Eq for Pathish {
}
impl PartialEq for Pathish {
fn eq(&self, other: &Pathish) -> bool {
self.inner.eq(&other.inner)
}
}
#[test]
fn test_inode_reuse() {
let mut table = InodeTable::new();
let path1 = Arc::new(PathBuf::from("/foo/a"));
let path2 = Arc::new(PathBuf::from("/foo/b"));
// Add a path.
let inode1 = table.add(path1.clone()).0;
assert!(inode1 != 1);
assert_eq!(*path1, *table.get_path(inode1).unwrap());
// Add a second path; verify that the inode number is different.
let inode2 = table.add(path2.clone()).0;
assert!(inode2 != inode1);
assert!(inode2 != 1);
assert_eq!(*path2, *table.get_path(inode2).unwrap());
// Forget the first inode; verify that lookups on it fail.
assert_eq!(0, table.forget(inode1, 1));
assert!(table.get_path(inode1).is_none());
// Add a third path; verify that the inode is reused.
let (inode3, generation3) = table.add(Arc::new(PathBuf::from("/foo/c")));
assert_eq!(inode1, inode3);
assert_eq!(1, generation3);
// Check that lookups on the third path succeed.
assert_eq!(Path::new("/foo/c"), *table.get_path(inode3).unwrap());
}
#[test]
fn test_add_or_get() {
let mut table = InodeTable::new();
let path1 = Arc::new(PathBuf::from("/foo/a"));
let path2 = Arc::new(PathBuf::from("/foo/b"));
// add_or_get() a path and verify that get by inode works before lookup() is done.
let inode1 = table.add_or_get(path1.clone()).0;
assert_eq!(*path1, *table.get_path(inode1).unwrap());
table.lookup(inode1);
// add() a second path and verify that get by path and inode work.
let inode2 = table.add(path2.clone()).0;
assert_eq!(*path2, *table.get_path(inode2).unwrap());
assert_eq!(inode2, table.add_or_get(path2).0);
table.lookup(inode2);
// Check the ref counts by doing a single forget.
assert_eq!(0, table.forget(inode1, 1));
assert_eq!(1, table.forget(inode2, 1));
}
#[test]
fn test_inode_rename() {
let mut table = InodeTable::new();
let path1 = Arc::new(PathBuf::from("/foo/a"));
let path2 = Arc::new(PathBuf::from("/foo/b"));
// Add a path; verify that get by path and inode work.
let inode = table.add(path1.clone()).0;
assert_eq!(*path1, *table.get_path(inode).unwrap());
assert_eq!(inode, table.get_inode(&path1).unwrap());
// Rename the inode; verify that get by the new path works and old path doesn't, and get by
// inode still works.
table.rename(&path1, path2.clone());
assert!(table.get_inode(&path1).is_none());
assert_eq!(inode, table.get_inode(&path2).unwrap());
assert_eq!(*path2, *table.get_path(inode).unwrap());
}
#[test]
fn test_unlink() {
let mut table = InodeTable::new();
let path = Arc::new(PathBuf::from("/foo/bar"));
// Add a path.
let inode = table.add(path.clone()).0;
// Unlink it and verify that get by path fails.
table.unlink(&path);
assert!(table.get_inode(&path).is_none());
// Getting the path for the inode should still return the path.
assert_eq!(*path, *table.get_path(inode).unwrap());
// Verify that forgetting it once drops the refcount to zero and then lookups by inode fail.
assert_eq!(0, table.forget(inode, 1));
assert!(table.get_path(inode).is_none());
}