From 1f5ce1a9f789d9c0de5d6fbdf79540bf71c5bc9b Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 29 Jun 2017 16:51:56 -0400 Subject: [PATCH] lib/repo: Add min-free-space-percent option, default 3% For ostree-as-host, we're the superuser, so we'll blow past any reserved free space by default. While deltas have size metadata, if one happens to do a loose fetch, we can fill up the disk. Another case is flatpak: the system helper has similar concerns here as ostree-as-host, and for `flatpak --user`, we also want to be nice and avoid filling up the user's quota. Closes: https://github.com/ostreedev/ostree/issues/962 Closes: #987 Approved by: jlebon --- man/ostree.repo-config.xml | 8 ++++++ src/libostree/ostree-repo-commit.c | 42 +++++++++++++++++++++++++++++ src/libostree/ostree-repo-private.h | 5 ++++ src/libostree/ostree-repo.c | 15 +++++++++++ tests/installed/itest-pull-space.sh | 24 +++++++++++++++++ 5 files changed, 94 insertions(+) create mode 100755 tests/installed/itest-pull-space.sh diff --git a/man/ostree.repo-config.xml b/man/ostree.repo-config.xml index d187f89f..60458dfa 100644 --- a/man/ostree.repo-config.xml +++ b/man/ostree.repo-config.xml @@ -190,6 +190,14 @@ Boston, MA 02111-1307, USA. unconfigured-state If set, pulls from this remote will fail with the configured text. This is intended for OS vendors which have a subscription process to access content. + + + min-free-space-percent + Integer percentage value (0-99) that specifies a minimum + percentage of total space (in blocks) in the underlying filesystem to + keep free. The default value is 3. + + diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index 9cc028e0..185ab82f 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -23,6 +23,7 @@ #include "config.h" #include +#include #include #include #include @@ -574,6 +575,24 @@ write_content_object (OstreeRepo *self, else size = 0; + /* Free space check; only applies during transactions */ + if (self->min_free_space_percent > 0 && self->in_transaction) + { + g_mutex_lock (&self->txn_stats_lock); + g_assert_cmpint (self->txn_blocksize, >, 0); + const fsblkcnt_t object_blocks = (size / self->txn_blocksize) + 1; + if (object_blocks > self->max_txn_blocks) + { + g_mutex_unlock (&self->txn_stats_lock); + g_autofree char *formatted_required = g_format_size ((guint64)object_blocks * self->txn_blocksize); + return glnx_throw (error, "min-free-space-percent '%u%%' would be exceeded, %s more required", + self->min_free_space_percent, formatted_required); + } + /* This is the main bit that needs mutex protection */ + self->max_txn_blocks -= object_blocks; + g_mutex_unlock (&self->txn_stats_lock); + } + /* For regular files, we create them with default mode, and only * later apply any xattrs and setuid bits. The rationale here * is that an attacker on the network with the ability to MITM @@ -1080,6 +1099,29 @@ ostree_repo_prepare_transaction (OstreeRepo *self, memset (&self->txn_stats, 0, sizeof (OstreeRepoTransactionStats)); self->in_transaction = TRUE; + if (self->min_free_space_percent > 0) + { + struct statvfs stvfsbuf; + if (TEMP_FAILURE_RETRY (fstatvfs (self->repo_dir_fd, &stvfsbuf)) < 0) + return glnx_throw_errno_prefix (error, "fstatvfs"); + g_mutex_lock (&self->txn_stats_lock); + self->txn_blocksize = stvfsbuf.f_bsize; + /* Convert fragment to blocks to compute the total */ + guint64 total_blocks = (stvfsbuf.f_frsize * stvfsbuf.f_blocks) / stvfsbuf.f_bsize; + /* Use the appropriate free block count if we're unprivileged */ + guint64 bfree = (getuid () != 0 ? stvfsbuf.f_bavail : stvfsbuf.f_bfree); + guint64 reserved_blocks = ((double)total_blocks) * (self->min_free_space_percent/100.0); + if (bfree > reserved_blocks) + self->max_txn_blocks = bfree - reserved_blocks; + else + { + g_mutex_unlock (&self->txn_stats_lock); + g_autofree char *formatted_free = g_format_size (bfree * self->txn_blocksize); + return glnx_throw (error, "min-free-space-percent '%u%%' would be exceeded, %s available", + self->min_free_space_percent, formatted_free); + } + g_mutex_unlock (&self->txn_stats_lock); + } gboolean ret_transaction_resume = FALSE; if (!_ostree_repo_allocate_tmpdir (self->tmp_dir_fd, diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index d518e52b..9e00cf40 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -20,6 +20,7 @@ #pragma once +#include #include "ostree-ref.h" #include "ostree-repo.h" #include "ostree-remote-private.h" @@ -102,6 +103,9 @@ struct OstreeRepo { GHashTable *txn_collection_refs; /* (element-type OstreeCollectionRef utf8) */ GMutex txn_stats_lock; OstreeRepoTransactionStats txn_stats; + /* Implementation of min-free-space-percent */ + gulong txn_blocksize; + fsblkcnt_t max_txn_blocks; GMutex cache_lock; guint dirmeta_cache_refcount; @@ -123,6 +127,7 @@ struct OstreeRepo { uid_t owner_uid; uid_t target_owner_uid; gid_t target_owner_gid; + guint min_free_space_percent; guint test_error_flags; /* OstreeRepoTestErrorFlags */ diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index a02214e1..e29b8fca 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -44,6 +44,7 @@ #include #include #include +#include /** * SECTION:ostree-repo @@ -1964,6 +1965,20 @@ reload_core_config (OstreeRepo *self, self->zlib_compression_level = OSTREE_ARCHIVE_DEFAULT_COMPRESSION_LEVEL; } + { g_autofree char *min_free_space_percent_str = NULL; + /* If changing this, be sure to change the man page too */ + const char *default_min_free_space = "3"; + + if (!ot_keyfile_get_value_with_default (self->config, "core", "min-free-space-percent", + default_min_free_space, + &min_free_space_percent_str, error)) + return FALSE; + + self->min_free_space_percent = g_ascii_strtoull (min_free_space_percent_str, NULL, 10); + if (self->min_free_space_percent > 99) + return glnx_throw (error, "Invalid min-free-space-percent '%s'", min_free_space_percent_str); + } + { g_clear_pointer (&self->collection_id, g_free); if (!ot_keyfile_get_value_with_default (self->config, "core", "collection-id", diff --git a/tests/installed/itest-pull-space.sh b/tests/installed/itest-pull-space.sh new file mode 100755 index 00000000..36703a40 --- /dev/null +++ b/tests/installed/itest-pull-space.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Test min-free-space-percent using loopback devices + +set -xeuo pipefail + +dn=$(dirname $0) +. ${dn}/libinsttest.sh + +test_tmpdir=$(prepare_tmpdir) +trap _tmpdir_cleanup EXIT + +cd ${test_tmpdir} +truncate -s 100MB testblk.img +blkdev=$(losetup --find --show $(pwd)/testblk.img) +mkfs.xfs ${blkdev} +mkdir mnt +mount ${blkdev} mnt +ostree --repo=mnt/repo init --mode=bare-user +if ostree --repo=mnt/repo pull-local /ostree/repo ${host_commit} 2>err.txt; then + fatal "succeeded in doing a pull with no free space" +fi +assert_file_has_content err.txt "min-free-space-percent" +umount mnt +losetup -d ${blkdev}