#ifdef HAVE_CONFIG_H # include #endif #include #include #ifndef _MSC_VER # include # include #endif #ifndef _FILE_OFFSET_BITS # define _FILE_OFFSET_BITS 64 #endif #ifdef HAVE_FEATURES_H # include #endif #include #include #include "ecore_file_private.h" int _ecore_file_log_dom = -1; static int _ecore_file_init_count = 0; /* externally accessible functions */ /** * Initialize Ecore_File and the services it will use. Call this function * once before you use any of the ecore file functions. * @return Return the number howoften ecore_file_init() was call succesfully; * 0 if it failed. */ EAPI int ecore_file_init() { if (++_ecore_file_init_count != 1) return _ecore_file_init_count; _ecore_file_log_dom = eina_log_domain_register("EcoreFile", 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; */ } /** * Shutdown the Ecore_File * @return returns the number of libraries that still uses Ecore_File */ 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; return _ecore_file_init_count; } /** * Get the time of the last modification to the give file * @param file The name of the file * @return Return the time of the last data modification, if an error should * occur it will return 0 */ EAPI long long ecore_file_mod_time(const char *file) { struct stat st; if (stat(file, &st) < 0) return 0; return st.st_mtime; } /** * Get the size of the given file * @param file The name of the file * @return The size of the file in byte */ EAPI long long ecore_file_size(const char *file) { struct stat st; if (stat(file, &st) < 0) return 0; return st.st_size; } /** * Check if file exists * @param file The name of the file * @return EINA_TRUE if file exists on local filesystem, EINA_FALSE otherwise */ EAPI Eina_Bool ecore_file_exists(const char *file) { struct stat st; /*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; } /** * Check if file is a directory * @param file The name of the file * @return EINA_TRUE if file exist and is a directory, EINA_FALSE otherwise */ EAPI Eina_Bool ecore_file_is_dir(const char *file) { struct stat st; if (stat(file, &st) < 0) return EINA_FALSE; if (S_ISDIR(st.st_mode)) return EINA_TRUE; return EINA_FALSE; } static mode_t default_mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; /** * Create a new directory * @param dir The name of the directory to create * @return EINA_TRUE on successfull creation, EINA_FALSE on failure * * The directory is created with the mode: S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH */ EAPI Eina_Bool ecore_file_mkdir(const char *dir) { if (mkdir(dir, default_mode) < 0) return EINA_FALSE; return EINA_TRUE; } /** * Create complete directory in a batch. * * @param dirs list of directories, null terminated. * @return number of successfull directories created, -1 if dirs is NULL. * * @see ecore_file_mkdir() and ecore_file_mkpaths() */ 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; } /** * Create complete list of sub-directories in a batch (optimized). * * @param base the base directory to act on, will be created if does * not exists. * @param subdirs list of directories, null terminated. These are * created similarly to ecore_file_mkdir(), so same mode and whole * path to that point must exists. So if creating base/a/b/c, * provide subdirs with "a", "a/b" and "a/b/c" in that order! * * @return number of successfull directories created, -1 if subdirs or * base is NULL or invalid. * * @see ecore_file_mkdir() and ecore_file_mkpaths() */ 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++) { struct stat st; #ifndef HAVE_ATFILE_SOURCE eina_strlcpy(buf + baselen, *subdirs, sizeof(buf) - baselen); if (stat(buf, &st) == 0) #else if (fstatat(fd, *subdirs, &st, 0) == 0) #endif { if (S_ISDIR(st.st_mode)) { i++; continue; } } else { if (errno == ENOENT) { #ifndef HAVE_ATFILE_SOURCE if (mkdir(buf, default_mode) == 0) #else if (mkdirat(fd, *subdirs, default_mode) == 0) #endif { i++; continue; } } } } #ifdef HAVE_ATFILE_SOURCE closedir(dir); #endif return i; } /** * Delete the given dir * @param dir The name of the directory to delete * @return EINA_TRUE on success, EINA_FALSE on failure */ EAPI Eina_Bool ecore_file_rmdir(const char *dir) { if (rmdir(dir) < 0) return EINA_FALSE; return EINA_TRUE; } /** * Delete the given file * @param file The name of the file to delete * @return EINA_TRUE on success, EINA_FALSE on failure */ EAPI Eina_Bool ecore_file_unlink(const char *file) { if (unlink(file) < 0) return EINA_FALSE; return EINA_TRUE; } /** * Remove the given file or directory * @param file The name of the file or directory to delete * @return EINA_TRUE on success, EINA_FALSE on failure */ EAPI Eina_Bool ecore_file_remove(const char *file) { if (remove(file) < 0) return EINA_FALSE; return EINA_TRUE; } /** * Delete a directory and all its contents * @param dir The name of the directory to delete * @return EINA_TRUE on success, EINA_FALSE on failure * * If dir is a link only the link is removed */ EAPI Eina_Bool ecore_file_recursive_rm(const char *dir) { DIR *dirp; struct dirent *dp; char path[PATH_MAX], buf[PATH_MAX]; struct stat st; int ret; if (readlink(dir, buf, sizeof(buf)) > 0) return ecore_file_unlink(dir); ret = stat(dir, &st); if ((ret == 0) && (S_ISDIR(st.st_mode))) { ret = 1; if (stat(dir, &st) == -1) return EINA_FALSE; dirp = opendir(dir); if (dirp) { while ((dp = readdir(dirp))) { if ((strcmp(dp->d_name, ".")) && (strcmp(dp->d_name, ".."))) { snprintf(path, PATH_MAX, "%s/%s", dir, dp->d_name); if (!ecore_file_recursive_rm(path)) ret = 0; } } closedir(dirp); } if (!ecore_file_rmdir(dir)) ret = 0; if (ret) return EINA_TRUE; else return EINA_FALSE; } else { if (ret == -1) return EINA_FALSE; return ecore_file_unlink(dir); } } static inline Eina_Bool _ecore_file_mkpath_if_not_exists(const char *path) { struct stat st; if (stat(path, &st) < 0) return ecore_file_mkdir(path); else if (!S_ISDIR(st.st_mode)) return EINA_FALSE; else return EINA_TRUE; } /** * Create a complete path * @param path The path to create * @return EINA_TRUE on success, EINA_FALSE on failure * * @see ecore_file_mkpaths() and ecore_file_mkdir() */ EAPI Eina_Bool ecore_file_mkpath(const char *path) { char ss[PATH_MAX]; unsigned int i; 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] == '/') && (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); } /** * Create complete paths in a batch. * * @param paths list of paths, null terminated. * @return number of successfull paths created, -1 if paths is NULL. * * @see ecore_file_mkpath() and ecore_file_mkdirs() */ 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; } /** * Copy a file * @param src The name of the source file * @param dst The name of the destination file * @return EINA_TRUE on success, EINA_FALSE on failure */ 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; } /** * Move a file * @param src The name of the source file * @param dst The name of the destination file * @return EINA_TRUE on success, EINA_FALSE on failure */ 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) { struct stat st; // Make sure this is a regular file before // we do anything fancy. stat(src, &st); if (S_ISREG(st.st_mode)) { char *dir; 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 = mkstemp(buf); if (fd < 0) { perror("mkstemp"); goto FAIL; } close(fd); // Copy to temp file if (!ecore_file_cp(src, buf)) goto FAIL; // Set file permissions of temp file to match src chmod(buf, st.st_mode); // Try to atomically move temp file to dst if (rename(buf, dst)) { // If we still cannot atomically move // do a normal copy and hope for the best. if (!ecore_file_cp(buf, dst)) goto FAIL; } // Delete temporary file and src ecore_file_unlink(buf); ecore_file_unlink(src); goto PASS; } } goto FAIL; } PASS: return EINA_TRUE; FAIL: return EINA_FALSE; } /** * Create a symbolic link * @param src The name of the file to link * @param dest The name of link * @return EINA_TRUE on success, EINA_FALSE on failure */ EAPI Eina_Bool ecore_file_symlink(const char *src, const char *dest) { if (!symlink(src, dest)) return EINA_TRUE; return EINA_FALSE; } /** * Get the canonicalized absolute pathname * @param file The file path * @return The canonicalized absolute pathname; on failure it will return * an empty string */ 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); } /** * Get the filename from a give path * @param path The complete path * @return Only the file name */ 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; return result; } /** * Get the directory where file reside * @param file The name of the file * @return The directory name */ 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); } /** * Check if file can be read * @param file The name of the file * @return EINA_TRUE if the file is readable, EINA_FALSE otherwise */ 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; } /** * Check if file can be written * @param file The name of the file * @return EINA_TRUE if the file is writable, EINA_FALSE otherwise */ 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; } /** * Check if file can be executed * @param file The name of the file * @return EINA_TRUE if the file can be executed, EINA_FALSE otherwise */ EAPI Eina_Bool ecore_file_can_exec(const char *file) { if (!file) return EINA_FALSE; if (!access(file, X_OK)) return EINA_TRUE; return EINA_FALSE; } /** * Get the path pointed by link * @param link The name of the link * @return The path pointed by link or NULL */ EAPI char * ecore_file_readlink(const char *link) { char buf[PATH_MAX]; int count; if ((count = readlink(link, buf, sizeof(buf) - 1)) < 0) return NULL; buf[count] = 0; return strdup(buf); } /** * Get the list of the files and directories in a given directory. The list * will be sorted with strcoll as compare function. That means that you may * want to set the current locale for the category LC_COLLATE with setlocale(). * For more information see the manual pages of strcoll and setlocale. * The list will not contain the directory entries for '.' and '..'. * @param dir The name of the directory to list * @return Return an Eina_List containing all the files in the directory; * on failure it returns NULL. */ EAPI Eina_List * ecore_file_ls(const char *dir) { char *f; DIR *dirp; struct dirent *dp; Eina_List *list = NULL; dirp = opendir(dir); if (!dirp) return NULL; while ((dp = readdir(dirp))) { if ((strcmp(dp->d_name, ".")) && (strcmp(dp->d_name, ".."))) { f = strdup(dp->d_name); list = eina_list_append(list, f); } } closedir(dirp); list = eina_list_sort(list, eina_list_count(list), EINA_COMPARE_CB(strcoll)); return list; } /** * FIXME: To be documented. */ EAPI char * ecore_file_app_exe_get(const char *app) { char *p, *pp, *exe1 = NULL, *exe2 = NULL; char *exe = NULL; int in_quot_dbl = 0, in_quot_sing = 0, restart = 0; if (!app) return NULL; p = (char *)app; restart: while ((*p) && (isspace(*p))) p++; exe1 = p; while (*p) { if (in_quot_sing) { if (*p == '\'') in_quot_sing = 0; } else if (in_quot_dbl) { if (*p == '\"') in_quot_dbl = 0; } else { if (*p == '\'') in_quot_sing = 1; else if (*p == '\"') in_quot_dbl = 1; if ((isspace(*p)) && (!((p > app) && (p[-1] != '\\')))) break; } p++; } exe2 = p; if (exe2 == exe1) return NULL; if (*exe1 == '~') { char *homedir; int len; /* Skip ~ */ exe1++; homedir = getenv("HOME"); if (!homedir) return NULL; len = strlen(homedir); if (exe) free(exe); exe = malloc(len + exe2 - exe1 + 2); if (!exe) return NULL; pp = exe; if (len) { strcpy(exe, homedir); pp += len; if (*(pp - 1) != '/') { *pp = '/'; pp++; } } } else { if (exe) free(exe); exe = malloc(exe2 - exe1 + 1); if (!exe) return NULL; pp = exe; } p = exe1; restart = 0; in_quot_dbl = 0; in_quot_sing = 0; while (*p) { if (in_quot_sing) { if (*p == '\'') in_quot_sing = 0; else { *pp = *p; pp++; } } else if (in_quot_dbl) { if (*p == '\"') in_quot_dbl = 0; else { /* techcincally this is wrong. double quotes also accept * special chars: * * $, `, \ */ *pp = *p; pp++; } } else { /* technically we should handle special chars: * * $, `, \, etc. */ if ((p > exe1) && (p[-1] == '\\')) { if (*p != '\n') { *pp = *p; pp++; } } else if ((p > exe1) && (*p == '=')) { restart = 1; *pp = *p; pp++; } else if (*p == '\'') in_quot_sing = 1; else if (*p == '\"') in_quot_dbl = 1; else if (isspace(*p)) { if (restart) goto restart; else break; } else { *pp = *p; pp++; } } p++; } *pp = 0; return exe; } /** * Add the escape sequence ('\\') to the given filename * @param filename The file name * @return The file name with special characters escaped; if the length of the * resulting string is longer than PATH_MAX it will return NULL */ EAPI char * ecore_file_escape_name(const char *filename) { const char *p; char *q; char buf[PATH_MAX]; p = filename; q = buf; while (*p) { if ((q - buf) > (PATH_MAX - 6)) return NULL; if ( (*p == ' ') || (*p == '\t') || (*p == '\n') || (*p == '\\') || (*p == '\'') || (*p == '\"') || (*p == ';') || (*p == '!') || (*p == '#') || (*p == '$') || (*p == '%') || (*p == '&') || (*p == '*') || (*p == '(') || (*p == ')') || (*p == '[') || (*p == ']') || (*p == '{') || (*p == '}') || (*p == '|') || (*p == '<') || (*p == '>') || (*p == '?') ) { *q = '\\'; q++; } *q = *p; q++; p++; } *q = 0; return strdup(buf); } /** * Remove the extension from a given path * @param path The name of the file * @return A newly allocated string with the extension stripped out or NULL on errors */ EAPI char * ecore_file_strip_ext(const char *path) { char *p, *file = 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; } /** * Check if the given directory is empty. The '.' and '..' files will be ignored. * @param dir The name of the directory to check * @return 1 if directory is empty, 0 if it has at least one file or -1 in case of errors */ EAPI int ecore_file_dir_is_empty(const char *dir) { DIR *dirp; struct dirent *dp; dirp = opendir(dir); if (!dirp) return -1; while ((dp = readdir(dirp))) { if ((strcmp(dp->d_name, ".")) && (strcmp(dp->d_name, ".."))) { closedir(dirp); return 0; } } closedir(dirp); return 1; }