ext4_rs 1.3.3

Cross-platform rust ext4.
Documentation
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
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
use crate::prelude::*;
use crate::utils::*;

use super::*;

#[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Ext4Inode {
    pub mode: u16,        // File type and permissions
    pub uid: u16,         // Owner user ID
    pub size: u32,        // Lower 32 bits of file size
    pub atime: u32,       // Last access time
    pub ctime: u32,       // Creation time
    pub mtime: u32,       // Last modification time
    pub dtime: u32,       // Deletion time
    pub gid: u16,         // Owner group ID
    pub links_count: u16, // Link count
    pub blocks: u32,      // Allocated blocks count
    pub flags: u32,       // File flags
    pub osd1: u32,        // OS-dependent field 1
    pub block: [u32; 15], // Data block pointers
    pub generation: u32,  // File version (NFS)
    pub file_acl: u32,    // File ACL
    pub size_hi: u32,     // Higher 32 bits of file size
    pub faddr: u32,       // Deprecated fragment address
    pub osd2: Linux2,     // OS-dependent field 2

    pub i_extra_isize: u16,  // Extra inode size
    pub i_checksum_hi: u16,  // High checksum (crc32c(uuid+inum+inode) BE)
    pub i_ctime_extra: u32,  // Extra creation time (nanosec << 2 | epoch)
    pub i_mtime_extra: u32,  // Extra modification time (nanosec << 2 | epoch)
    pub i_atime_extra: u32,  // Extra access time (nanosec << 2 | epoch)
    pub i_crtime: u32,       // Creation time
    pub i_crtime_extra: u32, // Extra creation time (nanosec << 2 | epoch)
    pub i_version_hi: u32,   // Higher 32 bits of version
}

#[repr(C)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Linux2 {
    pub l_i_blocks_high: u16,   // Higher 16 bits of allocated blocks count
    pub l_i_file_acl_high: u16, // Higher 16 bits of file ACL
    pub l_i_uid_high: u16,      // Higher 16 bits of user ID
    pub l_i_gid_high: u16,      // Higher 16 bits of group ID
    pub l_i_checksum_lo: u16,   // Lower checksum
    pub l_i_reserved: u16,      // Reserved field
}

bitflags! {
    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
    pub struct InodeFileType: u16 {
        const S_IFIFO = 0x1000;
        const S_IFCHR = 0x2000;
        const S_IFDIR = 0x4000;
        const S_IFBLK = 0x6000;
        const S_IFREG = 0x8000;
        const S_IFSOCK = 0xC000;
        const S_IFLNK = 0xA000;
    }
}

bitflags! {
    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
    pub struct InodePerm: u16 {
        const S_IREAD = 0x0100;
        const S_IWRITE = 0x0080;
        const S_IEXEC = 0x0040;
        const S_ISUID = 0x0800;
        const S_ISGID = 0x0400;
    }
}

impl Ext4Inode {
    pub fn mode(&self) -> u16 {
        self.mode
    }

    pub fn set_mode(&mut self, mode: u16) {
        self.mode = mode;
    }

    pub fn uid(&self) -> u16 {
        self.uid
    }

    pub fn set_uid(&mut self, uid: u16) {
        self.uid = uid;
    }

    pub fn size(&self) -> u64 {
        self.size as u64 | ((self.size_hi as u64) << 32)
    }

    pub fn set_size(&mut self, size: u64) {
        self.size = (size & 0xffffffff) as u32;
        self.size_hi = (size >> 32) as u32;
    }

    pub fn atime(&self) -> u32 {
        self.atime
    }

    pub fn set_atime(&mut self, atime: u32) {
        self.atime = atime;
    }

    pub fn ctime(&self) -> u32 {
        self.ctime
    }

    pub fn set_ctime(&mut self, ctime: u32) {
        self.ctime = ctime;
    }

    pub fn mtime(&self) -> u32 {
        self.mtime
    }

    pub fn set_mtime(&mut self, mtime: u32) {
        self.mtime = mtime;
    }

    pub fn dtime(&self) -> u32 {
        self.dtime
    }

    pub fn set_dtime(&mut self, dtime: u32) {
        self.dtime = dtime;
    }

    pub fn gid(&self) -> u16 {
        self.gid
    }

    pub fn set_gid(&mut self, gid: u16) {
        self.gid = gid;
    }

    pub fn links_count(&self) -> u16 {
        self.links_count
    }

    pub fn set_links_count(&mut self, links_count: u16) {
        self.links_count = links_count;
    }

    pub fn blocks_count(&self) -> u64 {
        let mut blocks = self.blocks as u64;
        if self.osd2.l_i_blocks_high != 0 {
            blocks |= (self.osd2.l_i_blocks_high as u64) << 32;
        }
        blocks
    }

    pub fn set_blocks_count(&mut self, blocks: u64) {
        self.blocks = (blocks & 0xFFFFFFFF) as u32;
        self.osd2.l_i_blocks_high = (blocks >> 32) as u16;
    }

    pub fn flags(&self) -> u32 {
        self.flags
    }

    pub fn set_flags(&mut self, flags: u32) {
        self.flags = flags;
    }

    pub fn osd1(&self) -> u32 {
        self.osd1
    }

    pub fn set_osd1(&mut self, osd1: u32) {
        self.osd1 = osd1;
    }

    pub fn block(&self) -> [u32; 15] {
        self.block
    }

    pub fn set_block(&mut self, block: [u32; 15]) {
        self.block = block;
    }

    pub fn generation(&self) -> u32 {
        self.generation
    }

    pub fn set_generation(&mut self, generation: u32) {
        self.generation = generation;
    }

    pub fn file_acl(&self) -> u32 {
        self.file_acl
    }

    pub fn set_file_acl(&mut self, file_acl: u32) {
        self.file_acl = file_acl;
    }

    pub fn size_hi(&self) -> u32 {
        self.size_hi
    }

    pub fn set_size_hi(&mut self, size_hi: u32) {
        self.size_hi = size_hi;
    }

    pub fn faddr(&self) -> u32 {
        self.faddr
    }

    pub fn set_faddr(&mut self, faddr: u32) {
        self.faddr = faddr;
    }

    pub fn osd2(&self) -> Linux2 {
        self.osd2
    }

    pub fn set_osd2(&mut self, osd2: Linux2) {
        self.osd2 = osd2;
    }

    pub fn i_extra_isize(&self) -> u16 {
        self.i_extra_isize
    }

    pub fn set_i_extra_isize(&mut self, i_extra_isize: u16) {
        self.i_extra_isize = i_extra_isize;
    }

    pub fn i_checksum_hi(&self) -> u16 {
        self.i_checksum_hi
    }

    pub fn set_i_checksum_hi(&mut self, i_checksum_hi: u16) {
        self.i_checksum_hi = i_checksum_hi;
    }

    pub fn i_ctime_extra(&self) -> u32 {
        self.i_ctime_extra
    }

    pub fn set_i_ctime_extra(&mut self, i_ctime_extra: u32) {
        self.i_ctime_extra = i_ctime_extra;
    }

    pub fn i_mtime_extra(&self) -> u32 {
        self.i_mtime_extra
    }

    pub fn set_i_mtime_extra(&mut self, i_mtime_extra: u32) {
        self.i_mtime_extra = i_mtime_extra;
    }

    pub fn i_atime_extra(&self) -> u32 {
        self.i_atime_extra
    }

    pub fn set_i_atime_extra(&mut self, i_atime_extra: u32) {
        self.i_atime_extra = i_atime_extra;
    }

    pub fn i_crtime(&self) -> u32 {
        self.i_crtime
    }

    pub fn set_i_crtime(&mut self, i_crtime: u32) {
        self.i_crtime = i_crtime;
    }

    pub fn i_crtime_extra(&self) -> u32 {
        self.i_crtime_extra
    }

    pub fn set_i_crtime_extra(&mut self, i_crtime_extra: u32) {
        self.i_crtime_extra = i_crtime_extra;
    }

    pub fn i_version_hi(&self) -> u32 {
        self.i_version_hi
    }

    pub fn set_i_version_hi(&mut self, i_version_hi: u32) {
        self.i_version_hi = i_version_hi;
    }
}

impl Ext4Inode {
    pub fn file_type(&self) -> InodeFileType {
        InodeFileType::from_bits_truncate(self.mode & EXT4_INODE_MODE_TYPE_MASK)
    }

    pub fn file_perm(&self) -> InodePerm {
        InodePerm::from_bits_truncate(self.mode & EXT4_INODE_MODE_PERM_MASK)
    }

    pub fn is_dir(&self) -> bool {
        self.file_type() == InodeFileType::S_IFDIR
    }

    pub fn is_file(&self) -> bool {
        self.file_type() == InodeFileType::S_IFREG
    }

    pub fn is_link(&self) -> bool {
        self.file_type() == InodeFileType::S_IFLNK
    }

    pub fn can_read(&self) -> bool {
        self.file_perm().contains(InodePerm::S_IREAD)
    }

    pub fn can_write(&self) -> bool {
        self.file_perm().contains(InodePerm::S_IWRITE)
    }

    pub fn can_exec(&self) -> bool {
        self.file_perm().contains(InodePerm::S_IEXEC)
    }

    pub fn set_file_type(&mut self, kind: InodeFileType) {
        self.mode |= kind.bits();
    }

    pub fn set_file_perm(&mut self, perm: InodePerm) {
        self.mode |= perm.bits();
    }
}

/// Reference to an inode.
#[derive(Clone)]
pub struct Ext4InodeRef {
    pub inode_num: u32,
    pub inode: Ext4Inode,
}

impl Ext4Inode {
    /// Get the depth of the extent tree from an inode.
    pub fn root_header_depth(&self) -> u16 {
        self.root_extent_header().depth
    }

    pub fn root_extent_header_ref(&self) -> &Ext4ExtentHeader {
        let header_ptr = self.block.as_ptr() as *const Ext4ExtentHeader;
        unsafe { &*header_ptr }
    }

    pub fn root_extent_header(&self) -> Ext4ExtentHeader {
        let header_ptr = self.block.as_ptr() as *const Ext4ExtentHeader;
        unsafe { *header_ptr }
    }

    pub fn root_extent_header_mut(&mut self) -> &mut Ext4ExtentHeader {
        let header_ptr = self.block.as_mut_ptr() as *mut Ext4ExtentHeader;
        unsafe { &mut *header_ptr }
    }

    pub fn root_extent_mut_at(&mut self, pos: usize) -> &mut Ext4Extent {
        let header_ptr = self.block.as_mut_ptr() as *mut Ext4ExtentHeader;
        unsafe { &mut *(header_ptr.add(1) as *mut Ext4Extent).add(pos) }
    }

    pub fn root_extent_ref_at(&mut self, pos: usize) -> &Ext4Extent {
        let header_ptr = self.block.as_ptr() as *const Ext4ExtentHeader;
        unsafe { &*(header_ptr.add(1) as *const Ext4Extent).add(pos) }
    }

    pub fn root_extent_at(&mut self, pos: usize) -> Ext4Extent {
        let header_ptr = self.block.as_ptr() as *const Ext4ExtentHeader;
        unsafe { *(header_ptr.add(1) as *const Ext4Extent).add(pos) }
    }

    pub fn root_first_index_mut(&mut self) -> &mut Ext4ExtentIndex {
        let header_ptr = self.block.as_mut_ptr() as *mut Ext4ExtentHeader;
        unsafe { &mut *(header_ptr.add(1) as *mut Ext4ExtentIndex) }
    }

    pub fn extent_tree_init(&mut self) {
        let header_ptr = self.block.as_mut_ptr() as *mut Ext4ExtentHeader;
        unsafe {
            (*header_ptr).set_magic();
            (*header_ptr).set_entries_count(0);
            (*header_ptr).set_max_entries_count(4);
            (*header_ptr).set_depth(0);
            (*header_ptr).set_generation(0);
        }
    }

    fn get_checksum(&self, super_block: &Ext4Superblock) -> u32 {
        let inode_size = super_block.inode_size;
        let mut v: u32 = self.osd2.l_i_checksum_lo as u32;
        if inode_size > 128 {
            v |= (self.i_checksum_hi as u32) << 16;
        }
        v
    }
    #[allow(unused)]
    pub fn set_inode_checksum_value(
        &mut self,
        super_block: &Ext4Superblock,
        inode_id: u32,
        checksum: u32,
    ) {
        let inode_size = super_block.inode_size();

        self.osd2.l_i_checksum_lo = (checksum & 0xffff) as u16;
        if inode_size > 128 {
            self.i_checksum_hi = (checksum >> 16) as u16;
        }
    }
    fn copy_to_slice(&self, slice: &mut [u8]) {
        unsafe {
            let inode_ptr = self as *const Ext4Inode as *const u8;
            let array_ptr = slice.as_ptr() as *mut u8;
            core::ptr::copy_nonoverlapping(inode_ptr, array_ptr, 0x9c);
        }
    }
    #[allow(unused)]
    pub fn get_inode_checksum(&mut self, inode_id: u32, super_block: &Ext4Superblock) -> u32 {
        let inode_size = super_block.inode_size();

        let orig_checksum = self.get_checksum(super_block);
        let mut checksum = 0;

        let ino_index = inode_id;
        let ino_gen = self.generation;

        // Preparation: temporarily set bg checksum to 0
        self.osd2.l_i_checksum_lo = 0;
        self.i_checksum_hi = 0;

        checksum = ext4_crc32c(
            EXT4_CRC32_INIT,
            &super_block.uuid,
            super_block.uuid.len() as u32,
        );
        checksum = ext4_crc32c(checksum, &ino_index.to_le_bytes(), 4);
        checksum = ext4_crc32c(checksum, &ino_gen.to_le_bytes(), 4);

        let mut raw_data = [0u8; 0x100];
        self.copy_to_slice(&mut raw_data);

        // inode checksum
        checksum = ext4_crc32c(checksum, &raw_data, inode_size as u32);

        self.set_inode_checksum_value(super_block, inode_id, checksum);

        if inode_size == 128 {
            checksum &= 0xFFFF;
        }

        checksum
    }

    pub fn set_inode_checksum(&mut self, super_block: &Ext4Superblock, inode_id: u32) {
        let inode_size = super_block.inode_size();
        let checksum = self.get_inode_checksum(inode_id, super_block);

        self.osd2.l_i_checksum_lo = ((checksum << 16) >> 16) as u16;
        if inode_size > 128 {
            self.i_checksum_hi = (checksum >> 16) as u16;
        }
    }

    pub fn sync_inode_to_disk(&self, block_device: &Arc<dyn BlockDevice>, inode_pos: usize) {
        let data = unsafe {
            core::slice::from_raw_parts(self as *const _ as *const u8, size_of::<Ext4Inode>())
        };
        block_device.write_offset(inode_pos, data);
    }

    pub fn root_extent_block(&self) -> u64 {
        let block_lo = self.block[0] as u64;
        let block_hi = self.block[1] as u64;
        block_lo | (block_hi << 32)
    }
}

impl Ext4InodeRef {
    pub fn set_attr(&mut self, attr: &FileAttr) {
        self.inode.set_size(attr.size);
        self.inode.set_blocks_count(attr.blocks);
        self.inode.set_atime(attr.atime);
        self.inode.set_mtime(attr.mtime);
        self.inode.set_ctime(attr.ctime);
        self.inode.set_i_crtime(attr.crtime);
        self.inode.set_file_type(attr.kind);
        self.inode.set_file_perm(attr.perm);
        self.inode.set_links_count(attr.nlink as u16);
        self.inode.set_uid(attr.uid as u16);
        self.inode.set_gid(attr.gid as u16);
        self.inode.set_faddr(attr.rdev);
        self.inode.set_flags(attr.flags);
    }
}

impl Ext4Inode {
    //    access() does not answer the "can I read/write/execute
    //    this file?" question.  It answers a slightly different question:
    //    "(assuming I'm a setuid binary) can the user who invoked me
    //    read/write/execute this file?", which gives set-user-ID programs
    //    the possibility to prevent malicious users from causing them to
    //    read files which users shouldn't be able to read.
    //   https://man7.org/linux/man-pages/man2/access.2.html
    // Check if a user can access the inode with the given UID, GID, and umask
    pub fn check_access(&self, uid: u16, gid: u16, access_mode: u16, umask: u16) -> bool {
        // Extract the owner, group, and other permission bits from the inode's mode
        let owner_perm = (self.mode & 0o700) >> 6;
        let group_perm = (self.mode & 0o070) >> 3;
        let other_perm = self.mode & 0o007;

        // Determine which permission bits to check based on the given UID and GID
        let perm = if self.uid == uid {
            owner_perm
        } else if self.gid == gid {
            group_perm
        } else {
            other_perm
        };

        // Adjust the permission bits based on the umask
        let adjusted_perm = perm & !((umask & 0o700) >> 6);

        // Check if the adjusted permission bits allow the requested access
        let check_read =
            (access_mode & R_OK as u16) == 0 || (adjusted_perm & R_OK as u16) == R_OK as u16;
        let check_write =
            (access_mode & W_OK as u16) == 0 || (adjusted_perm & W_OK as u16) == W_OK as u16;
        let check_execute =
            (access_mode & X_OK as u16) == 0 || (adjusted_perm & X_OK as u16) == X_OK as u16;

        check_read && check_write && check_execute
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_check_access_owner() {
        let inode = Ext4Inode {
            mode: 0o755, // rwxr-xr-x
            uid: 1000,
            gid: 1000,
            ..Default::default()
        };

        let uid = 1000;
        let gid = 1000;
        let umask = 0o022; // Default umask
        let access_mode = R_OK | X_OK;

        assert!(inode.check_access(uid, gid, umask, access_mode as u16));
    }

    #[test]
    fn test_check_access_group() {
        let inode = Ext4Inode {
            mode: 0o750, // rwxr-x---
            uid: 1000,
            gid: 1001,
            ..Default::default()
        };

        let uid = 1002;
        let gid = 1001;
        let umask = 0o022; // Default umask
        let access_mode = R_OK | X_OK;

        assert!(inode.check_access(uid, gid, access_mode as u16, umask));
    }

    #[test]
    fn test_check_access_other() {
        let inode = Ext4Inode {
            mode: 0o755, // rwxr-xr-x
            uid: 1000,
            gid: 1000,
            ..Default::default()
        };

        let uid = 1002;
        let gid = 1003;
        let umask = 0o022; // Default umask
        let access_mode = R_OK;

        assert!(inode.check_access(uid, gid, access_mode as u16, umask));
    }

    #[test]
    fn test_check_access_denied() {
        let inode = Ext4Inode {
            mode: 0o700, // rwx------
            uid: 1000,
            gid: 1000,
            ..Default::default()
        };

        let uid = 1002;
        let gid = 1003;
        let umask = 0o022; // Default umask
        let access_mode = R_OK;

        assert!(!inode.check_access(uid, gid, access_mode as u16, umask));
    }
    
    #[test]
    fn test_file_type() {
        let inode = Ext4Inode {
            mode: 0x8000, // Regular file
            ..Default::default()
        };
        assert!(inode.is_file());
        assert!(!inode.is_dir());
        assert!(!inode.is_link());
    }

    #[test]
    fn test_file_permissions() {
        let inode = Ext4Inode {
            mode: 0o755, // rwxr-xr-x
            ..Default::default()
        };
        assert!(inode.can_read());
        assert!(inode.can_write());
        assert!(inode.can_exec());
    }

    #[test]
    fn test_set_file_type_and_perm() {
        let mut inode = Ext4Inode {
            mode: 0,
            ..Default::default()
        };
        inode.set_file_type(InodeFileType::S_IFREG);
        assert_eq!(inode.mode, InodeFileType::S_IFREG.bits()); // Regular file with rwx permissions
        inode.set_file_perm(InodePerm::S_IREAD | InodePerm::S_IWRITE | InodePerm::S_IEXEC);
        assert_eq!(inode.mode, InodeFileType::S_IFREG.bits() | (InodePerm::S_IREAD | InodePerm::S_IWRITE | InodePerm::S_IEXEC).bits()); // Regular file with rwx permissions
    }
}