freespace.c 6.41 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/*  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"
29
#include "database.h"
30
#include "checksums.h"
31 32 33
#include "freespace.h"

struct device {
34
	/*@null@*/struct device *next;
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
	/* 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 {
50
	/*@null@*/struct device *root;
51
	off_t reserved;
52 53 54 55 56
};

void space_free(struct devices *devices) {
	struct device *d;

57
	if (devices == NULL)
58 59
		return;

60
	while ((d = devices->root) != NULL) {
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
		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;

76
	while (d != NULL && d->id != id)
77 78
		d = d->next;

79
	if (d != NULL) {
80 81 82 83 84
		*result = d;
		return RET_OK;
	}

	ret = statvfs(dirname, &s);
85
	if (ret != 0) {
86 87
		int e = errno;
		fprintf(stderr,
88
"Error judging free space for the filesystem '%s' belongs to: %d=%s\n"
89 90 91 92 93
"(Take a look at --spacecheck in the manpage on how to modify checking.)\n",
					dirname, e, strerror(e));
		return RET_ERRNO(e);
	}

94 95
	d = NEW(struct device);
	if (FAILEDTOALLOC(d))
96 97 98 99
		return RET_ERROR_OOM;
	d->next = devices->root;
	d->id = id;
	d->somepath = strdup(dirname);
100
	if (FAILEDTOALLOC(d->somepath)) {
101 102 103
		free(d);
		return RET_ERROR_OOM;
	}
104
	d->blocksize = s.f_bsize;
105 106 107 108
	/* 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 */
109
	d->reserved = devices->reserved/d->blocksize+1;
110 111 112 113 114
	devices->root = d;
	*result = d;
	return RET_OK;
}

115
retvalue space_prepare(struct devices **devices, enum spacecheckmode mode, off_t reservedfordb, off_t reservedforothers) {
116 117 118 119 120 121
	struct devices *n;
	struct device *d;
	struct stat s;
	int ret;
	retvalue r;

122
	if (mode == scm_NONE) {
123 124 125
		*devices = NULL;
		return RET_OK;
	}
126 127 128
	assert (mode == scm_FULL);
	n = NEW(struct devices);
	if (FAILEDTOALLOC(n))
129 130
		return RET_ERROR_OOM;
	n->root = NULL;
131
	n->reserved = reservedforothers;
132

133
	ret = stat(global.dbdir, &s);
134
	if (ret != 0) {
135
		int e = errno;
136
		fprintf(stderr, "Error stat'ing %s: %d=%s\n",
137
				global.dbdir, e, strerror(e));
138
		free(n);
139 140
		return RET_ERRNO(e);
	}
141
	r = device_find_or_create(n, s.st_dev, global.dbdir, &d);
142
	if (RET_WAS_ERROR(r)) {
143 144 145
		space_free(n);
		return r;
	}
146
	d->reserved += reservedfordb/d->blocksize+1;
147 148 149 150
	*devices = n;
	return RET_OK;
}

151
retvalue space_needed(struct devices *devices, const char *filename, const struct checksums *checksums) {
152 153 154 155 156 157 158 159 160
	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;

161
	if (devices == NULL)
162 163
		return RET_NOTHING;

164
	while (l > 0 && filename[l-1] != '/')
165
		l--;
166
	assert (l > 0);
167 168 169
	memcpy(buffer, filename, l);
	buffer[l] = '\0';

170 171
	ret = stat(buffer, &s);
	if (ret != 0) {
172 173 174 175 176 177
		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);
178
	if (RET_WAS_ERROR(r))
179
		return r;
180
	filesize = checksums_getfilesize(checksums);
181
	blocks = (filesize + device->blocksize - 1) / device->blocksize;
182
	device->needed += 1 + blocks;
183 184 185 186 187 188 189 190 191 192

	return RET_OK;
}

retvalue space_check(struct devices *devices) {
	struct device *device;
	struct statvfs s;
	int ret;
	retvalue result = RET_OK;

193

194
	if (devices == NULL)
195 196
		return RET_NOTHING;

197
	for (device = devices->root ; device != NULL ; device = device->next) {
198 199 200 201
		/* recalculate free space, as created directories
		 * and other stuff might have changed it */

		ret = statvfs(device->somepath, &s);
202
		if (ret != 0) {
203 204
			int e = errno;
			fprintf(stderr,
205
"Error judging free space for the filesystem '%s' belongs to: %d=%s\n"
206 207 208 209 210
"(As this worked before in this run, something must have changed strangely)\n",
					device->somepath,
					e, strerror(e));
			return RET_ERRNO(e);
		}
211
		if (device->blocksize != s.f_bsize) {
212
			fprintf(stderr,
Bernhard Link's avatar
Bernhard Link committed
213
"The block size of the filesystem belonging to '%s' has changed.\n"
214 215 216 217 218
"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;
219
		if (device->needed >= device->available) {
220 221 222
			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",
223 224 225 226
				(unsigned long)device->id, device->somepath,
				(unsigned long long)device->available,
				(unsigned long long)device->needed,
				(unsigned long long)device->blocksize);
227
			result = RET_ERROR;
228 229
		} else if (device->reserved >= device->available ||
		           device->needed >= device->available - device->reserved) {
230 231
			fprintf(stderr,
"NOT ENOUGH FREE SPACE on filesystem 0x%lx (the filesystem '%s' is on)\n"
232
"available blocks %llu, needed blocks %llu (+%llu safety margin), block size is %llu.\n"
233
"(Take a look at --spacecheck in the manpage for more information.)\n",
234 235 236 237 238
				(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);
239 240 241 242 243
			result = RET_ERROR;
		}
	}
	return result;
}