une implémentation simple de unix2dos pour windows

Sep 02 2020

Sous Linux, il existe un utilitaire appelé unix2dos qui convertit les EOL UNIX (\ n) en EOL DOS (\ r \ n). Cependant, sur Windows, il n'y a pas d'outil de ce type, j'ai donc décidé d'en créer un.

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

pour construire le code, utilisez

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

Réponses

5 chux-ReinstateMonica Sep 05 2020 at 15:07

Conception générale

Le code utilise largement les données sous forme de fichier de taille connue. Je préférerais une approche de flux où la conversion est effectuée au fur et à mesure que les données arrivent, puis écrites, ce qui élimine le besoin de grands tampons.

conception mode_overwrite

À mon avis, réécrire un fichier ne devrait pas détruire l'original tant que le nouveau fichier n'a pas été complètement écrit.

Je préférerais d'abord écrire dans un nouveau fichier temporaire, renommer les fichiers, puis détruire l'original.

Si une erreur se produit dans le processus, il est beaucoup plus facile de conserver le fichier d'origine pour la récupération.

Cartographie de la mémoire

L'utilisation CreateFileMappingW()après avoir parcouru tout le fichier avec newline_count()réduit les avantages de la cartographie. Il serait plus judicieux de mapper le fichier, puis de le lire CR/LF.

Erreur logique

En newline_count(), il n'y a pas besoin du 2ème. SetFilePointerEx().

newline_count()est également incorrect de relire le dernier caractère du tampon dans buffer[0]. Ce qui devrait être dans buffer[0]est la dernière valeur de la lecture de bloc précédente.

Erreur de calcul du pointeur

start1 - 1est invalide (UB) pour calculer quand start1 == src_file_buffer. Au lieu

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

L'accès est UB

start1[-1]est UB quand start1 == src_file_buffer.

Message d'erreur déroutant

SetFilePointerEx() peut signaler "Erreur: impossible de lire le fichier", mais l'erreur n'est pas en lecture, mais en recherche.

Évitez les nombres magiques sujets aux erreurs

Plutôt que ..., L"Error: could not resize ", 24, ...

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

Ou un autre code auto-calculant.

Accès potentiel hors de portée

argv[i + 1]est tenté sans le savoir i + 1 < argc.

Mineur

locaition -> location

3 D.Jurcau Sep 05 2020 at 16:28

Il faut un certain temps pour parcourir toute la journalisation des erreurs pour atteindre le code qui effectue réellement la conversion de fin de ligne.

Éviter les fonctions de bibliothèque standard ne signifie pas que vous ne pouvez pas écrire vous-même certaines fonctions utilitaires, comme un wrapper WriteConsoleWpour éviter de passer tous ces arguments dans le code.