This patch is essentially Paolo Bonzini's patch submission for glib, to add the ability to resolve canonical filenames:
https://bugzilla.gnome.org/show_bug.cgi?id=111848#c23 The differences are some minor bug fixes, and style changes for QEMU. In addition, a configure check for readlink was added. Signed-off-by: Jeff Cody <jc...@redhat.com> --- configure | 21 +++++ cutils.c | 270 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ qemu-common.h | 8 ++ 3 files changed, 299 insertions(+), 0 deletions(-) diff --git a/configure b/configure index 671b232..6b204de 100755 --- a/configure +++ b/configure @@ -2660,6 +2660,23 @@ if compile_prog "" "" ; then fi ########################################## +# check if we have readlink + +posix_readlink=no +cat > $TMPC << EOF +#include <unistd.h> + +int main(int argc, char **argv) { + char buf[1024]; + readlink("/test/path", buf, sizeof(buf)); + return 0; +} +EOF +if compile_prog "" "" ; then + posix_readlink=yes +fi + +########################################## # check if trace backend exists sh "$source_path/scripts/tracetool" "--$trace_backend" --check-backend > /dev/null 2> /dev/null @@ -3298,6 +3315,10 @@ if test "$linux_magic_h" = "yes" ; then echo "CONFIG_LINUX_MAGIC_H=y" >> $config_host_mak fi +if test "$posix_readlink" = "yes" ; then + echo "CONFIG_READLINK=y" >> $config_host_mak +fi + # USB host support case "$usb" in linux) diff --git a/cutils.c b/cutils.c index af308cd..dd82375 100644 --- a/cutils.c +++ b/cutils.c @@ -24,6 +24,10 @@ #include "qemu-common.h" #include "host-utils.h" #include <math.h> +#include <libgen.h> +#include <string.h> +#include <limits.h> +#include <sys/param.h> #include "qemu_socket.h" @@ -549,3 +553,269 @@ int qemu_sendv(int sockfd, struct iovec *iov, int len, int iov_offset) return do_sendv_recvv(sockfd, iov, len, iov_offset, 1); } +/** + * g_get_full_path: + * @program: a file name in the GLib file name encoding + * + * Returns an allocated string with the full path corresponding to the + * possibly relative @path in the current directory, or %NULL if an + * error happens, for example while following a symbolic link. + * + * @flags allows to modify the behavior of the function. Right now + * the only valid flag is %QEMU_FILE_PATH_RESOLVE_SYMLINKS, which + * directs the function to resolve symbolic links. "." and ".." + * directory entries are always resolved. + * + * If %NULL is returned, an error will be optionally stored in @error, + * if it is not %NULL. + * + * Return value: absolute path, or %NULL + * + **/ +gchar *qemu_file_get_full_path(const gchar *path, + QEMUFileFullPathFlags flags, + GError **error) +{ + gchar *result = NULL; + gchar *extra_buf = NULL; +#ifdef _WIN32 + wchar_t dummy[2], *wpath, *wresult; + int len; + + if (error) { + *error = NULL; + } + + g_return_val_if_fail(!(flags & ~QEMU_FILE_PATH_RESOLVE_SYMLINKS), NULL); + wpath = g_utf8_to_utf16(path, -1, NULL, NULL, error); + if (!wpath) { + return NULL; + } + + len = GetFullPathNameW(wpath, 2, dummy, NULL); + if (len != 0) { + wresult = g_new(wchar_t, len); + len = GetFullPathNameW(wpath, len, wresult, NULL); + + /* There is a tiny difference between the code below and + GetFullPathName; work around it. The 3 is for "d:\" */ + while (len > 3 && wresult[len - 1] == L'\\') { + wresult[--len] = L'\0'; + } + + if (len != 0) { + result = g_utf16_to_utf8(wresult, -1, NULL, NULL, error); + } else { + result = NULL; + } + + g_free(wresult); + } + + g_free(wpath); + + /* Shouldn't happen, how could GetFullPathNameW fail? Try to + be sensible, though. */ + if (!result && !(error && *error)) { + goto error_exit_ENAMETOOLONG; + } + return result; +#else + gchar *dest; + const gchar *start, *end; + guint size; + gboolean must_end; + struct stat st; +#ifdef CONFIG_READLINK + int num_links = 0; + gsize len, n; + gchar *buf; +#endif + + if (error) { + *error = NULL; + } + + g_return_val_if_fail(!(flags & ~QEMU_FILE_PATH_RESOLVE_SYMLINKS), NULL); + size = PATH_MAX; + result = g_malloc(size); + + if (path[0] != '/') { + if (!getcwd(result, size) || !result[0]) { + goto error_exit; + } + + dest = strchr(result, '\0'); + } else { + result[0] = '/'; + dest = result + 1; + } + + /* must_end signals that the last part of result..dest is not a directory, + and hence should terminate the string. */ + for (must_end = FALSE, start = end = path; *start; start = end) { + if (must_end) { + errno = ENOTDIR; + goto error_exit; + } + + /* Skip sequence of multiple path-separators. Exit if there is nothing + else after them. */ + while (*start == '/') { + ++start; + } + if (!*start) { + break; + } + + /* Find end of path component, handle special cases "." and "..". */ + end = start + 1; + while (*end && *end != '/') { + ++end; + } + + if (end - start == 1 && start[0] == '.') { + must_end = FALSE; + continue; + } + + if (end - start == 2 && start[0] == '.' && start[1] == '.') { + /* Back up to previous component, ignore if at root already. */ + while (dest > result + 1 && dest[-1] == '/') { + dest--; + } + do { + --dest; + } while (dest > result + 1 && dest[-1] != '/'); + must_end = FALSE; + continue; + } + + /* Tack on the next component. */ + if (dest[-1] != '/') { + *dest++ = '/'; + } + + if ((dest - result) + (end - start) >= size) { + gsize dest_offset = dest - result; + gsize new_size; + + /* Look out for overflow. */ + if (end - start + 1 > size || size * 2 < size) { + new_size = size + end - start + 1; + } else { + new_size = size * 2; + } + if (new_size < size) { + goto error_exit_ENAMETOOLONG; + } + + result = (char *) g_realloc(result, new_size); + dest = result + dest_offset; + } + + memcpy(dest, start, end - start); + dest += end - start; + *dest = '\0'; + if ((flags & QEMU_FILE_PATH_RESOLVE_SYMLINKS) == 0) { + continue; + } + + /* A non-existent file is okay at the last level. */ + if (lstat(result, &st) < 0) { + if (errno != ENOENT) { + goto error_exit; + } + + must_end = TRUE; + continue; + } + +#ifdef CONFIG_READLINK + if (!S_ISLNK(st.st_mode)) +#endif + { + must_end = !S_ISDIR(st.st_mode); + continue; + } + +#ifdef CONFIG_READLINK + if (num_links++ > MAXSYMLINKS) { + errno = ELOOP; + goto error_exit; + } + + buf = g_file_read_link(result, error); + if (!buf) { + return NULL; + } + + n = strlen(buf); + len = strlen(end); + if (!extra_buf) { + extra_buf = g_malloc(PATH_MAX); + } + if (n + len > PATH_MAX) { + goto error_exit_ENAMETOOLONG; + } + + /* Careful here, end may be a pointer into extra_buf. We have to + copy the tail first, and then place the symlink's destination. */ + g_memmove(&extra_buf[n], end, len + 1); + memcpy(extra_buf, buf, n); + end = extra_buf; + + /* Is it an absolute symlink? If not, and if not at root already, + back up to the previous component. Else reparse. */ + if (buf[0] == '/') { + dest = result + 1; + } else { + while (dest > result + 1 && dest[-1] == '/') { + dest--; + } + do { + --dest; + } while (dest > result + 1 && dest[-1] != '/'); + } + + g_free(buf); + must_end = FALSE; +#endif + } + + while (dest > result + 1 && dest[-1] == '/') { + --dest; + } + *dest = '\0'; + g_free(extra_buf); +#endif + + return result; + +error_exit_ENAMETOOLONG: +#ifdef ENAMETOOLONG + errno = ENAMETOOLONG; +#else + errno = EINVAL; +#endif + +error_exit: G_GNUC_UNUSED + if (error && !*error) { + int save_errno = errno; + gchar *display_path = g_filename_display_name(path); + + g_set_error(error, + G_FILE_ERROR, + g_file_error_from_errno(save_errno), + "Failed to resolve path '%s': %s", + display_path, + g_strerror(save_errno)); + } + + g_free(result); + g_free(extra_buf); + return NULL; +} + + + diff --git a/qemu-common.h b/qemu-common.h index 4647dd9..3c2af73 100644 --- a/qemu-common.h +++ b/qemu-common.h @@ -352,6 +352,14 @@ void qemu_progress_init(int enabled, float min_skip); void qemu_progress_end(void); void qemu_progress_print(float delta, int max); +typedef enum { + QEMU_FILE_PATH_RESOLVE_SYMLINKS = 1 << 0 +} QEMUFileFullPathFlags; + +gchar *qemu_file_get_full_path(const gchar *path, + QEMUFileFullPathFlags flags, + GError **error); + #define QEMU_FILE_TYPE_BIOS 0 #define QEMU_FILE_TYPE_KEYMAP 1 char *qemu_find_file(int type, const char *name); -- 1.7.9.rc2.1.g69204