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 что мне кажется правильным.

Вопросы?

Другие вопросы по тегам