[pacman-dev] [PATCH] Allow querying directory ownership
From: Andrew Gregory <andrew.gregory.8@gmail.com> The restriction of not checking the ownership of a directory is unnecessary given that all the package filelists contain this information. Additional Changes: * mbasename/mdirname behave more like their posix counterparts * check that files are in root before looking for an owner * only compare package files of the same type as the target file * add lrealpath, like realpath but for symlinks * resolve root and make sure it ends with '/' * removed resolve_path() Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- This is an alternative to Allan's recent patch. This pretty thoroughly rewrites query_fileowner() and attempts to simplify the process in addition to allowing querying directories. I mentioned before that my version assumed that package file paths didn't contain symlinks; that assumption has been removed. My public repo is available at: https://bitbucket.org/andrewgregory/pacman/changesets/tip/dir_own_final It includes a perl script in src/pacan used to test this patch against various file/directory/symlink situations. src/pacman/query.c | 185 ++++++++++++++++++++++++++++------------------------ src/pacman/util.c | 128 ++++++++++++++++++++++++++++-------- src/pacman/util.h | 4 +- 3 files changed, 205 insertions(+), 112 deletions(-) diff --git a/src/pacman/query.c b/src/pacman/query.c index a1cc1cf..6ef0744 100644 --- a/src/pacman/query.c +++ b/src/pacman/query.c @@ -36,23 +36,6 @@ #include "conf.h" #include "util.h" -static char *resolve_path(const char *file) -{ - char *str = NULL; - - str = calloc(PATH_MAX, sizeof(char)); - if(!str) { - return NULL; - } - - if(!realpath(file, str)) { - free(str); - return NULL; - } - - return str; -} - /* check if filename exists in PATH */ static int search_path(char **filename, struct stat *bufptr) { @@ -107,134 +90,166 @@ static void print_query_fileowner(const char *filename, alpm_pkg_t *info) static int query_fileowner(alpm_list_t *targets) { - int ret = 0; - char path[PATH_MAX]; - const char *root; - size_t rootlen; - alpm_list_t *t; - alpm_db_t *db_local; - - /* This code is here for safety only */ if(targets == NULL) { pm_printf(ALPM_LOG_ERROR, _("no file was specified for --owns\n")); return 1; } - /* Set up our root path buffer. We only need to copy the location of root in - * once, then we can just overwrite whatever file was there on the previous - * iteration. */ - root = alpm_option_get_root(config->handle); + struct stat buf; + char root[PATH_MAX]; + size_t rootlen; + char path[PATH_MAX]; /* absolute real path of the target */ + char ppath[PATH_MAX]; /* absolute real path of the package file */ + int ret = 0; + + /* make sure root is a real path */ + if(realpath(alpm_option_get_root(config->handle), root) == NULL) { + pm_printf(ALPM_LOG_ERROR, + _("cannot determine real path for '%s': %s\n"), + alpm_option_get_root(config->handle), strerror(errno)); + return 1; + }; rootlen = strlen(root); - if(rootlen + 1 > PATH_MAX) { - /* we are in trouble here */ + /* make sure root ends with '/' */ + if(root[rootlen - 1] != '/' && rootlen < PATH_MAX) { + root[rootlen] = '/'; + rootlen++; + root[rootlen] = '\0'; + } + /* make sure we actually have room to append the package files */ + if(rootlen >= PATH_MAX) { pm_printf(ALPM_LOG_ERROR, _("path too long: %s%s\n"), root, ""); return 1; } - strcpy(path, root); - db_local = alpm_get_localdb(config->handle); + alpm_db_t *db_local = alpm_get_localdb(config->handle); + alpm_list_t *packages = alpm_db_get_pkgcache(db_local); + /* loop through targets */ + alpm_list_t *t; for(t = targets; t; t = alpm_list_next(t)) { - char *filename, *dname, *rpath; - const char *bname; - struct stat buf; - alpm_list_t *i; + char *filename = strdup(t->data); + char *bname; int found = 0; + int is_dir = 0; - filename = strdup(t->data); - + /* read target */ if(lstat(filename, &buf) == -1) { - /* if it is not a path but a program name, then check in PATH */ if(strchr(filename, '/') == NULL) { + /* if it is not a path but a program name, then check in PATH */ if(search_path(&filename, &buf) == -1) { - pm_printf(ALPM_LOG_ERROR, _("failed to find '%s' in PATH: %s\n"), + pm_printf(ALPM_LOG_ERROR, + _("failed to find '%s' in PATH: %s\n"), filename, strerror(errno)); ret++; free(filename); continue; } } else { + /* filename is a path we can't read so bail */ + pm_printf(ALPM_LOG_ERROR, _("failed to read file '%s': %s\n"), + filename, strerror(errno)); + ret++; + free(filename); + continue; + } + } + if((is_dir = S_ISDIR(buf.st_mode))){ + /* make sure filename doesn't end with '/' because + * lstat('/var/mail') is not the same as lstat("/var/mail/") */ + size_t flast = strlen(filename) - 1; + while(filename[flast] == '/' && flast > 0) { + filename[flast] = '\0'; + flast--; + } + if(lstat(filename, &buf) == -1) { pm_printf(ALPM_LOG_ERROR, _("failed to read file '%s': %s\n"), filename, strerror(errno)); ret++; free(filename); continue; } + /* check if filename is actually a symlink */ + is_dir = S_ISDIR(buf.st_mode); } - if(S_ISDIR(buf.st_mode)) { + /* convert filename to its real path */ + if(lrealpath(filename, path) == NULL) { pm_printf(ALPM_LOG_ERROR, - _("cannot determine ownership of directory '%s'\n"), filename); + _("cannot determine real path for '%s': %s\n"), + filename, strerror(errno)); ret++; free(filename); continue; } - bname = mbasename(filename); - dname = mdirname(filename); - /* for files in '/', there is no directory name to match */ - if(strcmp(dname, "") == 0) { - rpath = NULL; - } else { - rpath = resolve_path(dname); - - if(!rpath) { - pm_printf(ALPM_LOG_ERROR, _("cannot determine real path for '%s': %s\n"), - filename, strerror(errno)); - free(filename); - free(dname); - free(rpath); - ret++; - continue; - } + /* make sure target is inside root */ + if( strlen(path) <= rootlen || strncmp(path, root, rootlen) != 0) { + pm_printf(ALPM_LOG_ERROR, + _("cannot determine ownership of file outside of root\n")); + ret++; + free(filename); + continue; } - free(dname); - for(i = alpm_db_get_pkgcache(db_local); i && !found; i = alpm_list_next(i)) { + bname = mbasename(path); + + /* loop through installed packages */ + alpm_list_t *i; + for(i = packages; i && (!found || is_dir); i = alpm_list_next(i)) { alpm_pkg_t *info = i->data; alpm_filelist_t *filelist = alpm_pkg_get_files(info); - size_t j; - for(j = 0; j < filelist->count; j++) { + /* loop through package files */ + size_t j; + for(j = 0; j < filelist->count && (!found || is_dir); j++) { const alpm_file_t *file = filelist->files + j; - char *ppath, *pdname; - const char *pkgfile = file->name; + size_t flen = strlen(file->name); - /* avoid the costly resolve_path usage if the basenames don't match */ - if(strcmp(mbasename(pkgfile), bname) != 0) { + if(rootlen + flen >= PATH_MAX) { + pm_printf(ALPM_LOG_ERROR, _("path too long: %s%s\n"), + root, file->name); + ret++; continue; - } + }; - /* for files in '/', there is no directory name to match */ - if(!rpath) { - print_query_fileowner(filename, info); - found = 1; + /* continue if target and file are not the same type */ + if(!is_dir == (file->name[flen - 1] == '/')) { continue; } - if(rootlen + 1 + strlen(pkgfile) > PATH_MAX) { - pm_printf(ALPM_LOG_ERROR, _("path too long: %s%s\n"), root, pkgfile); + /* check basenames before we bother resolving the path */ + char *pbname = mbasename(file->name); + if(pbname && strcmp(pbname, bname) != 0) { + free(pbname); + continue; } - /* concatenate our file and the root path */ - strcpy(path + rootlen, pkgfile); + free(pbname); - pdname = mdirname(path); - ppath = resolve_path(pdname); - free(pdname); + /* resolve and compare the path */ + strcpy(root + rootlen, file->name); + if(lrealpath(root, ppath) == NULL) { + pm_printf(ALPM_LOG_ERROR, + _("cannot determine real path for '%s': %s\n"), + file->name, strerror(errno)); + ret++; + continue; + }; - if(ppath && strcmp(ppath, rpath) == 0) { - print_query_fileowner(filename, info); + if(strcmp(ppath, path) == 0) { + print_query_fileowner(path, info); found = 1; } - free(ppath); } } + if(!found) { - pm_printf(ALPM_LOG_ERROR, _("No package owns %s\n"), filename); + pm_printf(ALPM_LOG_ERROR, _("No package owns %s\n"), path); ret++; } + + free(bname); free(filename); - free(rpath); } return ret; diff --git a/src/pacman/util.c b/src/pacman/util.c index 7f7f6a7..733da9f 100644 --- a/src/pacman/util.c +++ b/src/pacman/util.c @@ -211,45 +211,121 @@ int rmrf(const char *path) } /** Parse the basename of a program from a path. +* Equivalent to posix basename except \a path is never modified and the +* returned string needs to be freed. +* * @param path path to parse basename from * -* @return everything following the final '/' +* @return final component of \a path */ -const char *mbasename(const char *path) +char *mbasename(const char *path) { - const char *last = strrchr(path, '/'); - if(last) { - return last + 1; - } - return path; + char *tmp = strdup(path); + char *bname = strdup(basename(tmp)); + free(tmp); + return bname; } /** Parse the dirname of a program from a path. -* The path returned should be freed. +* Equivalent to posix dirname except \a path is never modified and the +* returned string needs to be freed. +* * @param path path to parse dirname from * -* @return everything preceding the final '/' +* @return everything preceding the final component of \a path */ char *mdirname(const char *path) { - char *ret, *last; + char *tmp = strdup(path); + char *dname = strdup(dirname(tmp)); + free(tmp); + return dname; +} - /* null or empty path */ - if(path == NULL || path == '\0') { - return strdup("."); +/** Resolve a path to its canonicalized absolute pathname. +* Equivalent to posix realpath except that if \a path points to a symlink the +* parent directory is resolved rather than the symlink itself. +* +* @param path path to resolve +* @param resolved_path string to hold resolved path, will be malloc'd if NULL +* +* @return pointer to resolved_path on success, NULL on error +*/ +char *lrealpath(const char *path, char *resolved_path) +{ + char *bname = NULL; + char *dname = NULL; + int need_free = 0; /* did we calloc resolved_path? */ + + bname = mbasename(path); + if(bname == NULL) { + goto error; + } else if(strcmp(bname, "..") == 0 + || strcmp(bname, ".") == 0 + || strcmp(bname, "/") == 0) { + /* handle a few special cases of bname */ + dname = strdup(path); + bname = NULL; + free(bname); + } else { + dname = mdirname(path); + } + if(dname == NULL) { + goto error; } - ret = strdup(path); - last = strrchr(ret, '/'); + if(resolved_path == NULL) { + resolved_path = calloc(PATH_MAX, sizeof(char)); + if(resolved_path == NULL) { + goto error; + } + need_free = 1; + } - if(last != NULL) { - /* we found a '/', so terminate our string */ - *last = '\0'; - return ret; + if(realpath(dname, resolved_path) == NULL) { + goto error; } - /* no slash found */ - free(ret); - return strdup("."); + + /* add bname back if we have a valid one */ + if(bname) { + size_t rlen = strlen(resolved_path); + + /* error out if resolved_path is too long to append bname */ + if((rlen + strlen(bname) + 2) >= PATH_MAX) { + goto error; + } + + /* append trailing '/' if needed */ + if(resolved_path[rlen - 1] != '/') { + resolved_path[rlen] = '/'; + resolved_path[rlen+1] = '\0'; + rlen++; + } + + strcpy(resolved_path + rlen, bname); + } + + if(bname) { + free(bname); + } + if(dname) { + free(dname); + } + + return resolved_path; + +error: + if(need_free) { + free(resolved_path); + } + if(bname) { + free(bname); + } + if(dname) { + free(dname); + } + + return NULL; } /* output a string, but wrap words properly with a specified indentation @@ -1661,9 +1737,9 @@ int pm_vfprintf(FILE *stream, alpm_loglevel_t level, const char *format, va_list /* A quick and dirty implementation derived from glibc */ static size_t strnlen(const char *s, size_t max) { - register const char *p; - for(p = s; *p && max--; ++p); - return (p - s); + register const char *p; + for(p = s; *p && max--; ++p); + return (p - s); } char *strndup(const char *s, size_t n) @@ -1672,7 +1748,7 @@ char *strndup(const char *s, size_t n) char *new = (char *) malloc(len + 1); if(new == NULL) - return NULL; + return NULL; new[len] = '\0'; return (char *)memcpy(new, s, len); diff --git a/src/pacman/util.h b/src/pacman/util.h index 0dfdc85..b51505a 100644 --- a/src/pacman/util.h +++ b/src/pacman/util.h @@ -23,6 +23,7 @@ #include <stdlib.h> #include <stdarg.h> #include <string.h> +#include <libgen.h> #include <alpm_list.h> @@ -49,8 +50,9 @@ int needs_root(void); int check_syncdbs(size_t need_repos, int check_valid); unsigned short getcols(int fd); int rmrf(const char *path); -const char *mbasename(const char *path); +char *mbasename(const char *path); char *mdirname(const char *path); +char *lrealpath(const char *path, char *resolved_path); void indentprint(const char *str, unsigned short indent, unsigned short cols); size_t strtrim(char *str); char *strreplace(const char *str, const char *needle, const char *replace); -- 1.7.10.2
participants (1)
-
andrew.gregory.8@gmail.com