From: Andrew Gregory <[email protected]>

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 <[email protected]>
---

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


Reply via email to