[pacman-dev] [PATCH] Allow querying directory ownership

andrew.gregory.8 at gmail.com andrew.gregory.8 at gmail.com
Thu May 24 20:42:46 EDT 2012


From: Andrew Gregory <andrew.gregory.8 at 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 at 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



More information about the pacman-dev mailing list