prosta implementacja unix2dos dla Windows

Sep 02 2020

W Linuksie jest narzędzie o nazwie unix2dos, które konwertuje UNIX EOL (\ n) na DOS EOL (\ r \ n). Jednak na Windowsie nie ma takiego narzędzia, więc postanowiłem je stworzyć.

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

do budowania kodu użyj

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

Odpowiedzi

5 chux-ReinstateMonica Sep 05 2020 at 15:07

Ogólny projekt

Kod w dużym stopniu wykorzystuje dane jako plik o znanym rozmiarze. Wolałbym podejście strumieniowe, w którym konwersja jest wykonywana w momencie nadejścia danych, a następnie ich zapisania, co wyklucza potrzebę stosowania dużych buforów.

mode_overwrite design

Moim zdaniem ponowne napisanie pliku nie powinno zniszczyć oryginału, dopóki nowy plik nie zostanie całkowicie zapisany.

Wolałbym najpierw zapisać do tymczasowego nowego pliku, zmienić nazwy plików, a następnie zniszczyć oryginał.

Jeśli w procesie wystąpi błąd, o wiele łatwiej będzie zachować oryginalny plik do odzyskania.

Mapowanie pamięci

Użycie CreateFileMappingW()po przejściu całego pliku newline_count()zmniejsza korzyści z mapowania. Bardziej sensowne byłoby zmapowanie pliku, a następnie odczytanie go CR/LF.

Błąd logiczny

W newline_count()nie ma potrzeby stosowania drugiego. SetFilePointerEx().

newline_count()błąd jest również nieprawidłowy w ponownym wczytywaniu ostatniego znaku bufora do buffer[0]. Powinna być buffer[0]ostatnia wartość z poprzedniego bloku odczytanego.

Błąd obliczania wskaźnika

start1 - 1jest nieprawidłowy (UB) do obliczenia kiedy start1 == src_file_buffer. Zamiast

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

Dostęp to UB

start1[-1]jest UB, kiedy start1 == src_file_buffer.

Mylący komunikat o błędzie

SetFilePointerEx() może zgłosić „Błąd: nie można odczytać pliku”, ale błąd nie dotyczy odczytu, ale wyszukiwania.

Unikaj podatnych na błędy liczb magicznych

Zamiast ..., L"Error: could not resize ", 24, ...

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

Lub inny samokalkulujący kod.

Potencjalny dostęp poza zasięgiem

argv[i + 1]jest podejmowana bez wiedzy i + 1 < argc.

Mniejszy

locaition -> location

3 D.Jurcau Sep 05 2020 at 16:28

Przejście przez wszystkie protokoły błędów, aby dotrzeć do kodu, który faktycznie dokonuje konwersji końca wiersza, zajmuje trochę czasu.

Unikanie standardowych funkcji bibliotecznych nie oznacza, że ​​nie możesz sam napisać niektórych funkcji narzędziowych, takich jak opakowanie, WriteConsoleWktóre zapobiega przekazywaniu wszystkich tych argumentów w całym kodzie.