prosta implementacja unix2dos dla Windows
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
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
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.