[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