una implementación simple de unix2dos para windows

Sep 02 2020

En Linux existe la utilidad llamada unix2dos que convierte las EOL de UNIX (\ n) a las EOL de DOS (\ r \ n). Sin embargo, en Windows no existe tal herramienta, por lo que decidí hacer una.

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 el uso del código

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

Respuestas

5 chux-ReinstateMonica Sep 05 2020 at 15:07

Diseño general

El código hace un uso intensivo de los datos como un archivo con un tamaño conocido. Preferiría un enfoque de transmisión en el que la conversión se realiza a medida que llegan los datos y luego se escriben, eliminando la necesidad de grandes búferes.

mode_overwrite diseño

En mi opinión, reescribir un archivo no debería destruir el original hasta que el nuevo archivo esté completamente escrito.

Preferiría escribir primero en un nuevo archivo temporal, cambiar el nombre de los archivos y luego destruir el original.

Si se produce un error en el proceso, es mucho más fácil tener el archivo original para su recuperación.

Mapeo de memoria

El uso de CreateFileMappingW()después de recorrer todo el archivo con newline_count()reduce el beneficio del mapeo. Tendría más sentido mapear el archivo y luego leerlo CR/LF.

Error de lógica

En newline_count(), no hay necesidad de la 2da. SetFilePointerEx().

newline_count()También está mal volver a leer el último carácter del búfer en buffer[0]. Lo que debería estar buffer[0]es el último valor del bloque anterior leído.

Error de cálculo del puntero

start1 - 1no es válido (UB) para calcular cuándo start1 == src_file_buffer. En lugar

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

El acceso es UB

start1[-1]es UB cuando start1 == src_file_buffer.

Mensaje de error confuso

SetFilePointerEx() puede informar "Error: no se pudo leer el archivo", pero el error no está en la lectura, sino en la búsqueda.

Evite los números mágicos propensos a errores

Más bien que ..., L"Error: could not resize ", 24, ...

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

U otro código autocalculante.

Potencial de acceso fuera de rango

argv[i + 1]se intenta sin saberlo i + 1 < argc.

Menor

locaition -> location

3 D.Jurcau Sep 05 2020 at 16:28

Se necesita algo de tiempo para navegar a través de todo el registro de errores para llegar al código que realmente hace la conversión final de línea.

Evitar las funciones de biblioteca estándar no significa que no pueda escribir algunas funciones de utilidad usted mismo, como un contenedor WriteConsoleWpara evitar pasar todos esos argumentos a lo largo del código.