512 lines
10 KiB
C
512 lines
10 KiB
C
/*
|
|
*
|
|
* Singe 2
|
|
* Copyright (C) 2006-2024 Scott Duensing <scott@kangaroopunch.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
|
|
#define ourMkdir(p,m) mkdir(p)
|
|
|
|
static const int CONSOLE_LINES = 1000;
|
|
#else
|
|
#define ourMkdir mkdir
|
|
#endif
|
|
|
|
|
|
#include <stdlib.h>
|
|
#include <dirent.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include "util.h"
|
|
|
|
|
|
static bool _consoleEnabled = true;
|
|
static bool _outputHappened = false;
|
|
static FILE *_utilTraceFile = NULL;
|
|
|
|
|
|
bool utilChMod(const char *path, const mode_t mode) {
|
|
bool result = true;
|
|
|
|
#ifdef _WIN32
|
|
(void)path;
|
|
(void)mode;
|
|
#else
|
|
result = (chmod(path, mode) >= 0);
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
char *utilCreateString(char *format, ...) {
|
|
va_list args;
|
|
char *string;
|
|
|
|
va_start(args, format);
|
|
string = utilCreateStringVArgs(format, args);
|
|
va_end(args);
|
|
|
|
return string;
|
|
}
|
|
|
|
|
|
__attribute__((__format__(__printf__, 1, 0)))
|
|
char *utilCreateStringVArgs(char *format, va_list args) {
|
|
va_list argsCopy;
|
|
int32_t size = 0;
|
|
char *buffer = NULL;
|
|
|
|
va_copy(argsCopy, args);
|
|
size = vsnprintf(NULL, 0, format, argsCopy) + 1;
|
|
va_end(argsCopy);
|
|
buffer = calloc(1, (size_t)size);
|
|
if (buffer) {
|
|
vsnprintf(buffer, (size_t)size, format, args);
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
|
|
__attribute__((__format__(__printf__, 1, 0)))
|
|
__attribute__((noreturn))
|
|
void utilDie(char *fmt, ...) {
|
|
va_list args;
|
|
if (_consoleEnabled) {
|
|
va_start(args, fmt);
|
|
vfprintf(stderr, fmt, args);
|
|
va_end(args);
|
|
printf("\n");
|
|
fflush(stderr);
|
|
#ifdef _WIN32
|
|
getchar();
|
|
#endif
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
|
|
void utilEnableConsole(bool enable) {
|
|
_consoleEnabled = enable;
|
|
}
|
|
|
|
|
|
bool utilFileExists(char *filename) {
|
|
FILE *file;
|
|
if ((file = fopen(filename, "r+"))) {
|
|
fclose(file);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void utilFixPathSeparators(char **path, bool slash) {
|
|
|
|
int32_t i = 0;
|
|
int32_t j = 0;
|
|
char *work = *path;
|
|
char *temp = NULL;
|
|
|
|
// Flip path separators to whatever our OS wants & remove repeated separators.
|
|
while (work[i] != 0) {
|
|
// Correct separator
|
|
if (work[i] == '\\' || work[i] == '/') {
|
|
// Was the prior character a seprator?
|
|
if (j == 0) {
|
|
work[j++] = utilGetPathSeparator();
|
|
} else {
|
|
if (work[j - 1] != utilGetPathSeparator()) {
|
|
// No, accept it.
|
|
work[j++] = utilGetPathSeparator();
|
|
}
|
|
}
|
|
} else {
|
|
work[j++] = work[i];
|
|
}
|
|
i++;
|
|
}
|
|
work[j] = 0;
|
|
|
|
if (slash) {
|
|
// Does this string end with a path separator?
|
|
if (work[strlen(work) - 1] != utilGetPathSeparator()) {
|
|
// No - append one.
|
|
temp = strdup(work);
|
|
free(work);
|
|
work = malloc(sizeof(char) * (strlen(temp) + 2));
|
|
strcpy(work, temp);
|
|
work[strlen(temp)] = utilGetPathSeparator();
|
|
work[strlen(temp) + 1] = 0;
|
|
free(temp);
|
|
}
|
|
}
|
|
|
|
*path = work;
|
|
}
|
|
|
|
|
|
bool utilGetConsoleEnabled(void) {
|
|
return _consoleEnabled && _outputHappened;
|
|
}
|
|
|
|
|
|
char *utilGetFileExtension(char *filename) {
|
|
char *start = filename + strlen(filename);
|
|
int32_t x;
|
|
|
|
// Scan through name and find the last '.'
|
|
for (x=0; x<(int32_t)strlen(filename); x++) {
|
|
if (filename[x] == '.') {
|
|
start = &filename[x + 1];
|
|
}
|
|
// Reset if we find a path separator
|
|
if (filename[x] == '\\' || filename[x] == '/') {
|
|
start = filename + strlen(filename);
|
|
}
|
|
}
|
|
|
|
return start;
|
|
}
|
|
|
|
|
|
char *utilGetLastPathComponent(char *pathname) {
|
|
static char *start;
|
|
int32_t x;
|
|
|
|
start = pathname;
|
|
|
|
// Scan through name and find the last path separator
|
|
for (x=0; x<(int32_t)strlen(pathname); x++) {
|
|
if (pathname[x] == '\\' || pathname[x] == '/') {
|
|
start = &pathname[x + 1];
|
|
}
|
|
}
|
|
|
|
return start;
|
|
}
|
|
|
|
|
|
char utilGetPathSeparator(void) {
|
|
#ifdef _WIN32
|
|
return '\\';
|
|
#else
|
|
return '/';
|
|
#endif
|
|
}
|
|
|
|
|
|
char *utilGetUpToLastPathComponent(char *pathname) {
|
|
static char *copy = NULL;
|
|
bool dumb = false; // Using (copy == NULL) below didn't work after optimizations, so enter the dummy.
|
|
int32_t x;
|
|
|
|
x = (int32_t)(strlen(pathname) - strlen(utilGetLastPathComponent(pathname))) - 1;
|
|
if (x < 0) x = 0;
|
|
if (dumb) {
|
|
free(copy);
|
|
copy = NULL;
|
|
} else {
|
|
dumb = true;
|
|
}
|
|
copy = strdup(pathname);
|
|
copy[x] = 0;
|
|
utilFixPathSeparators(©, true);
|
|
|
|
return copy;
|
|
}
|
|
|
|
|
|
bool utilMkDirP(const char *dir, const mode_t mode) {
|
|
char tmp[UTIL_PATH_MAX];
|
|
char *p = NULL;
|
|
struct stat sb;
|
|
size_t len;
|
|
|
|
// Make copy of dir.
|
|
len = strnlen(dir, UTIL_PATH_MAX);
|
|
if (len == 0 || len == UTIL_PATH_MAX) {
|
|
return -1;
|
|
}
|
|
memcpy(tmp, dir, len);
|
|
tmp[len] = '\0';
|
|
|
|
// Remove trailing slash.
|
|
if (tmp[len - 1] == utilGetPathSeparator()) {
|
|
tmp[len - 1] = '\0';
|
|
}
|
|
|
|
// Does it already exist?
|
|
if (stat(tmp, &sb) == 0) {
|
|
if (S_ISDIR (sb.st_mode)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Recursive mkdir.
|
|
for (p = tmp + 1; *p; p++) {
|
|
if (*p == utilGetPathSeparator()) {
|
|
*p = 0;
|
|
if (stat(tmp, &sb) != 0) {
|
|
// Does not exist - create it.
|
|
if (ourMkdir(tmp, mode) < 0) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!S_ISDIR(sb.st_mode)) {
|
|
// Not a directory
|
|
return false;
|
|
}
|
|
}
|
|
*p = utilGetPathSeparator();
|
|
}
|
|
}
|
|
// Check path
|
|
if (stat(tmp, &sb) != 0) {
|
|
// Does not exist - create it.
|
|
if (ourMkdir(tmp, mode) < 0) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!S_ISDIR(sb.st_mode)) {
|
|
// Not a directory
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool utilPathExists(char *pathname) {
|
|
DIR *dir = opendir(pathname);
|
|
if (dir) {
|
|
closedir(dir);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
char *utilReadFile(char *filename, size_t *bytes) {
|
|
char *data = NULL;
|
|
FILE *in = fopen(filename, "rb");
|
|
size_t read = 0;
|
|
|
|
(void)read;
|
|
|
|
*bytes = 0;
|
|
|
|
if (in) {
|
|
fseek(in, 0, SEEK_END);
|
|
*bytes = ftell(in);
|
|
fseek(in, 0, SEEK_SET);
|
|
data = malloc(sizeof(char) * (*bytes));
|
|
read = fread(data, sizeof(char), *bytes, in);
|
|
fclose(in);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
|
|
char *utilReadLine(char *haystack, size_t length, char **offset) {
|
|
size_t bytes = 0;
|
|
char *temp = *offset;
|
|
char *tail = temp;
|
|
char *result = NULL;
|
|
|
|
// They didn't know where to start
|
|
if (temp == NULL) {
|
|
temp = haystack;
|
|
tail = temp;
|
|
}
|
|
|
|
// Is there still data to read?
|
|
while ((size_t)(tail - haystack) < length) {
|
|
// Is this the end of a line?
|
|
if ((tail[0] == 10) || (tail[0] == 13)) {
|
|
// Yep!
|
|
bytes = tail - temp + 1;
|
|
result = malloc(sizeof(char) * bytes);
|
|
memcpy(result, temp, bytes - 1);
|
|
result[bytes - 1] = 0;
|
|
// Read past any additional CR/LFs
|
|
while ((tail[0] == 10) || (tail[0] == 13)) {
|
|
tail++;
|
|
}
|
|
// Return where we left off
|
|
temp = tail;
|
|
*offset = temp;
|
|
return result;
|
|
}
|
|
// Next character
|
|
tail++;
|
|
}
|
|
|
|
// Was there data at the end of the block with no CR/LF?
|
|
if (tail > temp) {
|
|
// Yep. Treat it as a line.
|
|
bytes = tail - temp + 1;
|
|
result = malloc(sizeof(char) * bytes);
|
|
memcpy(result, temp, bytes - 1);
|
|
result[bytes - 1] = 0;
|
|
temp = tail;
|
|
*offset = temp;
|
|
}
|
|
|
|
// Didn't find anything
|
|
return result;
|
|
}
|
|
|
|
|
|
void utilRedirectConsole(void) {
|
|
#ifdef _WIN32
|
|
// http://dslweb.nwnexus.com/~ast/dload/guicon.htm
|
|
int hConHandle; // Not changing this int.
|
|
intptr_t lStdHandle;
|
|
CONSOLE_SCREEN_BUFFER_INFO coninfo;
|
|
FILE *fp;
|
|
static bool consoleOpen = false;
|
|
|
|
if (!consoleOpen) {
|
|
consoleOpen = true;
|
|
|
|
// allocate a console for this app
|
|
AllocConsole();
|
|
|
|
// set the screen buffer to be big enough to let us scroll text
|
|
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
|
|
coninfo.dwSize.Y = CONSOLE_LINES;
|
|
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
|
|
|
|
// redirect unbuffered STDOUT to the console
|
|
lStdHandle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
|
|
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
|
|
fp = _fdopen(hConHandle, "w");
|
|
*stdout = *fp;
|
|
setvbuf(stdout, NULL, _IONBF, 0);
|
|
|
|
// redirect unbuffered STDIN to the console
|
|
lStdHandle = (intptr_t)GetStdHandle(STD_INPUT_HANDLE);
|
|
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
|
|
fp = _fdopen(hConHandle, "r");
|
|
*stdin = *fp;
|
|
setvbuf(stdin, NULL, _IONBF, 0);
|
|
|
|
// redirect unbuffered STDERR to the console
|
|
lStdHandle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
|
|
hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
|
|
fp = _fdopen(hConHandle, "w");
|
|
*stderr = *fp;
|
|
setvbuf(stderr, NULL, _IONBF, 0);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
__attribute__((__format__(__printf__, 1, 0)))
|
|
void utilSay(char *fmt, ...) {
|
|
va_list args;
|
|
if (_consoleEnabled) {
|
|
va_start(args, fmt);
|
|
vfprintf(stdout, fmt, args);
|
|
va_end(args);
|
|
printf("\n");
|
|
fflush(stdout);
|
|
_outputHappened = true;
|
|
}
|
|
}
|
|
|
|
|
|
bool utilStartsWith(char *string, char *start) {
|
|
return strncmp(start, string, strlen(start)) == 0;
|
|
}
|
|
|
|
|
|
int utilStricmp(char *a, char *b) {
|
|
for (;; a++, b++) {
|
|
int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
|
|
if (d != 0 || !*a) {
|
|
return d;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Windows does not have strndup() so we implement our own.
|
|
char *utilStrndup( const char *s1, size_t n) {
|
|
char *copy = (char *)malloc(n + 1);
|
|
memcpy(copy, s1, n);
|
|
copy[n] = 0;
|
|
return copy;
|
|
}
|
|
|
|
|
|
void utilTrace(char *fmt, ...) {
|
|
va_list args;
|
|
if (_utilTraceFile) {
|
|
va_start(args, fmt);
|
|
utilTraceVArgs(fmt, args);
|
|
va_end(args);
|
|
}
|
|
}
|
|
|
|
|
|
void utilTraceEnd(void) {
|
|
if (_utilTraceFile) fclose(_utilTraceFile);
|
|
}
|
|
|
|
|
|
FILE *utilTraceGetFile(void) {
|
|
return _utilTraceFile;
|
|
}
|
|
|
|
|
|
void utilTraceStart(char *filename) {
|
|
_utilTraceFile = fopen(filename, "wt");
|
|
if (!_utilTraceFile) utilDie("Unable to create trace file: %s", filename);
|
|
}
|
|
|
|
|
|
__attribute__((__format__(__printf__, 1, 0)))
|
|
void utilTraceVArgs(char *fmt, va_list args) {
|
|
#if defined(va_copy)
|
|
va_list args2;
|
|
#endif
|
|
|
|
if (_utilTraceFile) {
|
|
#if defined(va_copy)
|
|
va_copy(args2, args);
|
|
vprintf(fmt, args2);
|
|
printf("\n");
|
|
va_end(args2);
|
|
#endif
|
|
vfprintf(_utilTraceFile, fmt, args);
|
|
fprintf(_utilTraceFile, "\n");
|
|
fflush(_utilTraceFile);
|
|
}
|
|
}
|