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>
43 #include "diskspace.h"
44 #include "alpm_list.h"
50 static int mount_point_cmp(const void *p1
, const void *p2
)
52 const alpm_mountpoint_t
*mp1
= p1
;
53 const alpm_mountpoint_t
*mp2
= p2
;
54 /* the negation will sort all mountpoints before their parent */
55 return -strcmp(mp1
->mount_dir
, mp2
->mount_dir
);
58 static void mount_point_list_free(alpm_list_t
*mount_points
)
62 for(i
= mount_points
; i
; i
= i
->next
) {
63 alpm_mountpoint_t
*data
= i
->data
;
64 FREE(data
->mount_dir
);
66 FREELIST(mount_points
);
69 static alpm_list_t
*mount_point_list(alpm_handle_t
*handle
)
71 alpm_list_t
*mount_points
= NULL
, *ptr
;
72 alpm_mountpoint_t
*mp
;
74 #if defined HAVE_GETMNTENT
79 fp
= setmntent(MOUNTED
, "r");
85 while((mnt
= getmntent(fp
))) {
87 _alpm_log(handle
, ALPM_LOG_WARNING
, _("could not get filesystem information\n"));
90 if(statvfs(mnt
->mnt_dir
, &fsp
) != 0) {
91 _alpm_log(handle
, ALPM_LOG_WARNING
,
92 _("could not get filesystem information for %s: %s\n"),
93 mnt
->mnt_dir
, strerror(errno
));
97 CALLOC(mp
, 1, sizeof(alpm_mountpoint_t
), RET_ERR(handle
, ALPM_ERR_MEMORY
, NULL
));
98 mp
->mount_dir
= strdup(mnt
->mnt_dir
);
99 mp
->mount_dir_len
= strlen(mp
->mount_dir
);
100 memcpy(&(mp
->fsp
), &fsp
, sizeof(struct statvfs
));
101 mp
->read_only
= fsp
.f_flag
& ST_RDONLY
;
103 mount_points
= alpm_list_add(mount_points
, mp
);
107 #elif defined HAVE_GETMNTINFO
111 entries
= getmntinfo(&fsp
, MNT_NOWAIT
);
117 for(; entries
-- > 0; fsp
++) {
118 CALLOC(mp
, 1, sizeof(alpm_mountpoint_t
), RET_ERR(handle
, ALPM_ERR_MEMORY
, NULL
));
119 mp
->mount_dir
= strdup(fsp
->f_mntonname
);
120 mp
->mount_dir_len
= strlen(mp
->mount_dir
);
121 memcpy(&(mp
->fsp
), fsp
, sizeof(FSSTATSTYPE
));
122 #if defined(HAVE_GETMNTINFO_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_FLAG)
123 mp
->read_only
= fsp
->f_flag
& ST_RDONLY
;
124 #elif defined(HAVE_GETMNTINFO_STATFS) && defined(HAVE_STRUCT_STATFS_F_FLAGS)
125 mp
->read_only
= fsp
->f_flags
& MNT_RDONLY
;
128 mount_points
= alpm_list_add(mount_points
, mp
);
132 mount_points
= alpm_list_msort(mount_points
, alpm_list_count(mount_points
),
134 for(ptr
= mount_points
; ptr
!= NULL
; ptr
= ptr
->next
) {
136 _alpm_log(handle
, ALPM_LOG_DEBUG
, "mountpoint: %s\n", mp
->mount_dir
);
141 static alpm_mountpoint_t
*match_mount_point(const alpm_list_t
*mount_points
,
142 const char *real_path
)
144 const alpm_list_t
*mp
;
146 for(mp
= mount_points
; mp
!= NULL
; mp
= mp
->next
) {
147 alpm_mountpoint_t
*data
= mp
->data
;
149 if(strncmp(data
->mount_dir
, real_path
, data
->mount_dir_len
) == 0) {
154 /* should not get here... */
158 static int calculate_removed_size(alpm_handle_t
*handle
,
159 const alpm_list_t
*mount_points
, alpm_pkg_t
*pkg
)
162 alpm_filelist_t
*filelist
= alpm_pkg_get_files(pkg
);
164 if(!filelist
->count
) {
168 for(i
= 0; i
< filelist
->count
; i
++) {
169 const alpm_file_t
*file
= filelist
->files
+ i
;
170 alpm_mountpoint_t
*mp
;
173 const char *filename
= file
->name
;
175 snprintf(path
, PATH_MAX
, "%s%s", handle
->root
, filename
);
176 _alpm_lstat(path
, &st
);
178 /* skip directories and symlinks to be consistent with libarchive that
179 * reports them to be zero size */
180 if(S_ISDIR(st
.st_mode
) || S_ISLNK(st
.st_mode
)) {
184 mp
= match_mount_point(mount_points
, path
);
186 _alpm_log(handle
, ALPM_LOG_WARNING
,
187 _("could not determine mount point for file %s\n"), filename
);
191 /* the addition of (divisor - 1) performs ceil() with integer division */
193 (st
.st_size
+ mp
->fsp
.f_bsize
- 1) / mp
->fsp
.f_bsize
;
194 mp
->used
|= USED_REMOVE
;
200 static int calculate_installed_size(alpm_handle_t
*handle
,
201 const alpm_list_t
*mount_points
, alpm_pkg_t
*pkg
)
204 alpm_filelist_t
*filelist
= alpm_pkg_get_files(pkg
);
206 if(!filelist
->count
) {
210 for(i
= 0; i
< filelist
->count
; i
++) {
211 const alpm_file_t
*file
= filelist
->files
+ i
;
212 alpm_mountpoint_t
*mp
;
214 const char *filename
= file
->name
;
216 /* libarchive reports these as zero size anyways */
217 /* NOTE: if we do start accounting for directory size, a dir matching a
218 * mountpoint needs to be attributed to the parent, not the mountpoint. */
219 if(S_ISDIR(file
->mode
) || S_ISLNK(file
->mode
)) {
223 /* approximate space requirements for db entries */
224 if(filename
[0] == '.') {
225 filename
= handle
->dbpath
;
228 snprintf(path
, PATH_MAX
, "%s%s", handle
->root
, filename
);
230 mp
= match_mount_point(mount_points
, path
);
232 _alpm_log(handle
, ALPM_LOG_WARNING
,
233 _("could not determine mount point for file %s\n"), filename
);
237 /* the addition of (divisor - 1) performs ceil() with integer division */
239 (file
->size
+ mp
->fsp
.f_bsize
- 1) / mp
->fsp
.f_bsize
;
240 mp
->used
|= USED_INSTALL
;
246 static int check_mountpoint(alpm_handle_t
*handle
, alpm_mountpoint_t
*mp
)
248 /* cushion is roughly min(5% capacity, 20MiB) */
249 fsblkcnt_t fivepc
= (mp
->fsp
.f_blocks
/ 20) + 1;
250 fsblkcnt_t twentymb
= (20 * 1024 * 1024 / mp
->fsp
.f_bsize
) + 1;
251 fsblkcnt_t cushion
= fivepc
< twentymb
? fivepc
: twentymb
;
252 blkcnt_t needed
= mp
->max_blocks_needed
+ cushion
;
254 _alpm_log(handle
, ALPM_LOG_DEBUG
,
255 "partition %s, needed %jd, cushion %ju, free %ju\n",
256 mp
->mount_dir
, (intmax_t)mp
->max_blocks_needed
,
257 (uintmax_t)cushion
, (uintmax_t)mp
->fsp
.f_bfree
);
258 if(needed
>= 0 && (fsblkcnt_t
)needed
> mp
->fsp
.f_bfree
) {
259 _alpm_log(handle
, ALPM_LOG_ERROR
,
260 _("Partition %s too full: %jd blocks needed, %jd blocks free\n"),
261 mp
->mount_dir
, (intmax_t)needed
, (uintmax_t)mp
->fsp
.f_bfree
);
267 int _alpm_check_downloadspace(alpm_handle_t
*handle
, const char *cachedir
,
268 size_t num_files
, off_t
*file_sizes
)
270 alpm_list_t
*mount_points
;
271 alpm_mountpoint_t
*cachedir_mp
;
275 mount_points
= mount_point_list(handle
);
276 if(mount_points
== NULL
) {
277 _alpm_log(handle
, ALPM_LOG_ERROR
, _("could not determine filesystem mount points\n"));
281 cachedir_mp
= match_mount_point(mount_points
, cachedir
);
282 if(cachedir
== NULL
) {
283 _alpm_log(handle
, ALPM_LOG_ERROR
, _("could not determine cachedir mount point %s\n"),
289 /* there's no need to check for a R/O mounted filesystem here, as
290 * _alpm_filecache_setup will never give us a non-writable directory */
292 /* round up the size of each file to the nearest block and accumulate */
293 for(j
= 0; j
< num_files
; j
++) {
294 cachedir_mp
->max_blocks_needed
+= (file_sizes
[j
] + cachedir_mp
->fsp
.f_bsize
+ 1) /
295 cachedir_mp
->fsp
.f_bsize
;
298 if(check_mountpoint(handle
, cachedir_mp
)) {
303 mount_point_list_free(mount_points
);
306 RET_ERR(handle
, ALPM_ERR_DISK_SPACE
, -1);
312 int _alpm_check_diskspace(alpm_handle_t
*handle
)
314 alpm_list_t
*mount_points
, *i
;
315 alpm_mountpoint_t
*root_mp
;
316 size_t replaces
= 0, current
= 0, numtargs
;
319 alpm_trans_t
*trans
= handle
->trans
;
321 numtargs
= alpm_list_count(trans
->add
);
322 mount_points
= mount_point_list(handle
);
323 if(mount_points
== NULL
) {
324 _alpm_log(handle
, ALPM_LOG_ERROR
, _("could not determine filesystem mount points\n"));
327 root_mp
= match_mount_point(mount_points
, handle
->root
);
328 if(root_mp
== NULL
) {
329 _alpm_log(handle
, ALPM_LOG_ERROR
, _("could not determine root mount point %s\n"),
335 replaces
= alpm_list_count(trans
->remove
);
337 numtargs
+= replaces
;
338 for(targ
= trans
->remove
; targ
; targ
= targ
->next
, current
++) {
339 alpm_pkg_t
*local_pkg
;
340 int percent
= (current
* 100) / numtargs
;
341 PROGRESS(handle
, ALPM_PROGRESS_DISKSPACE_START
, "", percent
,
344 local_pkg
= targ
->data
;
345 calculate_removed_size(handle
, mount_points
, local_pkg
);
349 for(targ
= trans
->add
; targ
; targ
= targ
->next
, current
++) {
350 alpm_pkg_t
*pkg
, *local_pkg
;
351 int percent
= (current
* 100) / numtargs
;
352 PROGRESS(handle
, ALPM_PROGRESS_DISKSPACE_START
, "", percent
,
356 /* is this package already installed? */
357 local_pkg
= _alpm_db_get_pkgfromcache(handle
->db_local
, pkg
->name
);
359 calculate_removed_size(handle
, mount_points
, local_pkg
);
361 calculate_installed_size(handle
, mount_points
, pkg
);
363 for(i
= mount_points
; i
; i
= i
->next
) {
364 alpm_mountpoint_t
*data
= i
->data
;
365 if(data
->blocks_needed
> data
->max_blocks_needed
) {
366 data
->max_blocks_needed
= data
->blocks_needed
;
371 PROGRESS(handle
, ALPM_PROGRESS_DISKSPACE_START
, "", 100,
374 for(i
= mount_points
; i
; i
= i
->next
) {
375 alpm_mountpoint_t
*data
= i
->data
;
376 if(data
->used
&& data
->read_only
) {
377 _alpm_log(handle
, ALPM_LOG_ERROR
, _("Partition %s is mounted read only\n"),
380 } else if(data
->used
& USED_INSTALL
&& check_mountpoint(handle
, data
)) {
386 mount_point_list_free(mount_points
);
389 RET_ERR(handle
, ALPM_ERR_DISK_SPACE
, -1);
395 /* vim: set ts=2 sw=2 noet: */