una implementación simple de unix2dos para windows
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
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
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.