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
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
use crate::prelude::*;
use crate::return_errno_with_message;
use crate::ext4_defs::*;
impl Ext4 {
/// Find a directory entry in a directory
///
/// Params:
/// parent_inode: u32 - inode number of the parent directory
/// name: &str - name of the entry to find
/// result: &mut Ext4DirSearchResult - result of the search
///
/// Returns:
/// `Result<usize>` - status of the search
pub fn dir_find_entry(
&self,
parent_inode: u32,
name: &str,
result: &mut Ext4DirSearchResult,
) -> Result<usize> {
// load parent inode
let parent = self.get_inode_ref(parent_inode);
assert!(parent.inode.is_dir());
// start from the first logical block
let mut iblock = 0;
// physical block id
let mut fblock: Ext4Fsblk = 0;
// calculate total blocks
let inode_size: u64 = parent.inode.size();
let total_blocks: u64 = inode_size / BLOCK_SIZE as u64;
// iterate all blocks
while iblock < total_blocks {
let search_path = self.find_extent(&parent, iblock as u32);
if let Ok(path) = search_path {
// get the last path
let path = path.path.last().unwrap();
// get physical block id
fblock = path.pblock;
// load physical block
let mut ext4block =
Block::load(&self.block_device, fblock as usize * BLOCK_SIZE);
// find entry in block
let r = self.dir_find_in_block(&ext4block, name, result);
if r.is_ok() {
result.pblock_id = fblock as usize;
return Ok(EOK);
}
} else {
return_errno_with_message!(Errno::ENOENT, "dir search fail")
}
// go to next block
iblock += 1
}
return_errno_with_message!(Errno::ENOENT, "dir search fail");
}
/// Find a directory entry in a block
///
/// Params:
/// block: &mut Block - block to search in
/// name: &str - name of the entry to find
///
/// Returns:
/// result: Ext4DirEntry - result of the search
pub fn dir_find_in_block(
&self,
block: &Block,
name: &str,
result: &mut Ext4DirSearchResult,
) -> Result<Ext4DirEntry> {
let mut offset = 0;
let mut prev_de_offset = 0;
// start from the first entry
while offset < BLOCK_SIZE - core::mem::size_of::<Ext4DirEntryTail>() {
let de: Ext4DirEntry = block.read_offset_as(offset);
if !de.unused() && de.compare_name(name) {
result.dentry = de;
result.offset = offset;
result.prev_offset = prev_de_offset;
return Ok(de);
}
prev_de_offset = offset;
// go to next entry
offset += de.entry_len() as usize;
}
return_errno_with_message!(Errno::ENOENT, "dir find in block failed");
}
/// Get dir entries of a inode
///
/// Params:
/// inode: u32 - inode number of the directory
///
/// Returns:
/// `Vec<Ext4DirEntry>` - list of directory entries
pub fn dir_get_entries(&self, inode: u32) -> Vec<Ext4DirEntry> {
let mut entries = Vec::new();
// load inode
let inode_ref = self.get_inode_ref(inode);
assert!(inode_ref.inode.is_dir());
// calculate total blocks
let inode_size = inode_ref.inode.size();
let total_blocks = inode_size / BLOCK_SIZE as u64;
// start from the first logical block
let mut iblock = 0;
// iterate all blocks
while iblock < total_blocks {
// get physical block id of a logical block id
let search_path = self.find_extent(&inode_ref, iblock as u32);
if let Ok(path) = search_path {
// get the last path
let path = path.path.last().unwrap();
// get physical block id
let fblock = path.pblock;
// load physical block
let ext4block =
Block::load(&self.block_device, fblock as usize * BLOCK_SIZE);
let mut offset = 0;
// iterate all entries in a block
while offset < BLOCK_SIZE - core::mem::size_of::<Ext4DirEntryTail>() {
let de: Ext4DirEntry = ext4block.read_offset_as(offset);
if !de.unused() {
entries.push(de);
}
offset += de.entry_len() as usize;
}
}
// go ot next block
iblock += 1;
}
entries
}
pub fn dir_set_csum(&self, dst_blk: &mut Block, ino_gen: u32) {
let parent_de: Ext4DirEntry = dst_blk.read_offset_as(0);
let tail_offset = BLOCK_SIZE - size_of::<Ext4DirEntryTail>();
let mut tail: Ext4DirEntryTail = *dst_blk.read_offset_as_mut(tail_offset);
tail.tail_set_csum(&self.super_block, &parent_de, &dst_blk.data[..], ino_gen);
tail.copy_to_slice(&mut dst_blk.data);
}
/// Add a new entry to a directory
///
/// Params:
/// parent: &mut Ext4InodeRef - parent directory inode reference
/// child: &mut Ext4InodeRef - child inode reference
/// path: &str - path of the new entry
///
/// Returns:
/// `Result<usize>` - status of the operation
pub fn dir_add_entry(
&self,
parent: &mut Ext4InodeRef,
child: &Ext4InodeRef,
name: &str,
) -> Result<usize> {
// calculate total blocks
let inode_size: u64 = parent.inode.size();
let block_size = self.super_block.block_size();
let total_blocks: u64 = inode_size / block_size as u64;
// iterate all blocks
let mut iblock = 0;
while iblock < total_blocks {
// get physical block id of a logical block id
let pblock = self.get_pblock_idx(parent, iblock as u32)?;
// load physical block
let mut ext4block =
Block::load(&self.block_device, pblock as usize * BLOCK_SIZE);
let result = self.try_insert_to_existing_block(&mut ext4block, name, child.inode_num);
if result.is_ok() {
// set checksum
self.dir_set_csum(&mut ext4block, parent.inode.generation());
ext4block.sync_blk_to_disk(&self.block_device);
return Ok(EOK);
}
// go ot next block
iblock += 1;
}
// no space in existing blocks, need to add new block
let new_block = self.append_inode_pblk(parent)?;
// load new block
let mut new_ext4block =
Block::load(&self.block_device, new_block as usize * BLOCK_SIZE);
// write new entry to the new block
// must succeed, as we just allocated the block
let de_type = DirEntryType::EXT4_DE_DIR;
self.insert_to_new_block(&mut new_ext4block, child.inode_num, name, de_type);
// set checksum
self.dir_set_csum(&mut new_ext4block, parent.inode.generation());
new_ext4block.sync_blk_to_disk(&self.block_device);
Ok(EOK)
}
/// Try to insert a new entry to an existing block
///
/// Params:
/// block: &mut Block - block to insert the new entry
/// name: &str - name of the new entry
/// inode: u32 - inode number of the new entry
///
/// Returns:
/// `Result<usize>` - status of the operation
pub fn try_insert_to_existing_block(
&self,
block: &mut Block,
name: &str,
child_inode: u32,
) -> Result<usize> {
// required length aligned to 4 bytes
let required_len = {
let mut len = size_of::<Ext4DirEntry>() + name.len();
if len % 4 != 0 {
len += 4 - (len % 4);
}
len
};
let mut offset = 0;
// Start from the first entry
while offset < BLOCK_SIZE - size_of::<Ext4DirEntryTail>() {
let mut de = Ext4DirEntry::try_from(&block.data[offset..]).unwrap();
if de.unused() {
continue;
}
let inode = de.inode;
let rec_len = de.entry_len;
let used_len = de.name_len as usize;
let mut sz = core::mem::size_of::<Ext4FakeDirEntry>() + used_len;
if used_len % 4 != 0 {
sz += 4 - used_len % 4;
}
let free_space = rec_len as usize - sz;
// If there is enough free space
if free_space >= required_len {
// Create new directory entry
let mut new_entry = Ext4DirEntry::default();
// Update existing entry length and copy both entries back to block data
de.entry_len = sz as u16;
let de_type = DirEntryType::EXT4_DE_DIR;
new_entry.write_entry(free_space as u16, child_inode, name, de_type);
// update parent_de and new_de to blk_data
de.copy_to_slice(&mut block.data, offset);
new_entry.copy_to_slice(&mut block.data, offset + sz);
// Sync to disk
block.sync_blk_to_disk(&self.block_device);
return Ok(EOK);
}
// Move to the next entry
offset += de.entry_len() as usize;
}
return_errno_with_message!(Errno::ENOSPC, "No space in block for new entry");
}
/// Insert a new entry to a new block
///
/// Params:
/// block: &mut Block - block to insert the new entry
/// name: &str - name of the new entry
/// inode: u32 - inode number of the new entry
pub fn insert_to_new_block(
&self,
block: &mut Block,
inode: u32,
name: &str,
de_type: DirEntryType,
) {
// write new entry
let mut new_entry = Ext4DirEntry::default();
let el = BLOCK_SIZE - size_of::<Ext4DirEntryTail>();
new_entry.write_entry(el as u16, inode, name, de_type);
new_entry.copy_to_slice(&mut block.data, 0);
copy_dir_entry_to_array(&new_entry, &mut block.data, 0);
// init tail for new block
let tail = Ext4DirEntryTail::new();
tail.copy_to_slice(&mut block.data);
}
pub fn dir_remove_entry(&self, parent: &mut Ext4InodeRef, path: &str) -> Result<usize> {
// get remove_entry pos in parent and its prev entry
let mut result = Ext4DirSearchResult::new(Ext4DirEntry::default());
let r = self.dir_find_entry(parent.inode_num, path, &mut result)?;
let mut ext4block = Block::load(&self.block_device, result.pblock_id * BLOCK_SIZE);
// Invalidate entry first
let de_del: &mut Ext4DirEntry = ext4block.read_offset_as_mut(result.offset);
de_del.inode = 0;
// Store entry position in block
let pos = result.offset;
// If entry is not the first in block, it must be merged with previous entry
if pos != 0 {
let mut offset = 0;
// Start from the first entry in block
let mut tmp_de: Ext4DirEntry = ext4block.read_offset_as(offset);
let mut de_len = tmp_de.entry_len();
// Find direct predecessor of removed entry
while (offset + de_len as usize) < pos {
offset += de_len as usize;
tmp_de = ext4block.read_offset_as(offset);
de_len = tmp_de.entry_len();
}
assert!(de_len as usize + offset == pos, "Invalid predecessor calculation");
// Add removed entry length to predecessor's length
let del_len = result.dentry.entry_len();
let mut tmp_de_mut: &mut Ext4DirEntry = ext4block.read_offset_as_mut(offset);
tmp_de_mut.entry_len = de_len + del_len;
}
self.dir_set_csum(&mut ext4block, parent.inode.generation());
ext4block.sync_blk_to_disk(&self.block_device);
Ok(EOK)
}
pub fn dir_has_entry(&self, dir_inode: u32) -> bool {
// load parent inode
let parent = self.get_inode_ref(dir_inode);
assert!(parent.inode.is_dir());
// start from the first logical block
let mut iblock = 0;
// physical block id
let mut fblock: Ext4Fsblk = 0;
// calculate total blocks
let inode_size: u64 = parent.inode.size();
let total_blocks: u64 = inode_size / BLOCK_SIZE as u64;
// iterate all blocks
while iblock < total_blocks {
let search_path = self.find_extent(&parent, iblock as u32);
if let Ok(path) = search_path {
// get the last path
let path = path.path.last().unwrap();
// get physical block id
fblock = path.pblock;
// load physical block
let ext4block =
Block::load(&self.block_device, fblock as usize * BLOCK_SIZE);
// start from the first entry
let mut offset = 0;
while offset < BLOCK_SIZE - core::mem::size_of::<Ext4DirEntryTail>() {
let de: Ext4DirEntry = ext4block.read_offset_as(offset);
offset += de.entry_len as usize;
if de.inode == 0 {
continue;
}
// skip . and ..
if de.get_name() == "." || de.get_name() == ".." {
continue;
}
return true;
}
}
// go to next block
iblock += 1
}
false
}
pub fn dir_remove(&self, parent: u32, path: &str) -> Result<usize> {
let mut search_result = Ext4DirSearchResult::new(Ext4DirEntry::default());
let r = self.dir_find_entry(parent, path, &mut search_result)?;
let mut parent_inode_ref = self.get_inode_ref(parent);
let mut child_inode_ref = self.get_inode_ref(search_result.dentry.inode);
if self.dir_has_entry(child_inode_ref.inode_num){
return_errno_with_message!(Errno::ENOTSUP, "rm dir with children not supported")
}
self.truncate_inode(&mut child_inode_ref, 0)?;
self.unlink(&mut parent_inode_ref, &mut child_inode_ref, path)?;
self.write_back_inode(&mut parent_inode_ref);
// to do
// ext4_inode_set_del_time
// ext4_inode_set_links_cnt
// ext4_fs_free_inode(&child)
Ok(EOK)
}
}
pub fn copy_dir_entry_to_array(header: &Ext4DirEntry, array: &mut [u8], offset: usize) {
unsafe {
let de_ptr = header as *const Ext4DirEntry as *const u8;
let array_ptr = array as *mut [u8] as *mut u8;
let count = core::mem::size_of::<Ext4DirEntry>() / core::mem::size_of::<u8>();
core::ptr::copy_nonoverlapping(de_ptr, array_ptr.add(offset), count);
}
}