// SPDX-FileCopyrightText: 2007-2020 pancake // SPDX-License-Identifier: LGPL-3.0-only #include "rz_types.h" #include "rz_util.h" #include #include #include #include #include #include #if __UNIX__ #include #include #include #endif #if HAVE_COPYFILE #include #endif #if _MSC_VER #include #include #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 /**/ *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 /**/ *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; }