eine einfache Implementierung von unix2dos für Windows
Unter Linux gibt es das Dienstprogramm unix2dos, das UNIX-EOLs (\ n) in DOS-EOLs (\ r \ n) konvertiert. Unter Windows gibt es jedoch kein solches Tool. Daher habe ich mich für eines entschieden.
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);
}
um den Code zu erstellen, verwenden Sie
cl.exe -nologo -Oi -GS -Gs9999999 unix2dos.c -link -subsystem:console -nodefaultlib kernel32.lib shell32.lib -stack:0x100000,0x100000
Antworten
Gesamtkonzept
Code verwendet Daten in hohem Maße als Datei mit bekannter Größe. Ich würde einen Stream-Ansatz bevorzugen, bei dem die Konvertierung erfolgt, wenn Daten eintreffen und dann geschrieben werden, wodurch die Notwendigkeit großer Puffer entfällt.
mode_overwrite Design
Meiner Meinung nach sollte das erneute Schreiben einer Datei das Original erst zerstören, nachdem die neue Datei vollständig geschrieben wurde.
Ich würde es vorziehen, zuerst in eine temporäre neue Datei zu schreiben, Dateien umzubenennen und dann das Original zu zerstören.
Sollte dabei ein Fehler auftreten, ist es weitaus einfacher, die Originaldatei zur Wiederherstellung zur Verfügung zu haben.
Speicherzuordnung
Die Verwendung von CreateFileMappingW()nach dem Durchlaufen der gesamten Datei mit newline_count()verringert den Vorteil der Zuordnung. Es wäre sinnvoller, die Datei zuzuordnen und dann zu lesen CR/LF.
Logikfehler
In newline_count()gibt es keine Notwendigkeit für die 2 .. SetFilePointerEx().
newline_count()ist auch falsch beim erneuten Einlesen des letzten Zeichens des Puffers in buffer[0]. Was drin sein sollte, buffer[0]ist der letzte Wert aus dem vorherigen gelesenen Block.
Zeigerberechnungsfehler
start1 - 1ist ungültig (UB), um zu berechnen, wann start1 == src_file_buffer. Stattdessen
// start1 - 1 <= src_file_buffer
start1 <= src_file_buffer + 1
Der Zugang ist UB
start1[-1]ist UB wenn start1 == src_file_buffer.
Verwirrende Fehlermeldung
SetFilePointerEx() meldet möglicherweise "Fehler: Datei konnte nicht gelesen werden", der Fehler liegt jedoch nicht beim Lesen, sondern beim Suchen.
Vermeiden Sie fehleranfällige magische Zahlen
Eher, als ..., L"Error: could not resize ", 24, ...
wchar_t err[] = L"Error: could not resize ";
... err, sizeof err / sizeof err[0],...
Oder anderer selbstberechnender Code.
Möglicher Zugriff außerhalb der Reichweite
argv[i + 1]wird versucht, ohne es zu wissen i + 1 < argc.
Geringer
locaition -> location
Es dauert einige Zeit, durch die gesamte Fehlerprotokollierung zu navigieren, um den Code zu erreichen, der die Konvertierung am Zeilenende tatsächlich durchführt.
Das Vermeiden von Standardbibliotheksfunktionen bedeutet nicht, dass Sie einige Dienstprogrammfunktionen nicht selbst schreiben können, z. B. einen Wrapper, um WriteConsoleWzu verhindern, dass all diese Argumente im gesamten Code übergeben werden.