libalpm
Arch Linux Package Manager Library
|
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: */