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;
}

Я действительно не знаю, как с этим справиться. Очень надеюсь, что кто-то сможет помочь.


person Community    schedule 01.02.2014    source источник


Ответы (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 с 30 000-байтовой дырой, за которой следуют 10 000 нулей. examine() возвращает size=40000, blocksize=4096, stored=12288, sparse=28672, zeroed=12288, что мне кажется правильным.

Вопросы?

person Nominal Animal    schedule 01.02.2014
comment
Привет, я попробовал твое предложение. Но я, вероятно, использовал вышеперечисленные вещи неправильно, потому что я не получаю желаемых результатов, а также однажды у меня возникла ошибка сегментации. Не могли бы вы показать мне в приведенном выше коде, если это не слишком сложно для вас, как изменить приведенный выше код с вашими предложениями? - person ; 02.02.2014
comment
@ user2965601: Нет, я не буду делать за тебя домашнее задание. Однако я добавил пример реализации функции examine(), которая проверяет именованный файл и возвращает числовые значения, необходимые для вашего вывода. Я надеюсь, что вы найдете время, чтобы прочитать и понять его — не стесняйтесь спрашивать, есть ли что-то странное, чего вы не понимаете — и вместо этого напишите свой собственный код. Судя по фрагменту кода, у вас уже есть необходимые навыки. - person Nominal Animal; 02.02.2014