makepkg: prevent issues with files starting with a hyphen
[pacman-ng.git] / lib / libalpm / diskspace.c
blobac7dab0099474ebad7e28ef04242076e619b438b
1 /*
2 * diskspace.c
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/>.
20 #include <stdio.h>
21 #include <errno.h>
23 #if defined(HAVE_MNTENT_H)
24 #include <mntent.h>
25 #endif
26 #if defined(HAVE_SYS_MNTTAB_H)
27 #include <sys/mnttab.h>
28 #endif
29 #if defined(HAVE_SYS_STATVFS_H)
30 #include <sys/statvfs.h>
31 #endif
32 #if defined(HAVE_SYS_PARAM_H)
33 #include <sys/param.h>
34 #endif
35 #if defined(HAVE_SYS_MOUNT_H)
36 #include <sys/mount.h>
37 #endif
38 #if defined(HAVE_SYS_UCRED_H)
39 #include <sys/ucred.h>
40 #endif
41 #if defined(HAVE_SYS_TYPES_H)
42 #include <sys/types.h>
43 #endif
45 /* libalpm */
46 #include "diskspace.h"
47 #include "alpm_list.h"
48 #include "util.h"
49 #include "log.h"
50 #include "trans.h"
51 #include "handle.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)
63 alpm_list_t *i;
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)
78 /* Linux */
79 struct mntent *mnt;
80 FILE *fp;
82 fp = setmntent(MOUNTED, "r");
84 if(fp == NULL) {
85 return NULL;
88 while((mnt = getmntent(fp))) {
89 struct statvfs fsp;
90 if(!mnt) {
91 _alpm_log(handle, ALPM_LOG_WARNING,
92 _("could not get filesystem information\n"));
93 continue;
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));
99 continue;
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);
111 endmntent(fp);
112 #elif defined(HAVE_GETMNTENT) && defined(HAVE_MNTTAB_H)
113 /* Solaris, Illumos */
114 struct mnttab mnt;
115 FILE *fp;
116 int ret;
118 fp = fopen("/etc/mnttab", "r");
120 if(fp == NULL) {
121 return NULL;
124 while((ret = getmntent(fp, &mnt)) == 0) {
125 struct statvfs fsp;
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));
130 continue;
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);
141 /* -1 == EOF */
142 if(ret != -1) {
143 _alpm_log(handle, ALPM_LOG_WARNING,
144 _("could not get filesystem information\n"));
147 fclose(fp);
148 #elif defined(HAVE_GETMNTINFO)
149 /* FreeBSD (statfs), NetBSD (statvfs), OpenBSD (statfs), OS X (statfs) */
150 int entries;
151 FSSTATSTYPE *fsp;
153 entries = getmntinfo(&fsp, MNT_NOWAIT);
155 if(entries < 0) {
156 return NULL;
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;
168 #endif
170 mount_points = alpm_list_add(mount_points, mp);
172 #endif
174 mount_points = alpm_list_msort(mount_points, alpm_list_count(mount_points),
175 mount_point_cmp);
176 for(ptr = mount_points; ptr != NULL; ptr = ptr->next) {
177 mp = ptr->data;
178 _alpm_log(handle, ALPM_LOG_DEBUG, "mountpoint: %s\n", mp->mount_dir);
180 return mount_points;
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] == '/') {
198 return data;
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') {
202 return data;
208 /* should not get here... */
209 return NULL;
212 static int calculate_removed_size(alpm_handle_t *handle,
213 const alpm_list_t *mount_points, alpm_pkg_t *pkg)
215 size_t i;
216 alpm_filelist_t *filelist = alpm_pkg_get_files(pkg);
218 if(!filelist->count) {
219 return 0;
222 for(i = 0; i < filelist->count; i++) {
223 const alpm_file_t *file = filelist->files + i;
224 alpm_mountpoint_t *mp;
225 struct stat st;
226 char path[PATH_MAX];
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)) {
235 continue;
238 mp = match_mount_point(mount_points, path);
239 if(mp == NULL) {
240 _alpm_log(handle, ALPM_LOG_WARNING,
241 _("could not determine mount point for file %s\n"), filename);
242 continue;
245 /* the addition of (divisor - 1) performs ceil() with integer division */
246 mp->blocks_needed -=
247 (st.st_size + mp->fsp.f_bsize - 1) / mp->fsp.f_bsize;
248 mp->used |= USED_REMOVE;
251 return 0;
254 static int calculate_installed_size(alpm_handle_t *handle,
255 const alpm_list_t *mount_points, alpm_pkg_t *pkg)
257 size_t i;
258 alpm_filelist_t *filelist = alpm_pkg_get_files(pkg);
260 if(!filelist->count) {
261 return 0;
264 for(i = 0; i < filelist->count; i++) {
265 const alpm_file_t *file = filelist->files + i;
266 alpm_mountpoint_t *mp;
267 char path[PATH_MAX];
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)) {
274 continue;
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);
285 if(mp == NULL) {
286 _alpm_log(handle, ALPM_LOG_WARNING,
287 _("could not determine mount point for file %s\n"), filename);
288 continue;
291 /* the addition of (divisor - 1) performs ceil() with integer division */
292 mp->blocks_needed +=
293 (file->size + mp->fsp.f_bsize - 1) / mp->fsp.f_bsize;
294 mp->used |= USED_INSTALL;
297 return 0;
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);
316 return 1;
318 return 0;
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;
326 size_t j;
327 int error = 0;
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"));
332 return -1;
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"),
338 cachedir);
339 error = 1;
340 goto finish;
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)) {
353 error = 1;
356 finish:
357 mount_point_list_free(mount_points);
359 if(error) {
360 RET_ERR(handle, ALPM_ERR_DISK_SPACE, -1);
363 return 0;
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;
371 int error = 0;
372 alpm_list_t *targ;
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"));
379 return -1;
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"),
384 handle->root);
385 error = 1;
386 goto finish;
389 replaces = alpm_list_count(trans->remove);
390 if(replaces) {
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,
396 numtargs, current);
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,
407 numtargs, current);
409 pkg = targ->data;
410 /* is this package already installed? */
411 local_pkg = _alpm_db_get_pkgfromcache(handle->db_local, pkg->name);
412 if(local_pkg) {
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,
426 numtargs, current);
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"),
432 data->mount_dir);
433 error = 1;
434 } else if(data->used & USED_INSTALL && check_mountpoint(handle, data)) {
435 error = 1;
439 finish:
440 mount_point_list_free(mount_points);
442 if(error) {
443 RET_ERR(handle, ALPM_ERR_DISK_SPACE, -1);
446 return 0;
449 /* vim: set ts=2 sw=2 noet: */