/** * Normalize a path, eg, foo//bar, foo/foo2/../bar, foo/./bar all become * foo/bar. * * If the path begins with either a forward slash or a dot *and* a forward * slash, they will be preserved. * @param path * Path to normalize. * @return * The normalized path; never NULL. Must be freed. */ char *path_normalize(const char *path) { StringBuffer *sb; size_t pos, startsbpos; char component[MAX_BUF]; ssize_t last_slash; TOOLKIT_PROTECT(); if (string_isempty(path)) { return estrdup("."); } sb = stringbuffer_new(); pos = 0; if (string_startswith(path, "/")) { stringbuffer_append_string(sb, "/"); } else if (string_startswith(path, "./")) { stringbuffer_append_string(sb, "./"); } startsbpos = stringbuffer_length(sb); while (string_get_word(path, &pos, '/', component, sizeof(component), 0)) { if (strcmp(component, ".") == 0) { continue; } if (strcmp(component, "..") == 0) { if (stringbuffer_length(sb) > startsbpos) { last_slash = stringbuffer_rindex(sb, '/'); if (last_slash == -1) { LOG(BUG, "Should have found a forward slash, but didn't: %s", path); continue; } stringbuffer_seek(sb, last_slash); } } else { size_t len = stringbuffer_length(sb); if (len == 0 || stringbuffer_data(sb)[len - 1] != '/') { stringbuffer_append_string(sb, "/"); } stringbuffer_append_string(sb, component); } } if (stringbuffer_length(sb) == 0) { stringbuffer_append_string(sb, "."); } return stringbuffer_finish(sb); }
/** * Get absolute path to the specified relative path. This path will typically * point to the to client data directory (which is usually located in the user's * home/appdata directory), but depending on the specified mode, extra actions * may be performed. These ensure that if you're trying to access a file that * does not yet exist in the client data directory, it will be read from the * client installation directory instead (unless it's being appended to, in * which case it will be copied to the client data directory first). * * Generally, you should almost always use this when you need to construct a * path, or use one of the many @ref file_wrapper_functions. * @param fname * The file path. * @param mode * File mode. * @return * The absolute path. Must be freed. */ char *file_path(const char *path, const char *mode) { bool is_write, is_append; StringBuffer *sb; char version[MAX_BUF], client_path[HUGE_BUF], *new_path; HARD_ASSERT(path != NULL); HARD_ASSERT(mode != NULL); SOFT_ASSERT_RC(path[0] != '/', estrdup(path), "Path is already absolute: %s", path); sb = stringbuffer_new(); stringbuffer_append_printf(sb, "%s/.atrinik/%s/%s", get_config_dir(), package_get_version_partial(VS(version)), path); new_path = stringbuffer_sub(sb, 0, 0); is_write = is_append = false; if (strchr(mode, 'w') != NULL) { is_write = true; } else if (strchr(mode, '+') != NULL || strchr(mode, 'a') != NULL) { is_append = true; } if (is_write || is_append) { if (access(new_path, W_OK) != 0) { char *dirname; /* Ensure directories exist if we're going to use this path for * writing/appending. */ dirname = path_dirname(new_path); mkdir_recurse(dirname); efree(dirname); if (is_append) { get_data_dir_file(VS(client_path), path); copy_file(client_path, new_path); } } } else { if (access(new_path, R_OK) != 0) { get_data_dir_file(VS(client_path), path); stringbuffer_seek(sb, 0); stringbuffer_append_string(sb, client_path); } } efree(new_path); return stringbuffer_finish(sb); }