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 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)
82 fp
= setmntent(MOUNTED
, "r");
88 while((mnt
= getmntent(fp
))) {
91 _alpm_log(handle
, ALPM_LOG_WARNING
,
92 _("could not get filesystem information\n"));
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
));
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
);
112 #elif defined(HAVE_GETMNTENT) && defined(HAVE_MNTTAB_H)
113 /* Solaris, Illumos */
118 fp
= fopen("/etc/mnttab", "r");
124 while((ret
= getmntent(fp
, &mnt
)) == 0) {
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
));
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
);
143 _alpm_log(handle
, ALPM_LOG_WARNING
,
144 _("could not get filesystem information\n"));
148 #elif defined(HAVE_GETMNTINFO)
149 /* FreeBSD (statfs), NetBSD (statvfs), OpenBSD (statfs), OS X (statfs) */
153 entries
= getmntinfo(&fsp
, MNT_NOWAIT
);
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
;
170 mount_points
= alpm_list_add(mount_points
, mp
);
174 mount_points
= alpm_list_msort(mount_points
, alpm_list_count(mount_points
),
176 for(ptr
= mount_points
; ptr
!= NULL
; ptr
= ptr
->next
) {
178 _alpm_log(handle
, ALPM_LOG_DEBUG
, "mountpoint: %s\n", mp
->mount_dir
);
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] == '/') {
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') {
208 /* should not get here... */
212 static int calculate_removed_size(alpm_handle_t
*handle
,
213 const alpm_list_t
*mount_points
, alpm_pkg_t
*pkg
)
216 alpm_filelist_t
*filelist
= alpm_pkg_get_files(pkg
);
218 if(!filelist
->count
) {
222 for(i
= 0; i
< filelist
->count
; i
++) {
223 const alpm_file_t
*file
= filelist
->files
+ i
;
224 alpm_mountpoint_t
*mp
;
227 const char *filename
= file
->name
;
229 snprintf(path
, PATH_MAX
, "%s%s", handle
->root
, filename
);
230 _alpm_lstat(path
, &st
);
232 /* skip directories and symlinks to be consistent with libarchive that
233 * reports them to be zero size */
234 if(S_ISDIR(st
.st_mode
) || S_ISLNK(st
.st_mode
)) {
238 mp
= match_mount_point(mount_points
, path
);
240 _alpm_log(handle
, ALPM_LOG_WARNING
,
241 _("could not determine mount point for file %s\n"), filename
);
245 /* the addition of (divisor - 1) performs ceil() with integer division */
247 (st
.st_size
+ mp
->fsp
.f_bsize
- 1) / mp
->fsp
.f_bsize
;
248 mp
->used
|= USED_REMOVE
;
254 static int calculate_installed_size(alpm_handle_t
*handle
,
255 const alpm_list_t
*mount_points
, alpm_pkg_t
*pkg
)
258 alpm_filelist_t
*filelist
= alpm_pkg_get_files(pkg
);
260 if(!filelist
->count
) {
264 for(i
= 0; i
< filelist
->count
; i
++) {
265 const alpm_file_t
*file
= filelist
->files
+ i
;
266 alpm_mountpoint_t
*mp
;
268 const char *filename
= file
->name
;
270 /* libarchive reports these as zero size anyways */
271 /* NOTE: if we do start accounting for directory size, a dir matching a
272 * mountpoint needs to be attributed to the parent, not the mountpoint. */
273 if(S_ISDIR(file
->mode
) || S_ISLNK(file
->mode
)) {
277 /* approximate space requirements for db entries */
278 if(filename
[0] == '.') {
279 filename
= handle
->dbpath
;
282 snprintf(path
, PATH_MAX
, "%s%s", handle
->root
, filename
);
284 mp
= match_mount_point(mount_points
, path
);
286 _alpm_log(handle
, ALPM_LOG_WARNING
,
287 _("could not determine mount point for file %s\n"), filename
);
291 /* the addition of (divisor - 1) performs ceil() with integer division */
293 (file
->size
+ mp
->fsp
.f_bsize
- 1) / mp
->fsp
.f_bsize
;
294 mp
->used
|= USED_INSTALL
;
300 static int check_mountpoint(alpm_handle_t
*handle
, alpm_mountpoint_t
*mp
)
302 /* cushion is roughly min(5% capacity, 20MiB) */
303 fsblkcnt_t fivepc
= (mp
->fsp
.f_blocks
/ 20) + 1;
304 fsblkcnt_t twentymb
= (20 * 1024 * 1024 / mp
->fsp
.f_bsize
) + 1;
305 fsblkcnt_t cushion
= fivepc
< twentymb
? fivepc
: twentymb
;
306 blkcnt_t needed
= mp
->max_blocks_needed
+ cushion
;
308 _alpm_log(handle
, ALPM_LOG_DEBUG
,
309 "partition %s, needed %jd, cushion %ju, free %ju\n",
310 mp
->mount_dir
, (intmax_t)mp
->max_blocks_needed
,
311 (uintmax_t)cushion
, (uintmax_t)mp
->fsp
.f_bfree
);
312 if(needed
>= 0 && (fsblkcnt_t
)needed
> mp
->fsp
.f_bfree
) {
313 _alpm_log(handle
, ALPM_LOG_ERROR
,
314 _("Partition %s too full: %jd blocks needed, %jd blocks free\n"),
315 mp
->mount_dir
, (intmax_t)needed
, (uintmax_t)mp
->fsp
.f_bfree
);
321 int _alpm_check_downloadspace(alpm_handle_t
*handle
, const char *cachedir
,
322 size_t num_files
, off_t
*file_sizes
)
324 alpm_list_t
*mount_points
;
325 alpm_mountpoint_t
*cachedir_mp
;
329 mount_points
= mount_point_list(handle
);
330 if(mount_points
== NULL
) {
331 _alpm_log(handle
, ALPM_LOG_ERROR
, _("could not determine filesystem mount points\n"));
335 cachedir_mp
= match_mount_point(mount_points
, cachedir
);
336 if(cachedir
== NULL
) {
337 _alpm_log(handle
, ALPM_LOG_ERROR
, _("could not determine cachedir mount point %s\n"),
343 /* there's no need to check for a R/O mounted filesystem here, as
344 * _alpm_filecache_setup will never give us a non-writable directory */
346 /* round up the size of each file to the nearest block and accumulate */
347 for(j
= 0; j
< num_files
; j
++) {
348 cachedir_mp
->max_blocks_needed
+= (file_sizes
[j
] + cachedir_mp
->fsp
.f_bsize
+ 1) /
349 cachedir_mp
->fsp
.f_bsize
;
352 if(check_mountpoint(handle
, cachedir_mp
)) {
357 mount_point_list_free(mount_points
);
360 RET_ERR(handle
, ALPM_ERR_DISK_SPACE
, -1);
366 int _alpm_check_diskspace(alpm_handle_t
*handle
)
368 alpm_list_t
*mount_points
, *i
;
369 alpm_mountpoint_t
*root_mp
;
370 size_t replaces
= 0, current
= 0, numtargs
;
373 alpm_trans_t
*trans
= handle
->trans
;
375 numtargs
= alpm_list_count(trans
->add
);
376 mount_points
= mount_point_list(handle
);
377 if(mount_points
== NULL
) {
378 _alpm_log(handle
, ALPM_LOG_ERROR
, _("could not determine filesystem mount points\n"));
381 root_mp
= match_mount_point(mount_points
, handle
->root
);
382 if(root_mp
== NULL
) {
383 _alpm_log(handle
, ALPM_LOG_ERROR
, _("could not determine root mount point %s\n"),
389 replaces
= alpm_list_count(trans
->remove
);
391 numtargs
+= replaces
;
392 for(targ
= trans
->remove
; targ
; targ
= targ
->next
, current
++) {
393 alpm_pkg_t
*local_pkg
;
394 int percent
= (current
* 100) / numtargs
;
395 PROGRESS(handle
, ALPM_PROGRESS_DISKSPACE_START
, "", percent
,
398 local_pkg
= targ
->data
;
399 calculate_removed_size(handle
, mount_points
, local_pkg
);
403 for(targ
= trans
->add
; targ
; targ
= targ
->next
, current
++) {
404 alpm_pkg_t
*pkg
, *local_pkg
;
405 int percent
= (current
* 100) / numtargs
;
406 PROGRESS(handle
, ALPM_PROGRESS_DISKSPACE_START
, "", percent
,
410 /* is this package already installed? */
411 local_pkg
= _alpm_db_get_pkgfromcache(handle
->db_local
, pkg
->name
);
413 calculate_removed_size(handle
, mount_points
, local_pkg
);
415 calculate_installed_size(handle
, mount_points
, pkg
);
417 for(i
= mount_points
; i
; i
= i
->next
) {
418 alpm_mountpoint_t
*data
= i
->data
;
419 if(data
->blocks_needed
> data
->max_blocks_needed
) {
420 data
->max_blocks_needed
= data
->blocks_needed
;
425 PROGRESS(handle
, ALPM_PROGRESS_DISKSPACE_START
, "", 100,
428 for(i
= mount_points
; i
; i
= i
->next
) {
429 alpm_mountpoint_t
*data
= i
->data
;
430 if(data
->used
&& data
->read_only
) {
431 _alpm_log(handle
, ALPM_LOG_ERROR
, _("Partition %s is mounted read only\n"),
434 } else if(data
->used
& USED_INSTALL
&& check_mountpoint(handle
, data
)) {
440 mount_point_list_free(mount_points
);
443 RET_ERR(handle
, ALPM_ERR_DISK_SPACE
, -1);
449 /* vim: set ts=2 sw=2 noet: */