summaryrefslogtreecommitdiffstats
path: root/freespace.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--freespace.c243
1 files changed, 243 insertions, 0 deletions
diff --git a/freespace.c b/freespace.c
new file mode 100644
index 0000000..aa67c37
--- /dev/null
+++ b/freespace.c
@@ -0,0 +1,243 @@
+/* This file is part of "reprepro"
+ * Copyright (C) 2007 Bernhard R. Link
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+#include <config.h>
+
+#include <errno.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <unistd.h>
+#include <strings.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "error.h"
+#include "database.h"
+#include "checksums.h"
+#include "freespace.h"
+
+struct device {
+ /*@null@*/struct device *next;
+ /* stat(2)'s st_dev number identifying this device */
+ dev_t id;
+ /* some directory in this filesystem */
+ char *somepath;
+ /* size of one block on this device according to statvfs(2) */
+ unsigned long blocksize;
+ /* blocks available for us */
+ fsblkcnt_t available;
+ /* blocks already known to be needed on that device */
+ fsblkcnt_t needed;
+ /* calculated block to keep free */
+ fsblkcnt_t reserved;
+};
+
+struct devices {
+ /*@null@*/struct device *root;
+ off_t reserved;
+};
+
+void space_free(struct devices *devices) {
+ struct device *d;
+
+ if (devices == NULL)
+ return;
+
+ while ((d = devices->root) != NULL) {
+ devices->root = d->next;
+
+ free(d->somepath);
+ free(d);
+ }
+ free(devices);
+}
+
+static retvalue device_find_or_create(struct devices *devices, dev_t id, const char *dirname, /*@out@*/struct device **result) {
+ struct device *d;
+ struct statvfs s;
+ int ret;
+
+ d = devices->root;
+
+ while (d != NULL && d->id != id)
+ d = d->next;
+
+ if (d != NULL) {
+ *result = d;
+ return RET_OK;
+ }
+
+ ret = statvfs(dirname, &s);
+ if (ret != 0) {
+ int e = errno;
+ fprintf(stderr,
+"Error judging free space for the filesystem '%s' belongs to: %d=%s\n"
+"(Take a look at --spacecheck in the manpage on how to modify checking.)\n",
+ dirname, e, strerror(e));
+ return RET_ERRNO(e);
+ }
+
+ d = NEW(struct device);
+ if (FAILEDTOALLOC(d))
+ return RET_ERROR_OOM;
+ d->next = devices->root;
+ d->id = id;
+ d->somepath = strdup(dirname);
+ if (FAILEDTOALLOC(d->somepath)) {
+ free(d);
+ return RET_ERROR_OOM;
+ }
+ d->blocksize = s.f_bsize;
+ /* use bfree when being root? but why run as root? */
+ d->available = s.f_bavail;
+ d->needed = 0;
+ /* always keep at least one megabyte spare */
+ d->reserved = devices->reserved/d->blocksize+1;
+ devices->root = d;
+ *result = d;
+ return RET_OK;
+}
+
+retvalue space_prepare(struct devices **devices, enum spacecheckmode mode, off_t reservedfordb, off_t reservedforothers) {
+ struct devices *n;
+ struct device *d;
+ struct stat s;
+ int ret;
+ retvalue r;
+
+ if (mode == scm_NONE) {
+ *devices = NULL;
+ return RET_OK;
+ }
+ assert (mode == scm_FULL);
+ n = NEW(struct devices);
+ if (FAILEDTOALLOC(n))
+ return RET_ERROR_OOM;
+ n->root = NULL;
+ n->reserved = reservedforothers;
+
+ ret = stat(global.dbdir, &s);
+ if (ret != 0) {
+ int e = errno;
+ fprintf(stderr, "Error stat'ing %s: %d=%s\n",
+ global.dbdir, e, strerror(e));
+ free(n);
+ return RET_ERRNO(e);
+ }
+ r = device_find_or_create(n, s.st_dev, global.dbdir, &d);
+ if (RET_WAS_ERROR(r)) {
+ space_free(n);
+ return r;
+ }
+ d->reserved += reservedfordb/d->blocksize+1;
+ *devices = n;
+ return RET_OK;
+}
+
+retvalue space_needed(struct devices *devices, const char *filename, const struct checksums *checksums) {
+ size_t l = strlen(filename);
+ char buffer[l+1];
+ struct stat s;
+ struct device *device;
+ int ret;
+ retvalue r;
+ fsblkcnt_t blocks;
+ off_t filesize;
+
+ if (devices == NULL)
+ return RET_NOTHING;
+
+ while (l > 0 && filename[l-1] != '/')
+ l--;
+ assert (l > 0);
+ memcpy(buffer, filename, l);
+ buffer[l] = '\0';
+
+ ret = stat(buffer, &s);
+ if (ret != 0) {
+ int e = errno;
+ fprintf(stderr, "Error stat'ing %s: %d=%s\n", filename,
+ e, strerror(e));
+ return RET_ERRNO(e);
+ }
+ r = device_find_or_create(devices, s.st_dev, buffer, &device);
+ if (RET_WAS_ERROR(r))
+ return r;
+ filesize = checksums_getfilesize(checksums);
+ blocks = (filesize + device->blocksize - 1) / device->blocksize;
+ device->needed += 1 + blocks;
+
+ return RET_OK;
+}
+
+retvalue space_check(struct devices *devices) {
+ struct device *device;
+ struct statvfs s;
+ int ret;
+ retvalue result = RET_OK;
+
+
+ if (devices == NULL)
+ return RET_NOTHING;
+
+ for (device = devices->root ; device != NULL ; device = device->next) {
+ /* recalculate free space, as created directories
+ * and other stuff might have changed it */
+
+ ret = statvfs(device->somepath, &s);
+ if (ret != 0) {
+ int e = errno;
+ fprintf(stderr,
+"Error judging free space for the filesystem '%s' belongs to: %d=%s\n"
+"(As this worked before in this run, something must have changed strangely)\n",
+ device->somepath,
+ e, strerror(e));
+ return RET_ERRNO(e);
+ }
+ if (device->blocksize != s.f_bsize) {
+ fprintf(stderr,
+"The block size of the filesystem belonging to '%s' has changed.\n"
+"Either something was mounted or unmounted while reprepro was running,\n"
+"or some symlinks were changed. Aborting as utterly confused.\n",
+ device->somepath);
+ }
+ device->available = s.f_bavail;
+ if (device->needed >= device->available) {
+ fprintf(stderr,
+"NOT ENOUGH FREE SPACE on filesystem 0x%lx (the filesystem '%s' is on)\n"
+"available blocks %llu, needed blocks %llu, block size is %llu.\n",
+ (unsigned long)device->id, device->somepath,
+ (unsigned long long)device->available,
+ (unsigned long long)device->needed,
+ (unsigned long long)device->blocksize);
+ result = RET_ERROR;
+ } else if (device->reserved >= device->available ||
+ device->needed >= device->available - device->reserved) {
+ fprintf(stderr,
+"NOT ENOUGH FREE SPACE on filesystem 0x%lx (the filesystem '%s' is on)\n"
+"available blocks %llu, needed blocks %llu (+%llu safety margin), block size is %llu.\n"
+"(Take a look at --spacecheck in the manpage for more information.)\n",
+ (unsigned long)device->id, device->somepath,
+ (unsigned long long)device->available,
+ (unsigned long long)device->needed,
+ (unsigned long long)device->reserved,
+ (unsigned long long)device->blocksize);
+ result = RET_ERROR;
+ }
+ }
+ return result;
+}