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/>.
23 #if defined(HAVE_MNTENT_H)
26 #if defined(HAVE_SYS_STATVFS_H)
27 #include <sys/statvfs.h>
29 #if defined(HAVE_SYS_PARAM_H)
30 #include <sys/param.h>
32 #if defined(HAVE_SYS_MOUNT_H)
33 #include <sys/mount.h>
35 #if defined(HAVE_SYS_UCRED_H)
36 #include <sys/ucred.h>
38 #if defined(HAVE_SYS_TYPES_H)
39 #include <sys/types.h>
44 #include <archive_entry.h>
47 #include "diskspace.h"
48 #include "alpm_list.h"
54 static int mount_point_cmp(const void *p1
, const void *p2
)
56 const alpm_mountpoint_t
*mp1
= p1
;
57 const alpm_mountpoint_t
*mp2
= p2
;
58 /* the negation will sort all mountpoints before their parent */
59 return -strcmp(mp1
->mount_dir
, mp2
->mount_dir
);
62 static alpm_list_t
*mount_point_list(void)
64 alpm_list_t
*mount_points
= NULL
, *ptr
;
65 alpm_mountpoint_t
*mp
;
67 #if defined HAVE_GETMNTENT
72 fp
= setmntent(MOUNTED
, "r");
78 while((mnt
= getmntent(fp
))) {
80 _alpm_log(PM_LOG_WARNING
, _("could not get filesystem information\n"));
83 if(statvfs(mnt
->mnt_dir
, &fsp
) != 0) {
84 _alpm_log(PM_LOG_WARNING
,
85 _("could not get filesystem information for %s: %s\n"),
86 mnt
->mnt_dir
, strerror(errno
));
90 CALLOC(mp
, 1, sizeof(alpm_mountpoint_t
), RET_ERR(PM_ERR_MEMORY
, NULL
));
91 mp
->mount_dir
= strdup(mnt
->mnt_dir
);
92 mp
->mount_dir_len
= strlen(mp
->mount_dir
);
93 memcpy(&(mp
->fsp
), &fsp
, sizeof(struct statvfs
));
94 mp
->read_only
= fsp
.f_flag
& ST_RDONLY
;
96 mount_points
= alpm_list_add(mount_points
, mp
);
100 #elif defined HAVE_GETMNTINFO
104 entries
= getmntinfo(&fsp
, MNT_NOWAIT
);
110 for(; entries
-- > 0; fsp
++) {
111 CALLOC(mp
, 1, sizeof(alpm_mountpoint_t
), RET_ERR(PM_ERR_MEMORY
, NULL
));
112 mp
->mount_dir
= strdup(fsp
->f_mntonname
);
113 mp
->mount_dir_len
= strlen(mp
->mount_dir
);
114 memcpy(&(mp
->fsp
), fsp
, sizeof(FSSTATSTYPE
));
115 #if defined(HAVE_GETMNTINFO_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_FLAG)
116 mp
->read_only
= fsp
->f_flag
& ST_RDONLY
;
117 #elif defined(HAVE_GETMNTINFO_STATFS) && defined(HAVE_STRUCT_STATFS_F_FLAGS)
118 mp
->read_only
= fsp
->f_flags
& MNT_RDONLY
;
121 mount_points
= alpm_list_add(mount_points
, mp
);
125 mount_points
= alpm_list_msort(mount_points
, alpm_list_count(mount_points
),
127 for(ptr
= mount_points
; ptr
!= NULL
; ptr
= ptr
->next
) {
129 _alpm_log(PM_LOG_DEBUG
, "mountpoint: %s\n", mp
->mount_dir
);
134 static alpm_mountpoint_t
*match_mount_point(const alpm_list_t
*mount_points
,
135 const char *real_path
)
137 const alpm_list_t
*mp
;
139 for(mp
= mount_points
; mp
!= NULL
; mp
= mp
->next
) {
140 alpm_mountpoint_t
*data
= mp
->data
;
142 if(strncmp(data
->mount_dir
, real_path
, data
->mount_dir_len
) == 0) {
147 /* should not get here... */
151 static int calculate_removed_size(const alpm_list_t
*mount_points
,
156 alpm_list_t
*files
= alpm_pkg_get_files(pkg
);
157 for(file
= files
; file
; file
= file
->next
) {
158 alpm_mountpoint_t
*mp
;
161 const char *filename
= file
->data
;
163 snprintf(path
, PATH_MAX
, "%s%s", handle
->root
, filename
);
164 _alpm_lstat(path
, &st
);
166 /* skip directories and symlinks to be consistent with libarchive that
167 * reports them to be zero size */
168 if(S_ISDIR(st
.st_mode
) || S_ISLNK(st
.st_mode
)) {
172 mp
= match_mount_point(mount_points
, path
);
174 _alpm_log(PM_LOG_WARNING
,
175 _("could not determine mount point for file %s\n"), filename
);
179 /* the addition of (divisor - 1) performs ceil() with integer division */
181 (st
.st_size
+ mp
->fsp
.f_bsize
- 1l) / mp
->fsp
.f_bsize
;
182 mp
->used
|= USED_REMOVE
;
188 static int calculate_installed_size(const alpm_list_t
*mount_points
,
192 struct archive
*archive
;
193 struct archive_entry
*entry
;
195 if((archive
= archive_read_new()) == NULL
) {
196 pm_errno
= PM_ERR_LIBARCHIVE
;
201 archive_read_support_compression_all(archive
);
202 archive_read_support_format_all(archive
);
204 if(archive_read_open_filename(archive
, pkg
->origin_data
.file
,
205 ARCHIVE_DEFAULT_BYTES_PER_BLOCK
) != ARCHIVE_OK
) {
206 pm_errno
= PM_ERR_PKG_OPEN
;
211 while(archive_read_next_header(archive
, &entry
) == ARCHIVE_OK
) {
212 alpm_mountpoint_t
*mp
;
213 const char *filename
;
217 filename
= archive_entry_pathname(entry
);
218 mode
= archive_entry_mode(entry
);
220 /* libarchive reports these as zero size anyways */
221 /* NOTE: if we do start accounting for directory size, a dir matching a
222 * mountpoint needs to be attributed to the parent, not the mountpoint. */
223 if(S_ISDIR(mode
) || S_ISLNK(mode
)) {
227 /* approximate space requirements for db entries */
228 if(filename
[0] == '.') {
229 filename
= alpm_option_get_dbpath();
232 snprintf(path
, PATH_MAX
, "%s%s", handle
->root
, filename
);
234 mp
= match_mount_point(mount_points
, path
);
236 _alpm_log(PM_LOG_WARNING
,
237 _("could not determine mount point for file %s\n"), filename
);
241 /* the addition of (divisor - 1) performs ceil() with integer division */
243 (archive_entry_size(entry
) + mp
->fsp
.f_bsize
- 1l) / mp
->fsp
.f_bsize
;
244 mp
->used
|= USED_INSTALL
;
246 if(archive_read_data_skip(archive
)) {
247 _alpm_log(PM_LOG_ERROR
, _("error while reading package %s: %s\n"),
248 pkg
->name
, archive_error_string(archive
));
249 pm_errno
= PM_ERR_LIBARCHIVE
;
254 archive_read_finish(archive
);
260 int _alpm_check_diskspace(pmtrans_t
*trans
, pmdb_t
*db_local
)
262 alpm_list_t
*mount_points
, *i
;
263 alpm_mountpoint_t
*root_mp
;
264 size_t replaces
= 0, current
= 0, numtargs
;
268 numtargs
= alpm_list_count(trans
->add
);
269 mount_points
= mount_point_list();
270 if(mount_points
== NULL
) {
271 _alpm_log(PM_LOG_ERROR
, _("could not determine filesystem mount points\n"));
274 root_mp
= match_mount_point(mount_points
, handle
->root
);
275 if(root_mp
== NULL
) {
276 _alpm_log(PM_LOG_ERROR
, _("could not determine root mount point %s\n"),
281 replaces
= alpm_list_count(trans
->remove
);
283 numtargs
+= replaces
;
284 for(targ
= trans
->remove
; targ
; targ
= targ
->next
, current
++) {
286 int percent
= (current
* 100) / numtargs
;
287 PROGRESS(trans
, PM_TRANS_PROGRESS_DISKSPACE_START
, "", percent
,
290 local_pkg
= targ
->data
;
291 calculate_removed_size(mount_points
, local_pkg
);
295 for(targ
= trans
->add
; targ
; targ
= targ
->next
, current
++) {
296 pmpkg_t
*pkg
, *local_pkg
;
297 int percent
= (current
* 100) / numtargs
;
298 PROGRESS(trans
, PM_TRANS_PROGRESS_DISKSPACE_START
, "", percent
,
302 /* is this package already installed? */
303 local_pkg
= _alpm_db_get_pkgfromcache(db_local
, pkg
->name
);
305 calculate_removed_size(mount_points
, local_pkg
);
307 calculate_installed_size(mount_points
, pkg
);
309 for(i
= mount_points
; i
; i
= alpm_list_next(i
)) {
310 alpm_mountpoint_t
*data
= i
->data
;
311 if(data
->blocks_needed
> data
->max_blocks_needed
) {
312 data
->max_blocks_needed
= data
->blocks_needed
;
317 PROGRESS(trans
, PM_TRANS_PROGRESS_DISKSPACE_START
, "", 100,
320 for(i
= mount_points
; i
; i
= alpm_list_next(i
)) {
321 alpm_mountpoint_t
*data
= i
->data
;
322 if(data
->used
&& data
->read_only
) {
323 _alpm_log(PM_LOG_ERROR
, _("Partition %s is mounted read only\n"),
326 } else if(data
->used
& USED_INSTALL
) {
327 /* cushion is roughly min(5% capacity, 20MiB) */
328 long fivepc
= ((long)data
->fsp
.f_blocks
/ 20) + 1;
329 long twentymb
= (20 * 1024 * 1024 / (long)data
->fsp
.f_bsize
) + 1;
330 long cushion
= fivepc
< twentymb
? fivepc
: twentymb
;
332 _alpm_log(PM_LOG_DEBUG
, "partition %s, needed %ld, cushion %ld, free %ld\n",
333 data
->mount_dir
, data
->max_blocks_needed
, cushion
,
334 (unsigned long)data
->fsp
.f_bfree
);
335 if(data
->max_blocks_needed
+ cushion
>= 0 &&
336 (unsigned long)(data
->max_blocks_needed
+ cushion
) > data
->fsp
.f_bfree
) {
337 _alpm_log(PM_LOG_ERROR
, _("Partition %s too full: %ld blocks needed, %ld blocks free\n"),
338 data
->mount_dir
, data
->max_blocks_needed
+ cushion
,
339 (unsigned long)data
->fsp
.f_bfree
);
345 for(i
= mount_points
; i
; i
= alpm_list_next(i
)) {
346 alpm_mountpoint_t
*data
= i
->data
;
347 FREE(data
->mount_dir
);
349 FREELIST(mount_points
);
352 RET_ERR(PM_ERR_DISK_SPACE
, -1);
358 /* vim: set ts=2 sw=2 noet: */