Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
pkgdb: track uname, gname, permissions and fflags in pkgdb
strssndktn committed 7 months ago
commit 699f92e2cfc8f7843d0104b536d658a3a2d88bab
parent 9cf9e10
16 files changed +450 -62
modified docs/pkg-query.8
@@ -223,14 +223,38 @@ for the package origin, and
for the package version.
.It Cm \&%C
Expands to the list of categories the matched package belongs to.
-
.It Cm \&%F Ns Op ps
+
.It Cm \&%F Ns Op psugmft
Expands to the list of files of the matched package, where
.Cm p
-
stands for path, and
+
stands for path,
.Cm s
-
for sum.
+
for checksum,
+
.Cm u
+
for owner,
+
.Cm g
+
for group,
+
.Cm m
+
for mode (permissions),
+
.Cm f
+
for file flags, and
+
.Cm t
+
for the symlink target (or empty string if no symlink).
.It Cm \&%D
Expands to the list of directories of the matched package.
+
.It Cm \&%S Ns Op pugmf
+
Expands to the list of (sub-)directories of the matched package, where
+
.Cm p
+
stands for path,
+
.Cm u
+
for owner,
+
.Cm g
+
for group,
+
.Cm m
+
for mode (permissions), and
+
.Cm f
+
for file flags. Same as
+
.Cm \&%D
+
but allows specifying suboptions.
.It Cm \&%O Ns Op kvdD
Expands to the list of options of the matched package, where
.Cm k
modified docs/pkg_printf.3
@@ -485,6 +485,9 @@ Directories [array]
.Pp
Default row format:
.Cm "%D%{%Dn\en%|%}"
+
.It Cm %Df
+
Directory file flags [string]
+
.Vt struct pkg_dir *
.It Cm %Dg
Directory ownership: group name [string]
.Vt struct pkg_dir *
@@ -503,6 +506,9 @@ Files [array]
.Pp
Default row format:
.Cm "%F%{%Fn\en%|%}"
+
.It Cm %Ff
+
File flags [string]
+
.Vt struct pkg_file *
.It Cm %Fg
File ownership: group name [string]
.Vt struct pkg_file *
@@ -515,6 +521,9 @@ File permissions [mode]
.It Cm %Fs
File SHA256 checksum [string]
.Vt struct pkg_file *
+
.It Cm %Ft
+
File Symlink target [string]
+
.Vt struct pkg_file *
.It Cm %Fu
File ownership: user name [string]
.Vt struct pkg_file *
modified libpkg/pkg.c
@@ -493,13 +493,13 @@ pkg_addrdep(struct pkg *pkg, const char *name, const char *origin, const char *v
int
pkg_addfile(struct pkg *pkg, const char *path, const char *sum, bool check_duplicates)
{
-
	return (pkg_addfile_attr(pkg, path, sum, NULL, NULL, 0, 0, check_duplicates));
+
	return (pkg_addfile_attr(pkg, path, sum, NULL, NULL, 0, 0, NULL, check_duplicates));
}

int
pkg_addfile_attr(struct pkg *pkg, const char *path, const char *sum,
-
    const char *uname, const char *gname, mode_t perm, u_long fflags,
-
    bool check_duplicates)
+
		 const char *uname, const char *gname, mode_t perm, u_long fflags,
+
		 const char *symlink_target, bool check_duplicates)
{
	struct pkg_file *f = NULL;
	char abspath[MAXPATHLEN];
@@ -538,6 +538,9 @@ pkg_addfile_attr(struct pkg *pkg, const char *path, const char *sum,
	if (fflags != 0)
		f->fflags = fflags;

+
	if (symlink_target != NULL)
+
		strlcpy(f->symlink_target, symlink_target, sizeof(f->symlink_target));
+

	pkghash_safe_add(pkg->filehash, f->path, f, NULL);
	DL_APPEND(pkg->files, f);

modified libpkg/pkg_create.c
@@ -56,7 +56,8 @@ extern struct pkg_ctx ctx;

static int
pkg_create_from_dir(struct pkg *pkg, const char *root,
-
    struct pkg_create *pc, struct packing *pkg_archive)
+
		    struct pkg_create *pc, struct packing *pkg_archive,
+
		    bool trust_filesystem)
{
	char		 fpath[MAXPATHLEN];
	struct pkg_file	*file = NULL;
@@ -69,6 +70,7 @@ pkg_create_from_dir(struct pkg *pkg, const char *root,
	char		*manifest;
	ucl_object_t	*obj;
	hardlinks_t	 hardlinks = vec_init();
+
	ssize_t		 linklen;

	if (pkg_is_valid(pkg) != EPKG_OK) {
		pkg_emit_error("the package is not valid");
@@ -112,12 +114,24 @@ pkg_create_from_dir(struct pkg *pkg, const char *root,
			flatsize += file->size;
		}

-
		free(file->sum);
-
		file->sum = pkg_checksum_generate_file(fpath,
-
		    PKG_HASH_TYPE_SHA256_HEX);
-
		if (file->sum == NULL) {
-
			vec_free_and_free(&hardlinks, free);
-
			return (EPKG_FATAL);
+
		if (trust_filesystem) {
+
			free(file->sum);
+
			file->sum = pkg_checksum_generate_file(fpath,
+
							       PKG_HASH_TYPE_SHA256_HEX);
+
			if (file->sum == NULL) {
+
				vec_free_and_free(&hardlinks, free);
+
				return (EPKG_FATAL);
+
			}
+

+
			if (S_ISLNK(st.st_mode)) {
+
				linklen = readlink(fpath, file->symlink_target, sizeof(file->symlink_target) - 1);
+
				if (linklen == -1) {
+
					vec_free_and_free(&hardlinks, free);
+
					pkg_emit_errno("pkg_create_from_dir", "readlink failed");
+
					return (EPKG_FATAL);
+
				}
+
				file->symlink_target[linklen] = '\0';
+
			}
		}

		counter_count();
@@ -380,7 +394,7 @@ pkg_create_i(struct pkg_create *pc, struct pkg *pkg, bool hash)
		return (EPKG_FATAL);
	}

-
	if ((ret = pkg_create_from_dir(pkg, NULL, pc, pkg_archive)) != EPKG_OK) {
+
	if ((ret = pkg_create_from_dir(pkg, NULL, pc, pkg_archive, false)) != EPKG_OK) {
		pkg_emit_error("package creation failed");
	}
	packing_finish(pkg_archive);
@@ -422,7 +436,7 @@ pkg_create(struct pkg_create *pc, const char *metadata, const char *plist,
		return (EPKG_FATAL);
	}

-
	if ((ret = pkg_create_from_dir(pkg, pc->rootdir, pc, pkg_archive)) != EPKG_OK)
+
	if ((ret = pkg_create_from_dir(pkg, pc->rootdir, pc, pkg_archive, true)) != EPKG_OK)
		pkg_emit_error("package creation failed");

	packing_finish(pkg_archive);
modified libpkg/pkg_manifest.c
@@ -575,7 +575,9 @@ pkg_set_files_from_object(struct pkg *pkg, const ucl_object_t *obj)
	const char *sum = NULL;
	const char *uname = NULL;
	const char *gname = NULL;
+
	const char *symlink_target = NULL;
	mode_t perm = 0;
+
	u_long fflags = 0;
	char *fname = NULL;
	const char *key, *okey;

@@ -591,8 +593,7 @@ pkg_set_files_from_object(struct pkg *pkg, const ucl_object_t *obj)
			uname = ucl_object_tostring(cur);
		else if (STRIEQ(key, "gname") && cur->type == UCL_STRING)
			gname = ucl_object_tostring(cur);
-
		else if (STRIEQ(key, "sum") && cur->type == UCL_STRING &&
-
			 strlen(ucl_object_tostring(cur)) == 64)
+
		else if (STRIEQ(key, "sum") && cur->type == UCL_STRING)
			sum = ucl_object_tostring(cur);
		else if (STRIEQ(key, "perm") &&
			 (cur->type == UCL_STRING || cur->type == UCL_INT)) {
@@ -603,14 +604,28 @@ pkg_set_files_from_object(struct pkg *pkg, const ucl_object_t *obj)
			else
				perm = getmode(set, 0);
			free(set);
+
#ifdef HAVE_STRTOFFLAGS
+
		} else if (STRIEQ(key, "fflags") && cur->type == UCL_STRING) {
+
			char *str_flags = (char *)ucl_object_tostring(cur);
+
			u_long clearp;
+

+
			if (strtofflags(&str_flags, &fflags, &clearp) != 0 ||
+
			    clearp != 0) {
+
				fflags = 0;
+
				pkg_emit_error("Not valid file flags: %s", str_flags);
+
				continue;
+
			}
+
#endif
+
		} else if (STRIEQ(key, "symlink_target") && cur->type == UCL_STRING) {
+
			symlink_target = ucl_object_tostring(cur);
		} else {
			dbg(1, "Skipping unknown key for file(%s): %s",
			    fname, key);
		}
	}

-
	pkg_addfile_attr(pkg, fname, sum, uname, gname, perm, 0,
-
	    false);
+
	pkg_addfile_attr(pkg, fname, sum, uname, gname, perm, fflags,
+
			 symlink_target, false);
	free(fname);

	return (EPKG_OK);
@@ -624,6 +639,7 @@ pkg_set_dirs_from_object(struct pkg *pkg, const ucl_object_t *obj)
	const char *uname = NULL;
	const char *gname = NULL;
	mode_t perm = 0;
+
	u_long fflags = 0;
	char *dirname = NULL;
	const char *key, *okey;

@@ -648,6 +664,18 @@ pkg_set_dirs_from_object(struct pkg *pkg, const ucl_object_t *obj)
			else
				perm = getmode(set, 0);
			free(set);
+
#ifdef HAVE_STRTOFFLAGS
+
		} else if (STRIEQ(key, "fflags") && cur->type == UCL_STRING) {
+
			char *str_flags = (char *)ucl_object_tostring(cur);
+
			u_long clearp;
+

+
			if (strtofflags(&str_flags, &fflags, &clearp) != 0 ||
+
			    clearp != 0) {
+
				fflags = 0;
+
				pkg_emit_error("Not valid file flags: %s", str_flags);
+
				continue;
+
			}
+
#endif
		} else if (STRIEQ(key, "try") && cur->type == UCL_BOOLEAN) {
			/* ignore on purpose : compatibility*/
		} else {
@@ -656,7 +684,7 @@ pkg_set_dirs_from_object(struct pkg *pkg, const ucl_object_t *obj)
		}
	}

-
	pkg_adddir_attr(pkg, dirname, uname, gname, perm, 0, false);
+
	pkg_adddir_attr(pkg, dirname, uname, gname, perm, fflags, false);
	free(dirname);

	return (EPKG_OK);
@@ -911,6 +939,7 @@ pkg_emit_object(struct pkg *pkg, short flags)
	int i;
	const char *script_types = NULL;
	char legacyarch[BUFSIZ];
+
	char perm_str[sizeof("00000")];
	ucl_object_t *map, *seq, *submap;
	ucl_object_t *top = ucl_object_typed_new(UCL_OBJECT);

@@ -1112,6 +1141,7 @@ pkg_emit_object(struct pkg *pkg, short flags)
			while (pkg_files(pkg, &file) == EPKG_OK) {
				char dpath[MAXPATHLEN];
				const char *dp = file->path;
+
				ucl_object_t *file_attrs;

				if (pkg->oprefix != NULL) {
					size_t l = strlen(pkg->prefix);
@@ -1125,12 +1155,34 @@ pkg_emit_object(struct pkg *pkg, short flags)
				if (file->sum == NULL)
					file->sum = xstrdup("-");

+
				file_attrs = ucl_object_typed_new(UCL_OBJECT);
+
				ucl_object_insert_key(file_attrs,
+
						      ucl_object_fromstring(file->sum),
+
						      "sum", 0, false);
+
				ucl_object_insert_key(file_attrs,
+
						      ucl_object_fromstring(file->uname[0] != '\0' ? file->uname : "root"),
+
						      "uname", 0, false);
+
				ucl_object_insert_key(file_attrs,
+
						      ucl_object_fromstring(file->gname[0] != '\0' ? file->gname : "wheel"),
+
						      "gname", 0, false);
+
				snprintf(perm_str, sizeof(perm_str), "%#4.4o", file->perm);
+
				ucl_object_insert_key(file_attrs,
+
						      ucl_object_fromstring(perm_str),
+
						      "perm", 0, false);
+
				ucl_object_insert_key(file_attrs,
+
						      ucl_object_fromint(file->fflags),
+
						      "fflags", 0, false);
+
				if (file->symlink_target[0] != '\0') {
+
					ucl_object_insert_key(file_attrs,
+
							      ucl_object_fromstring(file->symlink_target),
+
							      "symlink_target", 0, false);
+
				}
+

				urlencode(dp, &tmpsbuf);
				if (map == NULL)
					map = ucl_object_typed_new(UCL_OBJECT);
-
				ucl_object_insert_key(map,
-
				    ucl_object_fromstring(file->sum),
-
				    tmpsbuf->buf, strlen(tmpsbuf->buf), true);
+
				ucl_object_insert_key(map, file_attrs,
+
						      tmpsbuf->buf, 0, true);
			}
			if (map)
				ucl_object_insert_key(top, map, "files", 5, false);
@@ -1149,12 +1201,28 @@ pkg_emit_object(struct pkg *pkg, short flags)
			dbg(4, "Emitting directories");
			map = NULL;
			while (pkg_dirs(pkg, &dir) == EPKG_OK) {
+
				ucl_object_t *dir_attrs;
+

+
				dir_attrs = ucl_object_typed_new(UCL_OBJECT);
+
				ucl_object_insert_key(dir_attrs,
+
						      ucl_object_fromstring(dir->uname),
+
						      "uname", 0, false);
+
				ucl_object_insert_key(dir_attrs,
+
						      ucl_object_fromstring(dir->gname),
+
						      "gname", 0, false);
+
				snprintf(perm_str, sizeof(perm_str), "%#4.4o", dir->perm);
+
				ucl_object_insert_key(dir_attrs,
+
						      ucl_object_fromstring(perm_str),
+
						      "perm", 0, false);
+
				ucl_object_insert_key(dir_attrs,
+
						      ucl_object_fromint(dir->fflags),
+
						      "fflags", 0, false);
+

				urlencode(dir->path, &tmpsbuf);
				if (map == NULL)
					map = ucl_object_typed_new(UCL_OBJECT);
-
				ucl_object_insert_key(map,
-
				    ucl_object_fromstring("y"),
-
				    tmpsbuf->buf, strlen(tmpsbuf->buf), true);
+
				ucl_object_insert_key(map, dir_attrs,
+
						      tmpsbuf->buf, strlen(tmpsbuf->buf), true);
			}
			if (map)
				ucl_object_insert_key(top, map, "directories", 11, false);
modified libpkg/pkg_ports.c
@@ -324,6 +324,8 @@ meta_file(struct plist *p, char *line, struct file_attr *a, bool is_config)
{
	size_t len;
	char path[MAXPATHLEN];
+
	ssize_t linklen = 0;
+
	char symlink_target[MAXPATHLEN];
	struct stat st;
	char *buf = NULL;
	bool regular = false;
@@ -361,8 +363,16 @@ meta_file(struct plist *p, char *line, struct file_attr *a, bool is_config)
			regular = !check_for_hardlink(&p->hardlinks, &st);
		else
			regular = true;
-
	} else if (S_ISLNK(st.st_mode))
+
	} else if (S_ISLNK(st.st_mode)) {
		regular = false;
+
		linklen = readlinkat(p->stagefd, RELATIVE_PATH(path),
+
				     symlink_target, sizeof(symlink_target) - 1);
+
		if (linklen == -1) {
+
			pkg_emit_errno("meta_file", "readlink failed");
+
			return (EPKG_FATAL);
+
		}
+
		symlink_target[linklen] = '\0';
+
	}

	buf = pkg_checksum_generate_fileat(p->stagefd, RELATIVE_PATH(path),
	    PKG_HASH_TYPE_SHA256_HEX);
@@ -394,13 +404,17 @@ meta_file(struct plist *p, char *line, struct file_attr *a, bool is_config)

	if (a != NULL) {
		ret = pkg_addfile_attr(p->pkg, path, buf,
-
		    a->owner ? a->owner : p->uname,
-
		    a->group ? a->group : p->gname,
-
		    a->mode ? a->mode : p->perm,
-
		    a->fflags, true);
+
				       a->owner ? a->owner : p->uname,
+
				       a->group ? a->group : p->gname,
+
				       a->mode ? a->mode : p->perm,
+
				       a->fflags,
+
				       linklen > 0 ? symlink_target : NULL,
+
				       true);
	} else {
		ret = pkg_addfile_attr(p->pkg, path, buf, p->uname,
-
		    p->gname, p->perm, 0, true);
+
				       p->gname, p->perm, 0,
+
				       linklen > 0 ? symlink_target : NULL,
+
				       true);
	}

	free(buf);
modified libpkg/pkg_printf.c
@@ -62,6 +62,7 @@
 * Cn pkg_category Category name
 *
 * D  pkg          List of directories
+
 * Df pkg_dir      File flags of directory
 * Dg pkg_dir      Group owner of directory
 * Dk pkg_dir      Keep flag
 * Dn pkg_dir      Directory path name
@@ -72,11 +73,13 @@
 * E
 *
 * F  pkg          List of files
+
 * Ff pkg_file     File flags of file
 * Fg pkg_file     Group owner of file
 * Fk pkg_file     Keep flag
 * Fn pkg_file     File path name
 * Fp pkg_file     File permissions
 * Fs pkg_file     File SHA256 checksum
+
 * Ft pkg_file     File symlink target
 * Fu pkg_file     User owner of file
 *
 * G  pkg          List of groups
@@ -246,6 +249,14 @@ static const struct pkg_printf_fmt fmt[] = {
		PP_PKG,
		&format_categories,
	},
+
	[PP_PKG_DIRECTORY_FFLAGS] = {
+
		'D',
+
		'f',
+
		false,
+
		false,
+
		PP_PKG|PP_D,
+
		&format_directory_fflags,
+
	},
        [PP_PKG_DIRECTORY_GROUP] =
	{
		'D',
@@ -291,6 +302,15 @@ static const struct pkg_printf_fmt fmt[] = {
		PP_PKG,
		&format_directories,
	},
+
	[PP_PKG_FILE_FFLAGS] =
+
	{
+
		'F',
+
		'f',
+
		false,
+
		false,
+
		PP_PKG|PP_F,
+
		&format_file_fflags,
+
	},
	[PP_PKG_FILE_GROUP] =
	{
		'F',
@@ -327,6 +347,15 @@ static const struct pkg_printf_fmt fmt[] = {
		PP_PKG|PP_F,
		&format_file_sha256,
	},
+
	[PP_PKG_FILE_SYMLINK_TARGET] =
+
	{
+
		'F',
+
		't',
+
		false,
+
		false,
+
		PP_PKG|PP_F,
+
		&format_file_symlink_target,
+
	},
	[PP_PKG_FILE_USER] =
	{
		'F',
@@ -837,6 +866,25 @@ static const struct pkg_printf_fmt fmt[] = {
	},
};

+
static xstring *
+
format_fflags(xstring *buf, u_long fflags, struct percent_esc *p)
+
{
+
	xstring *ret;
+

+
	if (fflags == 0) {
+
		ret = string_val(buf, "-", p);
+
	} else {
+
#ifdef HAVE_FFLAGSTOSTR
+
		char *fflags_str = fflagstostr(fflags);
+
		ret = string_val(buf, fflags_str, p);
+
		free(fflags_str);
+
#else
+
		ret = string_val(buf, "-", p);
+
#endif
+
	}
+
	return (ret);
+
}
+

/*
 * Note: List values -- special behaviour with ? and # modifiers.
 * Affects %A %B %C %D %F %G %L %O %U %b %d %r
@@ -1027,6 +1075,17 @@ format_directories(xstring *buf, const void *data, struct percent_esc *p)
}

/*
+
 * %Df -- Directory flags.
+
 */
+
xstring *
+
format_directory_fflags(xstring *buf, const void *data, struct percent_esc *p)
+
{
+
	const struct pkg_dir *dir = data;
+
	return (format_fflags(buf, dir->fflags, p));
+
}
+

+

+
/*
 * %Dg -- Directory group. TODO: numeric gid
 */
xstring *
@@ -1153,6 +1212,16 @@ format_file_sha256(xstring *buf, const void *data, struct percent_esc *p)
}

/*
+
 * %Ft -- File symlink target.
+
 */
+
xstring *
+
format_file_symlink_target(xstring *buf, const void *data, struct percent_esc *p)
+
{
+
	const struct pkg_file *file = data;
+
	return (string_val(buf, file->symlink_target, p));
+
}
+

+
/*
 * %Fu -- File user.
 */
xstring *
@@ -1164,6 +1233,16 @@ format_file_user(xstring *buf, const void *data, struct percent_esc *p)
}

/*
+
 * %Ff -- File fflags.
+
 */
+
xstring *
+
format_file_fflags(xstring *buf, const void *data, struct percent_esc *p)
+
{
+
	const struct pkg_file *file = data;
+
	return (format_fflags(buf, file->fflags, p));
+
}
+

+
/*
 * %G -- Groups. list of string values.  Optionally accepts following
 * per-field format in %{ %| %} where %Gn will be replaced by each
 * groupname or %#Gn by the gid -- a line from
modified libpkg/pkgdb.c
@@ -74,7 +74,7 @@ extern struct pkg_ctx ctx;
*/

#define DB_SCHEMA_MAJOR	0
-
#define DB_SCHEMA_MINOR	36
+
#define DB_SCHEMA_MINOR	37

#define DBVERSION (DB_SCHEMA_MAJOR * 1000 + DB_SCHEMA_MINOR)

@@ -416,12 +416,21 @@ pkgdb_init(sqlite3 *sdb)
	"CREATE TABLE files ("
		"path TEXT PRIMARY KEY,"
		"sha256 TEXT,"
+
		"uname TEXT,"
+
		"gname TEXT,"
+
		"perm INTEGER,"
+
		"fflags INTEGER,"
+
		"symlink_target TEXT,"
		"package_id INTEGER REFERENCES packages(id) ON DELETE CASCADE"
			" ON UPDATE CASCADE"
	");"
	"CREATE TABLE directories ("
		"id INTEGER PRIMARY KEY,"
-
		"path TEXT NOT NULL UNIQUE"
+
		"path TEXT NOT NULL UNIQUE,"
+
		"uname TEXT,"
+
		"gname TEXT,"
+
		"perm INTEGER,"
+
		"fflags INTEGER"
	");"
	"CREATE TABLE pkg_directories ("
		"package_id INTEGER REFERENCES packages(id) ON DELETE CASCADE"
@@ -1356,20 +1365,23 @@ static sql_prstmt sql_prepared_statements[PRSTMT_LAST] = {
	},
	[FILES] = {
		NULL,
-
		"INSERT INTO files (path, sha256, package_id) "
-
		"VALUES (?1, ?2, ?3)",
-
		"TTI",
+
		"INSERT INTO files (path, sha256, uname, gname, "
+
		"perm, fflags, symlink_target, package_id) "
+
		"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
+
		"TTTTIITI",
	},
	[FILES_REPLACE] = {
		NULL,
-
		"INSERT OR REPLACE INTO files (path, sha256, package_id) "
-
		"VALUES (?1, ?2, ?3)",
-
		"TTI",
+
		"INSERT OR REPLACE INTO files (path, sha256, uname, gname, "
+
		"perm, fflags, symlink_target, package_id) "
+
		"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
+
		"TTTTIITI",
	},
	[DIRS1] = {
		NULL,
-
		"INSERT OR IGNORE INTO directories(path) VALUES(?1)",
-
		"T",
+
		"INSERT OR IGNORE INTO directories(path, uname, gname, perm, fflags) "
+
		"VALUES(?1,?2,?3,?4,?5)",
+
		"TTTII",
	},
	[DIRS2] = {
		NULL,
@@ -1652,6 +1664,12 @@ prstmt_finalize(struct pkgdb *db)
	return;
}

+
static const char *
+
_pkgdb_empty_str_null(const char *str)
+
{
+
	return (str[0] == '\0' ? NULL : str);
+
}
+

/*
 * Register a package in the database.  If successful, the caller is required to
 * call pkgdb_register_finale() in order to either commit or roll back the
@@ -1747,7 +1765,12 @@ pkgdb_register_pkg(struct pkgdb *db, struct pkg *pkg, int forced,
			printf("matched\n");
		}

-
		ret = run_prstmt(FILES, file->path, file->sum, package_id);
+
		ret = run_prstmt(FILES, file->path, file->sum,
+
				 _pkgdb_empty_str_null(file->uname),
+
				 _pkgdb_empty_str_null(file->gname),
+
				 file->perm, file->fflags,
+
				 _pkgdb_empty_str_null(file->symlink_target),
+
				 package_id);
		if (ret == SQLITE_DONE)
			continue;
		if (ret != SQLITE_CONSTRAINT) {
@@ -1765,6 +1788,10 @@ pkgdb_register_pkg(struct pkgdb *db, struct pkg *pkg, int forced,
			/* Stray entry in the files table not related to
			   any known package: overwrite this */
			ret = run_prstmt(FILES_REPLACE, file->path, file->sum,
+
					 _pkgdb_empty_str_null(file->uname),
+
					 _pkgdb_empty_str_null(file->gname),
+
					 file->perm, file->fflags,
+
					 _pkgdb_empty_str_null(file->symlink_target),
					 package_id);
			pkgdb_it_free(it);
			if (ret == SQLITE_DONE)
@@ -1822,7 +1849,9 @@ pkgdb_register_pkg(struct pkgdb *db, struct pkg *pkg, int forced,
	 */

	while (pkg_dirs(pkg, &dir) == EPKG_OK) {
-
		if (run_prstmt(DIRS1, dir->path) != SQLITE_DONE) {
+
		if (run_prstmt(DIRS1, dir->path, _pkgdb_empty_str_null(dir->uname),
+
			       _pkgdb_empty_str_null(dir->gname),
+
			       dir->perm, dir->fflags) != SQLITE_DONE) {
			ERROR_STMT_SQLITE(s, STMT(DIRS1));
			goto cleanup;
		}
modified libpkg/pkgdb_iterator.c
@@ -388,7 +388,7 @@ pkgdb_load_files(sqlite3 *sqlite, struct pkg *pkg)
	sqlite3_stmt	*stmt = NULL;
	int		 ret;
	const char	 sql[] = ""
-
		"SELECT path, sha256"
+
		"SELECT path, sha256, uname, gname, perm, fflags, symlink_target "
		"  FROM files"
		"  WHERE package_id = ?1"
		"  ORDER BY PATH ASC";
@@ -412,8 +412,15 @@ pkgdb_load_files(sqlite3 *sqlite, struct pkg *pkg)
	pkgdb_debug(4, stmt);

	while (sqlite3_step(stmt) == SQLITE_ROW) {
-
		pkg_addfile(pkg, sqlite3_column_text(stmt, 0),
-
		    sqlite3_column_text(stmt, 1), false);
+
		const char *path = sqlite3_column_text(stmt, 0);
+
		const char *sum = sqlite3_column_text(stmt, 1);
+
		const char *uname = sqlite3_column_text(stmt, 2);
+
		const char *gname = sqlite3_column_text(stmt, 3);
+
		mode_t perm = sqlite3_column_int64(stmt, 4);
+
		u_long fflags = sqlite3_column_int64(stmt, 5);
+
		const char *symlink_target = sqlite3_column_text(stmt, 6);
+
		pkg_addfile_attr(pkg, path, sum, uname, gname, perm, fflags,
+
				 symlink_target, false);
	}
	sqlite3_finalize(stmt);

@@ -445,7 +452,7 @@ static int
pkgdb_load_dirs(sqlite3 *sqlite, struct pkg *pkg)
{
	const char	 sql[] = ""
-
		"SELECT path, try"
+
		"SELECT path, uname, gname, perm, fflags, try"
		"  FROM pkg_directories, directories"
		"  WHERE package_id = ?1"
		"    AND directory_id = directories.id"
@@ -467,7 +474,12 @@ pkgdb_load_dirs(sqlite3 *sqlite, struct pkg *pkg)
	pkgdb_debug(4, stmt);

	while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) {
-
		pkg_adddir(pkg, sqlite3_column_text(stmt, 0), false);
+
		const char *path = sqlite3_column_text(stmt, 0);
+
		const char *uname = sqlite3_column_text(stmt, 1);
+
		const char *gname = sqlite3_column_text(stmt, 2);
+
		mode_t perm = sqlite3_column_int64(stmt, 3);
+
		u_long fflags = sqlite3_column_int64(stmt, 4);
+
		pkg_adddir_attr(pkg, path, uname, gname, perm, fflags, false);
	}

	if (ret != SQLITE_DONE) {
modified libpkg/private/db_upgrades.h
@@ -728,6 +728,16 @@ static struct db_upgrades {
	"DROP VIEW IF EXISTS lua_scripts; "
	"DROP VIEW IF EXISTS options; "
	"DROP VIEW IF EXISTS scripts; "
+
	}, { 37,
+
	"ALTER TABLE files ADD COLUMN uname TEXT; "
+
	"ALTER TABLE files ADD COLUMN gname TEXT; "
+
	"ALTER TABLE files ADD COLUMN perm INTEGER; "
+
	"ALTER TABLE files ADD COLUMN fflags INTEGER; "
+
	"ALTER TABLE files ADD COLUMN symlink_target TEXT; "
+
	"ALTER TABLE directories ADD COLUMN uname TEXT; "
+
	"ALTER TABLE directories ADD COLUMN gname TEXT; "
+
	"ALTER TABLE directories ADD COLUMN perm INTEGER; "
+
	"ALTER TABLE directories ADD COLUMN fflags INTEGER; "
	},
	/* Mark the end of the array */
	{ -1, NULL }
modified libpkg/private/pkg.h
@@ -377,6 +377,7 @@ struct pkg_file {
	gid_t		 gid;
	char		 temppath[MAXPATHLEN];
	u_long		 fflags;
+
	char		 symlink_target[MAXPATHLEN];
	struct pkg_config_file *config;
	struct timespec	 time[2];
	struct pkg_file	*next, *prev;
@@ -791,7 +792,7 @@ int pkg_addfile(struct pkg *pkg, const char *path, const char *sha256,
    bool check_duplicates);
int pkg_addfile_attr(struct pkg *pkg, const char *path, const char *sha256,
    const char *uname, const char *gname, mode_t perm, u_long fflags,
-
    bool check_duplicates);
+
    const char *symlink_target, bool check_duplicates);

int pkg_adddir(struct pkg *pkg, const char *path, bool check_duplicates);
int pkg_adddir_attr(struct pkg *pkg, const char *path, const char *uname,
modified libpkg/private/pkg_printf.h
@@ -85,15 +85,18 @@ typedef enum _fmt_code_t {
	PP_PKG_SHLIBS_REQUIRED,
	PP_PKG_CATEGORY_NAME,
	PP_PKG_CATEGORIES,
+
	PP_PKG_DIRECTORY_FFLAGS,
	PP_PKG_DIRECTORY_GROUP,
	PP_PKG_DIRECTORY_PATH,
	PP_PKG_DIRECTORY_PERMS,
	PP_PKG_DIRECTORY_USER,
	PP_PKG_DIRECTORIES,
+
	PP_PKG_FILE_FFLAGS,
	PP_PKG_FILE_GROUP,
	PP_PKG_FILE_PATH,
	PP_PKG_FILE_PERMS,
	PP_PKG_FILE_SHA256,
+
	PP_PKG_FILE_SYMLINK_TARGET,
	PP_PKG_FILE_USER,
	PP_PKG_FILES,
	PP_PKG_GROUP_NAME,
@@ -176,6 +179,7 @@ _static xstring *format_shlib_name(xstring *, const void *, struct percent_esc *
_static xstring *format_categories(xstring *, const void *, struct percent_esc *);
_static xstring *format_category_name(xstring *, const void *, struct percent_esc *);
_static xstring *format_directories(xstring *, const void *, struct percent_esc *);
+
_static xstring *format_directory_fflags(xstring *, const void *, struct percent_esc *);
_static xstring *format_directory_group(xstring *, const void *, struct percent_esc *);
_static xstring *format_directory_path(xstring *, const void *, struct percent_esc *);
_static xstring *format_directory_perms(xstring *, const void *, struct percent_esc *);
@@ -186,6 +190,8 @@ _static xstring *format_file_path(xstring *, const void *, struct percent_esc *)
_static xstring *format_file_perms(xstring *, const void *, struct percent_esc *);
_static xstring *format_file_sha256(xstring *, const void *, struct percent_esc *);
_static xstring *format_file_user(xstring *, const void *, struct percent_esc *);
+
_static xstring *format_file_fflags(xstring *, const void *, struct percent_esc *);
+
_static xstring *format_file_symlink_target(xstring *, const void *, struct percent_esc *);
_static xstring *format_groups(xstring *, const void *, struct percent_esc *);
_static xstring *format_group_name(xstring *, const void *, struct percent_esc *);
_static xstring *format_row_counter(xstring *, const void *, struct percent_esc *);
modified src/query.c
@@ -45,9 +45,10 @@ static const struct query_flags accepted_query_flags[] = {
	{ 'd', "nov",		1, PKG_LOAD_DEPS },
	{ 'r', "nov",		1, PKG_LOAD_RDEPS },
	{ 'C', "",		1, PKG_LOAD_CATEGORIES },
-
	{ 'F', "ps",		1, PKG_LOAD_FILES },
+
	{ 'F', "psugmft",	1, PKG_LOAD_FILES },
	{ 'O', "kvdD",		1, PKG_LOAD_OPTIONS },
	{ 'D', "",		1, PKG_LOAD_DIRS },
+
	{ 'S', "pugmf",		1, PKG_LOAD_DIRS }, /* sub directories - directory with options */
	{ 'L', "",		1, PKG_LOAD_LICENSES },
	{ 'U', "",		1, PKG_LOAD_USERS },
	{ 'G', "",		1, PKG_LOAD_GROUPS },
@@ -154,6 +155,7 @@ format_str(struct pkg *pkg, xstring *dest, const char *qstr, const void *data)
					pkg_fprintf(dest->fp, "%?O", pkg);
					break;
				case 'D':
+
				case 'S':
					pkg_fprintf(dest->fp, "%?D", pkg);
					break;
				case 'L':
@@ -253,6 +255,16 @@ format_str(struct pkg *pkg, xstring *dest, const char *qstr, const void *data)
					pkg_fprintf(dest->fp, "%Fn", data);
				else if (qstr[0] == 's')
					pkg_fprintf(dest->fp, "%Fs", data);
+
				else if (qstr[0] == 'u')
+
					pkg_fprintf(dest->fp, "%Fu", data);
+
				else if (qstr[0] == 'g')
+
					pkg_fprintf(dest->fp, "%Fg", data);
+
				else if (qstr[0] == 'm')
+
					pkg_fprintf(dest->fp, "%Fp", data);
+
				else if (qstr[0] == 'f')
+
					pkg_fprintf(dest->fp, "%Ff", data);
+
				else if (qstr[0] == 't')
+
					pkg_fprintf(dest->fp, "%Ft", data);
				break;
			case 'O':
				qstr++;
@@ -268,6 +280,19 @@ format_str(struct pkg *pkg, xstring *dest, const char *qstr, const void *data)
			case 'D':
				pkg_fprintf(dest->fp, "%Dn", data);
				break;
+
			case 'S':
+
				qstr++;
+
				if (qstr[0] == 'p')
+
					pkg_fprintf(dest->fp, "%Dn", data);
+
				else if (qstr[0] == 'u')
+
					pkg_fprintf(dest->fp, "%Du", data);
+
				else if (qstr[0] == 'g')
+
					pkg_fprintf(dest->fp, "%Dg", data);
+
				else if (qstr[0] == 'm')
+
					pkg_fprintf(dest->fp, "%Dp", data);
+
				else if (qstr[0] == 'f')
+
					pkg_fprintf(dest->fp, "%Df", data);
+
				break;
			case 'L':
				pkg_fprintf(dest->fp, "%Ln", data);
				break;
@@ -391,6 +416,7 @@ print_query(struct pkg *pkg, char *qstr, char multiline)
		}
		break;
	case 'D':
+
	case 'S':
		while (pkg_dirs(pkg, &dir) == EPKG_OK) {
			format_str(pkg, output, qstr, dir);
			printf("%s\n", output->buf);
@@ -580,6 +606,7 @@ format_sql_condition(const char *str, xstring *sqlcond, bool for_remote)
							fprintf(sqlcond->fp, "(SELECT %s FROM pkg_option AS d WHERE d.package_id=p.id)", sqlop);
							break;
						case 'D':
+
						case 'S':
							if (for_remote)
								goto bad_option;
							fprintf(sqlcond->fp, "(SELECT %s FROM pkg_directories AS d WHERE d.package_id=p.id)", sqlop);
modified tests/frontend/create-parsebin.sh
@@ -25,7 +25,14 @@ genmanifest() {
        cp -a ${file1} ${TMPDIR}/${file1_base}

        PKG_FILES="${PKG_FILES}/${file1_base}: {perm: 0644}${NL}"
-
        PKG_SHA256="${PKG_SHA256}${NL}    /${file1_base} = \"1\$${file1_sha}\";"
+
	PKG_SHA256="${PKG_SHA256}
+
    /${file1_base} {
+
        sum = \"1\$${file1_sha}\";
+
        uname = \"root\";
+
        gname = \"wheel\";
+
        perm = \"0644\";
+
        fflags = 0;
+
    }"

        PKG_FLATSIZE=$((${PKG_FLATSIZE}+${file1_size}))
        shift
modified tests/frontend/create.sh
@@ -451,10 +451,21 @@ categories [
    "test",
]
files {
-
    /A = "1\$e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+
    /A {
+
        sum = "1\$e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+
        uname = "root";
+
        gname = "wheel";
+
        perm = "0000";
+
        fflags = 0;
+
    }
}
directories {
-
    /B = "y";
+
    /B {
+
        uname = "root";
+
        gname = "wheel";
+
        perm = "0000";
+
        fflags = 0;
+
    }
}
scripts {
    post-install = "# args: A B\necho A B";
@@ -494,7 +505,13 @@ categories [
    "test",
]
files {
-
    /testfile = "1\$e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+
    /testfile {
+
        sum = "1\$e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+
        uname = "root";
+
        gname = "wheel";
+
        perm = "0000";
+
        fflags = 0;
+
    }
}
EOF

@@ -510,9 +527,11 @@ create_from_manifest_body() {
	cat <<EOF >> +MANIFEST
files: {
     /testfile: {perm: 0644}
+
     /sym-file: {perm: 0644}
}
EOF
	touch testfile
+
	ln -s sym-target sym-file
	atf_check \
		-o empty \
		-e empty \
@@ -529,13 +548,27 @@ www = "http://test";
abi = "*";
arch = "*";
prefix = "/";
-
flatsize = 0;
+
flatsize = 10;
desc = "Yet another test";
categories [
    "test",
]
files {
-
    /testfile = "1\$e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+
    /testfile {
+
        sum = "1\$e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+
        uname = "root";
+
        gname = "wheel";
+
        perm = "0644";
+
        fflags = 0;
+
    }
+
    /sym-file {
+
        sum = "1\$a83552cc4e1e92707178239c630b7f05d51124ff2afa7c5595ff4e76cb96cfa4";
+
        uname = "root";
+
        gname = "wheel";
+
        perm = "0644";
+
        fflags = 0;
+
        symlink_target = "sym-target";
+
    }
}
EOF

modified tests/frontend/query.sh
@@ -8,15 +8,43 @@ tests_init \
query_body() {
	touch plop
	touch bla
+
	ln -s sym-target sym
+
	mkdir test-dir
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg test test 1
	cat >> test.ucl << EOF
options: {
	"OPT1": "on"
	"OPT2": "off"
}
+
directories: {
+
      "${TMPDIR}/test-dir": {
+
		"uname": "uroot"
+
		"gname": "groot"
+
		"perm": "660"
+
		"fflags": "nodump,arch"
+
      }
+
}
files: {
-
	"${TMPDIR}/plop": ""
-
	"${TMPDIR}/bla": ""
+
	"${TMPDIR}/plop": {
+
		"sum": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+
		"uname": "uroot"
+
		"gname": "groot"
+
		"perm": "777"
+
		"fflags": "schg,nodump"
+
	}
+
	"${TMPDIR}/bla": {
+
		"sum": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+
		"uname": "utoor"
+
		"gname": "gtoor"
+
		"perm": "1765"
+
	}
+
	"${TMPDIR}/sym": {
+
		"sum": ""
+
		"uname": "sym-root"
+
		"gname": "sym-gtoor"
+
		"perm": "0644"
+
		"symlink_target": "sym-target"
+
	}
}
EOF
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg test2 test 2
@@ -28,6 +56,7 @@ options: {
files: {
	"${TMPDIR}/plop": ""
	"${TMPDIR}/bla": ""
+
	"${TMPDIR}/sym": ""
}
EOF
	atf_check -s exit:0 sh ${RESOURCEDIR}/test_subr.sh new_pkg plop plop 1
@@ -121,11 +150,34 @@ EOF
		pkg query -e "%#d>0 && %#r>0" "%n"

	atf_check \
-
		-o empty \
+
		-o inline:"test\n" \
		-e empty \
		-s exit:0 \
		pkg query -e "%#O > 0 && %#D > 0" "%n"

+

+
	atf_check \
+
	    -o inline:"${TMPDIR}/test-dir\n" \
+
	    -e empty \
+
	    -s exit:0 \
+
	    pkg query "%D" test
+

+
	if [ "${OS}" = "FreeBSD" ]; then
+
	    atf_check \
+
		-o match:"^${TMPDIR}/bla utoor gtoor 1765 - $" \
+
		-o match:"^${TMPDIR}/plop uroot groot 777 schg,nodump $" \
+
		-o match:"^${TMPDIR}/sym sym-root sym-gtoor 644 - sym-target$" \
+
		-e empty \
+
		-s exit:0 \
+
		pkg query "%Fp %Fu %Fg %Fm %Ff %Ft" test
+

+
	    atf_check \
+
		-o inline:"${TMPDIR}/test-dir uroot groot 660 arch,nodump\n" \
+
		-e empty \
+
		-s exit:0 \
+
		pkg query "%Sp %Su %Sg %Sm %Sf" test
+
	fi
+

	atf_check \
		-o ignore \
		-e empty \
@@ -179,7 +231,7 @@ EOF
		pkg create -M plop.ucl

	atf_check \
-
		-o inline:"${TMPDIR}/plop\n${TMPDIR}/bla\n" \
+
		-o inline:"${TMPDIR}/plop\n${TMPDIR}/bla\n${TMPDIR}/sym\n" \
		-e empty \
		-s exit:0 \
		pkg query -F ./test-1.pkg '%Fp'
@@ -191,7 +243,7 @@ EOF
		pkg query -F ./test-1.pkg '%?F'

	atf_check \
-
		-o inline:"2\n" \
+
		-o inline:"3\n" \
		-e empty \
		-s exit:0 \
		pkg query -F ./test-1.pkg '%#F'