/** * @file shared.c * @author syxhe (https://t.me/syxhe) * @brief A collection of functions and macros shared between files, or without a better place * @version 0.1 * @date 2025-06-09 * * @copyright Copyright (c) 2025 * */ #ifndef __VXGG_REWRITE___SHARED_C___3880294315821___ #define __VXGG_REWRITE___SHARED_C___3880294315821___ 1 #define STATIC_ARRAY_LEN(arr) (sizeof((arr))/sizeof((arr)[0])) #define ERRRET(errval, retval) do {\ errno = (errval);\ return (retval);\ } while (0) /// Defines how `x___alloc()` functions should exit. `___VXGG___XALLOC_EXIT_ON_ERROR___ > 0` calls `error()`, and thus functions /// registered with `atexit()` and `on_exit()`. `___VXGG___XALLOC_EXIT_ON_ERROR___ <= 0` calls `abort()` on error. `x___alloc()` /// type functions will ALWAYS 'abort', doing otherwise defeats the purpose of the function type #define ___VXGG___XALLOC_EXIT_ON_ERROR___ 1 /// Defines whether vxgg functions that can error print out a short warning of the error when one is encountered. /// `___VXGG___VERBOSE_ERRORS___ > 0` will print diagnostic error messages, and will do nothing otherwise #define ___VXGG___VERBOSE_ERRORS___ 1 //! Macro to exit on an alloc error instead of doing the terrible nested if statement that was being used previously #define XALLOC_EXIT(msg, ...) do {\ if(!___VXGG___XALLOC_EXIT_ON_ERROR___)\ abort();\ if(!___VXGG___VERBOSE_ERRORS___)\ exit(EXIT_FAILURE);\ error(EXIT_FAILURE, errno, (msg)__VA_ARGS__);\ exit(EXIT_FAILURE); /* Makes gcc happy */\ } while (0) //! Holdover macro because I'm lazy. Used to call either malloc or xmalloc, but the xalloc functions were a bad idea, so I removed them #define VALLOC(nmemb, size) malloc((nmemb) * (size)) //! Error macro that gcc will not complain about #define ERROR(status, errnum, format, ...) do {error((status), (errnum), (format)__VA_ARGS__); exit((status));} while (0) //! Spit out a warning using `error` #define WARN(errnum, format, ...) do {error(0, (errnum), (format)__VA_ARGS__);} while (0) typedef int (*gcallback)(void*); //!< Generic callback signature typedef void (*fcallback)(void*); //!< free()-like callback signature /** * @brief A locally defined structure designed for easier function cleanup * */ typedef struct cl { fcallback *callbacks; //!< An array of free()-like callbacks. Actual Type: fcallback callbacks[] void * *arguments; //!< An array of void pointers. Actual Type: void *arguments[] int size; //!< The size of each array int used; //!< The current number of used elements in each array } cleanup; #include #include #include #include #include #include /** * @brief Read the entire contents of a file descriptor into a malloc()'ed buffer * * @param str Pointer to a string. Will be replaced with the malloc()'ed string * @param initsize The initial size of the malloc()'ed string * @param fd A file descriptor to read from * @retval (int)[-1, strlen(str)] Returns the size of the array on success, or -1 on error * @note The allocated buffer will expand and contract as necessary, but it's recommended to set `initsize` to some value close to or equal to the size of the file being read to reduce the number of resizes */ int rwbuf(char **str, unsigned long int initsize, int fd) { // Try to read bytes from fd into str // Bytes read == 0, return 0 // Bytes read < 0, free string, return -1; // When string hits capacity, double the capacity, and reallocate the string if(!str || initsize < 1) ERRRET(EINVAL, -1); const int ECODE = -100; char *lstr = NULL, *tmp = NULL; ssize_t bytesread = -1; int csize = initsize, ccap = initsize; lstr = calloc(initsize, sizeof(char)); if(!lstr) return -1; while((bytesread = read(fd, lstr + (csize - ccap), ccap)) > 0) { ccap -= bytesread; if(ccap <= 0) { csize *= 2; ccap = csize / 2; tmp = realloc(lstr, csize * sizeof(char)); if(!tmp) { if(___VXGG___VERBOSE_ERRORS___) error(0, errno, "Could not reallocate enough space for lstr"); free(lstr); lstr = NULL; // Need to set this because of the break bytesread = ECODE; break; } lstr = tmp; } } if(bytesread < 0 && bytesread != ECODE) { if(___VXGG___VERBOSE_ERRORS___) error(0, errno, "Ran into a read() error"); free(lstr); lstr = NULL; } if(lstr) { tmp = realloc(lstr, csize - ccap + 1); if(!tmp) { if(___VXGG___VERBOSE_ERRORS___) error(0, errno, "Could not shrink lstr after reading buffer"); free(lstr); bytesread = ECODE; } lstr = tmp; } if(lstr && fd == STDIN_FILENO) { lstr[csize - ccap - 1] = '\0'; // Don't include the newline } *str = lstr; return ((bytesread == 0) ? (csize - ccap) : -1); } /** * @brief Write the entire contents of a buffer into a file descriptor * * @param fd The file descriptor to write to * @param buf The buffer to write from * @param len The length of the buffer * @retval (int)[-1, 0] Returns 0 on success, -1 on error */ int wwbuf(int fd, const unsigned char *buf, int len) { if(!buf || len <= 0) ERRRET(EINVAL, -1); int total = 0; int left = len; int n = -1; while(total < len) { if((n = write(fd, buf + total, left)) < 0) break; total += n; left -= n; } return (n < 0) ? -1 : 0; } // Adapted from Beej's `sendall()` function // https://beej.us/guide/bgnet/html/split/slightly-advanced-techniques.html#sendall // Thanks Beej! /** * @brief `dirname()` reimplementation that returns a malloc()'ed string * * @param path The filepath to be inspected * @retval (char*)[NULL, char*] Returns a null-terminated string on success, or `null` on error */ char * vxdirname(const char * const path) { char *tmp = NULL; if(!path) { // Path being null is a special case which should return early, before anything else (as to avoid null dereference) tmp = strdup("."); if(!tmp) { WARN(errno, " could not strdup \".\" for set path result \"NULL\"", ); return NULL; } return tmp; } unsigned char flag = 0; if(strcmp(path, ".") == 0) {tmp = strdup("."); flag++;} if(strcmp(path, "/") == 0 && !flag) {tmp = strdup("/"); flag++;} if(strcmp(path, "..") == 0 && !flag) {tmp = strdup("."); flag++;} if(flag) { if(!tmp) { WARN(errno, " could not strdup a set path result", ); return NULL; } return tmp; } /* From the manpages: (man 3 dirname) // +=======================================+ // | path dirname basename | // +=======================================+ // | /usr/lib /usr lib | // | /usr/ / usr | // | usr . usr | // +=======================================+ */ // Get a temp copy of the path for manipulation purposes tmp = strdup(path); if(!tmp) { WARN(errno, " could not strdup the given path \"%s\" for internal manipulation", , path); return NULL; } // If there's a trailing '/', delete it size_t pathlen = strlen(path); if(tmp[pathlen - 1] == '/') { tmp[pathlen - 1] = '\0'; pathlen--; } // Ok, I think the easiest way to do this (if maybe a bit slow) is to count the number of '/'s in the string // If there's only one, return '/' // If there are 2 or more, find the last one in the list and set it to '\0' size_t count = 0; for(size_t i = 0; i < pathlen; i++) { if(tmp[i] == '/') count++; } if(count == 0 || count == 1) free(tmp); if(count == 0) { tmp = strdup("."); if(!tmp) { WARN(errno, " could not strdup \".\" for set path result", ); return NULL; } return tmp; } else if(count == 1) { tmp = strdup("/"); if(!tmp) { WARN(errno, " could not strdup \"/\" for set path result", ); return NULL; } return tmp; } // This is retarded, fix it for(size_t i = 0, c2 = 0; i < pathlen; i++) { if(tmp[i] == '/') c2++; if(c2 == count) tmp[i] = '\0'; } char * const actual = strdup(tmp); free(tmp); if(!actual) { WARN(errno, " could not strdup tmp string to make a shorter end string", ); return NULL; } return actual; } /** * @brief Initialize a cleanup object * * @param loc The cleanup object to be initialized * @param callbacks An array of free()-like callbacks. Must be `size` elements long * @param arguments An array of void pointers. Must be `size` elements long * @param size The number of elements the callbacks and arguments array are long * @retval (int)[-1, 0] Returns 0 on success, -1 on error */ int cleanup_init(cleanup *loc, fcallback callbacks[], void *arguments[], int size) { if(!loc || !callbacks || !arguments || size <= 0) ERRRET(EINVAL, -1); loc->callbacks = callbacks; loc->arguments = arguments; loc->size = size; loc->used = 0; return 0; } /** * @brief Register a new callback and argument onto a cleanup stack * * @param loc The cleanup object to modify * @param cb A free()-like callback to run * @param arg A piece of data for the callback to run * @retval (int)[-1, 0] Returns 0 on success, -1 on error */ int cleanup_register(cleanup *loc, fcallback cb, void *arg) { if(!loc || !cb) ERRRET(EINVAL, -1); if(loc->used >= loc->size || loc->used < 0) ERRRET(ENOMEM, -1); loc->callbacks[loc->used] = cb; loc->arguments[loc->used] = arg; loc->used++; return 0; } /** * @brief Conditionally register a callback and argument * * @param loc The cleanup object to modify * @param cb A free()-like callback to run * @param arg A piece of data for the callback to run * @param flag Whether or not the register should take place. Will not run if `flag` is non-zero * @retval (int)[-1, 0] Returns 0 on success or skip, -1 on error */ int cleanup_cndregister(cleanup *loc, fcallback cb, void *arg, unsigned char flag) { if(flag) return 0; return cleanup_register(loc, cb, arg); } /** * @brief Clear a cleanup object * @attention Does not free any registered callbacks or arguments, just marks them as available space * * @param loc The cleanup object to modify * @retval (int)[-1, 0] Returns 0 on success, -1 on error */ int cleanup_clear(cleanup *loc) { if(!loc) ERRRET(EINVAL, -1); loc->used = 0; return 0; } /** * @brief Fires all the registered callbacks and arguments in a cleanup object in FIFO (stack) order * * @param loc The cleanup object to fire * @retval (int)[-1, 0] Returns 0 on success, -1 on error */ int cleanup_fire(cleanup *loc) { if(!loc) ERRRET(EINVAL, -1); for(int i = (loc->used - 1); i >= 0; i--) { if(loc->callbacks[i] == NULL) { error(0, EINVAL, "cleanup_fire: refusing to run null callback..."); continue; } loc->callbacks[i](loc->arguments[i]); } cleanup_clear(loc); return 0; } /** * @brief Conditionally fires a cleanup object * * @param loc The cleanup object in question * @param flag Whether the object should be fired. Will skip firing if non-zero * @retval (int)[-1, 0] Returns 0 on success, -1 on error */ int cleanup_cndfire(cleanup *loc, unsigned char flag) { if(flag) return cleanup_fire(loc); return 0; } /** * @brief Initializes a set of variables suitable for use in the cleanup macros * @param size The number of elements long each array should be */ #define cleanup_CREATE(size) \ cleanup __CLEANUP; \ fcallback __CLEANUP_FUNCS[(size)]; \ void *__CLEANUP_ARGS[(size)]; \ unsigned char __FLAG = 0; \ cleanup_init(&__CLEANUP, __CLEANUP_FUNCS, __CLEANUP_ARGS, (size)) //! Register a callback-argument pair using the local cleanup object #define cleanup_REGISTER(cb, arg) cleanup_register(&__CLEANUP, (cb), (arg)) //! Conditionally register a callback-argument pair using the local cleanup object #define cleanup_CNDREGISTER(cb, arg) cleanup_cndregister(&__CLEANUP, (cb), (arg), __FLAG) //! Clean the local cleanup object #define cleanup_CLEAR() cleanup_clear(&__CLEANUP) //! Fire the local cleanup object #define cleanup_FIRE() cleanup_fire(&__CLEANUP) //! Conditionally fire the local cleanup object #define cleanup_CNDFIRE() cleanup_cndfire(&__CLEANUP, __FLAG) //! Set the local cleanup flag to a non-zero number #define cleanup_MARK() (__FLAG = 1) //! Set the local cleanup flag to zero #define cleanup_UNMARK() (__FLAG = 0) //! Check if the local cleanup flag is non-zero #define cleanup_ERRORFLAGGED (__FLAG != 0) //! Conditionally execute some `code` if the local cleanup flag has not been marked #define cleanup_CNDEXEC(code) while(!cleanup_ERRORFLAGGED) {code; break;} //! Conditionally fire the local cleanup object and return `ret` #define cleanup_CNDFIRERET(ret) do {cleanup_CNDFIRE(); return ret;} while (0) #endif