Fix a signed overflow error on i686 with GCC 4.7.0
[pacman-ng.git] / lib / libalpm / diskspace.c
blobdaee2447f07c47f6dcb5b828bae223cfa3f5be1f
1 /*
2 * diskspace.c
4 * Copyright (c) 2010-2012 Pacman Development Team <pacman-dev@archlinux.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include <stdio.h>
21 #include <errno.h>
23 #if defined(HAVE_MNTENT_H)
24 #include <mntent.h>
25 #endif
26 #if defined(HAVE_SYS_MNTTAB_H)
27 #include <sys/mnttab.h>
28 #endif
29 #if defined(HAVE_SYS_STATVFS_H)
30 #include <sys/statvfs.h>
31 #endif
32 #if defined(HAVE_SYS_PARAM_H)
33 #include <sys/param.h>
34 #endif
35 #if defined(HAVE_SYS_MOUNT_H)
36 #include <sys/mount.h>
37 #endif
38 #if defined(HAVE_SYS_UCRED_H)
39 #include <sys/ucred.h>
40 #endif
41 #if defined(HAVE_SYS_TYPES_H)
42 #include <sys/types.h>
43 #endif
45 /* libalpm */
46 #include "diskspace.h"
47 #include "alpm_list.h"
48 #include "util.h"
49 #include "log.h"
50 #include "trans.h"
51 #include "handle.h"
53 static int mount_point_cmp(const void *p1, const void *p2)
55 const alpm_mountpoint_t *mp1 = p1;
56 const alpm_mountpoint_t *mp2 = p2;
57 /* the negation will sort all mountpoints before their parent */
58 return -strcmp(mp1->mount_dir, mp2->mount_dir);
61 static void mount_point_list_free(alpm_list_t *mount_points)
63 alpm_list_t *i;
65 for(i = mount_points; i; i = i->next) {
66 alpm_mountpoint_t *data = i->data;
67 FREE(data->mount_dir);
69 FREELIST(mount_points);
72 static alpm_list_t *mount_point_list(alpm_handle_t *handle)
74 alpm_list_t *mount_points = NULL, *ptr;
75 alpm_mountpoint_t *mp;
77 #if defined(HAVE_GETMNTENT) && defined(HAVE_MNTENT_H)
78 /* Linux */
79 struct mntent *mnt;
80 FILE *fp;
82 fp = setmntent(MOUNTED, "r");
84 if(fp == NULL) {
85 return NULL;
88 while((mnt = getmntent(fp))) {
89 struct statvfs fsp;
90 if(!mnt) {
91 _alpm_log(handle, ALPM_LOG_WARNING,
92 _("could not get filesystem information\n"));
93 continue;
95 if(statvfs(mnt->mnt_dir, &fsp) != 0) {
96 _alpm_log(handle, ALPM_LOG_WARNING,
97 _("could not get filesystem information for %s: %s\n"),
98 mnt->mnt_dir, strerror(errno));
99 continue;
102 CALLOC(mp, 1, sizeof(alpm_mountpoint_t), RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
103 mp->mount_dir = strdup(mnt->mnt_dir);
104 mp->mount_dir_len = strlen(mp->mount_dir);
105 memcpy(&(mp->fsp), &fsp, sizeof(struct statvfs));
106 mp->read_only = fsp.f_flag & ST_RDONLY;
108 mount_points = alpm_list_add(mount_points, mp);
111 endmntent(fp);
112 #elif defined(HAVE_GETMNTENT) && defined(HAVE_MNTTAB_H)
113 /* Solaris, Illumos */
114 struct mnttab mnt;
115 FILE *fp;
116 int ret;
118 fp = fopen("/etc/mnttab", "r");
120 if(fp == NULL) {
121 return NULL;
124 while((ret = getmntent(fp, &mnt)) == 0) {
125 struct statvfs fsp;
126 if(statvfs(mnt->mnt_mountp, &fsp) != 0) {
127 _alpm_log(handle, ALPM_LOG_WARNING,
128 _("could not get filesystem information for %s: %s\n"),
129 mnt->mnt_mountp, strerror(errno));
130 continue;
133 CALLOC(mp, 1, sizeof(alpm_mountpoint_t), RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
134 mp->mount_dir = strdup(mnt->mnt_mountp);
135 mp->mount_dir_len = strlen(mp->mount_dir);
136 memcpy(&(mp->fsp), &fsp, sizeof(struct statvfs));
137 mp->read_only = fsp.f_flag & ST_RDONLY;
139 mount_points = alpm_list_add(mount_points, mp);
141 /* -1 == EOF */
142 if(ret != -1) {
143 _alpm_log(handle, ALPM_LOG_WARNING,
144 _("could not get filesystem information\n"));
147 fclose(fp);
148 #elif defined(HAVE_GETMNTINFO)
149 /* FreeBSD (statfs), NetBSD (statvfs), OpenBSD (statfs), OS X (statfs) */
150 int entries;
151 FSSTATSTYPE *fsp;
153 entries = getmntinfo(&fsp, MNT_NOWAIT);
155 if(entries < 0) {
156 return NULL;
159 for(; entries-- > 0; fsp++) {
160 CALLOC(mp, 1, sizeof(alpm_mountpoint_t), RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
161 mp->mount_dir = strdup(fsp->f_mntonname);
162 mp->mount_dir_len = strlen(mp->mount_dir);
163 memcpy(&(mp->fsp), fsp, sizeof(FSSTATSTYPE));
164 #if defined(HAVE_GETMNTINFO_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_FLAG)
165 mp->read_only = fsp->f_flag & ST_RDONLY;
166 #elif defined(HAVE_GETMNTINFO_STATFS) && defined(HAVE_STRUCT_STATFS_F_FLAGS)
167 mp->read_only = fsp->f_flags & MNT_RDONLY;
168 #endif
170 mount_points = alpm_list_add(mount_points, mp);
172 #endif
174 mount_points = alpm_list_msort(mount_points, alpm_list_count(mount_points),
175 mount_point_cmp);
176 for(ptr = mount_points; ptr != NULL; ptr = ptr->next) {
177 mp = ptr->data;
178 _alpm_log(handle, ALPM_LOG_DEBUG, "mountpoint: %s\n", mp->mount_dir);
180 return mount_points;
183 static alpm_mountpoint_t *match_mount_point(const alpm_list_t *mount_points,
184 const char *real_path)
186 const alpm_list_t *mp;
188 for(mp = mount_points; mp != NULL; mp = mp->next) {
189 alpm_mountpoint_t *data = mp->data;
191 /* first, check if the prefix matches */
192 if(strncmp(data->mount_dir, real_path, data->mount_dir_len) == 0) {
193 /* now, the hard work- a file like '/etc/myconfig' shouldn't map to a
194 * mountpoint '/e', but only '/etc'. If the mountpoint ends in a trailing
195 * slash, we know we didn't have a mismatch, otherwise we have to do some
196 * more sanity checks. */
197 if(data->mount_dir[data->mount_dir_len - 1] == '/') {
198 return data;
199 } else if(strlen(real_path) >= data->mount_dir_len) {
200 const char next = real_path[data->mount_dir_len];
201 if(next == '/' || next == '\0') {
202 return data;
208 /* should not get here... */
209 return NULL;
212 static int calculate_removed_size(alpm_handle_t *handle,
213 const alpm_list_t *mount_points, alpm_pkg_t *pkg)
215 size_t i;
216 alpm_filelist_t *filelist = alpm_pkg_get_files(pkg);
218 if(!filelist->count) {
219 return 0;
222 for(i = 0; i < filelist->count; i++) {
223 const alpm_file_t *file = filelist->files + i;
224 alpm_mountpoint_t *mp;
225 struct stat st;
226 char path[PATH_MAX];
227 blkcnt_t remove_size;
228 const char *filename = file->name;
230 snprintf(path, PATH_MAX, "%s%s", handle->root, filename);
231 _alpm_lstat(path, &st);
233 /* skip directories and symlinks to be consistent with libarchive that
234 * reports them to be zero size */
235 if(S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) {
236 continue;
239 mp = match_mount_point(mount_points, path);
240 if(mp == NULL) {
241 _alpm_log(handle, ALPM_LOG_WARNING,
242 _("could not determine mount point for file %s\n"), filename);
243 continue;
246 /* the addition of (divisor - 1) performs ceil() with integer division */
247 remove_size = (st.st_size + mp->fsp.f_bsize - 1) / mp->fsp.f_bsize;
248 mp->blocks_needed -= remove_size;
249 mp->used |= USED_REMOVE;
252 return 0;
255 static int calculate_installed_size(alpm_handle_t *handle,
256 const alpm_list_t *mount_points, alpm_pkg_t *pkg)
258 size_t i;
259 alpm_filelist_t *filelist = alpm_pkg_get_files(pkg);
261 if(!filelist->count) {
262 return 0;
265 for(i = 0; i < filelist->count; i++) {
266 const alpm_file_t *file = filelist->files + i;
267 alpm_mountpoint_t *mp;
268 char path[PATH_MAX];
269 blkcnt_t install_size;
270 const char *filename = file->name;
272 /* libarchive reports these as zero size anyways */
273 /* NOTE: if we do start accounting for directory size, a dir matching a
274 * mountpoint needs to be attributed to the parent, not the mountpoint. */
275 if(S_ISDIR(file->mode) || S_ISLNK(file->mode)) {
276 continue;
279 /* approximate space requirements for db entries */
280 if(filename[0] == '.') {
281 filename = handle->dbpath;
284 snprintf(path, PATH_MAX, "%s%s", handle->root, filename);
286 mp = match_mount_point(mount_points, path);
287 if(mp == NULL) {
288 _alpm_log(handle, ALPM_LOG_WARNING,
289 _("could not determine mount point for file %s\n"), filename);
290 continue;
293 /* the addition of (divisor - 1) performs ceil() with integer division */
294 install_size = (file->size + mp->fsp.f_bsize - 1) / mp->fsp.f_bsize;
295 mp->blocks_needed += install_size;
296 mp->used |= USED_INSTALL;
299 return 0;
302 static int check_mountpoint(alpm_handle_t *handle, alpm_mountpoint_t *mp)
304 /* cushion is roughly min(5% capacity, 20MiB) */
305 fsblkcnt_t fivepc = (mp->fsp.f_blocks / 20) + 1;
306 fsblkcnt_t twentymb = (20 * 1024 * 1024 / mp->fsp.f_bsize) + 1;
307 fsblkcnt_t cushion = fivepc < twentymb ? fivepc : twentymb;
308 blkcnt_t needed = mp->max_blocks_needed + cushion;
310 _alpm_log(handle, ALPM_LOG_DEBUG,
311 "partition %s, needed %jd, cushion %ju, free %ju\n",
312 mp->mount_dir, (intmax_t)mp->max_blocks_needed,
313 (uintmax_t)cushion, (uintmax_t)mp->fsp.f_bfree);
314 if(needed >= 0 && (fsblkcnt_t)needed > mp->fsp.f_bfree) {
315 _alpm_log(handle, ALPM_LOG_ERROR,
316 _("Partition %s too full: %jd blocks needed, %jd blocks free\n"),
317 mp->mount_dir, (intmax_t)needed, (uintmax_t)mp->fsp.f_bfree);
318 return 1;
320 return 0;
323 int _alpm_check_downloadspace(alpm_handle_t *handle, const char *cachedir,
324 size_t num_files, off_t *file_sizes)
326 alpm_list_t *mount_points;
327 alpm_mountpoint_t *cachedir_mp;
328 size_t j;
329 int error = 0;
331 mount_points = mount_point_list(handle);
332 if(mount_points == NULL) {
333 _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine filesystem mount points\n"));
334 return -1;
337 cachedir_mp = match_mount_point(mount_points, cachedir);
338 if(cachedir == NULL) {
339 _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine cachedir mount point %s\n"),
340 cachedir);
341 error = 1;
342 goto finish;
345 /* there's no need to check for a R/O mounted filesystem here, as
346 * _alpm_filecache_setup will never give us a non-writable directory */
348 /* round up the size of each file to the nearest block and accumulate */
349 for(j = 0; j < num_files; j++) {
350 cachedir_mp->max_blocks_needed += (file_sizes[j] + cachedir_mp->fsp.f_bsize + 1) /
351 cachedir_mp->fsp.f_bsize;
354 if(check_mountpoint(handle, cachedir_mp)) {
355 error = 1;
358 finish:
359 mount_point_list_free(mount_points);
361 if(error) {
362 RET_ERR(handle, ALPM_ERR_DISK_SPACE, -1);
365 return 0;
368 int _alpm_check_diskspace(alpm_handle_t *handle)
370 alpm_list_t *mount_points, *i;
371 alpm_mountpoint_t *root_mp;
372 size_t replaces = 0, current = 0, numtargs;
373 int error = 0;
374 alpm_list_t *targ;
375 alpm_trans_t *trans = handle->trans;
377 numtargs = alpm_list_count(trans->add);
378 mount_points = mount_point_list(handle);
379 if(mount_points == NULL) {
380 _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine filesystem mount points\n"));
381 return -1;
383 root_mp = match_mount_point(mount_points, handle->root);
384 if(root_mp == NULL) {
385 _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine root mount point %s\n"),
386 handle->root);
387 error = 1;
388 goto finish;
391 replaces = alpm_list_count(trans->remove);
392 if(replaces) {
393 numtargs += replaces;
394 for(targ = trans->remove; targ; targ = targ->next, current++) {
395 alpm_pkg_t *local_pkg;
396 int percent = (current * 100) / numtargs;
397 PROGRESS(handle, ALPM_PROGRESS_DISKSPACE_START, "", percent,
398 numtargs, current);
400 local_pkg = targ->data;
401 calculate_removed_size(handle, mount_points, local_pkg);
405 for(targ = trans->add; targ; targ = targ->next, current++) {
406 alpm_pkg_t *pkg, *local_pkg;
407 int percent = (current * 100) / numtargs;
408 PROGRESS(handle, ALPM_PROGRESS_DISKSPACE_START, "", percent,
409 numtargs, current);
411 pkg = targ->data;
412 /* is this package already installed? */
413 local_pkg = _alpm_db_get_pkgfromcache(handle->db_local, pkg->name);
414 if(local_pkg) {
415 calculate_removed_size(handle, mount_points, local_pkg);
417 calculate_installed_size(handle, mount_points, pkg);
419 for(i = mount_points; i; i = i->next) {
420 alpm_mountpoint_t *data = i->data;
421 if(data->blocks_needed > data->max_blocks_needed) {
422 data->max_blocks_needed = data->blocks_needed;
427 PROGRESS(handle, ALPM_PROGRESS_DISKSPACE_START, "", 100,
428 numtargs, current);
430 for(i = mount_points; i; i = i->next) {
431 alpm_mountpoint_t *data = i->data;
432 if(data->used && data->read_only) {
433 _alpm_log(handle, ALPM_LOG_ERROR, _("Partition %s is mounted read only\n"),
434 data->mount_dir);
435 error = 1;
436 } else if(data->used & USED_INSTALL && check_mountpoint(handle, data)) {
437 error = 1;
441 finish:
442 mount_point_list_free(mount_points);
444 if(error) {
445 RET_ERR(handle, ALPM_ERR_DISK_SPACE, -1);
448 return 0;
451 /* vim: set ts=2 sw=2 noet: */