Skip to content

Instantly share code, notes, and snippets.

@Theldus
Created April 18, 2022 07:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Theldus/cd0da7539ebaebbcdc4cc8d74ce448aa to your computer and use it in GitHub Desktop.
Save Theldus/cd0da7539ebaebbcdc4cc8d74ce448aa to your computer and use it in GitHub Desktop.
Read directory entries through ext4 filesystem layout, without opendir/readdir
/*
* 2022 April, 18
* The author disclaims copyright to this source code, and it is
* release to the public domain.
*
* This code serves and has been published for study purposes
* only, I am not responsible for any damage caused by it.
*/
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <inttypes.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
/*
* Iterate over all dir entries for a given folder
* using the ext4 layout, instead of opendir/readdir...
*
* Why? why not?
*
* Limitations:
* May not work for every folder out of the sun, since the folder
* may use a hash tree approach, for faster look up and stuff...
* I'm too lazy to implement this.
*
* Refer to 'Hash Tree Directories' in:
* https://www.kernel.org/doc/html/latest/filesystems/ext4/directory.html
* for more info.
*
* Also useful:
* https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
*/
/* File types to string. */
const char *ft2str[] =
{
"Unknown",
"Regular File",
"Directory",
"Char Device",
"Block Device",
"FIFO",
"Socket",
"Symbolic Link"
};
/* Ext4 dirent. */
#define EXT4_NAME_LEN 255
/* v2. */
struct ext4_dir_entry_2
{
/* Inode number */
uint32_t inode;
/* Directory entry length */
uint16_t rec_len;
/* Name length */
uint8_t name_len;
/* See file type macros EXT4_FT_* below */
uint8_t file_type;
/* File name */
char name[EXT4_NAME_LEN];
};
struct fiemap_extent
{
/*
* Logical offset in bytes for the start of
* the extent from the beginning of the file.
*/
uint64_t fe_logical;
/*
* Physical offset in bytes for the start
* of the extent from the beginning of the disk.
*/
uint64_t fe_physical;
/* Length in bytes for this extent. */
uint64_t fe_length;
uint64_t fe_reserved64[2];
/* FIEMAP_EXTENT_* flags for this extent. */
uint32_t fe_flags;
uint32_t fe_reserved[3];
};
struct fiemap
{
/*
* Logical offset (inclusive) at
* which to start mapping (in).
*/
uint64_t fm_start;
/*
* Logical length of mapping which
* userspace wants (in).
*/
uint64_t fm_length;
/* FIEMAP_FLAG_* flags for request (in/out). */
uint32_t fm_flags;
/* Number of extents that were mapped (out). */
uint32_t fm_mapped_extents;
/* Size of fm_extents array (in). */
uint32_t fm_extent_count;
uint32_t fm_reserved;
/* Array of mapped extents (out). */
struct fiemap_extent fm_extents[0];
};
/*
* @brief Get first physical byte of the given
* file descriptor fd.
*
* @param fd File to be retrieved the phys position.
*
* @return Returns the physical position on success,
* 0 otherwise.
*/
static uint64_t get_first_phys_byte(int fd)
{
uint8_t buf[128] = {0}; /* Fiemap/extent buffer. */
int rc; /* Return code. */
struct fiemap *fiemap = (struct fiemap *)buf;
struct fiemap_extent *fm_ext = &fiemap->fm_extents[0];
int count = (sizeof(buf) - sizeof(*fiemap)) /
sizeof(struct fiemap_extent);
fiemap->fm_length = ~0ULL;
fiemap->fm_flags = 0;
fiemap->fm_extent_count = count;
rc = ioctl(fd, FS_IOC_FIEMAP, (unsigned long) fiemap);
if (rc < 0)
{
fprintf(stderr, "Unable to FIEMAP fd...\n");
return (0);
}
return (fm_ext[0].fe_physical);
}
/*
* @brief For a given path folder, get position of the first physical
* byte on disk.
*
* @param path Folder path
* @param fd Returned fd.
*
* @return Returns the first physical byte.
*/
static uint64_t get_phys_byte_folder(const char *path, int *fd)
{
uint64_t phys;
*fd = open(path, O_RDONLY);
if (*fd < 0) {
fprintf(stderr, "Unable to get size: %s\n", path);
perror("reason ");
}
phys = get_first_phys_byte(*fd);
close(*fd);
return (phys);
}
/**
* @brief Dump a single dirent.
*
* @param dp Dirent to be dumped on screen.
*/
static void dump_ext4dirent(const struct ext4_dir_entry_2 *dp)
{
printf("inode: %d\n", dp->inode);
printf("rec_len: %d\n", dp->rec_len);
printf("name_len: %d\n", dp->name_len);
printf("file_type: %s\n", ft2str[dp->file_type]);
printf("name: %.*s\n", dp->name_len, dp->name);
}
/**
* @brief Loop through all dirents of a opened disk @p fd,
* starting at @p seek with at most @p max_dirents entry.
*
* @param fd Opened disk (read-only) to be read.
* @param seek First physical byte.
* @param max_dirents Max dirents to be read.
*
* @return Returns 0 if success, a negative number otherwise.
*/
static int loop_dirents(int fd, off_t seek, int max_dirents)
{
struct ext4_dir_entry_2 dp = {0};
ssize_t ret_read;
int i = 0;
do
{
/* Seek right position. */
seek = lseek(fd, seek + dp.rec_len, SEEK_SET);
if (seek < 0) {
fprintf(stderr, "Unable to seek\n");
return (-1);
}
memset(&dp, 0, sizeof dp);
/* Read entire dirent. */
ret_read = read(fd, (void*)&dp, sizeof dp);
if (ret_read <= 0) {
fprintf(stderr, "Unable to read dirent!, %d\n", (int)ret_read);
return (-1);
}
printf("dirent #%d:\n", i);
dump_ext4dirent(&dp);
printf("---------\n");
} while (++i < max_dirents && dp.inode);
return (0);
}
/* Main program. */
int main(int argc, char **argv)
{
uint64_t phys_fold;
int fd;
if (argc < 2) {
fprintf(stderr, "Usage: %s <path-to-folder>\n", argv[0]);
return (1);
}
phys_fold = get_phys_byte_folder(argv[1], &fd);
if (fd < 0)
return (1);
printf("Phys: %" PRId64 "\n\n", phys_fold);
fd = open("/dev/sdb2", O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Unable to open disk\n");
perror("reason ");
}
loop_dirents(fd, phys_fold, 1000);
close(fd);
return (0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment