pacman: list all unknown targets on removal operation
[pacman-ng.git] / lib / libalpm / diskspace.c
blobcfd6402cace316fb08f51a32726ff4b56dad8441
1 /*
2 * diskspace.c
4 * Copyright (c) 2010-2011 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 "config.h"
22 #include <errno.h>
23 #if defined(HAVE_MNTENT_H)
24 #include <mntent.h>
25 #endif
26 #if defined(HAVE_SYS_STATVFS_H)
27 #include <sys/statvfs.h>
28 #endif
29 #if defined(HAVE_SYS_PARAM_H)
30 #include <sys/param.h>
31 #endif
32 #if defined(HAVE_SYS_MOUNT_H)
33 #include <sys/mount.h>
34 #endif
35 #if defined(HAVE_SYS_UCRED_H)
36 #include <sys/ucred.h>
37 #endif
38 #if defined(HAVE_SYS_TYPES_H)
39 #include <sys/types.h>
40 #endif
42 /* libalpm */
43 #include "diskspace.h"
44 #include "alpm_list.h"
45 #include "util.h"
46 #include "log.h"
47 #include "trans.h"
48 #include "handle.h"
50 static int mount_point_cmp(const void *p1, const void *p2)
52 const alpm_mountpoint_t *mp1 = p1;
53 const alpm_mountpoint_t *mp2 = p2;
54 /* the negation will sort all mountpoints before their parent */
55 return -strcmp(mp1->mount_dir, mp2->mount_dir);
58 static void mount_point_list_free(alpm_list_t *mount_points)
60 alpm_list_t *i;
62 for(i = mount_points; i; i = i->next) {
63 alpm_mountpoint_t *data = i->data;
64 FREE(data->mount_dir);
66 FREELIST(mount_points);
69 static alpm_list_t *mount_point_list(alpm_handle_t *handle)
71 alpm_list_t *mount_points = NULL, *ptr;
72 alpm_mountpoint_t *mp;
74 #if defined HAVE_GETMNTENT
75 struct mntent *mnt;
76 FILE *fp;
77 struct statvfs fsp;
79 fp = setmntent(MOUNTED, "r");
81 if(fp == NULL) {
82 return NULL;
85 while((mnt = getmntent(fp))) {
86 if(!mnt) {
87 _alpm_log(handle, ALPM_LOG_WARNING, _("could not get filesystem information\n"));
88 continue;
90 if(statvfs(mnt->mnt_dir, &fsp) != 0) {
91 _alpm_log(handle, ALPM_LOG_WARNING,
92 _("could not get filesystem information for %s: %s\n"),
93 mnt->mnt_dir, strerror(errno));
94 continue;
97 CALLOC(mp, 1, sizeof(alpm_mountpoint_t), RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
98 mp->mount_dir = strdup(mnt->mnt_dir);
99 mp->mount_dir_len = strlen(mp->mount_dir);
100 memcpy(&(mp->fsp), &fsp, sizeof(struct statvfs));
101 mp->read_only = fsp.f_flag & ST_RDONLY;
103 mount_points = alpm_list_add(mount_points, mp);
106 endmntent(fp);
107 #elif defined HAVE_GETMNTINFO
108 int entries;
109 FSSTATSTYPE *fsp;
111 entries = getmntinfo(&fsp, MNT_NOWAIT);
113 if(entries < 0) {
114 return NULL;
117 for(; entries-- > 0; fsp++) {
118 CALLOC(mp, 1, sizeof(alpm_mountpoint_t), RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
119 mp->mount_dir = strdup(fsp->f_mntonname);
120 mp->mount_dir_len = strlen(mp->mount_dir);
121 memcpy(&(mp->fsp), fsp, sizeof(FSSTATSTYPE));
122 #if defined(HAVE_GETMNTINFO_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_FLAG)
123 mp->read_only = fsp->f_flag & ST_RDONLY;
124 #elif defined(HAVE_GETMNTINFO_STATFS) && defined(HAVE_STRUCT_STATFS_F_FLAGS)
125 mp->read_only = fsp->f_flags & MNT_RDONLY;
126 #endif
128 mount_points = alpm_list_add(mount_points, mp);
130 #endif
132 mount_points = alpm_list_msort(mount_points, alpm_list_count(mount_points),
133 mount_point_cmp);
134 for(ptr = mount_points; ptr != NULL; ptr = ptr->next) {
135 mp = ptr->data;
136 _alpm_log(handle, ALPM_LOG_DEBUG, "mountpoint: %s\n", mp->mount_dir);
138 return mount_points;
141 static alpm_mountpoint_t *match_mount_point(const alpm_list_t *mount_points,
142 const char *real_path)
144 const alpm_list_t *mp;
146 for(mp = mount_points; mp != NULL; mp = mp->next) {
147 alpm_mountpoint_t *data = mp->data;
149 if(strncmp(data->mount_dir, real_path, data->mount_dir_len) == 0) {
150 return data;
154 /* should not get here... */
155 return NULL;
158 static int calculate_removed_size(alpm_handle_t *handle,
159 const alpm_list_t *mount_points, alpm_pkg_t *pkg)
161 size_t i;
162 alpm_filelist_t *filelist = alpm_pkg_get_files(pkg);
164 if(!filelist->count) {
165 return 0;
168 for(i = 0; i < filelist->count; i++) {
169 const alpm_file_t *file = filelist->files + i;
170 alpm_mountpoint_t *mp;
171 struct stat st;
172 char path[PATH_MAX];
173 const char *filename = file->name;
175 snprintf(path, PATH_MAX, "%s%s", handle->root, filename);
176 _alpm_lstat(path, &st);
178 /* skip directories and symlinks to be consistent with libarchive that
179 * reports them to be zero size */
180 if(S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) {
181 continue;
184 mp = match_mount_point(mount_points, path);
185 if(mp == NULL) {
186 _alpm_log(handle, ALPM_LOG_WARNING,
187 _("could not determine mount point for file %s\n"), filename);
188 continue;
191 /* the addition of (divisor - 1) performs ceil() with integer division */
192 mp->blocks_needed -=
193 (st.st_size + mp->fsp.f_bsize - 1) / mp->fsp.f_bsize;
194 mp->used |= USED_REMOVE;
197 return 0;
200 static int calculate_installed_size(alpm_handle_t *handle,
201 const alpm_list_t *mount_points, alpm_pkg_t *pkg)
203 size_t i;
204 alpm_filelist_t *filelist = alpm_pkg_get_files(pkg);
206 if(!filelist->count) {
207 return 0;
210 for(i = 0; i < filelist->count; i++) {
211 const alpm_file_t *file = filelist->files + i;
212 alpm_mountpoint_t *mp;
213 char path[PATH_MAX];
214 const char *filename = file->name;
216 /* libarchive reports these as zero size anyways */
217 /* NOTE: if we do start accounting for directory size, a dir matching a
218 * mountpoint needs to be attributed to the parent, not the mountpoint. */
219 if(S_ISDIR(file->mode) || S_ISLNK(file->mode)) {
220 continue;
223 /* approximate space requirements for db entries */
224 if(filename[0] == '.') {
225 filename = handle->dbpath;
228 snprintf(path, PATH_MAX, "%s%s", handle->root, filename);
230 mp = match_mount_point(mount_points, path);
231 if(mp == NULL) {
232 _alpm_log(handle, ALPM_LOG_WARNING,
233 _("could not determine mount point for file %s\n"), filename);
234 continue;
237 /* the addition of (divisor - 1) performs ceil() with integer division */
238 mp->blocks_needed +=
239 (file->size + mp->fsp.f_bsize - 1) / mp->fsp.f_bsize;
240 mp->used |= USED_INSTALL;
243 return 0;
246 static int check_mountpoint(alpm_handle_t *handle, alpm_mountpoint_t *mp)
248 /* cushion is roughly min(5% capacity, 20MiB) */
249 fsblkcnt_t fivepc = (mp->fsp.f_blocks / 20) + 1;
250 fsblkcnt_t twentymb = (20 * 1024 * 1024 / mp->fsp.f_bsize) + 1;
251 fsblkcnt_t cushion = fivepc < twentymb ? fivepc : twentymb;
252 blkcnt_t needed = mp->max_blocks_needed + cushion;
254 _alpm_log(handle, ALPM_LOG_DEBUG,
255 "partition %s, needed %jd, cushion %ju, free %ju\n",
256 mp->mount_dir, (intmax_t)mp->max_blocks_needed,
257 (uintmax_t)cushion, (uintmax_t)mp->fsp.f_bfree);
258 if(needed >= 0 && (fsblkcnt_t)needed > mp->fsp.f_bfree) {
259 _alpm_log(handle, ALPM_LOG_ERROR,
260 _("Partition %s too full: %jd blocks needed, %jd blocks free\n"),
261 mp->mount_dir, (intmax_t)needed, (uintmax_t)mp->fsp.f_bfree);
262 return 1;
264 return 0;
267 int _alpm_check_downloadspace(alpm_handle_t *handle, const char *cachedir,
268 size_t num_files, off_t *file_sizes)
270 alpm_list_t *mount_points;
271 alpm_mountpoint_t *cachedir_mp;
272 size_t j;
273 int error = 0;
275 mount_points = mount_point_list(handle);
276 if(mount_points == NULL) {
277 _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine filesystem mount points\n"));
278 return -1;
281 cachedir_mp = match_mount_point(mount_points, cachedir);
282 if(cachedir == NULL) {
283 _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine cachedir mount point %s\n"),
284 cachedir);
285 error = 1;
286 goto finish;
289 /* there's no need to check for a R/O mounted filesystem here, as
290 * _alpm_filecache_setup will never give us a non-writable directory */
292 /* round up the size of each file to the nearest block and accumulate */
293 for(j = 0; j < num_files; j++) {
294 cachedir_mp->max_blocks_needed += (file_sizes[j] + cachedir_mp->fsp.f_bsize + 1) /
295 cachedir_mp->fsp.f_bsize;
298 if(check_mountpoint(handle, cachedir_mp)) {
299 error = 1;
302 finish:
303 mount_point_list_free(mount_points);
305 if(error) {
306 RET_ERR(handle, ALPM_ERR_DISK_SPACE, -1);
309 return 0;
312 int _alpm_check_diskspace(alpm_handle_t *handle)
314 alpm_list_t *mount_points, *i;
315 alpm_mountpoint_t *root_mp;
316 size_t replaces = 0, current = 0, numtargs;
317 int error = 0;
318 alpm_list_t *targ;
319 alpm_trans_t *trans = handle->trans;
321 numtargs = alpm_list_count(trans->add);
322 mount_points = mount_point_list(handle);
323 if(mount_points == NULL) {
324 _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine filesystem mount points\n"));
325 return -1;
327 root_mp = match_mount_point(mount_points, handle->root);
328 if(root_mp == NULL) {
329 _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine root mount point %s\n"),
330 handle->root);
331 error = 1;
332 goto finish;
335 replaces = alpm_list_count(trans->remove);
336 if(replaces) {
337 numtargs += replaces;
338 for(targ = trans->remove; targ; targ = targ->next, current++) {
339 alpm_pkg_t *local_pkg;
340 int percent = (current * 100) / numtargs;
341 PROGRESS(handle, ALPM_PROGRESS_DISKSPACE_START, "", percent,
342 numtargs, current);
344 local_pkg = targ->data;
345 calculate_removed_size(handle, mount_points, local_pkg);
349 for(targ = trans->add; targ; targ = targ->next, current++) {
350 alpm_pkg_t *pkg, *local_pkg;
351 int percent = (current * 100) / numtargs;
352 PROGRESS(handle, ALPM_PROGRESS_DISKSPACE_START, "", percent,
353 numtargs, current);
355 pkg = targ->data;
356 /* is this package already installed? */
357 local_pkg = _alpm_db_get_pkgfromcache(handle->db_local, pkg->name);
358 if(local_pkg) {
359 calculate_removed_size(handle, mount_points, local_pkg);
361 calculate_installed_size(handle, mount_points, pkg);
363 for(i = mount_points; i; i = i->next) {
364 alpm_mountpoint_t *data = i->data;
365 if(data->blocks_needed > data->max_blocks_needed) {
366 data->max_blocks_needed = data->blocks_needed;
371 PROGRESS(handle, ALPM_PROGRESS_DISKSPACE_START, "", 100,
372 numtargs, current);
374 for(i = mount_points; i; i = i->next) {
375 alpm_mountpoint_t *data = i->data;
376 if(data->used && data->read_only) {
377 _alpm_log(handle, ALPM_LOG_ERROR, _("Partition %s is mounted read only\n"),
378 data->mount_dir);
379 error = 1;
380 } else if(data->used & USED_INSTALL && check_mountpoint(handle, data)) {
381 error = 1;
385 finish:
386 mount_point_list_free(mount_points);
388 if(error) {
389 RET_ERR(handle, ALPM_ERR_DISK_SPACE, -1);
392 return 0;
395 /* vim: set ts=2 sw=2 noet: */