From 09e48d8f06bfb924b80ce3e95c281524c4891828 Mon Sep 17 00:00:00 2001 From: Timo Teräs Date: Tue, 7 Oct 2014 13:29:34 +0300 Subject: db: rework directory permission handling Apk used to reset directory permissions always, but this is undesirable if user has modified the permissions - especially during tmpfs boot. Though, it is desirable to update the permissions when packaging has changed permissions, or a new package is installed and the merged permission mask / owner changes. Thus the new code updates the permissions only if: 1) We are booting and directory is not in apkovl 2) The directory is modified by a package install/remove/upgrade 3) The filesystem directory permission matched database Additionally "apk fix --directory-permissions" can be used to reset all directory permissions to the database defaults. Fixes #2966 --- src/database.c | 133 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 54 deletions(-) (limited to 'src/database.c') diff --git a/src/database.c b/src/database.c index 8d633b4..064957e 100644 --- a/src/database.c +++ b/src/database.c @@ -39,8 +39,8 @@ static const apk_spn_match_def apk_spn_repo_separators = { }; enum { - APK_DISALLOW_RMDIR = 0, - APK_ALLOW_RMDIR = 1 + APK_DIR_FREE = 0, + APK_DIR_REMOVE }; int apk_verbosity = 1; @@ -222,59 +222,39 @@ struct apk_name *apk_db_get_name(struct apk_database *db, apk_blob_t name) return pn; } -static void apk_db_dir_mkdir(struct apk_database *db, struct apk_db_dir *dir) +static void apk_db_dir_prepare(struct apk_database *db, struct apk_db_dir *dir, mode_t newmode) { - if (apk_flags & APK_SIMULATE) - return; + struct stat st; - /* Don't mess with root, as no package provides it directly */ - if (dir->namelen == 0) - return; + if (dir->namelen == 0) return; + if (dir->created) return; - if ((dir->refs == 1) || - (fchmodat(db->root_fd, dir->name, dir->mode, 0) != 0 && - errno == ENOENT)) - if ((mkdirat(db->root_fd, dir->name, dir->mode) != 0 && - errno == EEXIST)) - if (fchmodat(db->root_fd, dir->name, dir->mode, 0) != 0) - ; - - if (fchownat(db->root_fd, dir->name, dir->uid, dir->gid, 0) != 0) - ; + if (fstatat(db->root_fd, dir->name, &st, AT_SYMLINK_NOFOLLOW) == 0) { + /* If directory exists and stats match what we expect, + * then we can allow auto updating the permissions */ + dir->created = 1; + dir->update_permissions |= + (st.st_mode & 07777) == (dir->mode & 07777) && + st.st_uid == dir->uid && st.st_gid == dir->gid; + } else if (newmode) { + if (!(apk_flags & APK_SIMULATE)) + mkdirat(db->root_fd, dir->name, newmode); + dir->created = 1; + dir->update_permissions = 1; + } } -void apk_db_dir_unref(struct apk_database *db, struct apk_db_dir *dir, int allow_rmdir) +void apk_db_dir_unref(struct apk_database *db, struct apk_db_dir *dir, int rmdir_mode) { - dir->refs--; - if (dir->refs > 0) { - if (allow_rmdir) { - dir->recalc_mode = 1; - dir->mode = 0; - dir->uid = (uid_t) -1; - dir->gid = (gid_t) -1; - } - return; - } - + if (--dir->refs > 0) return; db->installed.stats.dirs--; + if (dir->namelen == 0) return; - if (allow_rmdir) { - /* The final instance of this directory was removed, - * so this directory gets deleted in reality too. */ - dir->recalc_mode = 0; - dir->mode = 0; - dir->uid = (uid_t) -1; - dir->gid = (gid_t) -1; + if (rmdir_mode == APK_DIR_REMOVE && !(apk_flags & APK_SIMULATE)) + if (unlinkat(db->root_fd, dir->name, AT_REMOVEDIR)) + ; - if (dir->namelen) - unlinkat(db->root_fd, dir->name, AT_REMOVEDIR); - } else if (dir->recalc_mode) { - /* Directory permissions need a reset. */ - apk_db_dir_mkdir(db, dir); - } - - if (dir->parent != NULL) - apk_db_dir_unref(db, dir->parent, allow_rmdir); + apk_db_dir_unref(db, dir->parent, rmdir_mode); } struct apk_db_dir *apk_db_dir_ref(struct apk_db_dir *dir) @@ -406,13 +386,14 @@ static void apk_db_diri_set(struct apk_db_dir_instance *diri, mode_t mode, static void apk_db_diri_free(struct apk_database *db, struct apk_db_dir_instance *diri, - int allow_rmdir) + int rmdir_mode) { - if (allow_rmdir == APK_DISALLOW_RMDIR && - diri->dir->recalc_mode) - apk_db_dir_apply_diri_permissions(diri); + struct apk_db_dir *dir = diri->dir; - apk_db_dir_unref(db, diri->dir, allow_rmdir); + if (rmdir_mode == APK_DIR_REMOVE) + apk_db_dir_prepare(db, diri->dir, 0); + + apk_db_dir_unref(db, dir, rmdir_mode); free(diri); } @@ -731,6 +712,7 @@ int apk_db_read_overlay(struct apk_database *db, struct apk_bstream *bs) if (bfile.len == 0) { diri = apk_db_diri_new(db, pkg, bdir, &diri_node); file_diri_node = &diri->owned_files.first; + diri->dir->created = 1; } else { diri = find_diri(ipkg, bdir, diri, &file_diri_node); if (diri == NULL) { @@ -1752,7 +1734,7 @@ void apk_db_close(struct apk_database *db) * directories to be reset. */ list_for_each_entry(ipkg, &db->installed.packages, installed_pkgs_list) { hlist_for_each_entry_safe(diri, dc, dn, &ipkg->owned_dirs, pkg_dirs_list) { - apk_db_diri_free(db, diri, APK_DISALLOW_RMDIR); + apk_db_diri_free(db, diri, APK_DIR_FREE); } } @@ -1865,6 +1847,49 @@ int apk_db_fire_triggers(struct apk_database *db) return db->pending_triggers; } +static int update_permissions(apk_hash_item item, void *ctx) +{ + struct apk_database *db = (struct apk_database *) ctx; + struct apk_db_dir *dir = (struct apk_db_dir *) item; + struct stat st; + int r; + + if (dir->refs == 0) return 0; + if (!dir->update_permissions) return 0; + dir->seen = 0; + + r = fstatat(db->root_fd, dir->name, &st, AT_SYMLINK_NOFOLLOW); + if (r < 0 || (st.st_mode & 07777) != (dir->mode & 07777)) + fchmodat(db->root_fd, dir->name, dir->mode, 0); + if (r < 0 || st.st_uid != dir->uid || st.st_gid != dir->gid) + fchownat(db->root_fd, dir->name, dir->uid, dir->gid, 0); + + return 0; +} + +void apk_db_update_directory_permissions(struct apk_database *db) +{ + struct apk_installed_package *ipkg; + struct apk_db_dir_instance *diri; + struct apk_db_dir *dir; + struct hlist_node *dc, *dn; + + list_for_each_entry(ipkg, &db->installed.packages, installed_pkgs_list) { + hlist_for_each_entry_safe(diri, dc, dn, &ipkg->owned_dirs, pkg_dirs_list) { + dir = diri->dir; + if (!dir->update_permissions) continue; + if (!dir->seen) { + dir->seen = 1; + dir->mode = 0; + dir->uid = (uid_t) -1; + dir->gid = (gid_t) -1; + } + apk_db_dir_apply_diri_permissions(diri); + } + } + apk_hash_foreach(&db->installed.dirs, update_permissions, db); +} + int apk_db_cache_active(struct apk_database *db) { return db->cache_dir != apk_static_cache_dir; @@ -2388,8 +2413,8 @@ static int apk_db_install_archive_entry(void *_ctx, name.len--; diri = apk_db_install_directory_entry(ctx, name); + apk_db_dir_prepare(db, diri->dir, ae->mode); apk_db_diri_set(diri, ae->mode, ae->uid, ae->gid); - apk_db_dir_mkdir(db, diri->dir); } ctx->installed_size += ctx->current_file_size; @@ -2438,7 +2463,7 @@ static void apk_db_purge_pkg(struct apk_database *db, } } __hlist_del(dc, &ipkg->owned_dirs.first); - apk_db_diri_free(db, diri, APK_ALLOW_RMDIR); + apk_db_diri_free(db, diri, APK_DIR_REMOVE); } } -- cgit v1.2.3-60-g2f50