Inode.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. /** @file
  2. Inode related routines
  3. Copyright (c) 2021 - 2022 Pedro Falcato All rights reserved.
  4. SPDX-License-Identifier: BSD-2-Clause-Patent
  5. EpochToEfiTime copied from EmbeddedPkg/Library/TimeBaseLib.c
  6. Copyright (c) 2016, Hisilicon Limited. All rights reserved.
  7. Copyright (c) 2016-2019, Linaro Limited. All rights reserved.
  8. Copyright (c) 2021, Ampere Computing LLC. All rights reserved.
  9. **/
  10. #include "Ext4Dxe.h"
  11. /**
  12. Calculates the checksum of the given inode.
  13. @param[in] Partition Pointer to the opened EXT4 partition.
  14. @param[in] Inode Pointer to the inode.
  15. @param[in] InodeNum Inode number.
  16. @return The checksum.
  17. **/
  18. UINT32
  19. Ext4CalculateInodeChecksum (
  20. IN CONST EXT4_PARTITION *Partition,
  21. IN CONST EXT4_INODE *Inode,
  22. IN EXT4_INO_NR InodeNum
  23. )
  24. {
  25. UINT32 Crc;
  26. UINT16 Dummy;
  27. BOOLEAN HasSecondChecksumField;
  28. CONST VOID *RestOfInode;
  29. UINTN RestOfInodeLength;
  30. UINTN Length;
  31. HasSecondChecksumField = EXT4_INODE_HAS_FIELD (Inode, i_checksum_hi);
  32. Dummy = 0;
  33. Crc = Ext4CalculateChecksum (Partition, &InodeNum, sizeof (InodeNum), Partition->InitialSeed);
  34. Crc = Ext4CalculateChecksum (Partition, &Inode->i_generation, sizeof (Inode->i_generation), Crc);
  35. Crc = Ext4CalculateChecksum (
  36. Partition,
  37. Inode,
  38. OFFSET_OF (EXT4_INODE, i_osd2.data_linux.l_i_checksum_lo),
  39. Crc
  40. );
  41. Crc = Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Crc);
  42. RestOfInode = &Inode->i_osd2.data_linux.l_i_reserved;
  43. RestOfInodeLength = Partition->InodeSize - OFFSET_OF (EXT4_INODE, i_osd2.data_linux.l_i_reserved);
  44. if (HasSecondChecksumField) {
  45. Length = OFFSET_OF (EXT4_INODE, i_checksum_hi) - OFFSET_OF (EXT4_INODE, i_osd2.data_linux.l_i_reserved);
  46. Crc = Ext4CalculateChecksum (Partition, &Inode->i_osd2.data_linux.l_i_reserved, Length, Crc);
  47. Crc = Ext4CalculateChecksum (Partition, &Dummy, sizeof (Dummy), Crc);
  48. // 4 is the size of the i_extra_size field + the size of i_checksum_hi
  49. RestOfInodeLength = Partition->InodeSize - EXT4_GOOD_OLD_INODE_SIZE - 4;
  50. RestOfInode = &Inode->i_ctime_extra;
  51. }
  52. Crc = Ext4CalculateChecksum (Partition, RestOfInode, RestOfInodeLength, Crc);
  53. return Crc;
  54. }
  55. /**
  56. Reads from an EXT4 inode.
  57. @param[in] Partition Pointer to the opened EXT4 partition.
  58. @param[in] File Pointer to the opened file.
  59. @param[out] Buffer Pointer to the buffer.
  60. @param[in] Offset Offset of the read.
  61. @param[in out] Length Pointer to the length of the buffer, in bytes.
  62. After a successful read, it's updated to the number of read bytes.
  63. @return Status of the read operation.
  64. **/
  65. EFI_STATUS
  66. Ext4Read (
  67. IN EXT4_PARTITION *Partition,
  68. IN EXT4_FILE *File,
  69. OUT VOID *Buffer,
  70. IN UINT64 Offset,
  71. IN OUT UINTN *Length
  72. )
  73. {
  74. EXT4_INODE *Inode;
  75. UINT64 InodeSize;
  76. UINT64 CurrentSeek;
  77. UINTN RemainingRead;
  78. UINTN BeenRead;
  79. UINTN WasRead;
  80. EXT4_EXTENT Extent;
  81. UINT32 BlockOff;
  82. EFI_STATUS Status;
  83. BOOLEAN HasBackingExtent;
  84. UINT32 HoleOff;
  85. UINT64 HoleLen;
  86. UINT64 ExtentStartBytes;
  87. UINT64 ExtentLengthBytes;
  88. UINT64 ExtentLogicalBytes;
  89. // Our extent offset is the difference between CurrentSeek and ExtentLogicalBytes
  90. UINT64 ExtentOffset;
  91. UINTN ExtentMayRead;
  92. Inode = File->Inode;
  93. InodeSize = EXT4_INODE_SIZE (Inode);
  94. CurrentSeek = Offset;
  95. RemainingRead = *Length;
  96. BeenRead = 0;
  97. DEBUG ((DEBUG_FS, "[ext4] Ext4Read(%s, Offset %lu, Length %lu)\n", File->Dentry->Name, Offset, *Length));
  98. if (Offset > InodeSize) {
  99. return EFI_DEVICE_ERROR;
  100. }
  101. if (RemainingRead > InodeSize - Offset) {
  102. RemainingRead = (UINTN)(InodeSize - Offset);
  103. }
  104. while (RemainingRead != 0) {
  105. WasRead = 0;
  106. // The algorithm here is to get the extent corresponding to the current block
  107. // and then read as much as we can from the current extent.
  108. Status = Ext4GetExtent (
  109. Partition,
  110. File,
  111. DivU64x32Remainder (CurrentSeek, Partition->BlockSize, &BlockOff),
  112. &Extent
  113. );
  114. if ((Status != EFI_SUCCESS) && (Status != EFI_NO_MAPPING)) {
  115. return Status;
  116. }
  117. HasBackingExtent = Status != EFI_NO_MAPPING;
  118. if (!HasBackingExtent || EXT4_EXTENT_IS_UNINITIALIZED (&Extent)) {
  119. HoleOff = BlockOff;
  120. if (!HasBackingExtent) {
  121. HoleLen = Partition->BlockSize - HoleOff;
  122. } else {
  123. // Uninitialized extents behave exactly the same as file holes, except they have
  124. // blocks already allocated to them.
  125. HoleLen = MultU64x32 (Ext4GetExtentLength (&Extent), Partition->BlockSize) - HoleOff;
  126. }
  127. WasRead = HoleLen > RemainingRead ? RemainingRead : (UINTN)HoleLen;
  128. // Potential improvement: In the future, we could get the file hole's total
  129. // size and memset all that
  130. ZeroMem (Buffer, WasRead);
  131. } else {
  132. ExtentStartBytes = MultU64x32 (
  133. LShiftU64 (Extent.ee_start_hi, 32) |
  134. Extent.ee_start_lo,
  135. Partition->BlockSize
  136. );
  137. ExtentLengthBytes = Extent.ee_len * Partition->BlockSize;
  138. ExtentLogicalBytes = MultU64x32 ((UINT64)Extent.ee_block, Partition->BlockSize);
  139. ExtentOffset = CurrentSeek - ExtentLogicalBytes;
  140. ExtentMayRead = (UINTN)(ExtentLengthBytes - ExtentOffset);
  141. WasRead = ExtentMayRead > RemainingRead ? RemainingRead : ExtentMayRead;
  142. Status = Ext4ReadDiskIo (Partition, Buffer, WasRead, ExtentStartBytes + ExtentOffset);
  143. if (EFI_ERROR (Status)) {
  144. DEBUG ((
  145. DEBUG_ERROR,
  146. "[ext4] Error %r reading [%lu, %lu]\n",
  147. Status,
  148. ExtentStartBytes + ExtentOffset,
  149. ExtentStartBytes + ExtentOffset + WasRead - 1
  150. ));
  151. return Status;
  152. }
  153. }
  154. RemainingRead -= WasRead;
  155. Buffer = (VOID *)((CHAR8 *)Buffer + WasRead);
  156. BeenRead += WasRead;
  157. CurrentSeek += WasRead;
  158. }
  159. *Length = BeenRead;
  160. return EFI_SUCCESS;
  161. }
  162. /**
  163. Allocates a zeroed inode structure.
  164. @param[in] Partition Pointer to the opened EXT4 partition.
  165. @return Pointer to the allocated structure, from the pool,
  166. with size Partition->InodeSize.
  167. **/
  168. EXT4_INODE *
  169. Ext4AllocateInode (
  170. IN EXT4_PARTITION *Partition
  171. )
  172. {
  173. BOOLEAN NeedsToZeroRest;
  174. UINT32 InodeSize;
  175. EXT4_INODE *Inode;
  176. NeedsToZeroRest = FALSE;
  177. InodeSize = Partition->InodeSize;
  178. // We allocate a structure of at least sizeof(EXT4_INODE), but in the future, when
  179. // write support is added and we need to flush inodes to disk, we could have a bit better
  180. // distinction between the on-disk inode and a separate, nicer to work with inode struct.
  181. // It's important to note that EXT4_INODE includes fields that may not exist in an actual
  182. // filesystem (the minimum inode size is 128 byte and at the moment the size of EXT4_INODE
  183. // is 160 bytes).
  184. if (InodeSize < sizeof (EXT4_INODE)) {
  185. InodeSize = sizeof (EXT4_INODE);
  186. NeedsToZeroRest = TRUE;
  187. }
  188. Inode = AllocateZeroPool (InodeSize);
  189. if (Inode == NULL) {
  190. return NULL;
  191. }
  192. if (NeedsToZeroRest) {
  193. Inode->i_extra_isize = 0;
  194. }
  195. return Inode;
  196. }
  197. /**
  198. Checks if a file is a directory.
  199. @param[in] File Pointer to the opened file.
  200. @return TRUE if file is a directory.
  201. **/
  202. BOOLEAN
  203. Ext4FileIsDir (
  204. IN CONST EXT4_FILE *File
  205. )
  206. {
  207. return (File->Inode->i_mode & EXT4_INO_TYPE_DIR) == EXT4_INO_TYPE_DIR;
  208. }
  209. /**
  210. Checks if a file is a symlink.
  211. @param[in] File Pointer to the opened file.
  212. @return BOOLEAN Whether file is a symlink
  213. **/
  214. BOOLEAN
  215. Ext4FileIsSymlink (
  216. IN CONST EXT4_FILE *File
  217. )
  218. {
  219. return (File->Inode->i_mode & EXT4_INO_TYPE_SYMLINK) == EXT4_INO_TYPE_SYMLINK;
  220. }
  221. /**
  222. Checks if a file is a regular file.
  223. @param[in] File Pointer to the opened file.
  224. @return BOOLEAN TRUE if file is a regular file.
  225. **/
  226. BOOLEAN
  227. Ext4FileIsReg (
  228. IN CONST EXT4_FILE *File
  229. )
  230. {
  231. return (File->Inode->i_mode & EXT4_INO_TYPE_REGFILE) == EXT4_INO_TYPE_REGFILE;
  232. }
  233. /**
  234. Calculates the physical space used by a file.
  235. @param[in] File Pointer to the opened file.
  236. @return Physical space used by a file, in bytes.
  237. **/
  238. UINT64
  239. Ext4FilePhysicalSpace (
  240. IN EXT4_FILE *File
  241. )
  242. {
  243. BOOLEAN HugeFile;
  244. UINT64 Blocks;
  245. HugeFile = EXT4_HAS_RO_COMPAT (File->Partition, EXT4_FEATURE_RO_COMPAT_HUGE_FILE);
  246. Blocks = File->Inode->i_blocks;
  247. if (HugeFile) {
  248. Blocks |= LShiftU64 (File->Inode->i_osd2.data_linux.l_i_blocks_high, 32);
  249. // If HUGE_FILE is enabled and EXT4_HUGE_FILE_FL is set in the inode's flags, each unit
  250. // in i_blocks corresponds to an actual filesystem block
  251. if ((File->Inode->i_flags & EXT4_HUGE_FILE_FL) != 0) {
  252. return MultU64x32 (Blocks, File->Partition->BlockSize);
  253. }
  254. }
  255. // Else, each i_blocks unit corresponds to 512 bytes
  256. return MultU64x32 (Blocks, 512);
  257. }
  258. // Copied from EmbeddedPkg at my mentor's request.
  259. // The lack of comments and good variable names is frightening...
  260. /**
  261. Converts Epoch seconds (elapsed since 1970 JANUARY 01, 00:00:00 UTC) to EFI_TIME.
  262. @param[in] EpochSeconds Epoch seconds.
  263. @param[out] Time The time converted to UEFI format.
  264. **/
  265. STATIC
  266. VOID
  267. EFIAPI
  268. EpochToEfiTime (
  269. IN UINTN EpochSeconds,
  270. OUT EFI_TIME *Time
  271. )
  272. {
  273. UINTN a;
  274. UINTN b;
  275. UINTN c;
  276. UINTN d;
  277. UINTN g;
  278. UINTN j;
  279. UINTN m;
  280. UINTN y;
  281. UINTN da;
  282. UINTN db;
  283. UINTN dc;
  284. UINTN dg;
  285. UINTN hh;
  286. UINTN mm;
  287. UINTN ss;
  288. UINTN J;
  289. J = (EpochSeconds / 86400) + 2440588;
  290. j = J + 32044;
  291. g = j / 146097;
  292. dg = j % 146097;
  293. c = (((dg / 36524) + 1) * 3) / 4;
  294. dc = dg - (c * 36524);
  295. b = dc / 1461;
  296. db = dc % 1461;
  297. a = (((db / 365) + 1) * 3) / 4;
  298. da = db - (a * 365);
  299. y = (g * 400) + (c * 100) + (b * 4) + a;
  300. m = (((da * 5) + 308) / 153) - 2;
  301. d = da - (((m + 4) * 153) / 5) + 122;
  302. Time->Year = (UINT16)(y - 4800 + ((m + 2) / 12));
  303. Time->Month = ((m + 2) % 12) + 1;
  304. Time->Day = (UINT8)(d + 1);
  305. ss = EpochSeconds % 60;
  306. a = (EpochSeconds - ss) / 60;
  307. mm = a % 60;
  308. b = (a - mm) / 60;
  309. hh = b % 24;
  310. Time->Hour = (UINT8)hh;
  311. Time->Minute = (UINT8)mm;
  312. Time->Second = (UINT8)ss;
  313. Time->Nanosecond = 0;
  314. }
  315. // The time format used to (de/en)code timestamp and timestamp_extra is documented on
  316. // the ext4 docs page in kernel.org
  317. #define EXT4_EXTRA_TIMESTAMP_MASK ((1 << 2) - 1)
  318. #define EXT4_FILE_GET_TIME_GENERIC(Name, Field) \
  319. VOID \
  320. Ext4File ## Name (IN EXT4_FILE *File, OUT EFI_TIME *Time) \
  321. { \
  322. EXT4_INODE *Inode = File->Inode; \
  323. UINT64 SecondsEpoch = Inode->Field; \
  324. UINT32 Nanoseconds = 0; \
  325. \
  326. if (EXT4_INODE_HAS_FIELD (Inode, Field ## _extra)) { \
  327. SecondsEpoch |= LShiftU64 ((UINT64)(Inode->Field ## _extra & EXT4_EXTRA_TIMESTAMP_MASK), 32); \
  328. Nanoseconds = Inode->Field ## _extra >> 2; \
  329. } \
  330. EpochToEfiTime ((UINTN)SecondsEpoch, Time); \
  331. Time->Nanosecond = Nanoseconds; \
  332. }
  333. // Note: EpochToEfiTime should be adjusted to take in a UINT64 instead of a UINTN, in order to avoid Y2038
  334. // on 32-bit systems.
  335. /**
  336. Gets the file's last access time.
  337. @param[in] File Pointer to the opened file.
  338. @param[out] Time Pointer to an EFI_TIME structure.
  339. **/
  340. EXT4_FILE_GET_TIME_GENERIC (ATime, i_atime);
  341. /**
  342. Gets the file's last (data) modification time.
  343. @param[in] File Pointer to the opened file.
  344. @param[out] Time Pointer to an EFI_TIME structure.
  345. **/
  346. EXT4_FILE_GET_TIME_GENERIC (MTime, i_mtime);
  347. /**
  348. Gets the file's creation time.
  349. @param[in] File Pointer to the opened file.
  350. @param[out] Time Pointer to an EFI_TIME structure.
  351. **/
  352. STATIC
  353. EXT4_FILE_GET_TIME_GENERIC (
  354. CrTime,
  355. i_crtime
  356. );
  357. /**
  358. Gets the file's creation time, if possible.
  359. @param[in] File Pointer to the opened file.
  360. @param[out] Time Pointer to an EFI_TIME structure.
  361. In the case where the the creation time isn't recorded,
  362. Time is zeroed.
  363. **/
  364. VOID
  365. Ext4FileCreateTime (
  366. IN EXT4_FILE *File,
  367. OUT EFI_TIME *Time
  368. )
  369. {
  370. EXT4_INODE *Inode;
  371. Inode = File->Inode;
  372. if (!EXT4_INODE_HAS_FIELD (Inode, i_crtime)) {
  373. ZeroMem (Time, sizeof (EFI_TIME));
  374. return;
  375. }
  376. Ext4FileCrTime (File, Time);
  377. }
  378. /**
  379. Checks if the checksum of the inode is correct.
  380. @param[in] Partition Pointer to the opened EXT4 partition.
  381. @param[in] Inode Pointer to the inode.
  382. @param[in] InodeNum Inode number.
  383. @return TRUE if checksum is correct, FALSE if there is corruption.
  384. **/
  385. BOOLEAN
  386. Ext4CheckInodeChecksum (
  387. IN CONST EXT4_PARTITION *Partition,
  388. IN CONST EXT4_INODE *Inode,
  389. IN EXT4_INO_NR InodeNum
  390. )
  391. {
  392. UINT32 Csum;
  393. UINT32 DiskCsum;
  394. if (!EXT4_HAS_METADATA_CSUM (Partition)) {
  395. return TRUE;
  396. }
  397. Csum = Ext4CalculateInodeChecksum (Partition, Inode, InodeNum);
  398. DiskCsum = Inode->i_osd2.data_linux.l_i_checksum_lo;
  399. if (EXT4_INODE_HAS_FIELD (Inode, i_checksum_hi)) {
  400. DiskCsum |= ((UINT32)Inode->i_checksum_hi) << 16;
  401. } else {
  402. // Only keep the lower bits for the comparison if the checksum is 16 bits.
  403. Csum &= 0xffff;
  404. }
  405. return Csum == DiskCsum;
  406. }