#ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #ifdef _WIN32 # include # include /* mkdir realpath */ #endif #ifdef HAVE_FEATURES_H # include #endif #include #include #ifdef HAVE_ATFILE_SOURCE # include #endif #include "ecore_file_private.h" /* * FIXME: the following functions will certainly not work on Windows: * ecore_file_app_exe_get() * ecore_file_escape_name() */ int _ecore_file_log_dom = -1; static int _ecore_file_init_count = 0; static Eina_Bool _ecore_file_stat(const char *file, long long *mtime, long long *size, mode_t *mode, Eina_Bool *is_dir, Eina_Bool *is_reg) { struct stat st; #ifdef _WIN32 /* * On Windows, stat() returns -1 is file is a path finishing with * a slash or blackslash * see https://msdn.microsoft.com/en-us/library/14h5k7ff.aspx * ("Return Value" section) * * so we ensure that file never finishes with \ or / */ char f[MAX_PATH]; size_t len; len = strlen(file); if ((len + 1) > MAX_PATH) return EINA_FALSE; memcpy(f, file, len + 1); if ((f[len - 1] == '/') || (f[len - 1] == '\\')) f[len - 1] = '\0'; if (stat(f, &st) < 0) return EINA_FALSE; #else if (stat(file, &st) < 0) return EINA_FALSE; #endif if (mtime) *mtime = st.st_mtime; if (size) *size = st.st_size; if (mode) *mode = st.st_mode; if (is_dir) *is_dir = S_ISDIR(st.st_mode); if (is_reg) *is_reg = S_ISREG(st.st_mode); return EINA_TRUE; } EAPI int ecore_file_init() { if (++_ecore_file_init_count != 1) return _ecore_file_init_count; if (!ecore_init()) return --_ecore_file_init_count; _ecore_file_log_dom = eina_log_domain_register ("ecore_file", ECORE_FILE_DEFAULT_LOG_COLOR); if(_ecore_file_log_dom < 0) { EINA_LOG_ERR("Impossible to create a log domain for the ecore file module."); return --_ecore_file_init_count; } ecore_file_path_init(); ecore_file_monitor_init(); ecore_file_download_init(); /* FIXME: were the tests disabled for a good reason ? */ /* if (!ecore_file_monitor_init()) goto shutdown_ecore_file_path; if (!ecore_file_download_init()) goto shutdown_ecore_file_monitor; */ return _ecore_file_init_count; /* shutdown_ecore_file_monitor: ecore_file_monitor_shutdown(); shutdown_ecore_file_path: ecore_file_path_shutdown(); return --_ecore_file_init_count; */ } EAPI int ecore_file_shutdown() { if (--_ecore_file_init_count != 0) return _ecore_file_init_count; ecore_file_download_shutdown(); ecore_file_monitor_shutdown(); ecore_file_path_shutdown(); eina_log_domain_unregister(_ecore_file_log_dom); _ecore_file_log_dom = -1; ecore_shutdown(); return _ecore_file_init_count; } EAPI long long ecore_file_mod_time(const char *file) { long long time; if (!_ecore_file_stat(file, &time, NULL, NULL, NULL, NULL)) return 0; return time; } EAPI long long ecore_file_size(const char *file) { long long size; if (!_ecore_file_stat(file, NULL, &size, NULL, NULL, NULL)) return 0; return size; } EAPI Eina_Bool ecore_file_exists(const char *file) { #ifdef _WIN32 /* I prefer not touching the specific UNIX code... */ return _ecore_file_stat(file, NULL, NULL, NULL, NULL, NULL); #else struct stat st; if (!file) return EINA_FALSE; /*Workaround so that "/" returns a true, otherwise we can't monitor "/" in ecore_file_monitor*/ if (stat(file, &st) < 0 && strcmp(file, "/")) return EINA_FALSE; return EINA_TRUE; #endif } EAPI Eina_Bool ecore_file_is_dir(const char *file) { Eina_Bool is_dir; if (!_ecore_file_stat(file, NULL, NULL, NULL, &is_dir, NULL)) return EINA_FALSE; return is_dir; } static mode_t default_mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; EAPI Eina_Bool ecore_file_mkdir(const char *dir) { return (mkdir(dir, default_mode) == 0); } EAPI int ecore_file_mkdirs(const char **dirs) { int i = 0; if (!dirs) return -1; for (; *dirs; dirs++) if (ecore_file_mkdir(*dirs)) i++; return i; } EAPI int ecore_file_mksubdirs(const char *base, const char **subdirs) { #ifndef HAVE_ATFILE_SOURCE char buf[PATH_MAX]; int baselen; #else int fd; DIR *dir; #endif int i; if (!subdirs) return -1; if ((!base) || (base[0] == '\0')) return -1; if ((!ecore_file_is_dir(base)) && (!ecore_file_mkpath(base))) return 0; #ifndef HAVE_ATFILE_SOURCE baselen = eina_strlcpy(buf, base, sizeof(buf)); if ((baselen < 1) || (baselen + 1 >= (int)sizeof(buf))) return 0; if (buf[baselen - 1] != '/') { buf[baselen] = '/'; baselen++; } #else dir = opendir(base); if (!dir) return 0; fd = dirfd(dir); #endif i = 0; for (; *subdirs; subdirs++) { #ifdef HAVE_ATFILE_SOURCE struct stat st; #endif Eina_Bool is_dir; #ifndef HAVE_ATFILE_SOURCE eina_strlcpy(buf + baselen, *subdirs, sizeof(buf) - baselen); if (_ecore_file_stat(buf, NULL, NULL, NULL, &is_dir, NULL)) { #else if (fstatat(fd, *subdirs, &st, 0) == 0) { is_dir = S_ISDIR(st.st_mode); #endif if (is_dir) { i++; continue; } } else { if (errno == ENOENT) { #ifndef HAVE_ATFILE_SOURCE if (ecore_file_mkdir(buf)) #else if (mkdirat(fd, *subdirs, default_mode) == 0) #endif { i++; continue; } } } } #ifdef HAVE_ATFILE_SOURCE closedir(dir); #endif return i; } EAPI Eina_Bool ecore_file_rmdir(const char *dir) { if (rmdir(dir) < 0) return EINA_FALSE; return EINA_TRUE; } EAPI Eina_Bool ecore_file_unlink(const char *file) { if (unlink(file) < 0) return EINA_FALSE; return EINA_TRUE; } EAPI Eina_Bool ecore_file_remove(const char *file) { if (remove(file) < 0) return EINA_FALSE; return EINA_TRUE; } EAPI Eina_Bool ecore_file_recursive_rm(const char *dir) { #ifndef _WIN32 struct stat st; #endif Eina_Bool is_dir; #ifdef _WIN32 if (!_ecore_file_stat(dir, NULL, NULL, NULL, &is_dir, NULL)) return EINA_FALSE; #else if (lstat(dir, &st) == -1) return EINA_FALSE; is_dir = S_ISDIR(st.st_mode); #endif if (is_dir) { Eina_File_Direct_Info *info; Eina_Iterator *it; int ret; ret = 1; it = eina_file_direct_ls(dir); EINA_ITERATOR_FOREACH(it, info) { if (!ecore_file_recursive_rm(info->path)) ret = 0; } eina_iterator_free(it); if (!ecore_file_rmdir(dir)) ret = 0; if (ret) return EINA_TRUE; else return EINA_FALSE; } else { return ecore_file_unlink(dir); } } static inline Eina_Bool _ecore_file_mkpath_if_not_exists(const char *path) { Eina_Bool is_dir; /* Windows: path like C: or D: etc are valid, but stat() returns an error */ #ifdef _WIN32 if ((strlen(path) == 2) && ((path[0] >= 'a' && path[0] <= 'z') || (path[0] >= 'A' && path[0] <= 'Z')) && (path[1] == ':')) return EINA_TRUE; #endif if (!_ecore_file_stat(path, NULL, NULL, NULL, &is_dir, NULL)) return ecore_file_mkdir(path); else if (!is_dir) return EINA_FALSE; else return EINA_TRUE; } EAPI Eina_Bool ecore_file_mkpath(const char *path) { char ss[PATH_MAX]; unsigned int i; EINA_SAFETY_ON_NULL_RETURN_VAL(path, EINA_FALSE); if (ecore_file_is_dir(path)) return EINA_TRUE; for (i = 0; path[i] != '\0'; ss[i] = path[i], i++) { if (i == sizeof(ss) - 1) return EINA_FALSE; if (((path[i] == '/') || (path[i] == '\\')) && (i > 0)) { ss[i] = '\0'; if (!_ecore_file_mkpath_if_not_exists(ss)) return EINA_FALSE; } } ss[i] = '\0'; return _ecore_file_mkpath_if_not_exists(ss); } EAPI int ecore_file_mkpaths(const char **paths) { int i = 0; if (!paths) return -1; for (; *paths; paths++) if (ecore_file_mkpath(*paths)) i++; return i; } EAPI Eina_Bool ecore_file_cp(const char *src, const char *dst) { FILE *f1, *f2; char buf[16384]; char realpath1[PATH_MAX], realpath2[PATH_MAX]; size_t num; Eina_Bool ret = EINA_TRUE; if (!realpath(src, realpath1)) return EINA_FALSE; if (realpath(dst, realpath2) && !strcmp(realpath1, realpath2)) return EINA_FALSE; f1 = fopen(src, "rb"); if (!f1) return EINA_FALSE; f2 = fopen(dst, "wb"); if (!f2) { fclose(f1); return EINA_FALSE; } while ((num = fread(buf, 1, sizeof(buf), f1)) > 0) { if (fwrite(buf, 1, num, f2) != num) ret = EINA_FALSE; } fclose(f1); fclose(f2); return ret; } EAPI Eina_Bool ecore_file_mv(const char *src, const char *dst) { char buf[PATH_MAX]; int fd; if (rename(src, dst)) { // File cannot be moved directly because // it resides on a different mount point. if (errno == EXDEV) { mode_t mode; Eina_Bool is_reg; // Make sure this is a regular file before // we do anything fancy. if (!_ecore_file_stat(src, NULL, NULL, &mode, NULL, &is_reg)) goto FAIL; if (is_reg) { char *dir; Eina_Tmpstr *tmpstr = NULL; dir = ecore_file_dir_get(dst); // Since we can't directly rename, try to // copy to temp file in the dst directory // and then rename. snprintf(buf, sizeof(buf), "%s/.%s.tmp.XXXXXX", dir, ecore_file_file_get(dst)); free(dir); fd = eina_file_mkstemp(buf, &tmpstr); if (fd < 0) goto FAIL; close(fd); // Copy to temp file if (!ecore_file_cp(src, tmpstr)) { eina_tmpstr_del(tmpstr); goto FAIL; } // Set file permissions of temp file to match src if (chmod(tmpstr, mode) == -1) { eina_tmpstr_del(tmpstr); goto FAIL; } // Try to atomically move temp file to dst if (rename(tmpstr, dst)) { // If we still cannot atomically move // do a normal copy and hope for the best. if (!ecore_file_cp(tmpstr, dst)) { eina_tmpstr_del(tmpstr); goto FAIL; } } // Delete temporary file and src ecore_file_unlink(tmpstr); ecore_file_unlink(src); eina_tmpstr_del(tmpstr); goto PASS; } } #ifdef _WIN32 if (errno == ENOENT) { struct _stat s; _stat(dst, &s); if (_S_IFREG & s.st_mode) { ecore_file_unlink(dst); if (rename(src, dst)) { return EINA_TRUE; } } } #endif goto FAIL; } PASS: return EINA_TRUE; FAIL: return EINA_FALSE; } EAPI Eina_Bool ecore_file_symlink(const char *src, const char *dest) { #ifndef _WIN32 return !symlink(src, dest); #else return EINA_FALSE; (void)src; (void)dest; #endif } EAPI char * ecore_file_realpath(const char *file) { char buf[PATH_MAX]; /* * Some implementations of realpath do not conform to the SUS. * And as a result we must prevent a null arg from being passed. */ if (!file) return strdup(""); if (!realpath(file, buf)) return strdup(""); return strdup(buf); } EAPI const char * ecore_file_file_get(const char *path) { char *result = NULL; if (!path) return NULL; if ((result = strrchr(path, '/'))) result++; else result = (char *)path; #ifdef _WIN32 /* * Here, we know that there is no more / in the string beginning at * 'result'. So just check that there is no more \ from it. */ { char *result_backslash; if ((result_backslash = strrchr(result, '\\'))) result = ++result_backslash; } #endif return result; } EAPI char * ecore_file_dir_get(const char *file) { char *p; char buf[PATH_MAX]; if (!file) return NULL; strncpy(buf, file, PATH_MAX); buf[PATH_MAX - 1] = 0; p = dirname(buf); return strdup(p); } EAPI Eina_Bool ecore_file_can_read(const char *file) { if (!file) return EINA_FALSE; if (!access(file, R_OK)) return EINA_TRUE; return EINA_FALSE; } EAPI Eina_Bool ecore_file_can_write(const char *file) { if (!file) return EINA_FALSE; if (!access(file, W_OK)) return EINA_TRUE; return EINA_FALSE; } EAPI Eina_Bool ecore_file_can_exec(const char *file) { #ifdef _WIN32 HANDLE h; HANDLE fm; char *base; char *base_nt; LARGE_INTEGER sz; WORD characteristics; #endif if (!file || !*file) return EINA_FALSE; #ifdef _WIN32 /* * we parse the file to check if it is a PE file (EXE or DLL) * and we finally check whether it's a DLL or not. * Reference : * https://docs.microsoft.com/en-us/windows/win32/debug/pe-format */ h = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (h == INVALID_HANDLE_VALUE) goto test_bat; if (!GetFileSizeEx(h, &sz)) goto close_h; /* a PE file must have at least the DOS and NT headers */ if (sz.QuadPart < (LONGLONG)(sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS))) goto close_h; fm = CreateFileMapping(h, NULL, PAGE_READONLY, 0, 0, NULL); if (fm == NULL) goto close_h; base = (char *)MapViewOfFile(fm, FILE_MAP_READ, 0, 0, 0); CloseHandle(fm); if (base == NULL) goto close_h; /* * the PE file begins with the DOS header. * First magic number : the DOS header must begin with a DOS magic number, * that is "MZ", that is 0x5a4d, stored in a WORD. */ if (*((WORD *)base) != 0x5a4d) goto unmap_view; /* * The position of the NT header is located at the offset 0x3c. */ base_nt = base + *((DWORD *)(base + 0x3c)); /* * The NT header begins with the magic number "PE\0\0", that is * 0x00004550, stored in a DWORD. */ if (*((DWORD *)base_nt) != 0x00004550) goto unmap_view; /* * to get informations about executable (EXE or DLL), we look at * the 'Characteristics' member of the NT header, located at the offset * 22 (4 for the magic number, 18 for the offset) from base_nt. * https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#characteristics */ characteristics = *((WORD *)(base_nt + 4 + 18)); UnmapViewOfFile(base); CloseHandle(h); /* * 0x0002 : if set, EXE or DLL * 0x2000 : if set, DLL */ if ((characteristics & 0x0002) && !(characteristics & 0x2000)) return EINA_TRUE; unmap_view: UnmapViewOfFile(base); close_h: CloseHandle(h); test_bat: /* * a .bat file, considered as an executable, is only a text file, * so we rely on the extension. Not the best but we cannot do more. */ return eina_str_has_extension(file, ".bat"); #else if (!access(file, X_OK)) return EINA_TRUE; #endif return EINA_FALSE; } EAPI char * ecore_file_readlink(const char *link) { #ifndef _WIN32 char buf[PATH_MAX]; int count; if ((count = readlink(link, buf, sizeof(buf) - 1)) < 0) return NULL; buf[count] = 0; return strdup(buf); #else return NULL; (void)link; #endif } EAPI Eina_List * ecore_file_ls(const char *dir) { Eina_File_Direct_Info *info; Eina_Iterator *ls; Eina_List *list = NULL; ls = eina_file_direct_ls(dir); if (!ls) return NULL; EINA_ITERATOR_FOREACH(ls, info) { char *f; f = strdup(info->path + info->name_start); list = eina_list_append(list, f); } eina_iterator_free(ls); list = eina_list_sort(list, eina_list_count(list), EINA_COMPARE_CB(strcoll)); return list; } EAPI char * ecore_file_app_exe_get(const char *app) { Eina_Strbuf *buf; char *exe; const char *p; Eina_Bool in_qout_double = EINA_FALSE; Eina_Bool in_qout_single = EINA_FALSE; if (!app) return NULL; buf = eina_strbuf_new(); if (!buf) return NULL; p = app; if ((p[0] == '~') && (p[1] == '/')) { const char *home = eina_environment_home_get(); if (home) eina_strbuf_append(buf, home); p++; } for (; *p; p++) { if (in_qout_double) { if (*p == '\\') { if (p[1]) p++; eina_strbuf_append_char(buf, *p); } else if (*p == '"') in_qout_double = EINA_FALSE; else eina_strbuf_append_char(buf, *p); } else if (in_qout_single) { if (*p == '\\') { if (p[1]) p++; eina_strbuf_append_char(buf, *p); } else if (*p == '\'') in_qout_single = EINA_FALSE; else eina_strbuf_append_char(buf, *p); } else { if (*p == '\\') { if (p[1]) p++; eina_strbuf_append_char(buf, *p); } else if (*p == '"') in_qout_double = EINA_TRUE; else if (*p == '\'') in_qout_single = EINA_TRUE; else { if (isspace((unsigned char)(*p))) break; eina_strbuf_append_char(buf, *p); } } } exe = eina_strbuf_string_steal(buf); eina_strbuf_free(buf); return exe; } EAPI char * ecore_file_escape_name(const char *filename) { const char *p; char *q; char buf[PATH_MAX]; EINA_SAFETY_ON_NULL_RETURN_VAL(filename, NULL); p = filename; q = buf; while (*p) { if ((q - buf) > (PATH_MAX - 6)) return NULL; if ( (*p == ' ') || (*p == '\\') || (*p == '\'') || (*p == '\"') || (*p == ';') || (*p == '!') || (*p == '#') || (*p == '$') || (*p == '%') || (*p == '&') || (*p == '*') || (*p == '(') || (*p == ')') || (*p == '[') || (*p == ']') || (*p == '{') || (*p == '}') || (*p == '|') || (*p == '<') || (*p == '>') || (*p == '?') ) { *q = '\\'; q++; } else if (*p == '\t') { *q = '\\'; q++; *q = '\\'; q++; *q = 't'; q++; p++; continue; } else if (*p == '\n') { *q = '\\'; q++; *q = '\\'; q++; *q = 'n'; q++; p++; continue; } *q = *p; q++; p++; } *q = 0; return strdup(buf); } EAPI char * ecore_file_strip_ext(const char *path) { char *p, *file = NULL; if (!path) return NULL; p = strrchr(path, '.'); if (!p) file = strdup(path); else if (p != path) { file = malloc(((p - path) + 1) * sizeof(char)); if (file) { memcpy(file, path, (p - path)); file[p - path] = 0; } } return file; } EAPI int ecore_file_dir_is_empty(const char *dir) { Eina_File_Direct_Info *info; Eina_Iterator *it; it = eina_file_direct_ls(dir); if (!it) return -1; EINA_ITERATOR_FOREACH(it, info) { eina_iterator_free(it); return 0; } eina_iterator_free(it); return 1; }