C - Linux: список разреженных файлов и печать 0-заполненных блоков дисков
Я пытаюсь выполнить следующую задачу в течение нескольких часов. Но это все еще не работает, и я не знаю больше.
Задание:
Программа c, которая выводит список всех разреженных файлов в текущем каталоге, если вы передаете параметр -s в командной строке или программа должна вывести количество дисковых блоков, которые уже представляют пробелы в файле, и количество дисковых блоков, которые равны 0- заполнено, но занимает место на диске, если вы используете -c вместо -s.
Код:
int main(int argc, char* argv[]) {
if(argc > 3) {
fprintf(stderr,"usage: sparse-files [-s|-c <file name>]\n");
exit(EXIT_FAILURE);
}
DIR *dirp;
struct dirent *dp;
struct stat st = {0};
int option, sflag = 0, cflag = 0;
if ((dirp = opendir(".")) == NULL)
perror("Could not open '.'");
while((option = getopt(argc, argv,"sc:")) != -1) {
switch(option) {
case 'c':
cflag = 1;
break;
case 's':
sflag = 1;
break;
default:
break;
}
}
off_t sz;
while((dp = readdir(dirp)) != NULL) {
int counter = 0, counter2 = 0;
if(dp->d_type == DT_REG) {
if (sflag) {
char * file = dp->d_name;
if(stat(file, &st) == -1)
perror("stat()");
sz = (st.st_size + st.st_blksize -1) & ~st.st_blksize;
if ((st.st_blocks * st.st_blksize) < sz)
printf("%s\n", dp->d_name);
} else if (cflag) {
char * file = dp->d_name;
if(stat(file, &st) == -1)
perror("stat()");
int fd = open(file, O_RDONLY);
sz = (st.st_size + st.st_blksize -1) & ~st.st_blksize;
if ((st.st_blocks * st.st_blksize) < sz) {
while(lseek(fd, 0, SEEK_HOLE) != -1)
counter++;
while(lseek(fd, 0, SEEK_DATA) != -1)
counter2++;
printf("%d %d %s\n", counter, counter2, file);
close(fd);
}
}
}
closedir(dirp);
return 0;
}
Я действительно не знаю, как с этим справиться. Я действительно надеюсь, что кто-то сможет помочь.
1 ответ
Как вы, наверное, знаете, учитывая struct stat info
в ответ на звонок stat()
, info.st_size
это общий размер файла в байтах, и info.st_blocks*512
количество байтов, хранящихся на диске.
В Linux файловые системы хранят данные в выровненных порциях info.st_blksize
байт. (Это также означает, что info.st_blocks*512
может быть больше чем info.st_size
(максимум info.st_blksize-1
байт). Он также может быть меньше, если файл редкий.)
Как незарегистрированные данные (дыры в разреженном файле), так и явное обнуление хранимых данных читаются как нули.
Если вы хотите узнать, сколько в файле заполненных нулями блоков, вам нужно прочитать весь файл. Используйте буфер с размером, кратным целому числу info.st_blksize
байт. Для каждого выровненного блока st_blksize
байты, проверьте, все ли они равны нулю. Пусть общее количество блоков (включая последний, возможно, частичный блок) будет total_blocks
и количество блоков со всем нулевым содержимым будет zero_blocks
,
struct stat info;
/* Number of filesystem blocks for the file */
total_blocks = info.st_size / info.st_blksize
+ (info.st_size % info.st_blksize) ? 1 : 0;
/* Number of bytes stored for the file */
stored_bytes = 512 * info.st_blocks;
/* Number of filesystem blocks used for file data */
stored_blocks = stored_bytes / info.st_blksize
+ (stored_bytes % info.st_blksize) ? 1 : 0;
/* Number of sparse blocks */
sparse_blocks = total_blocks - stored_blocks;
/* TODO: count zero_blocks,
* by reading file in info.st_blksize chunks,
* and saving the number of all-zero chunks
* in zero_blocks. */
/* Number of stored zero blocks */
zeroed_blocks = zero_blocks - sparse_blocks;
Преобразовано в байты, у вас есть
info.st_size
размер файла в байтахstored_blocks*info.st_blksize
количество байтов, используемых на дискеsparse_blocks*info.st_blksize
количество байтов в разреженных отверстиях на дискеzeroed_blocks*info.st_blksize
количество ненужных сохраненных нулевых байтов на диске; это могло бы быть сохранено как разреженное отверстие вместо
Обратите внимание, что вы можете использовать cp --sparse=always --preserve=all SOURCEFILE TARGETFILE
создать идентичную копию файла, но "оптимизирующую" разреженность, чтобы вместо этого достаточно длинные последовательности нулевых байтов хранились как дыры; это может помочь вам в тестировании вашей программы. Увидеть man 1 cp
для деталей. Вы также можете создавать длинные последовательности нулей, используя dd if=/dev/zero of=TARGETFILE bs=BLOCKSIZE count=BLOCKS
; увидеть man 1 dd
а также man 4 null
для деталей.
Отредактировано, чтобы добавить:
Вот пример функции, examine()
, который открывает указанный файл, получает статистику и, если необходимо (т. е. запрашивается количество ненужно сохраненных нулей), считывает весь файл.
Я только слегка проверил это, но он должен реализовать логику выше.
Это очень грубо; Я уделил больше всего внимания правильной проверке ошибок и правильности динамического выделения / освобождения памяти. (Он должен проверять и возвращать все состояния ошибок, даже некоторые, которые никогда не должны возникать, и никогда не пропускать память. То есть, если в коде нет ошибок или соображений - исправления приветствуются.)
Было бы лучше разделить его на более мелкие, более простые в управлении функции.
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
/* Return nonzero if the buffer is all zeros.
*/
static inline int is_zero(const void *const ptr, const size_t len)
{
const char *p = (const char *)ptr;
const char *const q = (const char *const)ptr + len;
while (p < q)
if (*(p++))
return 0;
return 1;
}
/* Return 0 if success, errno error code otherwise.
* (*sizeptr): File size in bytes
* (*blocksizeptr): File block size in bytes
* (*storedptr): Bytes stored on disk
* (*sparseptr): Bytes in sparse holes
* (*zeroedptr): Unnecessarily stored zero bytes
* If zeroedptr is NULL, the file is only opened and
* statistics obtained via fstat(). Otherwise, the entire
* file will be read.
* Special errors:
* EINVAL: NULL or empty file name
* EISDIR: Name refers to a directory
* EISNAM: Name refers to a pipe or device
* EBUSY: File was modified during read
*/
int examine(const char *const filename,
uint64_t *const sizeptr,
uint64_t *const blocksizeptr,
uint64_t *const storedptr,
uint64_t *const sparseptr,
uint64_t *const zeroedptr)
{
struct stat info;
int fd, result;
size_t size, have;
uint64_t total, nonzero, stored;
int cause = 0;
char *data = NULL;
/* Check for NULL or empty filename. */
if (!filename || !*filename)
return errno = EINVAL;
/* Open the specified file. */
do {
fd = open(filename, O_RDONLY | O_NOCTTY);
} while (fd == -1 && errno == EINTR);
if (fd == -1)
return errno;
do {
/* Obtain file statistics. */
if (fstat(fd, &info) == -1) {
cause = errno;
break;
}
/* Count total, rounding up to next multiple of block size. */
total = (uint64_t)info.st_size;
if (total % (uint64_t)info.st_blksize)
total += (uint64_t)info.st_blksize - ((uint64_t)total % (uint64_t)info.st_blksize);
/* Count total stored bytes. */
stored = (uint64_t)512 * (uint64_t)info.st_blocks;
/* Fill in immediately known fields. */
if (sizeptr)
*sizeptr = (uint64_t)info.st_size;
if (blocksizeptr)
*blocksizeptr = (uint64_t)info.st_blksize;
if (storedptr)
*storedptr = stored;
if (sparseptr) {
if (total > stored)
*sparseptr = total - stored;
else
*sparseptr = 0;
}
if (zeroedptr)
*zeroedptr = 0;
/* Verify we have a regular file. */
if (S_ISDIR(info.st_mode)) {
cause = EISDIR;
break;
} else
if (!S_ISREG(info.st_mode)) {
cause = EISNAM;
break;
}
/* Verify we have a valid block size. */
if (info.st_blksize < (blksize_t)1) {
cause = ENOTSUP;
break;
}
/* If zeroedptr is NULL, we do not need to read the file. */
if (!zeroedptr) {
/* Close descriptor and return success. */
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
if (result == -1)
return errno;
return 0;
}
/* Use large enough chunks for I/O. */
if (info.st_blksize < (blksize_t)131072) {
const size_t chunks = (size_t)131072 / (size_t)info.st_blksize;
size = chunks * (size_t)info.st_blksize;
} else
size = (size_t)info.st_blksize;
/* Allocate buffer. */
data = malloc(size);
if (!data) {
cause = ENOMEM;
break;
}
/* Clear counters. */
total = 0;
nonzero = 0;
have = 0;
/* Read loop. */
while (1) {
size_t i;
ssize_t bytes;
int ended = 0;
while (have < (size_t)info.st_blksize) {
bytes = read(fd, data + have, size - have);
if (bytes > (ssize_t)0) {
have += bytes;
total += (uint64_t)bytes;
} else
if (bytes == (ssize_t)0) {
/* Clear the end of the buffer; just to be sure */
memset(data + have, 0, size - have);
ended = 1;
break;
} else
if (bytes != (ssize_t)-1) {
cause = EIO;
break;
} else
if (errno != EINTR) {
cause = errno;
break;
}
}
if (cause)
break;
/* Count number of zero/nonzero chunks in buffer, but add up as bytes. */
i = have / (size_t)info.st_blksize;
while (i-->0)
if (!is_zero(data + i * (size_t)info.st_blksize, (size_t)info.st_blksize))
nonzero += (uint64_t)info.st_blksize;
/* Followed by a partial chunk? */
{ const size_t overlap = have % (size_t)info.st_blksize;
if (overlap) {
if (have > overlap)
memcpy(data, data + have - overlap, overlap);
have = overlap;
} else
have = 0;
}
/* Next round of the loop, unless end of input. */
if (!ended)
continue;
/* Entire file has been processed. */
/* Partial chunk in buffer? */
if (have) {
if (!is_zero(data, have))
nonzero += (uint64_t)info.st_blksize;
}
/* If file size changed, update statistics. */
if (total != (uint64_t)info.st_size) {
if (fstat(fd, &info) == -1) {
cause = errno;
break;
}
/* File changed from under us? */
if (total != (uint64_t)info.st_size) {
cause = EBUSY;
break;
}
}
/* Align total size to (next) multiple of block size. */
if (total % (uint64_t)info.st_blksize)
total += (uint64_t)info.st_blksize - (total % (uint64_t)info.st_blksize);
/* Bytes stored on disk. */
stored = (uint64_t)512 * (uint64_t)info.st_blocks;
/* Sanity check. (File changed while we read it?) */
if (stored > total || nonzero > stored) {
cause = EBUSY;
break;
}
/* Update fields. */
if (sizeptr)
*sizeptr = (uint64_t)info.st_size;
if (storedptr)
*storedptr = (uint64_t)512 * (uint64_t)info.st_blocks;
if (sparseptr)
*sparseptr = total - stored;
if (zeroedptr)
*zeroedptr = (total - nonzero) - (total - stored);
/* Discard buffer. */
free(data);
/* Close file and return. */
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
if (result == -1)
return errno;
return 0;
}
} while (0);
/* Free buffer, if allocated. free(NULL) is safe. */
free(data);
/* Close file, and return with cause. */
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
return errno = cause;
}
Для переносимости все возвращаемые параметры являются 64-разрядными целыми числами без знака и указывают соответствующие размеры в байтах. Обратите внимание, что (*storedptr)+(*sparseptr)
определяет общее количество байтов, округленных до следующего кратного (*blocksizeptr)
, (*zeroesptr)
включает только явно сохраненные нули, а не разреженные дыры. Опять подумай о (*zeroesptr)
как количество ненужно сохраненных нулей.
я использовал rm -f test ; dd if=/dev/zero of=test bs=10000 seek=3 count=1
генерировать test
файл с 30000-байтовым отверстием, за которым следуют 10000 нулей. examine()
возвращается size=40000
, blocksize=4096
, stored=12288
, sparse=28672
, zeroed=12288
что мне кажется правильным.
Вопросы?