libalpm
Arch Linux Package Manager Library
diskspace.c
Go to the documentation of this file.
00001 /*
00002  *  diskspace.c
00003  *
00004  *  Copyright (c) 2010-2011 Pacman Development Team <pacman-dev@archlinux.org>
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License
00017  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00018  */
00019 
00020 #include <errno.h>
00021 #if defined(HAVE_MNTENT_H)
00022 #include <mntent.h>
00023 #endif
00024 #if defined(HAVE_SYS_STATVFS_H)
00025 #include <sys/statvfs.h>
00026 #endif
00027 #if defined(HAVE_SYS_PARAM_H)
00028 #include <sys/param.h>
00029 #endif
00030 #if defined(HAVE_SYS_MOUNT_H)
00031 #include <sys/mount.h>
00032 #endif
00033 #if defined(HAVE_SYS_UCRED_H)
00034 #include <sys/ucred.h>
00035 #endif
00036 #if defined(HAVE_SYS_TYPES_H)
00037 #include <sys/types.h>
00038 #endif
00039 
00040 /* libalpm */
00041 #include "diskspace.h"
00042 #include "alpm_list.h"
00043 #include "util.h"
00044 #include "log.h"
00045 #include "trans.h"
00046 #include "handle.h"
00047 
00048 static int mount_point_cmp(const void *p1, const void *p2)
00049 {
00050     const alpm_mountpoint_t *mp1 = p1;
00051     const alpm_mountpoint_t *mp2 = p2;
00052     /* the negation will sort all mountpoints before their parent */
00053     return -strcmp(mp1->mount_dir, mp2->mount_dir);
00054 }
00055 
00056 static void mount_point_list_free(alpm_list_t *mount_points)
00057 {
00058     alpm_list_t *i;
00059 
00060     for(i = mount_points; i; i = i->next) {
00061         alpm_mountpoint_t *data = i->data;
00062         FREE(data->mount_dir);
00063     }
00064     FREELIST(mount_points);
00065 }
00066 
00067 static alpm_list_t *mount_point_list(alpm_handle_t *handle)
00068 {
00069     alpm_list_t *mount_points = NULL, *ptr;
00070     alpm_mountpoint_t *mp;
00071 
00072 #if defined HAVE_GETMNTENT
00073     struct mntent *mnt;
00074     FILE *fp;
00075     struct statvfs fsp;
00076 
00077     fp = setmntent(MOUNTED, "r");
00078 
00079     if(fp == NULL) {
00080         return NULL;
00081     }
00082 
00083     while((mnt = getmntent(fp))) {
00084         if(!mnt) {
00085             _alpm_log(handle, ALPM_LOG_WARNING, _("could not get filesystem information\n"));
00086             continue;
00087         }
00088         if(statvfs(mnt->mnt_dir, &fsp) != 0) {
00089             _alpm_log(handle, ALPM_LOG_WARNING,
00090                     _("could not get filesystem information for %s: %s\n"),
00091                     mnt->mnt_dir, strerror(errno));
00092             continue;
00093         }
00094 
00095         CALLOC(mp, 1, sizeof(alpm_mountpoint_t), RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
00096         mp->mount_dir = strdup(mnt->mnt_dir);
00097         mp->mount_dir_len = strlen(mp->mount_dir);
00098         memcpy(&(mp->fsp), &fsp, sizeof(struct statvfs));
00099         mp->read_only = fsp.f_flag & ST_RDONLY;
00100 
00101         mount_points = alpm_list_add(mount_points, mp);
00102     }
00103 
00104     endmntent(fp);
00105 #elif defined HAVE_GETMNTINFO
00106     int entries;
00107     FSSTATSTYPE *fsp;
00108 
00109     entries = getmntinfo(&fsp, MNT_NOWAIT);
00110 
00111     if(entries < 0) {
00112         return NULL;
00113     }
00114 
00115     for(; entries-- > 0; fsp++) {
00116         CALLOC(mp, 1, sizeof(alpm_mountpoint_t), RET_ERR(handle, ALPM_ERR_MEMORY, NULL));
00117         mp->mount_dir = strdup(fsp->f_mntonname);
00118         mp->mount_dir_len = strlen(mp->mount_dir);
00119         memcpy(&(mp->fsp), fsp, sizeof(FSSTATSTYPE));
00120 #if defined(HAVE_GETMNTINFO_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_FLAG)
00121         mp->read_only = fsp->f_flag & ST_RDONLY;
00122 #elif defined(HAVE_GETMNTINFO_STATFS) && defined(HAVE_STRUCT_STATFS_F_FLAGS)
00123         mp->read_only = fsp->f_flags & MNT_RDONLY;
00124 #endif
00125 
00126         mount_points = alpm_list_add(mount_points, mp);
00127     }
00128 #endif
00129 
00130     mount_points = alpm_list_msort(mount_points, alpm_list_count(mount_points),
00131             mount_point_cmp);
00132     for(ptr = mount_points; ptr != NULL; ptr = ptr->next) {
00133         mp = ptr->data;
00134         _alpm_log(handle, ALPM_LOG_DEBUG, "mountpoint: %s\n", mp->mount_dir);
00135     }
00136     return mount_points;
00137 }
00138 
00139 static alpm_mountpoint_t *match_mount_point(const alpm_list_t *mount_points,
00140         const char *real_path)
00141 {
00142     const alpm_list_t *mp;
00143 
00144     for(mp = mount_points; mp != NULL; mp = mp->next) {
00145         alpm_mountpoint_t *data = mp->data;
00146 
00147         if(strncmp(data->mount_dir, real_path, data->mount_dir_len) == 0) {
00148             return data;
00149         }
00150     }
00151 
00152     /* should not get here... */
00153     return NULL;
00154 }
00155 
00156 static int calculate_removed_size(alpm_handle_t *handle,
00157         const alpm_list_t *mount_points, alpm_pkg_t *pkg)
00158 {
00159     size_t i;
00160     alpm_filelist_t *filelist = alpm_pkg_get_files(pkg);
00161 
00162     if(!filelist->count) {
00163         return 0;
00164     }
00165 
00166     for(i = 0; i < filelist->count; i++) {
00167         const alpm_file_t *file = filelist->files + i;
00168         alpm_mountpoint_t *mp;
00169         struct stat st;
00170         char path[PATH_MAX];
00171         const char *filename = file->name;
00172 
00173         snprintf(path, PATH_MAX, "%s%s", handle->root, filename);
00174         _alpm_lstat(path, &st);
00175 
00176         /* skip directories and symlinks to be consistent with libarchive that
00177          * reports them to be zero size */
00178         if(S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) {
00179             continue;
00180         }
00181 
00182         mp = match_mount_point(mount_points, path);
00183         if(mp == NULL) {
00184             _alpm_log(handle, ALPM_LOG_WARNING,
00185                     _("could not determine mount point for file %s\n"), filename);
00186             continue;
00187         }
00188 
00189         /* the addition of (divisor - 1) performs ceil() with integer division */
00190         mp->blocks_needed -=
00191             (st.st_size + mp->fsp.f_bsize - 1) / mp->fsp.f_bsize;
00192         mp->used |= USED_REMOVE;
00193     }
00194 
00195     return 0;
00196 }
00197 
00198 static int calculate_installed_size(alpm_handle_t *handle,
00199         const alpm_list_t *mount_points, alpm_pkg_t *pkg)
00200 {
00201     size_t i;
00202     alpm_filelist_t *filelist = alpm_pkg_get_files(pkg);
00203 
00204     if(!filelist->count) {
00205         return 0;
00206     }
00207 
00208     for(i = 0; i < filelist->count; i++) {
00209         const alpm_file_t *file = filelist->files + i;
00210         alpm_mountpoint_t *mp;
00211         char path[PATH_MAX];
00212         const char *filename = file->name;
00213 
00214         /* libarchive reports these as zero size anyways */
00215         /* NOTE: if we do start accounting for directory size, a dir matching a
00216          * mountpoint needs to be attributed to the parent, not the mountpoint. */
00217         if(S_ISDIR(file->mode) || S_ISLNK(file->mode)) {
00218             continue;
00219         }
00220 
00221         /* approximate space requirements for db entries */
00222         if(filename[0] == '.') {
00223             filename = handle->dbpath;
00224         }
00225 
00226         snprintf(path, PATH_MAX, "%s%s", handle->root, filename);
00227 
00228         mp = match_mount_point(mount_points, path);
00229         if(mp == NULL) {
00230             _alpm_log(handle, ALPM_LOG_WARNING,
00231                     _("could not determine mount point for file %s\n"), filename);
00232             continue;
00233         }
00234 
00235         /* the addition of (divisor - 1) performs ceil() with integer division */
00236         mp->blocks_needed +=
00237             (file->size + mp->fsp.f_bsize - 1) / mp->fsp.f_bsize;
00238         mp->used |= USED_INSTALL;
00239     }
00240 
00241     return 0;
00242 }
00243 
00244 static int check_mountpoint(alpm_handle_t *handle, alpm_mountpoint_t *mp)
00245 {
00246     /* cushion is roughly min(5% capacity, 20MiB) */
00247     fsblkcnt_t fivepc = (mp->fsp.f_blocks / 20) + 1;
00248     fsblkcnt_t twentymb = (20 * 1024 * 1024 / mp->fsp.f_bsize) + 1;
00249     fsblkcnt_t cushion = fivepc < twentymb ? fivepc : twentymb;
00250     blkcnt_t needed = mp->max_blocks_needed + cushion;
00251 
00252     _alpm_log(handle, ALPM_LOG_DEBUG,
00253             "partition %s, needed %jd, cushion %ju, free %ju\n",
00254             mp->mount_dir, (intmax_t)mp->max_blocks_needed,
00255             (uintmax_t)cushion, (uintmax_t)mp->fsp.f_bfree);
00256     if(needed >= 0 && (fsblkcnt_t)needed > mp->fsp.f_bfree) {
00257         _alpm_log(handle, ALPM_LOG_ERROR,
00258                 _("Partition %s too full: %jd blocks needed, %jd blocks free\n"),
00259                 mp->mount_dir, (intmax_t)needed, (uintmax_t)mp->fsp.f_bfree);
00260         return 1;
00261     }
00262     return 0;
00263 }
00264 
00265 int _alpm_check_downloadspace(alpm_handle_t *handle, const char *cachedir,
00266         size_t num_files, off_t *file_sizes)
00267 {
00268     alpm_list_t *mount_points;
00269     alpm_mountpoint_t *cachedir_mp;
00270     size_t j;
00271     int error = 0;
00272 
00273     mount_points = mount_point_list(handle);
00274     if(mount_points == NULL) {
00275         _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine filesystem mount points\n"));
00276         return -1;
00277     }
00278 
00279     cachedir_mp = match_mount_point(mount_points, cachedir);
00280     if(cachedir == NULL) {
00281         _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine cachedir mount point %s\n"),
00282                 cachedir);
00283         error = 1;
00284         goto finish;
00285     }
00286 
00287     /* there's no need to check for a R/O mounted filesystem here, as
00288      * _alpm_filecache_setup will never give us a non-writable directory */
00289 
00290     /* round up the size of each file to the nearest block and accumulate */
00291     for(j = 0; j < num_files; j++) {
00292         cachedir_mp->max_blocks_needed += (file_sizes[j] + cachedir_mp->fsp.f_bsize + 1) /
00293             cachedir_mp->fsp.f_bsize;
00294     }
00295 
00296     if(check_mountpoint(handle, cachedir_mp)) {
00297         error = 1;
00298     }
00299 
00300 finish:
00301     mount_point_list_free(mount_points);
00302 
00303     if(error) {
00304         RET_ERR(handle, ALPM_ERR_DISK_SPACE, -1);
00305     }
00306 
00307     return 0;
00308 }
00309 
00310 int _alpm_check_diskspace(alpm_handle_t *handle)
00311 {
00312     alpm_list_t *mount_points, *i;
00313     alpm_mountpoint_t *root_mp;
00314     size_t replaces = 0, current = 0, numtargs;
00315     int error = 0;
00316     alpm_list_t *targ;
00317     alpm_trans_t *trans = handle->trans;
00318 
00319     numtargs = alpm_list_count(trans->add);
00320     mount_points = mount_point_list(handle);
00321     if(mount_points == NULL) {
00322         _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine filesystem mount points\n"));
00323         return -1;
00324     }
00325     root_mp = match_mount_point(mount_points, handle->root);
00326     if(root_mp == NULL) {
00327         _alpm_log(handle, ALPM_LOG_ERROR, _("could not determine root mount point %s\n"),
00328                 handle->root);
00329         error = 1;
00330         goto finish;
00331     }
00332 
00333     replaces = alpm_list_count(trans->remove);
00334     if(replaces) {
00335         numtargs += replaces;
00336         for(targ = trans->remove; targ; targ = targ->next, current++) {
00337             alpm_pkg_t *local_pkg;
00338             int percent = (current * 100) / numtargs;
00339             PROGRESS(handle, ALPM_PROGRESS_DISKSPACE_START, "", percent,
00340                     numtargs, current);
00341 
00342             local_pkg = targ->data;
00343             calculate_removed_size(handle, mount_points, local_pkg);
00344         }
00345     }
00346 
00347     for(targ = trans->add; targ; targ = targ->next, current++) {
00348         alpm_pkg_t *pkg, *local_pkg;
00349         int percent = (current * 100) / numtargs;
00350         PROGRESS(handle, ALPM_PROGRESS_DISKSPACE_START, "", percent,
00351                 numtargs, current);
00352 
00353         pkg = targ->data;
00354         /* is this package already installed? */
00355         local_pkg = _alpm_db_get_pkgfromcache(handle->db_local, pkg->name);
00356         if(local_pkg) {
00357             calculate_removed_size(handle, mount_points, local_pkg);
00358         }
00359         calculate_installed_size(handle, mount_points, pkg);
00360 
00361         for(i = mount_points; i; i = i->next) {
00362             alpm_mountpoint_t *data = i->data;
00363             if(data->blocks_needed > data->max_blocks_needed) {
00364                 data->max_blocks_needed = data->blocks_needed;
00365             }
00366         }
00367     }
00368 
00369     PROGRESS(handle, ALPM_PROGRESS_DISKSPACE_START, "", 100,
00370             numtargs, current);
00371 
00372     for(i = mount_points; i; i = i->next) {
00373         alpm_mountpoint_t *data = i->data;
00374         if(data->used && data->read_only) {
00375             _alpm_log(handle, ALPM_LOG_ERROR, _("Partition %s is mounted read only\n"),
00376                     data->mount_dir);
00377             error = 1;
00378         } else if(data->used & USED_INSTALL && check_mountpoint(handle, data)) {
00379             error = 1;
00380         }
00381     }
00382 
00383 finish:
00384     mount_point_list_free(mount_points);
00385 
00386     if(error) {
00387         RET_ERR(handle, ALPM_ERR_DISK_SPACE, -1);
00388     }
00389 
00390     return 0;
00391 }
00392 
00393 /* vim: set ts=2 sw=2 noet: */