This code takes a list of filenames as arguments or from stdin (or from a provided file) and dumps a list of disk blocks used by those files.
An attempt is made to use the linux ioctl
function fiemap
and if that fails (ex. on FAT filesystems) falls back to using the fibmap
function.
This code compiles with VS Community 2017 on a Ubuntu Bionic system.
(Any suggestions as to better argument switches [such as using -o
for output file
and something else for offset
will also be appreciated)
/*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
/*
* Copyright (C) 2018
* Author afuna
* parts Copyright (C) 2010 Canonical: Colin Ian King, [email protected]
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <linux/fiemap.h>
void syntax(char **argv)
{
printf("lsblocks - Dumps a list of blocks occupied by a file using FIEMAP or FIBMAP.\n");
printf("\nUsage: %s [file1 file2...]\n", argv[0]);
printf("\nYou can provide a list of newline delimited filenames from stdin.\n");
printf("\nOptions:\n");
printf(
" -h show this help screen.\n"
" -v print file information to stderr before dumping block numbers.\n"
" use -vv to print to the output file\n"
" -i <path> read filenames from <path>\n"
" -s <path> save output to <path>\n"
" -o <count> offset values by <count>.\n"
" -d <dir> change working directory to <dir>.\n"
" -f <format> format block values using the given string;\n"
" you must provide the terminator (\\n or ' ' etc.)\n"
);
}
static unsigned long long offset = 0;
static int verbosity = 0;
static char* format = NULL;
static char* infile = NULL;
static char* outfile = NULL;
static FILE* fd_in = NULL;
static FILE* fd_out = NULL;
static FILE* fd_info = NULL;
struct fiemap *read_fiemap(int fd)
{
struct fiemap *fiemap;
int extents_size;
if ((fiemap = (struct fiemap*)malloc(sizeof(struct fiemap))) == NULL) {
fprintf(stderr, "Out of memory allocating fiemap\n");
return NULL;
}
memset(fiemap, 0, sizeof(struct fiemap));
fiemap->fm_start = 0;
fiemap->fm_length = ~0; /* Lazy */
fiemap->fm_flags = 0;
fiemap->fm_extent_count = 0;
fiemap->fm_mapped_extents = 0;
/* Find out how many extents there are */
if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0) {
perror("fiemap ioctl() failed: ");
//fprintf(stderr, "fiemap ioctl() failed\n");
return NULL;
}
/* Read in the extents */
extents_size = sizeof(struct fiemap_extent) *
(fiemap->fm_mapped_extents);
/* Resize fiemap to allow us to read in the extents */
if ((fiemap = (struct fiemap*)realloc(fiemap, sizeof(struct fiemap) +
extents_size)) == NULL) {
perror("Out of memory allocating fiemap: ");
return NULL;
}
memset(fiemap->fm_extents, 0, extents_size);
fiemap->fm_extent_count = fiemap->fm_mapped_extents;
fiemap->fm_mapped_extents = 0;
if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0) {
perror("fiemap ioctl() failed: ");
return NULL;
}
return fiemap;
}
void dump_fiemap(struct fiemap *fiemap, char *filename) {
if (verbosity > 0)
fprintf(fd_info, "blocksize %d\n", 512);
for (int i = 0; i < fiemap->fm_mapped_extents; i++) {
unsigned long long base = offset + (fiemap->fm_extents[i].fe_physical / 512);
unsigned long long count = fiemap->fm_extents[i].fe_length / 512;
for (int j = 0; j < count; j++) {
fprintf(fd_out, format, base++);
}
}
}
void dump_fibmap(int fd) {
int block, blocksize, blkcnt;
struct stat st;
if (ioctl(fd, FIGETBSZ, &blocksize)) {
perror("FIBMAP ioctl failed: ");
return;
}
if (fstat(fd, &st)) {
perror("fstat error");
return;
}
blkcnt = (st.st_size + blocksize - 1) / blocksize;
if (verbosity > 0)
fprintf(fd_info, "size %d blocks %d blocksize %d\n", (int)st.st_size, blkcnt, blocksize);
for (int i = 0; i < blkcnt; i++) {
block = i;
if (ioctl(fd, FIBMAP, &block)) {
perror("FIBMAP ioctl failed: ");
}
else {
int result;
result = fprintf(fd_out, format, offset + block);
if (result < 0) {
perror("failed write: ");
}
}
}
}
void dump(char* path)
{
int fd;
if ((fd = open(path, O_RDONLY)) < 0) {
fprintf(stderr, "Cannot open file %s\n", path);
perror(NULL);
}
else {
fprintf(fd_info, "Dumping file %s ", path);
struct fiemap *fiemap;
fiemap = read_fiemap(fd);
fiemap != NULL ? dump_fiemap(fiemap, path) : dump_fibmap(fd);
close(fd);
}
}
int main(int argc, char **argv)
{
char* path = NULL;
opterr = 0;
int opt = 0;
while ((opt = getopt(argc, argv, "o:f:d:i:s:vh")) != -1)
switch (opt)
{
case 'o':
offset = strtoull(optarg, NULL, 10);
break;
case 'f':
format = optarg;
break;
case 'd':
if (chdir(optarg) != 0) {
perror("Failed to change working directory: ");
return EXIT_FAILURE;
}
break;
case 'i':
infile = optarg;
break;
case 's':
outfile = optarg;
case 'v':
verbosity++;
break;
case 'h':
syntax(argv);
return EXIT_SUCCESS;
case '?':
if (optopt == 'c')
fprintf(stderr, "Option -%c requires an argument.\n", optopt);
else if (isprint(optopt))
fprintf(stderr, "Unknown option '-%c'.\n", optopt);
else
fprintf(stderr, "Unknown option character '\\x%x'.\n", optopt);
return EXIT_FAILURE;
default:
abort();
}
if (format == NULL)
format = "%llu\n";
if (infile == NULL)
fd_in = stdin;
else if ((fd_in = fopen(infile, "rb")) == NULL) {
perror("Cannot open input file: ");
exit(EXIT_FAILURE);
}
if (outfile == NULL)
fd_out = stdout;
else if ((fd_out = fopen(outfile, "w")) == NULL) {
perror("Cannot open output file: ");
return EXIT_FAILURE;
}
fd_info = verbosity <= 1 ? stderr : fd_out;
if (optind == argc | infile != NULL) { // read stdin or infile
char *buffer;
size_t bufsize = 2048;
size_t characters;
buffer = (char *)malloc(bufsize * sizeof(char));
if (buffer == NULL)
{
perror("Unable to allocate buffer");
return EXIT_FAILURE;
}
int size = 0;
while ((size = getline(&buffer, &bufsize, fd_in)) > 0)
{
buffer[size - 1] = '\0';
dump(buffer);
}
}
else {
for (int idx = optind; idx < argc; idx++)
dump(argv[idx]);
}
if (fd_in != stdin && !fclose(fd_in)) {
perror("Failed to close input file: ");
}
fflush(fd_out);
if (fd_out != stdout && fclose(fd_out)) {
perror("Failed to close output file: ");
}
return EXIT_SUCCESS;
}