mallailua
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
static inline u32 has_mask(u32 flags, u32 mask) { return flags & mask; }
|
static inline u32 has_mask(u32 flags, u32 mask) { return flags & mask; }
|
||||||
|
|
||||||
#define MIN(A, B) ((A) < (B) ? (A) : (B))
|
#define MIN(A, B) ((A) < (B) ? (A) : (B))
|
||||||
|
#define MAX(A, B) ((A) > (B) ? (A) : (B))
|
||||||
#define ARRLEN(A) (sizeof((A)) / (sizeof((A)[0])))
|
#define ARRLEN(A) (sizeof((A)) / (sizeof((A)[0])))
|
||||||
|
|
||||||
#endif // COMMON_H_
|
#endif // COMMON_H_
|
||||||
|
|||||||
123
src/files.c
123
src/files.c
@@ -2,7 +2,14 @@
|
|||||||
|
|
||||||
#include "logger/logger.h"
|
#include "logger/logger.h"
|
||||||
#include "string/string.h"
|
#include "string/string.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#ifndef HI_FILE_BUFFER_SIZE
|
||||||
|
#define HI_FILE_BUFFER_SIZE 4096
|
||||||
|
#endif
|
||||||
|
|
||||||
char *hi_file_to_str_dyn(const char *filename) {
|
char *hi_file_to_str_dyn(const char *filename) {
|
||||||
|
|
||||||
@@ -14,3 +21,119 @@ char *hi_file_to_str_dyn(const char *filename) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HiFileType hi_file_interpret_as_type(const char *path) {
|
||||||
|
const char *pathname_end = path + strlen(path);
|
||||||
|
char last_char = *(pathname_end - 1);
|
||||||
|
bool as_dir = (last_char == '/') || (last_char == '\\');
|
||||||
|
return as_dir ? HI_FILE_TYPE_DIR : HI_FILE_TYPE_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *hi_file_name_from_path(const char *path) {
|
||||||
|
const char *filename = path;
|
||||||
|
|
||||||
|
// Find the last directory separator
|
||||||
|
const char *last_slash = strrchr(path, '/');
|
||||||
|
const char *last_backslash = strrchr(path, '\\');
|
||||||
|
|
||||||
|
// Determine which separator was found last (if any)
|
||||||
|
if (last_slash && (!last_backslash || last_slash > last_backslash)) {
|
||||||
|
filename = last_slash + 1;
|
||||||
|
} else if (last_backslash) {
|
||||||
|
filename = last_backslash + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
HiloadResult hi_file_copy(const char *srcname, const char *destination) {
|
||||||
|
|
||||||
|
HiloadResult ret = HILOAD_FAIL;
|
||||||
|
|
||||||
|
FILE *src = fopen(srcname, "rb");
|
||||||
|
if (src == NULL) {
|
||||||
|
log_error("Couldn't open file: %s for copy\n", srcname);
|
||||||
|
return HILOAD_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[512];
|
||||||
|
|
||||||
|
const char *dstname = destination;
|
||||||
|
|
||||||
|
HiFileType dst_type = hi_file_interpret_as_type(destination);
|
||||||
|
if (dst_type == HI_FILE_TYPE_DIR) {
|
||||||
|
hi_strncat_buf(sizeof(buf), buf, destination,
|
||||||
|
hi_file_name_from_path(srcname));
|
||||||
|
dstname = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *dst = fopen(dstname, "wb");
|
||||||
|
if (dst == NULL) {
|
||||||
|
log_error("Failed to open destination: %s\n", dstname);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[HI_FILE_BUFFER_SIZE];
|
||||||
|
size_t bytes_read = 0;
|
||||||
|
while ((bytes_read = fread(buffer, 1, sizeof buffer, src)) > 0) {
|
||||||
|
if (fwrite(buffer, 1, bytes_read, dst) != bytes_read) {
|
||||||
|
log_error("Error writing to destination: %s\n", dstname);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ferror(src)) {
|
||||||
|
log_error("Error reading from source: %s\n");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = HILOAD_OK;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
if (src) fclose(src);
|
||||||
|
if (dst) fclose(dst);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check file type from path.
|
||||||
|
*
|
||||||
|
* TAG: posix
|
||||||
|
*/
|
||||||
|
HiFileType hi_file_type(const char *path) {
|
||||||
|
struct stat path_stat;
|
||||||
|
int err = stat(path, &path_stat);
|
||||||
|
if (err == -1) {
|
||||||
|
if (errno == ENOENT) {
|
||||||
|
// Doesn't exist, don't count as error
|
||||||
|
return HI_FILE_TYPE_NONE;
|
||||||
|
}
|
||||||
|
log_error("Opening file failed: %s\n", strerror(errno));
|
||||||
|
return HI_FILE_TYPE_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (S_ISREG(path_stat.st_mode)) {
|
||||||
|
return HI_FILE_TYPE_FILE;
|
||||||
|
}
|
||||||
|
if (S_ISDIR(path_stat.st_mode)) {
|
||||||
|
return HI_FILE_TYPE_DIR;
|
||||||
|
}
|
||||||
|
if (S_ISCHR(path_stat.st_mode)) {
|
||||||
|
return HI_FILE_TYPE_CHR;
|
||||||
|
}
|
||||||
|
if (S_ISBLK(path_stat.st_mode)) {
|
||||||
|
return HI_FILE_TYPE_BLK;
|
||||||
|
}
|
||||||
|
if (S_ISFIFO(path_stat.st_mode)) {
|
||||||
|
return HI_FILE_TYPE_FIFO;
|
||||||
|
}
|
||||||
|
if (S_ISLNK(path_stat.st_mode)) {
|
||||||
|
return HI_FILE_TYPE_LNK;
|
||||||
|
}
|
||||||
|
if (S_ISSOCK(path_stat.st_mode)) {
|
||||||
|
return HI_FILE_TYPE_SOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HI_FILE_TYPE_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
27
src/files.h
27
src/files.h
@@ -1,6 +1,21 @@
|
|||||||
#ifndef FILES_H_
|
#ifndef FILES_H_
|
||||||
#define FILES_H_
|
#define FILES_H_
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
HI_FILE_TYPE_FILE = 0,
|
||||||
|
HI_FILE_TYPE_DIR,
|
||||||
|
HI_FILE_TYPE_CHR,
|
||||||
|
HI_FILE_TYPE_BLK,
|
||||||
|
HI_FILE_TYPE_FIFO,
|
||||||
|
HI_FILE_TYPE_LNK,
|
||||||
|
HI_FILE_TYPE_SOCK,
|
||||||
|
|
||||||
|
HI_FILE_TYPE_COUNT,
|
||||||
|
HI_FILE_TYPE_NONE,
|
||||||
|
} HiFileType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read file dynamically to a string
|
* Read file dynamically to a string
|
||||||
*
|
*
|
||||||
@@ -8,7 +23,19 @@
|
|||||||
* with no size. The realistic optimum case contains two allocations, one for
|
* with no size. The realistic optimum case contains two allocations, one for
|
||||||
* the initial memory and a reallocation to match the string size.
|
* the initial memory and a reallocation to match the string size.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
char *hi_file_to_str_dyn(const char *filename);
|
char *hi_file_to_str_dyn(const char *filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy file \p filename to \p dest
|
||||||
|
*
|
||||||
|
* If \p dest has either '/' or '\' as the last character, it is interpreted as
|
||||||
|
* a directory and the file is copied to the directory with the same filename.
|
||||||
|
*/
|
||||||
|
HiloadResult hi_file_copy(const char *filename, const char *dest);
|
||||||
|
|
||||||
|
HiFileType hi_file_type(const char *path);
|
||||||
|
|
||||||
|
const char *hi_file_name_from_path(const char *path);
|
||||||
|
|
||||||
#endif // FILES_H_
|
#endif // FILES_H_
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ int hi_init(unsigned n, const char **enabled_modules) {
|
|||||||
sc_array_add(&context.enabled_modules, module_name);
|
sc_array_add(&context.enabled_modules, module_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sc_array_init(&context.memory_regions);
|
||||||
if (read_memory_maps_self(&context.memory_regions) != HILOAD_OK) {
|
if (read_memory_maps_self(&context.memory_regions) != HILOAD_OK) {
|
||||||
log_error("Could not populate program memory maps.\n");
|
log_error("Could not populate program memory maps.\n");
|
||||||
return HILOAD_FAIL;
|
return HILOAD_FAIL;
|
||||||
|
|||||||
26
src/memory.c
26
src/memory.c
@@ -31,8 +31,7 @@ HiloadResult memory_find_region(uptr ptr, struct sc_array_memreg *const regions,
|
|||||||
}
|
}
|
||||||
|
|
||||||
HiloadResult read_memory_maps_self(struct sc_array_memreg *regions) {
|
HiloadResult read_memory_maps_self(struct sc_array_memreg *regions) {
|
||||||
sc_array_clear(regions);
|
memory_clear_memregs(regions);
|
||||||
sc_array_init(regions);
|
|
||||||
|
|
||||||
char *maps_str = hi_file_to_str_dyn("/proc/self/maps");
|
char *maps_str = hi_file_to_str_dyn("/proc/self/maps");
|
||||||
if (!maps_str)
|
if (!maps_str)
|
||||||
@@ -103,10 +102,31 @@ memory_get_module_span(const struct sc_array_memreg *const regions,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we passed the module regions
|
// we passed the module regions
|
||||||
if (end > 0) break;
|
if (end > 0)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(start < ~0ull && end > 0);
|
assert(start < ~0ull && end > 0);
|
||||||
return (MemoryRegionSpan){.region_start = start, .region_end = end};
|
return (MemoryRegionSpan){.region_start = start, .region_end = end};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void memory_clear_memregs(struct sc_array_memreg *regions) {
|
||||||
|
for (size_t i = 0; i < sc_array_size(regions); i++) {
|
||||||
|
memory_free_memreg(&sc_array_at(regions, i));
|
||||||
|
}
|
||||||
|
sc_array_clear(regions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void memory_term_memregs(struct sc_array_memreg *regions) {
|
||||||
|
for (size_t i = 0; i < sc_array_size(regions); i++) {
|
||||||
|
memory_free_memreg(&sc_array_at(regions, i));
|
||||||
|
}
|
||||||
|
sc_array_term(regions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void memory_free_memreg(MemoryRegion *reg) {
|
||||||
|
if (reg) {
|
||||||
|
free((char *)reg->pathname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,9 +27,15 @@ typedef struct {
|
|||||||
const char *pathname;
|
const char *pathname;
|
||||||
u32 permission; // enum MemoryPermissions
|
u32 permission; // enum MemoryPermissions
|
||||||
} MemoryRegion;
|
} MemoryRegion;
|
||||||
|
|
||||||
sc_array_def(MemoryRegion, memreg);
|
sc_array_def(MemoryRegion, memreg);
|
||||||
|
|
||||||
|
// Free memory and init to zero
|
||||||
|
void memory_term_memregs(struct sc_array_memreg *regions);
|
||||||
|
// Doesn't free underlying array memory, only for each region
|
||||||
|
void memory_clear_memregs(struct sc_array_memreg *regions);
|
||||||
|
// Free child memory
|
||||||
|
void memory_free_memreg(MemoryRegion *reg);
|
||||||
|
|
||||||
/* A pointer that can be used to place the memory regions into. Clears regions
|
/* A pointer that can be used to place the memory regions into. Clears regions
|
||||||
* before use, but uses the same buffer. */
|
* before use, but uses the same buffer. */
|
||||||
HiloadResult read_memory_maps_self(struct sc_array_memreg *regions);
|
HiloadResult read_memory_maps_self(struct sc_array_memreg *regions);
|
||||||
|
|||||||
@@ -7,18 +7,14 @@
|
|||||||
#include <libelf.h>
|
#include <libelf.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
void *hi_elf_load_module(const char *filename) {
|
void *hi_elf_load_module(void *address, size_t n) {
|
||||||
int fd = open(filename, O_RDONLY);
|
|
||||||
|
|
||||||
Elf *elf = elf_begin(fd, ELF_C_READ, NULL);
|
|
||||||
|
|
||||||
|
Elf *elf = elf_memory(address, n);
|
||||||
Elf_Kind modkind = elf_kind(elf);
|
Elf_Kind modkind = elf_kind(elf);
|
||||||
log_debug("kind: %u\n", modkind);
|
log_debug("kind: %u\n", modkind);
|
||||||
|
|
||||||
uptr modbase = elf_getbase(elf);
|
elf_end(elf);
|
||||||
|
return address;
|
||||||
close(fd);
|
|
||||||
return (void*)modbase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void hi_elf_print_module_from_memory(void *address, size_t size) {
|
void hi_elf_print_module_from_memory(void *address, size_t size) {
|
||||||
@@ -36,7 +32,7 @@ void hi_elf_print_module_from_memory(void *address, size_t size) {
|
|||||||
|
|
||||||
for (size_t i = 0; i < phdrnum; ++i) {
|
for (size_t i = 0; i < phdrnum; ++i) {
|
||||||
Elf64_Phdr *p = phdr + i;
|
Elf64_Phdr *p = phdr + i;
|
||||||
log_debugv("segment type: %s\n", segment_type_to_str(p->p_type));
|
log_debug("segment type: %s\n", segment_type_to_str(p->p_type));
|
||||||
|
|
||||||
size_t segment_size = p->p_memsz;
|
size_t segment_size = p->p_memsz;
|
||||||
void *segment_start = address + p->p_vaddr;
|
void *segment_start = address + p->p_vaddr;
|
||||||
@@ -57,7 +53,7 @@ void hi_elf_print_module_from_memory(void *address, size_t size) {
|
|||||||
if (p->p_type == PT_DYNAMIC) {
|
if (p->p_type == PT_DYNAMIC) {
|
||||||
Elf64_Dyn *dyn = (Elf64_Dyn *)segment_start;
|
Elf64_Dyn *dyn = (Elf64_Dyn *)segment_start;
|
||||||
while ((void *)dyn < segment_end) {
|
while ((void *)dyn < segment_end) {
|
||||||
log_debugv(" dyn type: %s\n", dyn_type_to_str(dyn->d_tag));
|
log_debug(" dyn type: %s\n", dyn_type_to_str(dyn->d_tag));
|
||||||
|
|
||||||
if (dyn->d_tag == DT_STRTAB) {
|
if (dyn->d_tag == DT_STRTAB) {
|
||||||
strtab = (void *)dyn->d_un.d_ptr;
|
strtab = (void *)dyn->d_un.d_ptr;
|
||||||
@@ -84,7 +80,7 @@ void hi_elf_print_module_from_memory(void *address, size_t size) {
|
|||||||
}
|
}
|
||||||
++dyn;
|
++dyn;
|
||||||
}
|
}
|
||||||
log_debugv("\nstrtab: %p\n"
|
log_debug("\nstrtab: %p\n"
|
||||||
"symtab: %p\n"
|
"symtab: %p\n"
|
||||||
"strsz: %zu\n"
|
"strsz: %zu\n"
|
||||||
"syment: %zu\n"
|
"syment: %zu\n"
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
#ifndef ELF_H_
|
#ifndef ELF_H_
|
||||||
#define ELF_H_
|
#define ELF_H_
|
||||||
|
|
||||||
|
// TODO: Move these into .c file after API has found its form
|
||||||
|
#include <elf.h>
|
||||||
#include <libelf.h>
|
#include <libelf.h>
|
||||||
#include <gelf.h>
|
#include <gelf.h>
|
||||||
|
|
||||||
void *hi_elf_load_module(const char* filename);
|
void *hi_elf_load_module(void *address, size_t n);
|
||||||
void hi_elf_print_module_from_memory(void *address, size_t size);
|
void hi_elf_print_module_from_memory(void *address, size_t size);
|
||||||
|
|
||||||
const char *dyn_type_to_str(unsigned type);
|
const char *dyn_type_to_str(unsigned type);
|
||||||
|
|||||||
@@ -1,21 +1,64 @@
|
|||||||
#include "moduler.h"
|
#include "moduler.h"
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
#include "logger/logger.h"
|
#include "logger/logger.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
#include "moduler/elf.h"
|
#include "moduler/elf.h"
|
||||||
|
#include "string/string.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <libelf.h>
|
||||||
|
#include <link.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
static HiloadResult moduler_apply_patch(void *address, size_t n) {
|
||||||
|
|
||||||
|
HiloadResult ret = HILOAD_FAIL;
|
||||||
|
|
||||||
|
Elf *elf = elf_memory(address, n);
|
||||||
|
|
||||||
|
hi_elf_print_module_from_memory(address, n);
|
||||||
|
ret = HILOAD_OK;
|
||||||
|
|
||||||
|
elf_end(elf);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
HiloadResult moduler_reload(HiModuleData *module,
|
HiloadResult moduler_reload(HiModuleData *module,
|
||||||
const struct sc_array_memreg *const memregs) {
|
struct sc_array_memreg *memregs) {
|
||||||
|
|
||||||
MemoryRegionSpan memspan = memory_get_module_span(memregs, module->name);
|
MemoryRegionSpan memspan = memory_get_module_span(memregs, module->name);
|
||||||
log_debugv("Module: %s - [%p, %p]\n", module->name, memspan.region_start,
|
log_debugv("Module: %s - [%p, %p]\n", module->name, memspan.region_start,
|
||||||
memspan.region_end);
|
memspan.region_end);
|
||||||
|
|
||||||
void *address = hi_elf_load_module(module->name);
|
dlerror();
|
||||||
log_debug("modaddress: %p\n", address);
|
char patch_name[512];
|
||||||
|
size_t written =
|
||||||
|
hi_strncat_buf(sizeof(patch_name), patch_name, module->name, ".patch");
|
||||||
|
if (!written) {
|
||||||
|
log_error("Failed to concat %s and %s\n", module->name, ".patch");
|
||||||
|
return HILOAD_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_debug("Opening: %s\n", patch_name);
|
||||||
|
|
||||||
|
void *new_handle = dlopen(patch_name, RTLD_NOW);
|
||||||
|
if (!new_handle) {
|
||||||
|
log_error("Couldn't load: %s\n", dlerror());
|
||||||
|
module->state = HI_MODULE_STATE_CLEAN;
|
||||||
|
return HILOAD_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
read_memory_maps_self(memregs);
|
||||||
|
memspan = memory_get_module_span(memregs, patch_name);
|
||||||
|
|
||||||
|
void *patch_address = (void *)memspan.region_start;
|
||||||
|
log_debug("patch address: %p\n", patch_address);
|
||||||
|
|
||||||
|
moduler_apply_patch(patch_address, memspan.region_end - memspan.region_start);
|
||||||
|
|
||||||
module->state = HI_MODULE_STATE_CLEAN;
|
module->state = HI_MODULE_STATE_CLEAN;
|
||||||
return HILOAD_OK;
|
return HILOAD_OK;
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ sc_array_def(HiModuleData, module);
|
|||||||
typedef struct sc_array_module HiModuleArray;
|
typedef struct sc_array_module HiModuleArray;
|
||||||
|
|
||||||
HiloadResult moduler_reload(HiModuleData *module,
|
HiloadResult moduler_reload(HiModuleData *module,
|
||||||
const struct sc_array_memreg *const memregs);
|
struct sc_array_memreg *memregs);
|
||||||
|
|
||||||
|
|
||||||
#endif // MODULER_H_
|
#endif // MODULER_H_
|
||||||
|
|||||||
@@ -1,10 +1,33 @@
|
|||||||
#include "string.h"
|
#include "string.h"
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
size_t hi_strncat_buf(size_t buflen, char buf[buflen], const char *first, const char *second) {
|
||||||
|
if (!buf) return 0;
|
||||||
|
|
||||||
|
size_t first_len = strlen(first);
|
||||||
|
size_t second_len = strlen(second);
|
||||||
|
|
||||||
|
if (buf + first_len + second_len > buf + buflen - 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char * first_end = buf + first_len;
|
||||||
|
char * second_end = first_end + second_len;
|
||||||
|
|
||||||
|
strncpy(buf, first, first_end - buf);
|
||||||
|
strncpy(first_end, second, second_end - first_end);
|
||||||
|
|
||||||
|
*second_end = '\0';
|
||||||
|
|
||||||
|
return second_end - buf;
|
||||||
|
}
|
||||||
|
|
||||||
int hi_path_has_filename(const char *path, const char *filename) {
|
int hi_path_has_filename(const char *path, const char *filename) {
|
||||||
|
|
||||||
const char *compared_filename = path;
|
const char *compared_filename = path;
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenate two strings into a buffer.
|
||||||
|
*
|
||||||
|
* If resulting string would be longer than buflen - 1, the resulting string in \p
|
||||||
|
* buf is unchanged.
|
||||||
|
*
|
||||||
|
* @param first Null terminated character string
|
||||||
|
* @param second Null terminated character string
|
||||||
|
*/
|
||||||
|
size_t hi_strncat_buf(size_t bufsize, char buf[bufsize], const char *first,
|
||||||
|
const char *second);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Copy file content to a null terminated string, allocating memory while
|
* @brief Copy file content to a null terminated string, allocating memory while
|
||||||
* reading.
|
* reading.
|
||||||
@@ -18,8 +30,7 @@
|
|||||||
* @param nmax if not 0, this amount of memory in bytes is read and used as
|
* @param nmax if not 0, this amount of memory in bytes is read and used as
|
||||||
* initial allocation
|
* initial allocation
|
||||||
*/
|
*/
|
||||||
char *hi_string_from_file_dyn(const char *filename, size_t *nread,
|
char *hi_string_from_file_dyn(const char *filename, size_t *nread, size_t nmax);
|
||||||
size_t nmax);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find if filename exists at the end of path.
|
* Find if filename exists at the end of path.
|
||||||
|
|||||||
Reference in New Issue
Block a user