uma implementação simples de unix2dos para windows

Sep 02 2020

No linux, existe o utilitário chamado unix2dos que converte UNIX EOLs (\ n) em DOS EOLs (\ r \ n). No entanto, no Windows não existe essa ferramenta, por isso decidi fazer uma.

unix2dos.c:

#include <windows.h>
#include <stdint.h>
#include <stddef.h>

#define chunksize (1 << 13)
#define nullptr ((void *)0)
uint8_t buffer[chunksize + 1] = { 0 };

int64_t newline_count(HANDLE filehandle)
{
    DWORD bytes_read = 0;
    int64_t result = 0;
    do
    {
        if (ReadFile(filehandle, buffer + 1, chunksize, &bytes_read, nullptr) == 0)
        {
            WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not read file", 26, nullptr, nullptr);
            ExitProcess(GetLastError());
        }

        if (SetFilePointerEx(filehandle, (LARGE_INTEGER) { .QuadPart = -1 }, nullptr, SEEK_CUR) == 0)
        {
            WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not read file", 26, nullptr, nullptr);
            ExitProcess(GetLastError());
        }

        if (ReadFile(filehandle, buffer, 1, nullptr, nullptr) == 0)
        {
            WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not read file", 26, nullptr, nullptr);
            ExitProcess(GetLastError());
        }

        if (SetFilePointerEx(filehandle, (LARGE_INTEGER) { .QuadPart = -1 }, nullptr, SEEK_CUR) == 0)
        {
            WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not read file", 26, nullptr, nullptr);
            ExitProcess(GetLastError());
        }

        for (uint8_t *start = buffer + 1; start != buffer + 1 + (int64_t)bytes_read; ++start)
        {
            if (start[0] == '\n' && start[-1] != '\r') ++result;
        }
    } while (bytes_read == chunksize);
    return result;
}

void unix2dos1(wchar_t const *const src, wchar_t const *const dst)
{
    HANDLE const dst_file = CreateFileW(dst, GENERIC_ALL, 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
    if (dst_file == INVALID_HANDLE_VALUE)
    {
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not open ", 22, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), dst, lstrlenW(dst), nullptr, nullptr);
        ExitProcess(GetLastError());
    }

    HANDLE const src_file = CreateFileW(src, GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
    if (src_file == INVALID_HANDLE_VALUE)
    {
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not open ", 22, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), src, lstrlenW(src), nullptr, nullptr);
        ExitProcess(GetLastError());
    }

    int64_t invalid_newline_count = newline_count(src_file);

    LARGE_INTEGER end_locaition = { 0 };
    if (GetFileSizeEx(src_file, &end_locaition) == 0)
    {
        CloseHandle(src_file);
        CloseHandle(dst_file);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not get the size of ", 33, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), src, lstrlenW(src), nullptr, nullptr);
        ExitProcess(GetLastError());
    }

    if (SetFilePointerEx(dst_file, (LARGE_INTEGER) { .QuadPart = invalid_newline_count + end_locaition.QuadPart }, &end_locaition, FILE_BEGIN) == 0)
    {
        CloseHandle(src_file);
        CloseHandle(dst_file);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not resize ", 24, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), dst, lstrlenW(dst), nullptr, nullptr);
        ExitProcess(GetLastError());
    }

    if (SetEndOfFile(dst_file) == 0)
    {
        CloseHandle(dst_file);
        CloseHandle(src_file);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not resize ", 24, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), dst, lstrlenW(dst), nullptr, nullptr);
        ExitProcess(GetLastError());
    }

    HANDLE const dst_memory_mapped_file = CreateFileMappingW(
        dst_file,
        nullptr,
        PAGE_READWRITE,
        0, 0,
        nullptr
    );

    if (dst_memory_mapped_file == nullptr)
    {
        CloseHandle(src_file);
        CloseHandle(dst_file);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not create file mapping object for ", 48, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), dst, lstrlenW(dst), nullptr, nullptr);
        ExitProcess(GetLastError());
    }

    HANDLE const src_memory_mapped_file = CreateFileMappingW(
        src_file,
        nullptr,
        PAGE_READONLY,
        0, 0,
        nullptr
    );


    if (src_memory_mapped_file == nullptr)
    {
        CloseHandle(dst_memory_mapped_file);
        CloseHandle(src_file);
        CloseHandle(dst_file);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not create file mapping object for ", 48, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), src, lstrlenW(src), nullptr, nullptr);
        ExitProcess(GetLastError());
    }

    uint8_t *const src_file_buffer = MapViewOfFile(src_memory_mapped_file, FILE_MAP_READ, 0, 0, end_locaition.QuadPart - invalid_newline_count);

    if (src_file_buffer == nullptr)
    {
        CloseHandle(dst_memory_mapped_file);
        CloseHandle(src_memory_mapped_file);
        CloseHandle(src_file);
        CloseHandle(dst_file);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not map view of ", 29, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), src, lstrlenW(src), nullptr, nullptr);
        ExitProcess(GetLastError());
    }


    uint8_t *const dst_file_buffer = MapViewOfFile(dst_memory_mapped_file, FILE_MAP_ALL_ACCESS, 0, 0, end_locaition.QuadPart);

    if (dst_file_buffer == nullptr)
    {
        UnmapViewOfFile(src_file_buffer);
        CloseHandle(dst_memory_mapped_file);
        CloseHandle(src_memory_mapped_file);
        CloseHandle(src_file);
        CloseHandle(dst_file);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not map view of ", 29, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), dst, lstrlenW(dst), nullptr, nullptr);
        ExitProcess(GetLastError());
    }


    uint8_t *start1 = src_file_buffer;
    uint8_t *start2 = dst_file_buffer;

    end_locaition.QuadPart -= invalid_newline_count;
    for (; end_locaition.QuadPart; ++start1, ++start2, --end_locaition.QuadPart)
    {
        if (start1[0] == '\n')
        {
            if (start1 - 1 <= src_file_buffer || start1[-1] != '\r')
            {
                *start2++ = '\r';
            }

        }
        start2[0] = start1[0];
    }

    UnmapViewOfFile(src_file_buffer);
    UnmapViewOfFile(dst_file_buffer);
    CloseHandle(dst_memory_mapped_file);
    CloseHandle(src_memory_mapped_file);
    CloseHandle(src_file);
    CloseHandle(dst_file);
}

void unix2dos2(const wchar_t *const filepath)
{
    HANDLE const file = CreateFileW(filepath, GENERIC_ALL, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
    if (file == INVALID_HANDLE_VALUE)
    {
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not open ", 22, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), filepath, lstrlenW(filepath), nullptr, nullptr);
        ExitProcess(GetLastError());
    }

    int64_t invalid_newline_count = newline_count(file);
    if (invalid_newline_count == 0)
    {
        CloseHandle(file);
        return;
    }

    LARGE_INTEGER end_locaition = { 0 };
    if (SetFilePointerEx(file, (LARGE_INTEGER) { .QuadPart = invalid_newline_count }, &end_locaition, FILE_END) == 0)
    {
        CloseHandle(file);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not resize ", 24, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), filepath, lstrlenW(filepath), nullptr, nullptr);
        ExitProcess(GetLastError());
    }

    if (SetEndOfFile(file) == 0)
    {
        CloseHandle(file);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not resize ", 24, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), filepath, lstrlenW(filepath), nullptr, nullptr);
        ExitProcess(GetLastError());
    }

    HANDLE const memory_mapped_file = CreateFileMappingW(
        file,
        nullptr,
        PAGE_READWRITE,
        0, 0,
        nullptr
    );

    if (memory_mapped_file == nullptr)
    {
        CloseHandle(file);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not create file mapping object for ", 48, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), filepath, lstrlenW(filepath), nullptr, nullptr);
        ExitProcess(GetLastError());
    }

    uint8_t *const file_buffer = MapViewOfFile(memory_mapped_file, FILE_MAP_ALL_ACCESS, 0, 0, end_locaition.QuadPart);

    if (file_buffer == nullptr)
    {
        CloseHandle(file);
        CloseHandle(memory_mapped_file);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), L"Error: could not map view of ", 29, nullptr, nullptr);
        WriteConsoleW(GetStdHandle(STD_ERROR_HANDLE), filepath, lstrlenW(filepath), nullptr, nullptr);
        ExitProcess(GetLastError());
    }

    uint8_t *start1 = file_buffer + end_locaition.QuadPart - invalid_newline_count - 1;
    uint8_t *start2 = file_buffer + end_locaition.QuadPart - 1;

    for (; start1 - file_buffer >= 0; --start1, --start2)
    {
        start2[0] = start1[0];
        if (start1[0] == '\n')
        {
            if (start1 - 1 <= file_buffer || start1[-1] != '\r')
            {
                *--start2 = '\r';
            }

        }
    }

    /* cleanup */
    UnmapViewOfFile(file_buffer);
    CloseHandle(memory_mapped_file);
    CloseHandle(file);
}



void __cdecl mainCRTStartup()
{
    int argc;
    wchar_t **const argv = CommandLineToArgvW(GetCommandLineW(), &argc) + 1;
    --argc;

    enum mode
    {
        mode_overwrite = 0x0,
        mode_create_file = 0x1,
    } current_mode = { mode_overwrite };

    for (int i = 0; i < argc; ++i)
    {

        if (lstrcmpW(argv[i], L"-o") == 0)
        {
            current_mode = mode_overwrite;
        }
        else if (lstrcmpW(argv[i], L"-n") == 0)
        {
            current_mode = mode_create_file;
        }
        else
        {
            switch (current_mode)
            {
                case mode_overwrite:
                    unix2dos2(argv[i]);
                    break;
                case mode_create_file:
                    if (lstrcmpW(argv[i], argv[i + 1]) != 0)
                    {
                        unix2dos1(argv[i], argv[i + 1]);
                    }
                    else
                    {
                        unix2dos2(argv[i]);
                    }
                    ++i;
                    break;
            }
        }
    }
    /* free memory and exit */
    LocalFree(argv - 1);
    ExitProcess(0);
}

para construir o código, use

cl.exe -nologo -Oi -GS -Gs9999999 unix2dos.c -link -subsystem:console -nodefaultlib kernel32.lib shell32.lib -stack:0x100000,0x100000

Respostas

5 chux-ReinstateMonica Sep 05 2020 at 15:07

Design geral

O código faz uso intenso de dados como um arquivo de tamanho conhecido. Eu prefiro uma abordagem de fluxo em que a conversão seja feita conforme os dados chegam e, em seguida, são gravados, negando a necessidade de qualquer buffer grande.

design mode_overwrite

Em minha opinião, reescrever um arquivo não deve destruir o original até que o novo arquivo esteja completamente escrito.

Eu preferiria gravar primeiro em um novo arquivo temporário, renomear os arquivos e depois destruir o original.

Se ocorrer um erro no processo, muito mais fácil ainda ter o arquivo original para recuperação.

Mapeamento de memória

O uso de CreateFileMappingW()após percorrer todo o arquivo newline_count()reduz o benefício do mapeamento. Faria mais sentido mapear o arquivo e depois lê-lo CR/LF.

Erro lógico

Em newline_count(), não há necessidade do 2º. SetFilePointerEx().

newline_count()também está errado em reler o último caractere do buffer em buffer[0]. O que deve estar buffer[0]é o último valor da leitura do bloco anterior.

Erro de cálculo do ponteiro

start1 - 1é inválido (UB) para calcular quando start1 == src_file_buffer. Em vez de

// start1 - 1 <= src_file_buffer
start1 <= src_file_buffer + 1

O acesso é UB

start1[-1]é UB quando start1 == src_file_buffer.

Mensagem de erro confusa

SetFilePointerEx() pode relatar "Erro: não foi possível ler o arquivo", mas o erro não está na leitura, mas na busca.

Evite números mágicos sujeitos a erros

Ao invés de ..., L"Error: could not resize ", 24, ...

wchar_t err[] = L"Error: could not resize ";
... err, sizeof err / sizeof err[0],...

Ou outro código de autocálculo.

Acesso potencial fora de alcance

argv[i + 1]é tentado sem saber i + 1 < argc.

Menor

locaition -> location

3 D.Jurcau Sep 05 2020 at 16:28

Leva algum tempo para navegar por todo o registro de erros para chegar ao código que realmente faz a conversão de final de linha.

Evitar funções de biblioteca padrão não significa que você não pode escrever algumas funções de utilitário sozinho, como um invólucro WriteConsoleWpara evitar a passagem de todos esses argumentos por todo o código.