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 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
)) {
239 mp
= match_mount_point(mount_points
, path
);
241 _alpm_log(handle
, ALPM_LOG_WARNING
,
242 _("could not determine mount point for file %s\n"), filename
);
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
;
255 static int calculate_installed_size(alpm_handle_t
*handle
,
256 const alpm_list_t
*mount_points
, alpm_pkg_t
*pkg
)
259 alpm_filelist_t
*filelist
= alpm_pkg_get_files(pkg
);
261 if(!filelist
->count
) {
265 for(i
= 0; i
< filelist
->count
; i
++) {
266 const alpm_file_t
*file
= filelist
->files
+ i
;
267 alpm_mountpoint_t
*mp
;
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
)) {
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
);
288 _alpm_log(handle
, ALPM_LOG_WARNING
,
289 _("could not determine mount point for file %s\n"), filename
);
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
;
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
);
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
;
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"));
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"),
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
)) {
359 mount_point_list_free(mount_points
);
362 RET_ERR(handle
, ALPM_ERR_DISK_SPACE
, -1);
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
;
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"));
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"),
391 replaces
= alpm_list_count(trans
->remove
);
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
,
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
,
412 /* is this package already installed? */
413 local_pkg
= _alpm_db_get_pkgfromcache(handle
->db_local
, pkg
->name
);
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,
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"),
436 } else if(data
->used
& USED_INSTALL
&& check_mountpoint(handle
, data
)) {
442 mount_point_list_free(mount_points
);
445 RET_ERR(handle
, ALPM_ERR_DISK_SPACE
, -1);
451 /* vim: set ts=2 sw=2 noet: */