Move important information up in -Si output
[pacman-ng.git] / lib / libalpm / diskspace.c
blob2582c4c33925a86b62e03a80d419d02923a450e2
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 int mount_point_load_fsinfo(alpm_handle_t *handle, alpm_mountpoint_t *mountpoint)
74 #if defined(HAVE_GETMNTENT)
75 /* grab the filesystem usage */
76 if(statvfs(mountpoint->mount_dir, &(mountpoint->fsp)) != 0) {
77 _alpm_log(handle, ALPM_LOG_WARNING,
78 _("could not get filesystem information for %s: %s\n"),
79 mountpoint->mount_dir, strerror(errno));
80 mountpoint->fsinfo_loaded = MOUNT_FSINFO_FAIL;
81 return -1;
84 _alpm_log(handle, ALPM_LOG_DEBUG, "loading fsinfo for %s\n", mountpoint->mount_dir);
85 mountpoint->read_only = mountpoint->fsp.f_flag & ST_RDONLY;
86 mountpoint->fsinfo_loaded = MOUNT_FSINFO_LOADED;
87 #endif
89 return 0;
92 static alpm_list_t *mount_point_list(alpm_handle_t *handle)
94 alpm_list_t *mount_points = NULL, *ptr;
95 alpm_mountpoint_t *mp;
97 #if defined(HAVE_GETMNTENT) && defined(HAVE_MNTENT_H)
98 /* Linux */
99 struct mntent *mnt;
100 FILE *fp;
102 fp = setmntent(MOUNTED, "r");
104 if(fp == NULL) {
105 _alpm_log(handle, ALPM_LOG_ERROR, _("could not open file: %s: %s\n"),
106 MOUNTED, strerror(errno));
107 return NULL;
110 while((mnt = getmntent(fp))) {
111 CALLOC(mp, 1, sizeof(alpm_mountpoint_t), RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
112 mp->mount_dir = strdup(mnt->mnt_dir);
113 mp->mount_dir_len = strlen(mp->mount_dir);
115 mount_points = alpm_list_add(mount_points, mp);
118 endmntent(fp);
119 #elif defined(HAVE_GETMNTENT) && defined(HAVE_MNTTAB_H)
120 /* Solaris, Illumos */
121 struct mnttab mnt;
122 FILE *fp;
123 int ret;
125 fp = fopen("/etc/mnttab", "r");
127 if(fp == NULL) {
128 _alpm_log(handle, ALPM_LOG_ERROR, _("could not open file %s: %s\n"),
129 "/etc/mnttab", strerror(errno));
130 return NULL;
133 while((ret = getmntent(fp, &mnt)) == 0) {
134 CALLOC(mp, 1, sizeof(alpm_mountpoint_t), RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
135 mp->mount_dir = strdup(mnt->mnt_mountp);
136 mp->mount_dir_len = strlen(mp->mount_dir);
138 mount_points = alpm_list_add(mount_points, mp);
140 /* -1 == EOF */
141 if(ret != -1) {
142 _alpm_log(handle, ALPM_LOG_WARNING,
143 _("could not get filesystem information\n"));
146 fclose(fp);
147 #elif defined(HAVE_GETMNTINFO)
148 /* FreeBSD (statfs), NetBSD (statvfs), OpenBSD (statfs), OS X (statfs) */
149 int entries;
150 FSSTATSTYPE *fsp;
152 entries = getmntinfo(&fsp, MNT_NOWAIT);
154 if(entries < 0) {
155 _alpm_log(handle, ALPM_LOG_ERROR,
156 _("could not get filesystem information\n"));
157 return NULL;
160 for(; entries-- > 0; fsp++) {
161 CALLOC(mp, 1, sizeof(alpm_mountpoint_t), RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
162 mp->mount_dir = strdup(fsp->f_mntonname);
163 mp->mount_dir_len = strlen(mp->mount_dir);
164 memcpy(&(mp->fsp), fsp, sizeof(FSSTATSTYPE));
165 #if defined(HAVE_GETMNTINFO_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_FLAG)
166 mp->read_only = fsp->f_flag & ST_RDONLY;
167 #elif defined(HAVE_GETMNTINFO_STATFS) && defined(HAVE_STRUCT_STATFS_F_FLAGS)
168 mp->read_only = fsp->f_flags & MNT_RDONLY;
169 #endif
171 /* we don't support lazy loading on this platform */
172 mp->fsinfo_loaded = MOUNT_FSINFO_LOADED;
174 mount_points = alpm_list_add(mount_points, mp);
176 #endif
178 mount_points = alpm_list_msort(mount_points, alpm_list_count(mount_points),
179 mount_point_cmp);
180 for(ptr = mount_points; ptr != NULL; ptr = ptr->next) {
181 mp = ptr->data;
182 _alpm_log(handle, ALPM_LOG_DEBUG, "discovered mountpoint: %s\n", mp->mount_dir);
184 return mount_points;
187 static alpm_mountpoint_t *match_mount_point(const alpm_list_t *mount_points,
188 const char *real_path)
190 const alpm_list_t *mp;
192 for(mp = mount_points; mp != NULL; mp = mp->next) {
193 alpm_mountpoint_t *data = mp->data;
195 /* first, check if the prefix matches */
196 if(strncmp(data->mount_dir, real_path, data->mount_dir_len) == 0) {
197 /* now, the hard work- a file like '/etc/myconfig' shouldn't map to a
198 * mountpoint '/e', but only '/etc'. If the mountpoint ends in a trailing
199 * slash, we know we didn't have a mismatch, otherwise we have to do some
200 * more sanity checks. */
201 if(data->mount_dir[data->mount_dir_len - 1] == '/') {
202 return data;
203 } else if(strlen(real_path) >= data->mount_dir_len) {
204 const char next = real_path[data->mount_dir_len];
205 if(next == '/' || next == '\0') {
206 return data;
212 /* should not get here... */
213 return NULL;
216 static int calculate_removed_size(alpm_handle_t *handle,
217 const alpm_list_t *mount_points, alpm_pkg_t *pkg)
219 size_t i;
220 alpm_filelist_t *filelist = alpm_pkg_get_files(pkg);
222 if(!filelist->count) {
223 return 0;
226 for(i = 0; i < filelist->count; i++) {
227 const alpm_file_t *file = filelist->files + i;
228 alpm_mountpoint_t *mp;
229 struct stat st;
230 char path[PATH_MAX];
231 blkcnt_t remove_size;
232 const char *filename = file->name;
234 snprintf(path, PATH_MAX, "%s%s", handle->root, filename);
235 _alpm_lstat(path, &st);
237 /* skip directories and symlinks to be consistent with libarchive that
238 * reports them to be zero size */
239 if(S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) {
240 continue;
243 mp = match_mount_point(mount_points, path);
244 if(mp == NULL) {
245 _alpm_log(handle, ALPM_LOG_WARNING,
246 _("could not determine mount point for file %s\n"), filename);
247 continue;
250 /* don't check a mount that we know we can't stat */
251 if(mp && mp->fsinfo_loaded == MOUNT_FSINFO_FAIL) {
252 continue;
255 /* lazy load filesystem info */
256 if(mp->fsinfo_loaded == MOUNT_FSINFO_UNLOADED) {
257 if(mount_point_load_fsinfo(handle, mp) < 0) {
258 continue;
262 /* the addition of (divisor - 1) performs ceil() with integer division */
263 remove_size = (st.st_size + mp->fsp.f_bsize - 1) / mp->fsp.f_bsize;
264 mp->blocks_needed -= remove_size;
265 mp->used |= USED_REMOVE;
268 return 0;
271 static int calculate_installed_size(alpm_handle_t *handle,
272 const alpm_list_t *mount_points, alpm_pkg_t *pkg)
274 size_t i;
275 alpm_filelist_t *filelist = alpm_pkg_get_files(pkg);
277 if(!filelist->count) {
278 return 0;
281 for(i = 0; i < filelist->count; i++) {
282 const alpm_file_t *file = filelist->files + i;
283 alpm_mountpoint_t *mp;
284 char path[PATH_MAX];
285 blkcnt_t install_size;
286 const char *filename = file->name;
288 /* libarchive reports these as zero size anyways */
289 /* NOTE: if we do start accounting for directory size, a dir matching a
290 * mountpoint needs to be attributed to the parent, not the mountpoint. */
291 if(S_ISDIR(file->mode) || S_ISLNK(file->mode)) {
292 continue;
295 /* approximate space requirements for db entries */
296 if(filename[0] == '.') {
297 filename = handle->dbpath;
300 snprintf(path, PATH_MAX, "%s%s", handle->root, filename);
302 mp = match_mount_point(mount_points, path);
303 if(mp == NULL) {
304 _alpm_log(handle, ALPM_LOG_WARNING,
305 _("could not determine mount point for file %s\n"), filename);
306 continue;
309 /* don't check a mount that we know we can't stat */
310 if(mp && mp->fsinfo_loaded == MOUNT_FSINFO_FAIL) {
311 continue;
314 /* lazy load filesystem info */
315 if(mp->fsinfo_loaded == MOUNT_FSINFO_UNLOADED) {
316 if(mount_point_load_fsinfo(handle, mp) < 0) {
317 continue;
321 /* the addition of (divisor - 1) performs ceil() with integer division */
322 install_size = (file->size + mp->fsp.f_bsize - 1) / mp->fsp.f_bsize;
323 mp->blocks_needed += install_size;
324 mp->used |= USED_INSTALL;
327 return 0;
330 static int check_mountpoint(alpm_handle_t *handle, alpm_mountpoint_t *mp)
332 /* cushion is roughly min(5% capacity, 20MiB) */
333 fsblkcnt_t fivepc = (mp->fsp.f_blocks / 20) + 1;
334 fsblkcnt_t twentymb = (20 * 1024 * 1024 / mp->fsp.f_bsize) + 1;
335 fsblkcnt_t cushion = fivepc < twentymb ? fivepc : twentymb;
336 blkcnt_t needed = mp->max_blocks_needed + cushion;
338 _alpm_log(handle, ALPM_LOG_DEBUG,
339 "partition %s, needed %jd, cushion %ju, free %ju\n",
340 mp->mount_dir, (intmax_t)mp->max_blocks_needed,
341 (uintmax_t)cushion, (uintmax_t)mp->fsp.f_bfree);
342 if(needed >= 0 && (fsblkcnt_t)needed > mp->fsp.f_bfree) {
343 _alpm_log(handle, ALPM_LOG_ERROR,
344 _("Partition %s too full: %jd blocks needed, %jd blocks free\n"),
345 mp->mount_dir, (intmax_t)needed, (uintmax_t)mp->fsp.f_bfree);
346 return 1;
348 return 0;
351 int _alpm_check_downloadspace(alpm_handle_t *handle, const char *cachedir,
352 size_t num_files, off_t *file_sizes)
354 alpm_list_t *mount_points;
355 alpm_mountpoint_t *cachedir_mp;
356 char resolved_cachedir[PATH_MAX];
357 size_t j;
358 int error = 0;
360 /* resolve the cachedir path to ensure we check the right mountpoint. We
361 * handle failures silently, and continue to use the possibly unresolved
362 * path. */
363 if(realpath(cachedir, resolved_cachedir) != NULL) {
364 cachedir = resolved_cachedir;
367 mount_points = mount_point_list(handle);
368 if(mount_points == NULL) {
369 _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine filesystem mount points\n"));
370 return -1;
373 cachedir_mp = match_mount_point(mount_points, cachedir);
374 if(cachedir_mp == NULL) {
375 _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine cachedir mount point %s\n"),
376 cachedir);
377 error = 1;
378 goto finish;
381 if(cachedir_mp->fsinfo_loaded == MOUNT_FSINFO_UNLOADED) {
382 if(mount_point_load_fsinfo(handle, cachedir_mp)) {
383 error = 1;
384 goto finish;
388 /* there's no need to check for a R/O mounted filesystem here, as
389 * _alpm_filecache_setup will never give us a non-writable directory */
391 /* round up the size of each file to the nearest block and accumulate */
392 for(j = 0; j < num_files; j++) {
393 cachedir_mp->max_blocks_needed += (file_sizes[j] + cachedir_mp->fsp.f_bsize + 1) /
394 cachedir_mp->fsp.f_bsize;
397 if(check_mountpoint(handle, cachedir_mp)) {
398 error = 1;
401 finish:
402 mount_point_list_free(mount_points);
404 if(error) {
405 RET_ERR(handle, ALPM_ERR_DISK_SPACE, -1);
408 return 0;
411 int _alpm_check_diskspace(alpm_handle_t *handle)
413 alpm_list_t *mount_points, *i;
414 alpm_mountpoint_t *root_mp;
415 size_t replaces = 0, current = 0, numtargs;
416 int error = 0;
417 alpm_list_t *targ;
418 alpm_trans_t *trans = handle->trans;
420 numtargs = alpm_list_count(trans->add);
421 mount_points = mount_point_list(handle);
422 if(mount_points == NULL) {
423 _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine filesystem mount points\n"));
424 return -1;
426 root_mp = match_mount_point(mount_points, handle->root);
427 if(root_mp == NULL) {
428 _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine root mount point %s\n"),
429 handle->root);
430 error = 1;
431 goto finish;
434 replaces = alpm_list_count(trans->remove);
435 if(replaces) {
436 numtargs += replaces;
437 for(targ = trans->remove; targ; targ = targ->next, current++) {
438 alpm_pkg_t *local_pkg;
439 int percent = (current * 100) / numtargs;
440 PROGRESS(handle, ALPM_PROGRESS_DISKSPACE_START, "", percent,
441 numtargs, current);
443 local_pkg = targ->data;
444 calculate_removed_size(handle, mount_points, local_pkg);
448 for(targ = trans->add; targ; targ = targ->next, current++) {
449 alpm_pkg_t *pkg, *local_pkg;
450 int percent = (current * 100) / numtargs;
451 PROGRESS(handle, ALPM_PROGRESS_DISKSPACE_START, "", percent,
452 numtargs, current);
454 pkg = targ->data;
455 /* is this package already installed? */
456 local_pkg = _alpm_db_get_pkgfromcache(handle->db_local, pkg->name);
457 if(local_pkg) {
458 calculate_removed_size(handle, mount_points, local_pkg);
460 calculate_installed_size(handle, mount_points, pkg);
462 for(i = mount_points; i; i = i->next) {
463 alpm_mountpoint_t *data = i->data;
464 if(data->blocks_needed > data->max_blocks_needed) {
465 data->max_blocks_needed = data->blocks_needed;
470 PROGRESS(handle, ALPM_PROGRESS_DISKSPACE_START, "", 100,
471 numtargs, current);
473 for(i = mount_points; i; i = i->next) {
474 alpm_mountpoint_t *data = i->data;
475 if(data->used && data->read_only) {
476 _alpm_log(handle, ALPM_LOG_ERROR, _("Partition %s is mounted read only\n"),
477 data->mount_dir);
478 error = 1;
479 } else if(data->used & USED_INSTALL && check_mountpoint(handle, data)) {
480 error = 1;
484 finish:
485 mount_point_list_free(mount_points);
487 if(error) {
488 RET_ERR(handle, ALPM_ERR_DISK_SPACE, -1);
491 return 0;
494 /* vim: set ts=2 sw=2 noet: */