Fwrite не копирует все байты в двоичном файле - PullRequest
0 голосов
/ 04 мая 2019

Я пытаюсь скопировать двоичные файлы из src в dst.Этот скрипт, кажется, копирует все байты.НО, когда я открываю оба файла в Hex Workshop, я вижу, что в файле dst всегда отсутствует 3 байта в конце файла.Эти 3 байта должны были быть 00 00 00, эта проблема не позволяет мне открыть файл dst.

void binaryCopy(char **argv) {
    int *buf = 0;
    int elements = 0;
    int size = 0, wantOverwrite = 0;
    FILE *src = fopen(argv[SRC_POS], "rb");
    FILE *dst = fopen(argv[DST_POS], "w+b");
    if (src) {
        if (dst) {
            wantOverwrite = overwrite();
        }
        if (wantOverwrite) {
            fseek(src, 0L, SEEK_END);
            size = ftell(src);
            fseek(src, 0L, SEEK_SET);
            buf = (int *)malloc(size);
            elements = fread(buf, BYTE_SIZE, size / BYTE_SIZE, src);
            fwrite(buf, BYTE_SIZE, elements, dst);
            printf("copy completed");
            free(buf);
        }
    }
    fclose(dst);
    fclose(src);
}

Ответы [ 2 ]

2 голосов
/ 05 мая 2019

Наиболее вероятная причина для вашего наблюдения - размер файла не кратен BYTE_SIZE: fread(buf, BYTE_SIZE, size / BYTE_SIZE , src); читает кратное BYTE_SIZE, а вызов fwrite записывает прочитанные байты.

Если BYTE_SIZE равно 4, как указывает тип int* buf = 0;, и если исходный файл имеет на 3 байта больше, чем кратное 4, ваши наблюдения будут полностью объяснены.

Вы можете устранить проблему, сделав buf unsigned char * и изменив код на:

        elements = fread(buf, 1, size , src);
        fwrite(buf, 1, elements, dst);

Обратите внимание, что нет необходимости открывать файлы в режиме обновления (+ в строке режима), ошибки не обрабатываются явно, а вызовы fclose() неуместны.

Также кажется неправильным обрезать файл назначения, если overwrite() возвращает 0.

Вот исправленная версия с лучшей обработкой ошибок:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

int binaryCopy(char *argv[]) {
    FILE *src, *dst;
    long file_size;
    size_t size, size_read, size_written;
    int wantOverwrite;
    unsigned char *buf;

    if ((src = fopen(argv[SRC_POS], "rb")) == NULL) {
        printf("cannot open input file %s: %s\n", argv[SRC_POS], strerror(errno));
        return -1;
    }
    wantOverwrite = overwrite();
    if (!wantOverwrite) {
        fclose(src);
        return 0;
    }
    if ((dst = fopen(argv[DST_POS], "wb")) == NULL) {
        printf("cannot open output file %s: %s\n", argv[DST_POS], strerror(errno));
        fclose(src);
        return -1;
    }
    fseek(src, 0L, SEEK_END);
    file_size = ftell(src);
    fseek(src, 0L, SEEK_SET);
    size = (size_t)file_size;
    if ((long)size != file_size) {
        printf("file size too large for a single block: %ld\n", file_size);
        fclose(src);
        fclose(dst);
        return -1;
    }
    buf = malloc(size);
    if (buf == NULL) {
        printf("cannot allocate block of %zu bytes\n", size);
        fclose(src);
        fclose(dst);
        return -1;
    }
    size_read = fread(buf, 1, size, src);
    if (size_read != size) {
        printf("read error: %zu bytes read out of %zu\n", size_read, size);
    }
    size_written = fwrite(buf, 1, size_read, dst);
    if (size_written != size_read) {
        printf("write error: %zu bytes written out of %zu\n", size_written, size_read);
    }
    if (size_written == size) {
        printf("copy completed\n");
    }
    free(buf);
    fclose(dst);
    fclose(src);
    return 0;
}
2 голосов
/ 05 мая 2019

Есть несколько проблем в вашей функции, как написано.

  • fopen(dstFilename, "w+b"); обрезает файл, поэтому ваша проверка на перезапись позже не имеет смысла.
  • Вы не проверяете на NULLпосле malloc, и ваш буфер должен быть unsigned char*, поскольку fread/fwrite будет интерпретировать его как.
  • В конце обе функции fclose могут быть вызваны с указателями файлов NULL, что может привести кавария.Вы должны переместить их в области, где вы знаете, что каждый был успешно открыт.
  • Большая проблема, которая вызвала этот вопрос, заключается в том, что вы не обрабатываете случаи, когда размер файла не является даже кратнымвсего, что есть BYTE_SIZEПоскольку вы выделили достаточно памяти для всего файла, вы должны просто прочитать и записать весь файл.fread(buf, 1, size, src); и fwrite(buf, 1, size, dst);.В общем случае лучше всего сделать параметр размера элемента равным fread/fwrite 1 и подсчитать количество байтов, которое вы хотите прочитать или записать.Там нет математики, чтобы пойти не так, и вы можете точно сказать, сколько байтов было прочитано / записано.

Вот версия вашей исходной функции, которую я исправил и аннотировал, так что она работает, если ничего не идет не так, как надо.

void originalBinaryCopy(const char *srcFilename, const char *dstFilename)
{
    //odd size to ensure remainder
    const size_t BYTE_SIZE = 777;

    int *buf = 0;
    int elements = 0;
    int size = 0, wantOverwrite = 0;
    FILE *src = fopen(srcFilename, "rb");
    //This truncates dst, so the overwirte check is meaningless
    FILE *dst = fopen(dstFilename, "w+b");
    if (src)
    {
        if (dst)
        {
            fseek(src, 0L, SEEK_END);
            size = ftell(src);
            fseek(src, 0L, SEEK_SET);
            //always check for NULL after malloc - This should be a char*
            buf = (int *)malloc(size);
            if (!buf)
            {
                fclose(dst);
                fclose(src);
                return;
            }
            elements = fread(buf, BYTE_SIZE, size / BYTE_SIZE, src);
            fwrite(buf, BYTE_SIZE, elements, dst);

            //added, copy remainder
            elements = fread(buf, 1, size % BYTE_SIZE, src);
            fwrite(buf, 1, size % BYTE_SIZE, dst);
            //end

            printf("copy completed %s -> %s\n", srcFilename, dstFilename);
            free(buf);
        }
    }
    //dst could be NULL here, move inside if(dst) scope above
    fclose(dst);
    //src could be NULL here, move inside if(src) scope above
    fclose(src);

    if (comp(srcFilename, dstFilename) != 0)
    {
        printf("compare failed - %s -> %s\n", srcFilename, dstFilename);
    }
}

Обратите внимание, как обрабатывается остаток в конце.

Вот как я буду обрабатывать копирование файлов вместе с набором тестов для создания, копирования и проверки набора файлов.,Он показывает, как избежать усечения места назначения, если вы этого не хотите, и имеет небольшую проверку ошибок в реальных функциях.Я не включил какую-либо конкретную проверку ошибок на стороне вызывающей стороны, но для реального кода я бы перечислил все возможные ошибки и использовал бы эти возвращаемые значения для передачи в функцию обработки ошибок, которая могла бы их распечатать и, возможно, выйти из программы.

Управление файлами - это одна вещь, к которой вы хотите ОЧЕНЬ проявлять осторожность, поскольку существует вероятность потери данных, если ваш код не работает, поэтому прежде чем использовать его с реальнымфайлы должны быть на 100% твердыми с тестовыми файлами.

#include <malloc.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define TEST_FILE_MIN 1024
#define TEST_FILE_MAX 1024 * 1024
const char *src_pattern = "src_file_%08x.bin";
const char *dst_pattern = "dst_file_%08x.bin";

void createTestFiles(const char *pattern)
{
    char filename[256] = { 0 };
    char buffer[1024];

    for (size_t i = 0; i < sizeof(buffer); ++i)
    {
        buffer[i] = rand();
    }

    for (size_t i = TEST_FILE_MIN; i <= TEST_FILE_MAX; i *= 2)
    {
        sprintf(filename, pattern, i);
        FILE *dst = fopen(filename, "wb");
        if (dst)
        {
            size_t reps = i / TEST_FILE_MIN;
            for (size_t w = 0; w < reps; ++w)
            {
                fwrite(buffer, 1, sizeof(buffer), dst);
            }
            fclose(dst);
        }
    }
}

int comp(const char *srcFilename, const char *dstFilename)
{
    FILE *src = fopen(srcFilename, "rb");
    if (!src)
    {
        return -1;
    }
    //open for reading to check for existence
    FILE *dst = fopen(dstFilename, "rb");
    if (!dst)
    {
        fclose(src);
        return -2;
    }

    fseek(src, 0, SEEK_END);
    size_t srcSize = ftell(src);
    fseek(src, 0, SEEK_SET);

    fseek(dst, 0, SEEK_END);
    size_t dstSize = ftell(dst);
    fseek(dst, 0, SEEK_SET);

    if (srcSize == 0 || dstSize == 0 || srcSize != dstSize)
    {
        fclose(src);
        fclose(dst);
        return -3;
    }

    unsigned char *srcBuf = (unsigned char *)calloc(1, srcSize);
    unsigned char *dstBuf = (unsigned char *)calloc(1, srcSize);
    if (!srcBuf || !dstBuf)
    {
        fclose(src);
        fclose(dst);
        return -4;
    }

    if (fread(srcBuf, 1, srcSize, src) != srcSize)
    {
        fclose(src);
        fclose(dst);
        return -5;
    }
    if (fread(dstBuf, 1, dstSize, dst) != dstSize)
    {
        fclose(src);
        fclose(dst);
        return -6;
    }
    fclose(src);
    fclose(dst);

    //result * 100 to make this error outside te range of the other general errors from this function.
    int result = memcmp(srcBuf, dstBuf, srcSize) * 100;
    free(srcBuf);
    free(dstBuf);

    return result;
}

void originalBinaryCopy(const char *srcFilename, const char *dstFilename)
{
    //odd size to ensure remainder
    const size_t BYTE_SIZE = 777;

    int *buf = 0;
    int elements = 0;
    int size = 0, wantOverwrite = 0;
    FILE *src = fopen(srcFilename, "rb");
    //This truncates dst, so the overwirte check is meaningless
    FILE *dst = fopen(dstFilename, "w+b");
    if (src)
    {
        if (dst)
        {
            fseek(src, 0L, SEEK_END);
            size = ftell(src);
            fseek(src, 0L, SEEK_SET);
            //always check for NULL after malloc - This should be a char*
            buf = (int *)malloc(size);
            if (!buf)
            {
                fclose(dst);
                fclose(src);
                return;
            }
            elements = fread(buf, BYTE_SIZE, size / BYTE_SIZE, src);
            fwrite(buf, BYTE_SIZE, elements, dst);

            //added, copy remainder
            elements = fread(buf, 1, size % BYTE_SIZE, src);
            fwrite(buf, 1, size % BYTE_SIZE, dst);
            //end

            printf("copy completed %s -> %s\n", srcFilename, dstFilename);
            free(buf);
        }
    }
    //dst could be NULL here, move inside if(dst) scope above
    fclose(dst);
    //src could be NULL here, move inside if(src) scope above
    fclose(src);

    if (comp(srcFilename, dstFilename) != 0)
    {
        printf("compare failed - %s -> %s\n", srcFilename, dstFilename);
    }
}

int binaryCopy(const char *srcFilename, const char *dstFilename, bool overwrite)
{
    //arbitrary odd size so we can make sure we handle a partial buffer.
    //assuming the code tests successfully I'd use something like 64 * 1024.
    unsigned char buffer[7777] = { 0 };

    FILE *src = fopen(srcFilename, "rb");
    if (!src)
    {
        //Error, source file could not be opened
        return -1;
    }
    //open for reading to check for existence
    FILE *dst = fopen(dstFilename, "rb");
    if (dst)
    {
        if (!overwrite)
        {
            //Error, dest file exists and we can't overwrite it
            fclose(src);
            fclose(dst);
            return -2;
        }

        //reopen dst it for writing
        if (!freopen(dstFilename, "wb", dst))
        {
            fclose(src);
            fclose(dst);
            dst = NULL;
        }
    }
    else
    {
        //it didn't exist, create it.
        dst = fopen(dstFilename, "wb");
    }

    if (!dst)
    {
        //Error, dest file couldn't be opened
        fclose(src);
        return -3;
    }

    //Get the size of the source file for comparison with what we read and write.
    fseek(src, 0, SEEK_END);
    size_t srcSize = ftell(src);
    fseek(src, 0, SEEK_SET);

    size_t totalRead = 0;
    size_t totalWritten = 0;

    size_t bytesRead = 0;
    while (bytesRead = fread(buffer, 1, sizeof(buffer), src))
    {
        totalRead += bytesRead;
        totalWritten += fwrite(buffer, 1, bytesRead, dst);
    }
    fclose(dst);
    fclose(src);

    if (totalRead != srcSize)
    {
        //src read error
        return -4;
    }
    if (totalWritten != srcSize)
    {
        //dst write error
        return -5;
    }
    return 0;
}

int main()
{
    srand((unsigned)time(0));

    createTestFiles(src_pattern);

    for (size_t i = TEST_FILE_MIN; i <= TEST_FILE_MAX; i *= 2)
    {
        char srcName[256];
        char dstName[256];
        sprintf(srcName, src_pattern, i);
        sprintf(dstName, dst_pattern, i);

        //use my copy to create dest file
        if (binaryCopy(srcName, dstName, true) != 0)
        {
            printf("File: '%s' failed initial copy.", srcName);
        }

        originalBinaryCopy(srcName, dstName);

        if (binaryCopy(srcName, dstName, true) != 0)
        {
            printf("File: '%s' failed overwrite copy.", srcName);
        }
        if (binaryCopy(srcName, dstName, false) == 0)
        {
            printf("File: '%s' succeeded when file exists and overwrite was not set.", srcName);
        }
        //If compare succeeds delete the files, otherwise leave them for external comparison and print an error.
        if (comp(srcName, dstName) == 0)
        {
            if (remove(srcName) != 0)
            {
                perror("Could not remove src.");
            }
            if (remove(dstName) != 0)
            {
                perror("Could not remove dst.");
            }
        }
        else
        {
            printf("File: '%s' did not compare equal to '%s'.", srcName, dstName);
        }
    }
    return 0;
}

Надеюсь, это даст вам возможность поэкспериментировать, чтобы убедиться, что ваш копир настолько хорош, насколько это возможно.Также стоит отметить, я бы не стал различать копирование текстовых / бинарных файлов.Файлы - это файлы, и если ваша цель - скопировать их, вы всегда должны делать это в двоичном режиме, чтобы копия была идентичной.В операционных системах, отличных от Windows, это не имеет значения, но в Windows есть ряд ловушек, с которыми вы можете столкнуться в текстовом режиме.Лучше всего избегать их, если сможешь.

Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...