1
\$\begingroup\$

On linux there is the utility called unix2dos which converts UNIX EOLs(\n) to DOS EOLs(\r\n). However on windows there is no such tool so as a result I decided to make one.

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

to build the code use

cl.exe -nologo -Oi -GS -Gs9999999 unix2dos.c -link -subsystem:console -nodefaultlib kernel32.lib shell32.lib -stack:0x100000,0x100000
\$\endgroup\$
3
  • 3
    \$\begingroup\$ Is this still part of your challenge not to use anything from the standard library? \$\endgroup\$
    – G. Sliepen
    Commented Sep 1, 2020 at 18:36
  • 3
    \$\begingroup\$ Also, the documentation of the dos2unix tool mentions it can be built with MSVC, see: sourceforge.net/p/dos2unix/dos2unix/ci/master/tree/dos2unix/… \$\endgroup\$
    – G. Sliepen
    Commented Sep 1, 2020 at 18:40
  • \$\begingroup\$ Yes it is still part of my challenge. \$\endgroup\$
    – nullptr
    Commented Sep 1, 2020 at 18:42

2 Answers 2

4
+50
\$\begingroup\$

Overall design

Code makes heavy use of data as a file with a known size. I'd favor a stream approach where the conversion is done as data arrives and then written, negating the need for any large buffers.

mode_overwrite design

In my opinion, re-writing a file should not destroy the original until after the new file is completely written.

I'd favor writing to a temporary new file first, rename files and then destroy the original.

Should an error occur in the process, far easier to still have the original file around for recovery.

Memory mapping

The use of CreateFileMappingW() after walking the entire file with newline_count() reduces the benefit of mapping. It would make more sense to map the file and then read it for CR/LF.

Logic error

In newline_count(), there is no need for the 2nd. SetFilePointerEx().

newline_count() is also amiss in re-reading the the last character of the buffer into buffer[0]. What should be in buffer[0] is the last value from the previous block read.

Pointer computation error

start1 - 1 is invalid (UB) to compute when start1 == src_file_buffer. Instead

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

Access is UB

start1[-1] is UB when start1 == src_file_buffer.

Confusing error message

SetFilePointerEx() may report "Error: could not read file", yet the error is not in reading, but seeking.

Avoid error prone magic numbers

Rather than ..., L"Error: could not resize ", 24, ...

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

Or other self-calculating code.

Potential out of range access

argv[i + 1] is attempted without knowing i + 1 < argc.

Minor

locaition --> location

\$\endgroup\$
2
  • \$\begingroup\$ the extra SetFilePointerEx() is needed because by reading chunks you could end up only reading part of an EOL which would cause a false positive when reading the next chunk. \$\endgroup\$
    – nullptr
    Commented Sep 5, 2020 at 15:03
  • \$\begingroup\$ also even if start1 == src_file_buffer is true start1[-1] would not be evaluated in that case because of short circuiting. \$\endgroup\$
    – nullptr
    Commented Sep 5, 2020 at 15:44
3
\$\begingroup\$

It takes some time to navigate through all the error logging to reach the code that actually does the line ending conversion.

Avoiding standard library functions doesn't mean you can't write some utility functions yourself, like a wrapper around WriteConsoleW to prevent passing all those arguments throughout the code.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.