rizin/librz/util/file.c

1440 lines
30 KiB
C

// SPDX-FileCopyrightText: 2007-2020 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only
#include "rz_types.h"
#include "rz_util.h"
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <rz_lib.h>
#if __UNIX__
#include <sys/time.h>
#include <sys/mman.h>
#include <limits.h>
#endif
#if HAVE_COPYFILE
#include <copyfile.h>
#endif
#if _MSC_VER
#include <rz_windows.h>
#include <process.h>
#endif
#define BS 1024
#ifdef __WINDOWS__
#define StructStat struct _stat64
#else
#define StructStat struct stat
#endif
static int file_stat(const char *file, StructStat *pStat) {
rz_return_val_if_fail(file && pStat, -1);
#if __WINDOWS__
wchar_t *wfile = rz_utf8_to_utf16(file);
if (!wfile) {
return -1;
}
int ret = _wstati64(wfile, pStat);
free(wfile);
return ret;
#else // __WINDOWS__
return stat(file, pStat);
#endif // __WINDOWS__
}
RZ_API bool rz_file_truncate(const char *filename, ut64 newsize) {
rz_return_val_if_fail(filename, false);
int fd;
if (rz_file_is_directory(filename)) {
return false;
}
if (!rz_file_exists(filename) || !rz_file_is_regular(filename)) {
return false;
}
#if __WINDOWS__
fd = rz_sys_open(filename, O_RDWR, 0644);
#else
fd = rz_sys_open(filename, O_RDWR | O_SYNC, 0644);
#endif
if (fd == -1) {
return false;
}
#ifdef _MSC_VER
int r = _chsize(fd, newsize);
#else
int r = ftruncate(fd, newsize);
#endif
if (r != 0) {
eprintf("Could not resize %s file\n", filename);
close(fd);
return false;
}
close(fd);
return true;
}
/*
Example:
str = rz_file_basename ("home/inisider/Downloads/user32.dll");
// str == user32.dll
*/
RZ_API const char *rz_file_basename(const char *path) {
rz_return_val_if_fail(path, NULL);
const char *ptr = rz_str_rchr(path, NULL, '/');
if (ptr) {
path = ptr + 1;
}
#if __WINDOWS__
if ((ptr = rz_str_rchr(path, NULL, '\\'))) {
path = ptr + 1;
}
#endif
return path;
}
/* \brief Returns file name from a path accepting both `/` and `\` as directory separators
*
* \param path Path of file to get the file name
* \return const char * Pointer to the file name
*/
RZ_API const char *rz_file_dos_basename(RZ_BORROW RZ_NONNULL const char *path) {
rz_return_val_if_fail(path, NULL);
const char *ptr = rz_str_rchr(path, NULL, '/');
if (ptr) {
path = ptr + 1;
}
if ((ptr = rz_str_rchr(path, NULL, '\\'))) {
path = ptr + 1;
}
return path;
}
/*
Example:
str = rz_file_dirname ("home/inisider/Downloads/user32.dll");
// str == "home/inisider/Downloads"
free (str);
*/
RZ_API char *rz_file_dirname(const char *path) {
rz_return_val_if_fail(path, NULL);
char *newpath = rz_str_dup(path);
char *ptr = (char *)rz_str_rchr(newpath, NULL, '/');
if (ptr) {
if (ptr == newpath) {
ptr++;
}
*ptr = 0;
} else {
ptr = (char *)rz_str_rchr(newpath, NULL, '\\');
if (!ptr) {
ptr = newpath;
}
if (ptr && ptr == newpath && *ptr == '.') { // keep '.'
ptr++;
if (*ptr == '.') { // keep '..'
ptr++;
}
}
if (ptr) {
*ptr = 0;
}
}
return newpath;
}
RZ_API bool rz_file_is_c(const char *file) {
rz_return_val_if_fail(file, false);
const char *ext = rz_str_lchr(file, '.'); // TODO: add api in rz_file_extension or rz_str_ext for this
if (ext) {
ext++;
if (!strcmp(ext, "cparse") || !strcmp(ext, "c") || !strcmp(ext, "h")) {
return true;
}
}
return false;
}
RZ_API bool rz_file_is_regular(const char *str) {
StructStat buf = { 0 };
if (!str || !*str || file_stat(str, &buf) == -1) {
return false;
}
return ((S_IFREG & buf.st_mode) == S_IFREG);
}
/**
* \brief Checks if the given path is a directory.
*
* \return true if \p str points to a directory.
* \return false otherwise.
*/
RZ_API bool rz_file_is_directory(RZ_NULLABLE const char *str) {
if (RZ_STR_ISEMPTY(str)) {
return false;
}
StructStat buf = { 0 };
if (file_stat(str, &buf) == -1) {
return false;
}
#ifdef S_IFBLK
if ((S_IFBLK & buf.st_mode) == S_IFBLK) {
return false;
}
#endif
return S_IFDIR == (S_IFDIR & buf.st_mode);
}
RZ_API bool rz_file_fexists(const char *fmt, ...) {
int ret;
char string[BS];
va_list ap;
va_start(ap, fmt);
vsnprintf(string, sizeof(string), fmt, ap);
ret = rz_file_exists(string);
va_end(ap);
return ret;
}
/**
* \brief Checks if the given path points to an existing file.
*
* \return true if the file at \p str exists.
* \return false otherwise.
*/
RZ_API bool rz_file_exists(RZ_NULLABLE const char *str) {
if (RZ_STR_ISEMPTY(str)) {
return false;
}
char *absfile = rz_file_abspath(str);
StructStat buf = { 0 };
if (file_stat(absfile, &buf) == -1) {
free(absfile);
return false;
}
free(absfile);
return S_IFREG == (S_IFREG & buf.st_mode);
}
RZ_API ut64 rz_file_size(const char *str) {
rz_return_val_if_fail(!RZ_STR_ISEMPTY(str), 0);
StructStat buf = { 0 };
if (file_stat(str, &buf) == -1) {
return 0;
}
return (ut64)buf.st_size;
}
RZ_API bool rz_file_is_abspath(const char *file) {
rz_return_val_if_fail(!RZ_STR_ISEMPTY(file), 0);
return ((*file && file[1] == ':') || *file == '/');
}
RZ_API RZ_OWN char *rz_file_abspath_rel(const char *cwd, const char *file) {
char *ret = NULL;
if (RZ_STR_ISEMPTY(file) || !strcmp(file, ".") || !strcmp(file, "./")) {
return rz_str_dup(cwd);
}
if (strstr(file, "://")) {
return rz_str_dup(file);
}
ret = rz_path_home_expand(file);
#if __UNIX__
if (cwd && *ret != '/') {
char *tmp = rz_str_newf("%s" RZ_SYS_DIR "%s", cwd, ret);
if (!tmp) {
free(ret);
return NULL;
}
free(ret);
ret = tmp;
}
#elif __WINDOWS__
// Network path
if (!strncmp(ret, "\\\\", 2)) {
return rz_str_dup(ret);
}
if (!strchr(ret, ':')) {
char *tmp = rz_str_newf("%s" RZ_SYS_DIR "%s", cwd, ret);
if (!tmp) {
free(ret);
return NULL;
}
free(ret);
ret = tmp;
}
#endif
#if HAVE_REALPATH
char rp[PATH_MAX] = { 0 };
char *abspath = realpath(ret, rp); // second arg == NULL is only an extension
if (abspath) {
abspath = rz_str_dup(abspath);
if (abspath) {
free(ret);
ret = abspath;
}
}
#endif
return ret;
}
RZ_API RZ_OWN char *rz_file_abspath(const char *file) {
rz_return_val_if_fail(file, NULL);
char *cwd = rz_sys_getdir();
if (cwd) {
char *ret = rz_file_abspath_rel(cwd, file);
free(cwd);
return ret;
}
return NULL;
}
RZ_API char *rz_file_relpath(const char *base, const char *path) {
// skip longest common prefix
while (*base && *path) {
while (*base == *RZ_SYS_DIR) {
base++;
}
while (*path == *RZ_SYS_DIR) {
path++;
}
while (*base && *path && *base != *RZ_SYS_DIR && *path != *RZ_SYS_DIR) {
if (*base != *path) {
goto diverge;
}
base++;
path++;
}
}
while (*path == *RZ_SYS_DIR) {
path++;
}
size_t ups;
diverge:
// count number of ".." needed which is just the number of remaining tokens in base
ups = 0;
while (*base) {
while (*base == *RZ_SYS_DIR) {
base++;
}
if (!*base) {
break;
}
ups++;
while (*base && *base != *RZ_SYS_DIR) {
base++;
}
}
// put all the ".."s and append the rest of the path
size_t suff_len = strlen(path);
char *r = malloc(ups * 3 + suff_len + 1); // ups * strlen("../") + strlen(path)
if (!r) {
return NULL;
}
size_t i;
for (i = 0; i < ups; i++) {
r[i * 3] = '.';
r[i * 3 + 1] = '.';
r[i * 3 + 2] = *RZ_SYS_DIR;
}
memcpy(r + i * 3, path, suff_len + 1);
return r;
}
RZ_API char *rz_file_path_local_to_unix(const char *path) {
char *r = rz_str_dup(path);
if (!r) {
return NULL;
}
#if __WINDOWS__
rz_str_replace(r, RZ_SYS_DIR, "/", true);
#endif
return r;
}
RZ_API char *rz_file_path_unix_to_local(const char *path) {
char *r = rz_str_dup(path);
if (!r) {
return NULL;
}
#if __WINDOWS__
rz_str_replace(r, "/", RZ_SYS_DIR, true);
#endif
return r;
}
RZ_API char *rz_file_path(const char *bin) {
rz_return_val_if_fail(bin, NULL);
char *file = NULL;
char *path = NULL;
char *str, *ptr;
const char *extension = "";
if (!strncmp(bin, "./", 2)) {
return rz_file_exists(bin)
? rz_file_abspath(bin)
: NULL;
}
char *path_env = (char *)rz_sys_getenv("PATH");
#if __WINDOWS__
if (!rz_str_endswith(bin, ".exe")) {
extension = ".exe";
}
#endif
if (path_env) {
str = path = rz_str_dup(path_env);
do {
ptr = strchr(str, RZ_SYS_ENVSEP[0]);
if (ptr) {
*ptr = '\0';
file = rz_str_newf(RZ_JOIN_2_PATHS("%s", "%s%s"), str, bin, extension);
if (rz_file_exists(file)) {
free(path);
free(path_env);
return file;
}
str = ptr + 1;
free(file);
}
} while (ptr);
}
free(path_env);
free(path);
return rz_str_dup(bin);
}
RZ_API char *rz_file_binsh(void) {
char *bin_sh = rz_sys_getenv("SHELL");
if (RZ_STR_ISNOTEMPTY(bin_sh)) {
return bin_sh;
}
free(bin_sh);
bin_sh = rz_file_path("sh");
if (RZ_STR_ISNOTEMPTY(bin_sh)) {
return bin_sh;
}
free(bin_sh);
bin_sh = rz_str_dup("/bin/sh");
return bin_sh;
}
RZ_API char *rz_stdin_slurp(int *sz) {
#if __UNIX__ || __WINDOWS__
int i, ret, newfd;
if ((newfd = dup(0)) < 0) {
return NULL;
}
char *buf = malloc(BS);
if (!buf) {
close(newfd);
return NULL;
}
for (i = 0; i >= 0; i += ret) {
char *new = realloc(buf, i + BS);
if (!new) {
eprintf("Cannot realloc to %d\n", i + BS);
free(buf);
close(newfd);
return NULL;
}
buf = new;
ret = read(0, buf + i, BS);
if (ret < 1) {
break;
}
}
if (i < 1) {
i = 0;
RZ_FREE(buf);
} else {
buf[i] = 0;
dup2(newfd, 0);
close(newfd);
}
if (sz) {
*sz = i;
}
if (!i) {
RZ_FREE(buf);
}
return buf;
#else
#warning TODO rz_stdin_slurp
return NULL;
#endif
}
RZ_API RZ_OWN char *rz_file_slurp(const char *str, RZ_NULLABLE size_t *usz) {
rz_return_val_if_fail(str, NULL);
if (usz) {
*usz = 0;
}
if (!rz_file_exists(str)) {
return NULL;
}
FILE *fd = rz_sys_fopen(str, "rb");
if (!fd) {
return NULL;
}
if (fseek(fd, 0, SEEK_END) == -1) {
// cannot determine the size of the file
}
long sz = ftell(fd);
if (sz < 0) {
fclose(fd);
return NULL;
}
if (!sz) {
if (rz_file_is_regular(str)) {
char *buf = NULL;
long size = 0;
(void)fseek(fd, 0, SEEK_SET);
do {
char *nbuf = realloc(buf, size + BS);
if (!nbuf) {
break;
}
buf = nbuf;
size_t r = fread(buf + size, 1, BS, fd);
if (ferror(fd)) {
RZ_FREE(buf);
goto regular_err;
}
size += r;
} while (!feof(fd));
char *nbuf = realloc(buf, size + 1);
if (!nbuf) {
fclose(fd);
free(buf);
return NULL;
}
buf = nbuf;
buf[size] = '\0';
if (usz) {
*usz = size;
}
regular_err:
fclose(fd);
return buf;
}
// try to read 64K
sz = UT16_MAX;
}
rewind(fd);
char *ret = (char *)malloc(sz + 1);
if (!ret) {
fclose(fd);
return NULL;
}
size_t rsz = fread(ret, 1, sz, fd);
if (rsz != sz) {
eprintf("Warning: rz_file_slurp: fread: truncated read\n");
sz = rsz;
}
fclose(fd);
ret[sz] = '\0';
if (usz) {
*usz = sz;
}
return ret;
}
RZ_API ut8 *rz_file_gzslurp(const char *str, int *outlen, int origonfail) {
rz_return_val_if_fail(str, NULL);
if (outlen) {
*outlen = 0;
}
size_t sz;
ut8 *in = (ut8 *)rz_file_slurp(str, &sz);
if (!in) {
return NULL;
}
ut8 *out = rz_inflate(in, (int)sz, NULL, outlen);
if (!out && origonfail) {
// if uncompression fails, return orig buffer ?
if (outlen) {
*outlen = (int)sz;
}
in[sz] = 0;
return in;
}
free(in);
return out;
}
RZ_API ut8 *rz_file_slurp_hexpairs(const char *str, int *usz) {
rz_return_val_if_fail(str, NULL);
if (usz) {
*usz = 0;
}
ut8 *ret;
long sz;
int c, bytes = 0;
FILE *fd = rz_sys_fopen(str, "rb");
if (!fd) {
return NULL;
}
(void)fseek(fd, 0, SEEK_END);
sz = ftell(fd);
(void)fseek(fd, 0, SEEK_SET);
ret = (ut8 *)malloc((sz >> 1) + 1);
if (!ret) {
fclose(fd);
return NULL;
}
for (;;) {
if (fscanf(fd, " #%*[^\n]") == 1) {
continue;
}
if (fscanf(fd, "%02x", &c) == 1) {
ret[bytes++] = c;
continue;
}
if (feof(fd)) {
break;
}
free(ret);
fclose(fd);
return NULL;
}
ret[bytes] = '\0';
fclose(fd);
if (usz) {
*usz = bytes;
}
return ret;
}
/**
* \brief Reads \p sz bytes from the file \p str at \p off. Note, this function
* doesn't support files larger than INT_MAX bytes currently.
*
* \param str The file name.
* \param off The offset to start reading in the file.
* \param sz The number of bytes to read.
* \param osz If not NULL, the number of bytes read.
*
* \return Buffer with the read content. The buffer is always '\0' terminated.
*/
RZ_API RZ_OWN char *rz_file_slurp_range(RZ_NONNULL const char *str, ut64 off, int sz, RZ_OUT RZ_NULLABLE int *osz) {
rz_return_val_if_fail(str, NULL);
char *ret;
size_t read_items;
FILE *fd = rz_sys_fopen(str, "rb");
if (!fd) {
return NULL;
}
// XXX handle out of bound reads (eof)
if (fseek(fd, off, SEEK_SET) < 0) {
fclose(fd);
return NULL;
}
ret = (char *)malloc(sz + 1);
if (ret) {
if (osz) {
*osz = (int)(size_t)fread(ret, 1, sz, fd);
} else {
read_items = fread(ret, 1, sz, fd);
if (!read_items) {
fclose(fd);
return ret;
}
}
ret[sz] = '\0';
}
fclose(fd);
return ret;
}
RZ_API char *rz_file_slurp_random_line(const char *file) {
rz_return_val_if_fail(file, NULL);
int i = 0;
return rz_file_slurp_random_line_count(file, &i);
}
RZ_API char *rz_file_slurp_random_line_count(const char *file, int *line) {
rz_return_val_if_fail(file && line, NULL);
/* Reservoir Sampling */
char *ptr = NULL, *str;
size_t i, lines, selection = -1;
int start = *line;
if ((str = rz_file_slurp(file, NULL))) {
rz_num_irand();
for (i = 0; str[i]; i++) {
if (str[i] == '\n') {
// here rand doesn't have any security implication
// https://www.securecoding.cert.org/confluence/display/c/MSC30-C.+Do+not+use+the+rand()+function+for+generating+pseudorandom+numbers
if (!(rz_num_rand32((++(*line))))) {
selection = (*line - 1); /* The line we want. */
}
}
}
if ((selection < start) || (selection == -1)) {
free(str);
return NULL;
} else {
lines = selection - start;
}
if (lines > 0) {
for (i = 0; str[i] && lines; i++) {
if (str[i] == '\n') {
lines--;
}
}
ptr = str + i;
for (i = 0; ptr[i]; i++) {
if (ptr[i] == '\n') {
ptr[i] = '\0';
break;
}
}
ptr = rz_str_dup(ptr);
}
free(str);
}
return ptr;
}
RZ_API char *rz_file_slurp_line(const char *file, int line, int context) {
rz_return_val_if_fail(file, NULL);
int i, lines = 0;
size_t sz;
char *ptr = NULL, *str = rz_file_slurp(file, &sz);
// TODO: Implement context
if (str) {
for (i = 0; str[i]; i++) {
if (str[i] == '\n') {
lines++;
}
}
if (line > lines) {
free(str);
return NULL;
}
lines = line - 1;
for (i = 0; str[i] && lines; i++) {
if (str[i] == '\n') {
lines--;
}
}
ptr = str + i;
for (i = 0; ptr[i]; i++) {
if (ptr[i] == '\n') {
ptr[i] = '\0';
break;
}
}
ptr = rz_str_dup(ptr);
free(str);
}
return ptr;
}
RZ_API RZ_OWN char *rz_file_slurp_lines_from_bottom(const char *file, int line) {
rz_return_val_if_fail(file, NULL);
int i, lines = 0;
size_t sz;
char *ptr = NULL, *str = rz_file_slurp(file, &sz);
// TODO: Implement context
if (str) {
for (i = 0; str[i]; i++) {
if (str[i] == '\n') {
lines++;
}
}
if (line > lines) {
return str; // number of lines requested in more than present, return all
}
i--;
for (; str[i] && line; i--) {
if (str[i] == '\n') {
line--;
}
}
ptr = str + i;
ptr = rz_str_dup(ptr);
free(str);
}
return ptr;
}
RZ_API char *rz_file_slurp_lines(const char *file, int line, int count) {
rz_return_val_if_fail(file, NULL);
int i, lines = 0;
size_t sz;
char *ptr = NULL, *str = rz_file_slurp(file, &sz);
// TODO: Implement context
if (str) {
for (i = 0; str[i]; i++) {
if (str[i] == '\n') {
lines++;
}
}
if (line > lines) {
free(str);
return NULL;
}
lines = line - 1;
for (i = 0; str[i] && lines; i++) {
if (str[i] == '\n') {
lines--;
}
}
ptr = str + i;
for (i = 0; ptr[i]; i++) {
if (ptr[i] == '\n') {
if (count) {
count--;
} else {
ptr[i] = '\0';
break;
}
}
}
ptr = rz_str_dup(ptr);
free(str);
}
return ptr;
}
RZ_API char *rz_file_root(const char *root, const char *path) {
rz_return_val_if_fail(root && path, NULL);
char *ret, *s = rz_str_replace(rz_str_dup(path), "..", "", 1);
// XXX ugly hack
while (strstr(s, "..")) {
s = rz_str_replace(s, "..", "", 1);
}
while (strstr(s, "./")) {
s = rz_str_replace(s, "./", "", 1);
}
while (strstr(s, "//")) {
s = rz_str_replace(s, "//", "", 1);
}
ret = rz_str_append(rz_str_dup(root), RZ_SYS_DIR);
ret = rz_str_append(ret, s);
free(s);
return ret;
}
RZ_API bool rz_file_hexdump(const char *file, const ut8 *buf, int len, int append) {
FILE *fd;
int i, j;
if (!file || !*file || !buf || len < 0) {
eprintf("rz_file_hexdump file: %s buf: %p\n", file, buf);
return false;
}
if (append) {
fd = rz_sys_fopen(file, "ab");
} else {
rz_sys_truncate(file, 0);
fd = rz_sys_fopen(file, "wb");
}
if (!fd) {
eprintf("Cannot open '%s' for writing\n", file);
return false;
}
for (i = 0; i < len; i += 16) {
int l = RZ_MIN(16, len - i);
fprintf(fd, "0x%08" PFMT64x " ", (ut64)i);
for (j = 0; j + 2 <= l; j += 2) {
fprintf(fd, "%02x%02x ", buf[i + j], buf[i + j + 1]);
}
if (j < l) {
fprintf(fd, "%02x ", buf[i + j]);
j += 2;
}
if (j < 16) {
fprintf(fd, "%*s ", (16 - j) / 2 * 5, "");
}
for (j = 0; j < 16; j++) {
fprintf(fd, "%c", j < l && IS_PRINTABLE(buf[i + j]) ? buf[i + j] : '.');
}
fprintf(fd, "\n");
}
fclose(fd);
return true;
}
RZ_API bool rz_file_touch(const char *file) {
rz_return_val_if_fail(file, false);
return rz_file_dump(file, NULL, 0, true);
}
RZ_API bool rz_file_dump(const char *file, const ut8 *buf, int len, bool append) {
rz_return_val_if_fail(!RZ_STR_ISEMPTY(file), false);
FILE *fd;
if (append) {
fd = rz_sys_fopen(file, "ab");
} else {
rz_sys_truncate(file, 0);
fd = rz_sys_fopen(file, "wb");
}
if (!fd) {
eprintf("Cannot open '%s' for writing\n", file);
return false;
}
if (buf) {
if (len < 0) {
len = strlen((const char *)buf);
}
if (len > 0 && fwrite(buf, len, 1, fd) != 1) {
rz_sys_perror("rz_file_dump: fwrite: error\n");
fclose(fd);
return false;
}
}
fclose(fd);
return true;
}
RZ_API bool rz_file_rm(const char *file) {
if (RZ_STR_ISEMPTY(file)) {
return false;
}
if (rz_file_is_directory(file)) {
#if __WINDOWS__
LPWSTR wfile = rz_utf8_to_utf16(file);
bool ret = RemoveDirectoryW(wfile);
free(wfile);
return !ret;
#else
return !rmdir(file);
#endif
} else {
#if __WINDOWS__
LPWSTR wfile = rz_utf8_to_utf16(file);
bool ret = DeleteFileW(wfile);
free(wfile);
return !ret;
#else
return !unlink(file);
#endif
}
}
RZ_API char *rz_file_readlink(const char *path) {
rz_return_val_if_fail(!RZ_STR_ISEMPTY(path), false);
#if __UNIX__
int ret;
char pathbuf[4096] = { 0 };
strncpy(pathbuf, path, sizeof(pathbuf) - 1);
repeat:
ret = readlink(path, pathbuf, sizeof(pathbuf) - 1);
if (ret != -1) {
pathbuf[ret] = 0;
path = pathbuf;
goto repeat;
}
return rz_str_dup(pathbuf);
#endif
return NULL;
}
#if __WINDOWS__
static RzMmap *file_mmap(RzMmap *m) {
bool is_write = (m->perm & O_WRONLY) || (m->perm & O_RDWR);
HANDLE fh = (HANDLE)_get_osfhandle(m->fd);
m->len = (DWORD)GetFileSize(fh, (LPDWORD)((char *)&m->len + sizeof(DWORD)));
if (m->len == INVALID_FILE_SIZE) {
rz_sys_perror("GetFileSize");
goto err;
}
if (m->len != 0) {
m->fm = CreateFileMappingW(fh,
NULL,
is_write ? PAGE_READWRITE : PAGE_READONLY,
0, 0, NULL);
if (!m->fm) {
rz_sys_perror("CreateFileMapping mmap");
goto err;
}
m->buf = MapViewOfFileEx(m->fm,
is_write ? (FILE_MAP_READ | FILE_MAP_WRITE) : FILE_MAP_READ,
0, 0, 0, (void *)m->base);
if (!m->buf) {
rz_sys_perror("MapViewOfFileEx");
goto err;
}
}
return m;
err:
rz_file_mmap_free(m);
return NULL;
}
#elif __UNIX__
static RzMmap *file_mmap(RzMmap *m) {
m->len = lseek(m->fd, (off_t)0, SEEK_END);
if (m->len > 0) {
bool is_write = (m->perm & O_WRONLY) || (m->perm & O_RDWR);
m->buf = mmap((void *)(size_t)m->base,
m->len,
is_write ? PROT_READ | PROT_WRITE : PROT_READ,
MAP_SHARED, m->fd, 0);
if (m->buf == MAP_FAILED) {
rz_sys_perror("mmap");
rz_file_mmap_free(m);
return NULL;
}
}
return m;
}
#else
static RzMmap *file_mmap(RzMmap *m) {
m->len = lseek(m->fd, (off_t)0, SEEK_END);
m->buf = malloc(m->len);
if (!m->buf) {
rz_file_mmap_free(m);
return NULL;
}
lseek(m->fd, (off_t)0, SEEK_SET);
rz_xread(m->fd, m->buf, m->len);
return m;
}
#endif
RZ_API RzMmap *rz_file_mmap(const char *file, int perm, int mode, ut64 base) {
RzMmap *m = NULL;
m = RZ_NEW0(RzMmap);
if (!m) {
return NULL;
}
m->base = base;
m->perm = perm;
m->len = 0;
m->filename = rz_str_dup(file);
m->mode = mode;
if (!m->filename) {
rz_file_mmap_free(m);
return NULL;
}
m->fd = rz_sys_open(m->filename, m->perm, m->mode);
if (m->fd == -1) {
rz_file_mmap_free(m);
return NULL;
}
return file_mmap(m);
}
RZ_API void rz_file_mmap_free(RzMmap *m) {
if (!m) {
return;
}
#if __WINDOWS__
if (m->buf) {
UnmapViewOfFile(m->buf);
}
if (m->fm) {
CloseHandle(m->fm);
}
if (m->fd != -1) {
_close(m->fd);
}
#elif __UNIX__
munmap(m->buf, m->len);
if (m->fd != -1) {
close(m->fd);
}
#endif
free(m->filename);
free(m);
}
RZ_API void *rz_file_mmap_resize(RzMmap *m, ut64 newsize) {
#if __WINDOWS__
if (m->buf) {
UnmapViewOfFile(m->buf);
}
if (m->fm) {
CloseHandle(m->fm);
}
if (m->fd != -1) {
_close(m->fd);
}
#elif __UNIX__
if (m->buf && m->len != 0 && munmap(m->buf, m->len) != 0) {
return NULL;
}
#endif
if (!rz_sys_truncate(m->filename, newsize)) {
return NULL;
}
m->fd = rz_sys_open(m->filename, m->perm, m->mode);
if (m->fd == -1) {
rz_file_mmap_free(m);
return NULL;
}
// In case of mmap failure it frees the RzMmap and return NULL
if (!file_mmap(m)) {
return NULL;
}
return m->buf;
}
RZ_API char *rz_file_temp(const char *prefix) {
if (!prefix) {
prefix = "";
}
char *path = rz_file_tmpdir();
char *res = rz_str_newf("%s" RZ_SYS_DIR "%s.%" PFMT64x, path, prefix, rz_time_now());
free(path);
return res;
}
RZ_API int rz_file_mkstemp(RZ_NULLABLE const char *prefix, char **oname) {
int h = -1;
char *path = rz_file_tmpdir();
if (!prefix) {
prefix = "rz";
}
#if __WINDOWS__
LPWSTR wname = malloc(sizeof(WCHAR) * (MAX_PATH + 1));
LPWSTR wpath = rz_utf8_to_utf16(path);
LPWSTR wprefix = prefix ? rz_utf8_to_utf16(prefix) : _wcsdup(L"");
if (!(wname && wpath && wprefix)) {
goto err_r_file_mkstemp;
}
if (GetTempFileNameW(wpath, wprefix, 0, wname)) {
char *name = rz_utf16_to_utf8(wname);
h = rz_sys_open(name, O_RDWR | O_EXCL | O_BINARY, 0644);
if (oname) {
if (h != -1) {
*oname = name;
} else {
*oname = NULL;
free(name);
}
} else {
free(name);
}
}
err_r_file_mkstemp:
free(wname);
free(wpath);
free(wprefix);
#else
char pfxx[1024];
const char *suffix = strchr(prefix, '*');
if (suffix) {
suffix++;
rz_str_ncpy(pfxx, prefix, (size_t)(suffix - prefix));
prefix = pfxx;
} else {
suffix = "";
}
char *name = rz_str_newf("%s/rz.%s.XXXXXX%s", path, prefix, suffix);
mode_t mask = umask(S_IWGRP | S_IWOTH);
if (suffix && *suffix) {
#if defined(__GLIBC__) && defined(__GLIBC_MINOR__) && 2 <= __GLIBC__ && 19 <= __GLIBC__MINOR__
h = mkstemps(name, strlen(suffix));
#else
char *const xpos = strrchr(name, 'X');
const char c = (char)(NULL != xpos ? *(xpos + 1) : 0);
if (0 != c) {
xpos[1] = 0;
h = mkstemp(name);
xpos[1] = c;
} else {
h = -1;
}
#endif
} else {
h = mkstemp(name);
}
umask(mask);
if (oname) {
*oname = (h != -1) ? rz_str_dup(name) : NULL;
}
free(name);
#endif
free(path);
return h;
}
RZ_API char *rz_file_tmpdir(void) {
#if __WINDOWS__
char *path = NULL;
DWORD len = 0;
LPWSTR tmpdir = calloc(1, sizeof(WCHAR) * (MAX_PATH + 1));
if (!tmpdir) {
return NULL;
}
if ((len = GetTempPathW(MAX_PATH + 1, tmpdir)) == 0) {
path = rz_sys_getenv("TEMP");
if (!path) {
path = rz_str_dup("C:\\WINDOWS\\Temp\\");
}
} else {
tmpdir[len] = 0;
// Windows XP sometimes returns short path name
GetLongPathNameW(tmpdir, tmpdir, MAX_PATH + 1);
path = rz_utf16_to_utf8(tmpdir);
}
free(tmpdir);
// Windows 7, stat() function fail if tmpdir ends with '\\'
if (path) {
size_t path_len = strlen(path);
if (path_len > 0 && path[path_len - 1] == '\\') {
path[path_len - 1] = '\0';
}
}
#else
char *path = rz_sys_getenv("TMPDIR");
if (path && !*path) {
RZ_FREE(path);
}
if (!path) {
#if __ANDROID__
path = rz_str_dup("/data/data/org.rizin.rizininstaller/rizin/tmp");
#else
path = rz_str_dup("/tmp");
#endif
}
#endif
if (!rz_file_is_directory(path)) {
eprintf("Cannot find temporary directory '%s'\n", path);
}
return path;
}
RZ_API bool rz_file_copy(const char *src, const char *dst) {
/* TODO: implement in C */
/* TODO: Use NO_CACHE for iOS dyldcache copying */
#if HAVE_COPYFILE
return copyfile(src, dst, 0, COPYFILE_DATA | COPYFILE_XATTR) != -1;
#elif HAVE_COPY_FILE_RANGE
int srcfd = open(src, O_RDONLY);
if (srcfd == -1) {
RZ_LOG_ERROR("rz_file_copy: Failed to open %s\n", src);
return false;
}
int mask = (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
int dstfd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, mask);
if (dstfd == -1) {
RZ_LOG_ERROR("rz_file_copy: Failed to open %s\n", dst);
close(srcfd);
return false;
}
/* copy_file_path can handle large file up to SSIZE_MAX
* with optimised performances.
*/
off_t sz = lseek(srcfd, 0, SEEK_END);
lseek(srcfd, 0, SEEK_SET);
ssize_t ret = copy_file_range(srcfd, 0, dstfd, 0, SSIZE_MAX, 0);
close(dstfd);
close(srcfd);
return ret == sz;
#elif __WINDOWS__
PWCHAR s = rz_utf8_to_utf16(src);
PWCHAR d = rz_utf8_to_utf16(dst);
if (!s || !d) {
RZ_LOG_ERROR("rz_file_copy: Failed to allocate memory\n");
free(s);
free(d);
return false;
}
bool ret = CopyFileW(s, d, 0);
if (!ret) {
rz_sys_perror("rz_file_copy");
}
free(s);
free(d);
return ret;
#else
char *src2 = rz_str_replace(rz_str_dup(src), "'", "\\'", 1);
char *dst2 = rz_str_replace(rz_str_dup(dst), "'", "\\'", 1);
int rc = rz_sys_cmdf("cp -f '%s' '%s'", src2, dst2);
free(src2);
free(dst2);
return rc == 0;
#endif
}
static void recursive_search_glob(const char *path, const char *glob, RzList /*<char *>*/ *list, int depth) {
if (depth < 1) {
return;
}
char *file;
RzListIter *iter;
RzList *dir = rz_sys_dir(path);
rz_list_foreach (dir, iter, file) {
if (!strcmp(file, ".") || !strcmp(file, "..")) {
continue;
}
char *filename = malloc(strlen(path) + strlen(file) + 2);
if (!filename) {
rz_list_free(dir);
return;
}
strcpy(filename, path);
strcat(filename, file);
if (rz_file_is_directory(filename)) {
strcat(filename, RZ_SYS_DIR);
recursive_search_glob(filename, glob, list, depth - 1);
free(filename);
} else if (rz_str_glob(file, glob)) {
rz_list_append(list, filename);
} else {
free(filename);
}
}
rz_list_free(dir);
}
RZ_API RzList /*<char *>*/ *rz_file_globsearch(const char *_globbed_path, int maxdepth) {
char *globbed_path = rz_str_dup(_globbed_path);
RzList *files = rz_list_newf(free);
char *glob = strchr(globbed_path, '*');
if (!glob) {
rz_list_append(files, rz_str_dup(globbed_path));
} else {
*glob = '\0';
char *last_slash = (char *)rz_str_last(globbed_path, RZ_SYS_DIR);
*glob = '*';
char *path, *glob_ptr;
if (last_slash) {
glob_ptr = last_slash + 1;
if (globbed_path[0] == '~') {
char *rpath = rz_str_newlen(globbed_path + 2, last_slash - globbed_path - 1);
path = rz_str_home(rpath ? rpath : "");
free(rpath);
} else {
path = rz_str_newlen(globbed_path, last_slash - globbed_path + 1);
}
} else {
glob_ptr = globbed_path;
path = rz_str_newf(".%s", RZ_SYS_DIR);
}
if (!path) {
rz_list_free(files);
free(globbed_path);
return NULL;
}
if (*(glob + 1) == '*') { // "**"
recursive_search_glob(path, glob_ptr, files, maxdepth);
} else { // "*"
recursive_search_glob(path, glob_ptr, files, 1);
}
free(path);
}
free(globbed_path);
return files;
}
/**
* \brief Concatenate two paths to create a new one with s1+s2 with the correct path separator
*
* \param s1 First path
* \param s2 Second path, can be NULL
* \return Full path
*/
RZ_API RZ_OWN char *rz_file_path_join(RZ_NONNULL const char *s1, RZ_NULLABLE const char *s2) {
rz_return_val_if_fail(s1, NULL);
if (s1[0] == 0) {
return rz_str_dup(s2);
}
if (!s2) {
return rz_str_dup(s1);
}
bool ends_with_dir = s1[strlen(s1) - 1] == RZ_SYS_DIR[0];
const char *sep = ends_with_dir ? "" : RZ_SYS_DIR;
return rz_str_newf("%s%s%s", s1, sep, s2);
}
/**
* \brief zip the contents of src and store in dst
* \param src source file (string containing filename)
* \param dst destination file (string containing filename)
* \return true, if successful; false otherwise
*/
RZ_API bool rz_file_deflate(RZ_NONNULL const char *src, RZ_NONNULL const char *dst) {
rz_return_val_if_fail(src && dst, false);
bool ret = false;
RzBuffer *src_buf = rz_buf_new_file(src, O_RDONLY, 0);
RzBuffer *dst_buf = rz_buf_new_file(dst, O_WRONLY | O_CREAT, 0644);
if (!(src_buf && dst_buf)) {
goto return_goto;
}
ut64 block_size = 1 << 18; // 256 KB
if (!rz_deflate_buf(src_buf, dst_buf, block_size, NULL)) {
goto return_goto;
}
ret = true;
return_goto:
rz_buf_free(src_buf);
rz_buf_free(dst_buf);
return ret;
}
/**
* \brief unzip the contents of src and store in dst
* \param src source file (string containing filename)
* \param dst destination file (string containing filename)
* \return true, if successful; false otherwise
*/
RZ_API bool rz_file_inflate(RZ_NONNULL const char *src, RZ_NONNULL const char *dst) {
rz_return_val_if_fail(src && dst, false);
bool ret = false;
RzBuffer *src_buf = rz_buf_new_file(src, O_RDONLY, 0);
RzBuffer *dst_buf = rz_buf_new_file(dst, O_WRONLY | O_CREAT, 0644);
if (!(src_buf && dst_buf)) {
goto return_goto;
}
ut64 block_size = 1 << 13; // 8 KB
if (!rz_inflate_buf(src_buf, dst_buf, block_size, NULL)) {
goto return_goto;
}
ret = true;
return_goto:
rz_buf_free(src_buf);
rz_buf_free(dst_buf);
return ret;
}
/**
* \brief check whether a file is a deflated (gzip) file
* \param src source file (string containing filename)
* \return true, if src is a deflated (gzip) file; false otherwise
*/
RZ_API bool rz_file_is_deflated(RZ_NONNULL const char *src) {
rz_return_val_if_fail(src, false);
bool ret = false;
unsigned char *header = (unsigned char *)rz_file_slurp_range(src, 0, 3, NULL);
if (!header || rz_str_nlen((char *)header, 3) != 3) {
goto return_goto;
}
ret = (header[0] == 0x1f && header[1] == 0x8b && header[2] == 0x08); // 1f 8b 08
return_goto:
free(header);
return ret;
}