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/>.
22 #if defined(HAVE_MNTENT_H)
25 #if defined(HAVE_SYS_STATVFS_H)
26 #include <sys/statvfs.h>
28 #if defined(HAVE_SYS_PARAM_H)
29 #include <sys/param.h>
31 #if defined(HAVE_SYS_MOUNT_H)
32 #include <sys/mount.h>
34 #if defined(HAVE_SYS_UCRED_H)
35 #include <sys/ucred.h>
37 #if defined(HAVE_SYS_TYPES_H)
38 #include <sys/types.h>
43 #include <archive_entry.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 alpm_list_t
*mount_point_list(void)
63 alpm_list_t
*mount_points
= NULL
;
64 alpm_mountpoint_t
*mp
;
66 #if defined HAVE_GETMNTENT
71 fp
= setmntent(MOUNTED
, "r");
77 while((mnt
= getmntent(fp
))) {
78 if(statvfs(mnt
->mnt_dir
, &fsp
) != 0) {
79 _alpm_log(PM_LOG_WARNING
,
80 _("could not get filesystem information for %s\n"), mnt
->mnt_dir
);
84 CALLOC(mp
, 1, sizeof(alpm_mountpoint_t
), RET_ERR(PM_ERR_MEMORY
, NULL
));
85 mp
->mount_dir
= strdup(mnt
->mnt_dir
);
86 mp
->mount_dir_len
= strlen(mp
->mount_dir
);
87 memcpy(&(mp
->fsp
), &fsp
, sizeof(FSSTATSTYPE
));
89 mount_points
= alpm_list_add(mount_points
, mp
);
93 #elif defined HAVE_GETMNTINFO
97 entries
= getmntinfo(&fsp
, MNT_NOWAIT
);
103 for(; entries
-- > 0; fsp
++) {
104 CALLOC(mp
, 1, sizeof(alpm_mountpoint_t
), RET_ERR(PM_ERR_MEMORY
, NULL
));
105 mp
->mount_dir
= strdup(fsp
->f_mntonname
);
106 mp
->mount_dir_len
= strlen(mp
->mount_dir
);
107 memcpy(&(mp
->fsp
), fsp
, sizeof(FSSTATSTYPE
));
109 mount_points
= alpm_list_add(mount_points
, mp
);
113 mount_points
= alpm_list_msort(mount_points
, alpm_list_count(mount_points
),
115 return(mount_points
);
118 static alpm_mountpoint_t
*match_mount_point(const alpm_list_t
*mount_points
,
119 const char *real_path
)
121 const alpm_list_t
*mp
;
123 for(mp
= mount_points
; mp
!= NULL
; mp
= mp
->next
) {
124 alpm_mountpoint_t
*data
= mp
->data
;
126 if(strncmp(data
->mount_dir
, real_path
, data
->mount_dir_len
) == 0) {
131 /* should not get here... */
135 static int calculate_removed_size(const alpm_list_t
*mount_points
,
140 alpm_list_t
*files
= alpm_pkg_get_files(pkg
);
141 for(file
= files
; file
; file
= file
->next
) {
142 alpm_mountpoint_t
*mp
;
145 const char *filename
= file
->data
;
147 snprintf(path
, PATH_MAX
, "%s%s", handle
->root
, filename
);
148 _alpm_lstat(path
, &st
);
150 /* skip directories and symlinks to be consistent with libarchive that
151 * reports them to be zero size */
152 if(S_ISDIR(st
.st_mode
) || S_ISLNK(st
.st_mode
)) {
156 mp
= match_mount_point(mount_points
, path
);
158 _alpm_log(PM_LOG_WARNING
,
159 _("could not determine mount point for file %s"), filename
);
163 /* the addition of (divisor - 1) performs ceil() with integer division */
165 (st
.st_size
+ mp
->fsp
.f_bsize
- 1l) / mp
->fsp
.f_bsize
;
171 static int calculate_installed_size(const alpm_list_t
*mount_points
,
175 struct archive
*archive
;
176 struct archive_entry
*entry
;
178 if ((archive
= archive_read_new()) == NULL
) {
179 pm_errno
= PM_ERR_LIBARCHIVE
;
184 archive_read_support_compression_all(archive
);
185 archive_read_support_format_all(archive
);
187 if(archive_read_open_filename(archive
, pkg
->origin_data
.file
,
188 ARCHIVE_DEFAULT_BYTES_PER_BLOCK
) != ARCHIVE_OK
) {
189 pm_errno
= PM_ERR_PKG_OPEN
;
194 while(archive_read_next_header(archive
, &entry
) == ARCHIVE_OK
) {
195 alpm_mountpoint_t
*mp
;
196 const char *filename
;
200 filename
= archive_entry_pathname(entry
);
201 mode
= archive_entry_mode(entry
);
203 /* libarchive reports these as zero size anyways */
204 /* NOTE: if we do start accounting for directory size, a dir matching a
205 * mountpoint needs to be attributed to the parent, not the mountpoint. */
206 if(S_ISDIR(mode
) || S_ISLNK(mode
)) {
210 /* approximate space requirements for db entries */
211 if(filename
[0] == '.') {
212 filename
= alpm_option_get_dbpath();
215 snprintf(path
, PATH_MAX
, "%s%s", handle
->root
, filename
);
217 mp
= match_mount_point(mount_points
, path
);
219 _alpm_log(PM_LOG_WARNING
,
220 _("could not determine mount point for file %s"), filename
);
224 /* the addition of (divisor - 1) performs ceil() with integer division */
226 (archive_entry_size(entry
) + mp
->fsp
.f_bsize
- 1l) / mp
->fsp
.f_bsize
;
229 if(archive_read_data_skip(archive
)) {
230 _alpm_log(PM_LOG_ERROR
, _("error while reading package %s: %s\n"),
231 pkg
->name
, archive_error_string(archive
));
232 pm_errno
= PM_ERR_LIBARCHIVE
;
237 archive_read_finish(archive
);
243 int _alpm_check_diskspace(pmtrans_t
*trans
, pmdb_t
*db_local
)
245 alpm_list_t
*mount_points
, *i
;
246 size_t replaces
= 0, current
= 0, numtargs
;
250 numtargs
= alpm_list_count(trans
->add
);
251 mount_points
= mount_point_list();
252 if(mount_points
== NULL
) {
253 _alpm_log(PM_LOG_ERROR
, _("could not determine filesystem mount points"));
257 replaces
= alpm_list_count(trans
->remove
);
259 numtargs
+= replaces
;
260 for(targ
= trans
->remove
; targ
; targ
= targ
->next
, current
++) {
262 int percent
= (current
* 100) / numtargs
;
263 PROGRESS(trans
, PM_TRANS_PROGRESS_DISKSPACE_START
, "", percent
,
266 local_pkg
= targ
->data
;
267 calculate_removed_size(mount_points
, local_pkg
);
271 for(targ
= trans
->add
; targ
; targ
= targ
->next
, current
++) {
272 pmpkg_t
*pkg
, *local_pkg
;
273 int percent
= (current
* 100) / numtargs
;
274 PROGRESS(trans
, PM_TRANS_PROGRESS_DISKSPACE_START
, "", percent
,
278 /* is this package already installed? */
279 local_pkg
= _alpm_db_get_pkgfromcache(db_local
, pkg
->name
);
281 calculate_removed_size(mount_points
, local_pkg
);
283 calculate_installed_size(mount_points
, pkg
);
285 for(i
= mount_points
; i
; i
= alpm_list_next(i
)) {
286 alpm_mountpoint_t
*data
= i
->data
;
287 if(data
->blocks_needed
> data
->max_blocks_needed
) {
288 data
->max_blocks_needed
= data
->blocks_needed
;
293 PROGRESS(trans
, PM_TRANS_PROGRESS_DISKSPACE_START
, "", 100,
296 for(i
= mount_points
; i
; i
= alpm_list_next(i
)) {
297 alpm_mountpoint_t
*data
= i
->data
;
298 if(data
->used
== 1) {
299 /* cushion is roughly min(5% capacity, 20MiB) */
300 long fivepc
= ((long)data
->fsp
.f_blocks
/ 20) + 1;
301 long twentymb
= (20 * 1024 * 1024 / (long)data
->fsp
.f_bsize
) + 1;
302 long cushion
= fivepc
< twentymb
? fivepc
: twentymb
;
304 _alpm_log(PM_LOG_DEBUG
, "partition %s, needed %ld, cushion %ld, free %ld\n",
305 data
->mount_dir
, data
->max_blocks_needed
, cushion
,
306 (unsigned long)data
->fsp
.f_bfree
);
307 if(data
->max_blocks_needed
+ cushion
>= 0 &&
308 (unsigned long)(data
->max_blocks_needed
+ cushion
) > data
->fsp
.f_bfree
) {
309 _alpm_log(PM_LOG_ERROR
, _("Partition %s too full: %ld blocks needed, %ld blocks free)\n"),
310 data
->mount_dir
, data
->max_blocks_needed
+ cushion
,
311 (unsigned long)data
->fsp
.f_bfree
);
317 for(i
= mount_points
; i
; i
= alpm_list_next(i
)) {
318 alpm_mountpoint_t
*data
= i
->data
;
319 FREE(data
->mount_dir
);
321 FREELIST(mount_points
);
324 RET_ERR(PM_ERR_DISK_SPACE
, -1);
330 /* vim: set ts=2 sw=2 noet: */