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/>.
23 #if defined(HAVE_MNTENT_H)
26 #if defined(HAVE_SYS_MNTTAB_H)
27 #include <sys/mnttab.h>
29 #if defined(HAVE_SYS_STATVFS_H)
30 #include <sys/statvfs.h>
32 #if defined(HAVE_SYS_PARAM_H)
33 #include <sys/param.h>
35 #if defined(HAVE_SYS_MOUNT_H)
36 #include <sys/mount.h>
38 #if defined(HAVE_SYS_UCRED_H)
39 #include <sys/ucred.h>
41 #if defined(HAVE_SYS_TYPES_H)
42 #include <sys/types.h>
46 #include "diskspace.h"
47 #include "alpm_list.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
)
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
;
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
;
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)
102 fp
= setmntent(MOUNTED
, "r");
105 _alpm_log(handle
, ALPM_LOG_ERROR
, _("could not open file: %s: %s\n"),
106 MOUNTED
, strerror(errno
));
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
);
119 #elif defined(HAVE_GETMNTENT) && defined(HAVE_MNTTAB_H)
120 /* Solaris, Illumos */
125 fp
= fopen("/etc/mnttab", "r");
128 _alpm_log(handle
, ALPM_LOG_ERROR
, _("could not open file %s: %s\n"),
129 "/etc/mnttab", strerror(errno
));
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
);
142 _alpm_log(handle
, ALPM_LOG_WARNING
,
143 _("could not get filesystem information\n"));
147 #elif defined(HAVE_GETMNTINFO)
148 /* FreeBSD (statfs), NetBSD (statvfs), OpenBSD (statfs), OS X (statfs) */
152 entries
= getmntinfo(&fsp
, MNT_NOWAIT
);
155 _alpm_log(handle
, ALPM_LOG_ERROR
,
156 _("could not get filesystem information\n"));
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
;
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
);
178 mount_points
= alpm_list_msort(mount_points
, alpm_list_count(mount_points
),
180 for(ptr
= mount_points
; ptr
!= NULL
; ptr
= ptr
->next
) {
182 _alpm_log(handle
, ALPM_LOG_DEBUG
, "discovered mountpoint: %s\n", mp
->mount_dir
);
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] == '/') {
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') {
212 /* should not get here... */
216 static int calculate_removed_size(alpm_handle_t
*handle
,
217 const alpm_list_t
*mount_points
, alpm_pkg_t
*pkg
)
220 alpm_filelist_t
*filelist
= alpm_pkg_get_files(pkg
);
222 if(!filelist
->count
) {
226 for(i
= 0; i
< filelist
->count
; i
++) {
227 const alpm_file_t
*file
= filelist
->files
+ i
;
228 alpm_mountpoint_t
*mp
;
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
)) {
243 mp
= match_mount_point(mount_points
, path
);
245 _alpm_log(handle
, ALPM_LOG_WARNING
,
246 _("could not determine mount point for file %s\n"), filename
);
250 /* don't check a mount that we know we can't stat */
251 if(mp
&& mp
->fsinfo_loaded
== MOUNT_FSINFO_FAIL
) {
255 /* lazy load filesystem info */
256 if(mp
->fsinfo_loaded
== MOUNT_FSINFO_UNLOADED
) {
257 if(mount_point_load_fsinfo(handle
, mp
) < 0) {
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
;
271 static int calculate_installed_size(alpm_handle_t
*handle
,
272 const alpm_list_t
*mount_points
, alpm_pkg_t
*pkg
)
275 alpm_filelist_t
*filelist
= alpm_pkg_get_files(pkg
);
277 if(!filelist
->count
) {
281 for(i
= 0; i
< filelist
->count
; i
++) {
282 const alpm_file_t
*file
= filelist
->files
+ i
;
283 alpm_mountpoint_t
*mp
;
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
)) {
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
);
304 _alpm_log(handle
, ALPM_LOG_WARNING
,
305 _("could not determine mount point for file %s\n"), filename
);
309 /* don't check a mount that we know we can't stat */
310 if(mp
&& mp
->fsinfo_loaded
== MOUNT_FSINFO_FAIL
) {
314 /* lazy load filesystem info */
315 if(mp
->fsinfo_loaded
== MOUNT_FSINFO_UNLOADED
) {
316 if(mount_point_load_fsinfo(handle
, mp
) < 0) {
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
;
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
);
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
];
360 /* resolve the cachedir path to ensure we check the right mountpoint. We
361 * handle failures silently, and continue to use the possibly unresolved
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"));
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"),
381 if(cachedir_mp
->fsinfo_loaded
== MOUNT_FSINFO_UNLOADED
) {
382 if(mount_point_load_fsinfo(handle
, cachedir_mp
)) {
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
)) {
402 mount_point_list_free(mount_points
);
405 RET_ERR(handle
, ALPM_ERR_DISK_SPACE
, -1);
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
;
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"));
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"),
434 replaces
= alpm_list_count(trans
->remove
);
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
,
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
,
455 /* is this package already installed? */
456 local_pkg
= _alpm_db_get_pkgfromcache(handle
->db_local
, pkg
->name
);
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,
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"),
479 } else if(data
->used
& USED_INSTALL
&& check_mountpoint(handle
, data
)) {
485 mount_point_list_free(mount_points
);
488 RET_ERR(handle
, ALPM_ERR_DISK_SPACE
, -1);
494 /* vim: set ts=2 sw=2 noet: */