Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/ext2/src/inode.rs
5394 views
1
// Copyright 2024 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
//! Defines the inode structure.
6
7
use std::mem::MaybeUninit;
8
use std::os::unix::fs::MetadataExt;
9
10
use anyhow::bail;
11
use anyhow::Result;
12
use enumn::N;
13
use zerocopy::FromBytes;
14
use zerocopy::Immutable;
15
use zerocopy::IntoBytes;
16
use zerocopy::KnownLayout;
17
18
use crate::arena::Arena;
19
use crate::arena::BlockId;
20
use crate::blockgroup::GroupMetaData;
21
use crate::xattr::InlineXattrs;
22
23
/// Types of inodes.
24
#[derive(Debug, PartialEq, Eq, Clone, Copy, N)]
25
pub enum InodeType {
26
Fifo = 0x1,
27
Char = 0x2,
28
Directory = 0x4,
29
Block = 0x6,
30
Regular = 0x8,
31
Symlink = 0xa,
32
Socket = 0xc,
33
}
34
35
impl InodeType {
36
/// Converts to a file type for directory entry.
37
/// The value is defined in "Table 4.2. Defined Inode File Type Values" in the spec.
38
pub fn into_dir_entry_file_type(self) -> u8 {
39
match self {
40
InodeType::Regular => 1,
41
InodeType::Directory => 2,
42
InodeType::Char => 3,
43
InodeType::Block => 4,
44
InodeType::Fifo => 5,
45
InodeType::Socket => 6,
46
InodeType::Symlink => 7,
47
}
48
}
49
}
50
51
// Represents an inode number.
52
// This is 1-indexed.
53
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
54
pub(crate) struct InodeNum(pub u32);
55
56
impl InodeNum {
57
pub fn new(inode: u32) -> Result<Self> {
58
if inode == 0 {
59
bail!("inode number is 1-indexed");
60
}
61
Ok(Self(inode))
62
}
63
64
// Returns index in the inode table.
65
pub fn to_table_index(self) -> usize {
66
// (num - 1) because inode is 1-indexed.
67
self.0 as usize - 1
68
}
69
}
70
71
impl From<InodeNum> for u32 {
72
fn from(inode: InodeNum) -> Self {
73
inode.0
74
}
75
}
76
77
impl From<InodeNum> for usize {
78
fn from(inode: InodeNum) -> Self {
79
inode.0 as usize
80
}
81
}
82
83
/// Size of the `block` field in Inode.
84
const INODE_BLOCK_LEN: usize = 60;
85
/// Represents 60-byte region for block in Inode.
86
/// This region is used for various ways depending on the file type.
87
/// For regular files and directories, it's used for storing 32-bit indices of blocks.
88
///
89
/// This is a wrapper of `[u8; 60]` to implement `Default` manually.
90
#[repr(C)]
91
#[derive(Debug, Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
92
pub(crate) struct InodeBlock(pub [u8; INODE_BLOCK_LEN]);
93
94
impl Default for InodeBlock {
95
fn default() -> Self {
96
Self([0; INODE_BLOCK_LEN])
97
}
98
}
99
100
impl InodeBlock {
101
// Each inode contains 12 direct pointers (0-11), one singly indirect pointer (12), one
102
// doubly indirect block pointer (13), and one triply indirect pointer (14).
103
pub const NUM_DIRECT_BLOCKS: usize = 12;
104
const INDIRECT_BLOCK_TABLE_ID: usize = Self::NUM_DIRECT_BLOCKS;
105
const DOUBLE_INDIRECT_BLOCK_TABLE_ID: usize = 13;
106
107
/// Set a block id at the given index.
108
pub fn set_block_id(&mut self, index: usize, block_id: &BlockId) -> Result<()> {
109
let offset = index * std::mem::size_of::<BlockId>();
110
let bytes = block_id.as_bytes();
111
if self.0.len() < offset + bytes.len() {
112
bail!("index out of bounds when setting block_id to InodeBlock: index={index}, block_id: {:?}", block_id);
113
}
114
self.0[offset..offset + bytes.len()].copy_from_slice(bytes);
115
Ok(())
116
}
117
118
/// Set an array of direct block IDs.
119
pub fn set_direct_blocks_from(
120
&mut self,
121
start_idx: usize,
122
block_ids: &[BlockId],
123
) -> Result<()> {
124
let bytes = block_ids.as_bytes();
125
if bytes.len() + start_idx * 4 > self.0.len() {
126
bail!(
127
"length of direct blocks is {} bytes, but it must not exceed {}",
128
bytes.len(),
129
self.0.len()
130
);
131
}
132
self.0[start_idx * 4..(start_idx * 4 + bytes.len())].copy_from_slice(bytes);
133
Ok(())
134
}
135
136
/// Set an array of direct block IDs.
137
pub fn set_direct_blocks(&mut self, block_ids: &[BlockId]) -> Result<()> {
138
self.set_direct_blocks_from(0, block_ids)
139
}
140
141
/// Set a block id to be used as the indirect block table.
142
pub fn set_indirect_block_table(&mut self, block_id: &BlockId) -> Result<()> {
143
self.set_block_id(Self::INDIRECT_BLOCK_TABLE_ID, block_id)
144
}
145
146
/// Set a block id to be used as the double indirect block table.
147
pub fn set_double_indirect_block_table(&mut self, block_id: &BlockId) -> Result<()> {
148
self.set_block_id(Self::DOUBLE_INDIRECT_BLOCK_TABLE_ID, block_id)
149
}
150
151
/// Returns the max length of symbolic links that can be stored in the inode data.
152
/// This length contains the trailing `\0`.
153
pub const fn max_inline_symlink_len() -> usize {
154
INODE_BLOCK_LEN
155
}
156
157
/// Stores a given string as an inlined symbolic link data.
158
pub fn set_inline_symlink(&mut self, symlink: &str) -> Result<()> {
159
let bytes = symlink.as_bytes();
160
if bytes.len() >= Self::max_inline_symlink_len() {
161
bail!(
162
"symlink '{symlink}' exceeds or equals tomax length: {} >= {}",
163
bytes.len(),
164
Self::max_inline_symlink_len()
165
);
166
}
167
self.0[..bytes.len()].copy_from_slice(bytes);
168
Ok(())
169
}
170
}
171
172
/// The ext2 inode.
173
///
174
/// The field names are based on [the specification](https://www.nongnu.org/ext2-doc/ext2.html#inode-table).
175
#[repr(C)]
176
#[derive(Debug, Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
177
pub(crate) struct Inode {
178
mode: u16,
179
uid: u16,
180
pub size: u32,
181
atime: u32,
182
ctime: u32,
183
mtime: u32,
184
_dtime: u32,
185
gid: u16,
186
pub links_count: u16,
187
pub blocks: InodeBlocksCount,
188
_flags: u32,
189
_osd1: u32,
190
pub block: InodeBlock,
191
_generation: u32,
192
_file_acl: u32,
193
_dir_acl: u32,
194
_faddr: u32,
195
_fragment_num: u8,
196
_fragment_size: u8,
197
_reserved1: u16,
198
uid_high: u16,
199
gid_high: u16,
200
_reserved2: u32, // 128-th byte
201
202
// We don't use any inode metadata region beyond the basic 128 bytes.
203
// However set `extra_size` to the minimum value to let Linux kernel know that there are
204
// inline extended attribute data. The minimum possible is 4 bytes, so define extra_size
205
// and add the next padding.
206
pub extra_size: u16,
207
_paddings: u16, // padding for 32-bit alignment
208
}
209
210
impl Default for Inode {
211
fn default() -> Self {
212
// SAFETY: zero-filled value is a valid value.
213
let mut r: Self = unsafe { MaybeUninit::zeroed().assume_init() };
214
// Set extra size to 4 for `extra_size` and `paddings` fields.
215
r.extra_size = 4;
216
r
217
}
218
}
219
220
/// Used in `Inode` to represent how many 512-byte blocks are used by a file.
221
///
222
/// The block size '512' byte is fixed and not related to the actual block size of the file system.
223
/// For more details, see notes for `i_blocks_lo` in the specification.
224
#[repr(C)]
225
#[derive(Default, Debug, Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
226
pub struct InodeBlocksCount(u32);
227
228
impl InodeBlocksCount {
229
const INODE_BLOCKS_SIZE: u32 = 512;
230
231
pub fn from_bytes_len(len: u32) -> Self {
232
Self(len / Self::INODE_BLOCKS_SIZE)
233
}
234
235
pub fn add(&mut self, v: u32) {
236
self.0 += v / Self::INODE_BLOCKS_SIZE;
237
}
238
}
239
240
impl Inode {
241
/// Size of the inode record in bytes.
242
///
243
/// From ext2 revision 1, inode size larger than 128 bytes is supported.
244
/// We use 256 byte here, which is the default value for ext4.
245
///
246
/// Note that inode "record" size can be larger that inode "structure" size.
247
/// The gap between the end of the inode structure and the end of the inode record can be used
248
/// to store extended attributes.
249
pub const INODE_RECORD_SIZE: usize = 256;
250
251
/// Size of the region that inline extended attributes can be written.
252
pub const XATTR_AREA_SIZE: usize = Inode::INODE_RECORD_SIZE - std::mem::size_of::<Inode>();
253
254
pub fn new<'a>(
255
arena: &'a Arena<'a>,
256
group: &mut GroupMetaData,
257
inode_num: InodeNum,
258
typ: InodeType,
259
size: u32,
260
xattr: Option<InlineXattrs>,
261
) -> Result<&'a mut Self> {
262
const EXT2_S_IRUSR: u16 = 0x0100; // user read
263
const EXT2_S_IXUSR: u16 = 0x0040; // user execute
264
const EXT2_S_IRGRP: u16 = 0x0020; // group read
265
const EXT2_S_IXGRP: u16 = 0x0008; // group execute
266
const EXT2_S_IROTH: u16 = 0x0004; // others read
267
const EXT2_S_IXOTH: u16 = 0x0001; // others execute
268
269
let inode_offset = inode_num.to_table_index() * Inode::INODE_RECORD_SIZE;
270
let inode =
271
arena.allocate::<Inode>(BlockId::from(group.group_desc.inode_table), inode_offset)?;
272
273
// Give read and execute permissions
274
let mode = ((typ as u16) << 12)
275
| EXT2_S_IRUSR
276
| EXT2_S_IXUSR
277
| EXT2_S_IRGRP
278
| EXT2_S_IXGRP
279
| EXT2_S_IROTH
280
| EXT2_S_IXOTH;
281
282
let now = std::time::SystemTime::now()
283
.duration_since(std::time::UNIX_EPOCH)?
284
.as_secs() as u32;
285
286
// SAFETY: geteuid never fail.
287
let uid = unsafe { libc::geteuid() };
288
let uid_high = (uid >> 16) as u16;
289
let uid_low = uid as u16;
290
// SAFETY: getegid never fail.
291
let gid = unsafe { libc::getegid() };
292
let gid_high = (gid >> 16) as u16;
293
let gid_low = gid as u16;
294
295
*inode = Self {
296
mode,
297
size,
298
atime: now,
299
ctime: now,
300
mtime: now,
301
uid: uid_low,
302
gid: gid_low,
303
uid_high,
304
gid_high,
305
..Default::default()
306
};
307
if let Some(xattr) = xattr {
308
Self::add_xattr(arena, group, inode, inode_offset, xattr)?;
309
}
310
311
Ok(inode)
312
}
313
314
pub fn from_metadata<'a>(
315
arena: &'a Arena<'a>,
316
group: &mut GroupMetaData,
317
inode_num: InodeNum,
318
m: &std::fs::Metadata,
319
size: u32,
320
links_count: u16,
321
blocks: InodeBlocksCount,
322
block: InodeBlock,
323
xattr: Option<InlineXattrs>,
324
) -> Result<&'a mut Self> {
325
let inodes_per_group = group.inode_bitmap.len();
326
// (inode_num - 1) because inode is 1-indexed.
327
let inode_offset =
328
((usize::from(inode_num) - 1) % inodes_per_group) * Inode::INODE_RECORD_SIZE;
329
let inode =
330
arena.allocate::<Inode>(BlockId::from(group.group_desc.inode_table), inode_offset)?;
331
332
let mode = m.mode() as u16;
333
334
let uid = m.uid();
335
let uid_high = (uid >> 16) as u16;
336
let uid_low: u16 = uid as u16;
337
let gid = m.gid();
338
let gid_high = (gid >> 16) as u16;
339
let gid_low: u16 = gid as u16;
340
341
let atime = m.atime() as u32;
342
let ctime = m.ctime() as u32;
343
let mtime = m.mtime() as u32;
344
345
*inode = Inode {
346
mode,
347
uid: uid_low,
348
gid: gid_low,
349
size,
350
atime,
351
ctime,
352
mtime,
353
links_count,
354
blocks,
355
block,
356
uid_high,
357
gid_high,
358
..Default::default()
359
};
360
361
if let Some(xattr) = xattr {
362
Self::add_xattr(arena, group, inode, inode_offset, xattr)?;
363
}
364
365
Ok(inode)
366
}
367
368
fn add_xattr<'a>(
369
arena: &'a Arena<'a>,
370
group: &mut GroupMetaData,
371
inode: &mut Inode,
372
inode_offset: usize,
373
xattr: InlineXattrs,
374
) -> Result<()> {
375
let xattr_region = arena.allocate::<[u8; Inode::XATTR_AREA_SIZE]>(
376
BlockId::from(group.group_desc.inode_table),
377
inode_offset + std::mem::size_of::<Inode>(),
378
)?;
379
380
if !xattr.entry_table.is_empty() {
381
// Linux and debugfs uses extra_size to check if inline xattr is stored so we need to
382
// set a positive value here. 4 (= sizeof(extra_size) + sizeof(_paddings))
383
// is the smallest value.
384
inode.extra_size = 4;
385
let InlineXattrs {
386
entry_table,
387
values,
388
} = xattr;
389
390
if entry_table.len() + values.len() > Inode::XATTR_AREA_SIZE {
391
bail!("xattr size is too large for inline store: entry_table.len={}, values.len={}, inline region size={}",
392
entry_table.len(), values.len(), Inode::XATTR_AREA_SIZE);
393
}
394
// `entry_table` should be aligned to the beginning of the region.
395
xattr_region[..entry_table.len()].copy_from_slice(&entry_table);
396
xattr_region[Inode::XATTR_AREA_SIZE - values.len()..].copy_from_slice(&values);
397
}
398
Ok(())
399
}
400
401
pub fn update_metadata(&mut self, m: &std::fs::Metadata) {
402
self.mode = m.mode() as u16;
403
404
let uid: u32 = m.uid();
405
self.uid_high = (uid >> 16) as u16;
406
self.uid = uid as u16;
407
let gid = m.gid();
408
self.gid_high = (gid >> 16) as u16;
409
self.gid = gid as u16;
410
411
self.atime = m.atime() as u32;
412
self.ctime = m.ctime() as u32;
413
self.mtime = m.mtime() as u32;
414
}
415
416
pub fn typ(&self) -> Option<InodeType> {
417
InodeType::n((self.mode >> 12) as u8)
418
}
419
}
420
421
#[cfg(test)]
422
mod tests {
423
use super::*;
424
425
#[test]
426
fn test_inode_size() {
427
assert_eq!(std::mem::offset_of!(Inode, extra_size), 128);
428
// Check that no implicit paddings is inserted after the padding field.
429
assert_eq!(
430
std::mem::offset_of!(Inode, _paddings) + std::mem::size_of::<u16>(),
431
std::mem::size_of::<Inode>()
432
);
433
434
assert!(128 < std::mem::size_of::<Inode>());
435
assert!(std::mem::size_of::<Inode>() <= Inode::INODE_RECORD_SIZE);
436
}
437
}
438
439