2012-08-26 04:41:25 -08:00
|
|
|
/* ncdu - NCurses Disk Usage
|
|
|
|
|
|
2016-08-24 10:49:20 -08:00
|
|
|
Copyright (c) 2007-2016 Yoran Heling
|
2012-08-26 04:41:25 -08:00
|
|
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining
|
|
|
|
|
a copy of this software and associated documentation files (the
|
|
|
|
|
"Software"), to deal in the Software without restriction, including
|
|
|
|
|
without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
|
distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
|
permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
|
the following conditions:
|
|
|
|
|
|
|
|
|
|
The above copyright notice and this permission notice shall be included
|
|
|
|
|
in all copies or substantial portions of the Software.
|
|
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
|
|
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
|
|
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
|
|
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
|
|
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "global.h"
|
|
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <dirent.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* set S_BLKSIZE if not defined already in sys/stat.h */
|
|
|
|
|
#ifndef S_BLKSIZE
|
|
|
|
|
# define S_BLKSIZE 512
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int dir_scan_smfs; /* Stay on the same filesystem */
|
|
|
|
|
|
2012-08-27 11:10:07 -08:00
|
|
|
static uint64_t curdev; /* current device we're scanning on */
|
2012-08-26 04:41:25 -08:00
|
|
|
|
2018-01-23 03:17:06 -09:00
|
|
|
/* scratch space */
|
|
|
|
|
static struct dir *buf_dir;
|
|
|
|
|
static struct dir_ext buf_ext[1];
|
2012-08-26 04:41:25 -08:00
|
|
|
|
2018-01-23 03:17:06 -09:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Populates the buf_dir and buf_ext with information from the stat struct.
|
|
|
|
|
* Sets everything necessary for output_dir.item() except FF_ERR and FF_EXL. */
|
|
|
|
|
static void stat_to_dir(struct stat *fs) {
|
|
|
|
|
buf_dir->flags |= FF_EXT; /* We always read extended data because it doesn't have an additional cost */
|
|
|
|
|
buf_dir->ino = (uint64_t)fs->st_ino;
|
|
|
|
|
buf_dir->dev = (uint64_t)fs->st_dev;
|
2012-08-26 04:41:25 -08:00
|
|
|
|
|
|
|
|
if(S_ISREG(fs->st_mode))
|
2018-01-23 03:17:06 -09:00
|
|
|
buf_dir->flags |= FF_FILE;
|
2012-08-26 04:41:25 -08:00
|
|
|
else if(S_ISDIR(fs->st_mode))
|
2018-01-23 03:17:06 -09:00
|
|
|
buf_dir->flags |= FF_DIR;
|
2012-08-26 04:41:25 -08:00
|
|
|
|
|
|
|
|
if(!S_ISDIR(fs->st_mode) && fs->st_nlink > 1)
|
2018-01-23 03:17:06 -09:00
|
|
|
buf_dir->flags |= FF_HLNKC;
|
2012-08-26 04:41:25 -08:00
|
|
|
|
2018-01-23 03:17:06 -09:00
|
|
|
if(dir_scan_smfs && curdev != buf_dir->dev)
|
|
|
|
|
buf_dir->flags |= FF_OTHFS;
|
2012-08-26 04:41:25 -08:00
|
|
|
|
2018-01-23 03:17:06 -09:00
|
|
|
if(!(buf_dir->flags & (FF_OTHFS|FF_EXL))) {
|
|
|
|
|
buf_dir->size = fs->st_blocks * S_BLKSIZE;
|
|
|
|
|
buf_dir->asize = fs->st_size;
|
2012-08-26 04:41:25 -08:00
|
|
|
}
|
2018-01-23 03:17:06 -09:00
|
|
|
|
|
|
|
|
buf_ext->mode = fs->st_mode;
|
|
|
|
|
buf_ext->mtime = fs->st_mtim;
|
|
|
|
|
buf_ext->uid = (int)fs->st_uid;
|
|
|
|
|
buf_ext->gid = (int)fs->st_gid;
|
2012-08-26 04:41:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Reads all filenames in the currently chdir'ed directory and stores it as a
|
|
|
|
|
* nul-separated list of filenames. The list ends with an empty filename (i.e.
|
|
|
|
|
* two nuls). . and .. are not included. Returned memory should be freed. *err
|
|
|
|
|
* is set to 1 if some error occured. Returns NULL if that error was fatal.
|
|
|
|
|
* The reason for reading everything in memory first and then walking through
|
|
|
|
|
* the list is to avoid eating too many file descriptors in a deeply recursive
|
|
|
|
|
* directory. */
|
|
|
|
|
static char *dir_read(int *err) {
|
|
|
|
|
DIR *dir;
|
|
|
|
|
struct dirent *item;
|
|
|
|
|
char *buf = NULL;
|
|
|
|
|
int buflen = 512;
|
|
|
|
|
int off = 0;
|
|
|
|
|
|
|
|
|
|
if((dir = opendir(".")) == NULL) {
|
|
|
|
|
*err = 1;
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buf = malloc(buflen);
|
|
|
|
|
errno = 0;
|
|
|
|
|
|
|
|
|
|
while((item = readdir(dir)) != NULL) {
|
|
|
|
|
if(item->d_name[0] == '.' && (item->d_name[1] == 0 || (item->d_name[1] == '.' && item->d_name[2] == 0)))
|
|
|
|
|
continue;
|
|
|
|
|
int req = off+3+strlen(item->d_name);
|
|
|
|
|
if(req > buflen) {
|
|
|
|
|
buflen = req < buflen*2 ? buflen*2 : req;
|
|
|
|
|
buf = realloc(buf, buflen);
|
|
|
|
|
}
|
|
|
|
|
strcpy(buf+off, item->d_name);
|
|
|
|
|
off += strlen(item->d_name)+1;
|
|
|
|
|
}
|
|
|
|
|
if(errno)
|
|
|
|
|
*err = 1;
|
|
|
|
|
if(closedir(dir) < 0)
|
|
|
|
|
*err = 1;
|
|
|
|
|
|
|
|
|
|
buf[off] = 0;
|
|
|
|
|
buf[off+1] = 0;
|
|
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int dir_walk(char *);
|
|
|
|
|
|
|
|
|
|
|
2018-01-23 03:17:06 -09:00
|
|
|
/* Tries to recurse into the current directory item (buf_dir is assumed to be the current dir) */
|
|
|
|
|
static int dir_scan_recurse(const char *name) {
|
2012-08-26 04:41:25 -08:00
|
|
|
int fail = 0;
|
|
|
|
|
char *dir;
|
|
|
|
|
|
2018-01-23 03:17:06 -09:00
|
|
|
if(chdir(name)) {
|
2012-08-26 04:41:25 -08:00
|
|
|
dir_setlasterr(dir_curpath);
|
2018-01-23 03:17:06 -09:00
|
|
|
buf_dir->flags |= FF_ERR;
|
|
|
|
|
if(dir_output.item(buf_dir, name, buf_ext) || dir_output.item(NULL, 0, NULL)) {
|
2012-08-29 00:27:12 -08:00
|
|
|
dir_seterr("Output error: %s", strerror(errno));
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2012-08-26 04:41:25 -08:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if((dir = dir_read(&fail)) == NULL) {
|
|
|
|
|
dir_setlasterr(dir_curpath);
|
2018-01-23 03:17:06 -09:00
|
|
|
buf_dir->flags |= FF_ERR;
|
|
|
|
|
if(dir_output.item(buf_dir, name, buf_ext) || dir_output.item(NULL, 0, NULL)) {
|
2012-08-29 00:27:12 -08:00
|
|
|
dir_seterr("Output error: %s", strerror(errno));
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2012-08-26 06:53:37 -08:00
|
|
|
if(chdir("..")) {
|
|
|
|
|
dir_seterr("Error going back to parent directory: %s", strerror(errno));
|
|
|
|
|
return 1;
|
|
|
|
|
} else
|
|
|
|
|
return 0;
|
2012-08-26 04:41:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* readdir() failed halfway, not fatal. */
|
|
|
|
|
if(fail)
|
2018-01-23 03:17:06 -09:00
|
|
|
buf_dir->flags |= FF_ERR;
|
2012-08-26 04:41:25 -08:00
|
|
|
|
2018-01-23 03:17:06 -09:00
|
|
|
if(dir_output.item(buf_dir, name, buf_ext)) {
|
2012-08-29 00:27:12 -08:00
|
|
|
dir_seterr("Output error: %s", strerror(errno));
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2012-08-26 04:41:25 -08:00
|
|
|
fail = dir_walk(dir);
|
2018-01-23 03:17:06 -09:00
|
|
|
if(dir_output.item(NULL, 0, NULL)) {
|
2012-08-29 00:27:12 -08:00
|
|
|
dir_seterr("Output error: %s", strerror(errno));
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2012-08-26 04:41:25 -08:00
|
|
|
|
|
|
|
|
/* Not being able to chdir back is fatal */
|
2012-08-26 06:53:37 -08:00
|
|
|
if(!fail && chdir("..")) {
|
|
|
|
|
dir_seterr("Error going back to parent directory: %s", strerror(errno));
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2012-08-26 04:41:25 -08:00
|
|
|
|
|
|
|
|
return fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Scans and adds a single item. Recurses into dir_walk() again if this is a
|
|
|
|
|
* directory. Assumes we're chdir'ed in the directory in which this item
|
2018-01-23 03:17:06 -09:00
|
|
|
* resides. */
|
|
|
|
|
static int dir_scan_item(const char *name) {
|
2012-08-26 04:41:25 -08:00
|
|
|
struct stat st;
|
|
|
|
|
int fail = 0;
|
|
|
|
|
|
|
|
|
|
#ifdef __CYGWIN__
|
|
|
|
|
/* /proc/registry names may contain slashes */
|
2018-01-23 03:17:06 -09:00
|
|
|
if(strchr(name, '/') || strchr(name, '\\')) {
|
|
|
|
|
buf_dir->flags |= FF_ERR;
|
2012-08-26 04:41:25 -08:00
|
|
|
dir_setlasterr(dir_curpath);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if(exclude_match(dir_curpath))
|
2018-01-23 03:17:06 -09:00
|
|
|
buf_dir->flags |= FF_EXL;
|
2012-08-26 04:41:25 -08:00
|
|
|
|
2018-01-23 03:17:06 -09:00
|
|
|
if(!(buf_dir->flags & (FF_ERR|FF_EXL)) && lstat(name, &st)) {
|
|
|
|
|
buf_dir->flags |= FF_ERR;
|
2012-08-26 04:41:25 -08:00
|
|
|
dir_setlasterr(dir_curpath);
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-23 03:17:06 -09:00
|
|
|
if(!(buf_dir->flags & (FF_ERR|FF_EXL)))
|
|
|
|
|
stat_to_dir(&st);
|
2012-08-26 04:41:25 -08:00
|
|
|
|
2018-01-23 03:17:06 -09:00
|
|
|
if(cachedir_tags && (buf_dir->flags & FF_DIR) && !(buf_dir->flags & (FF_ERR|FF_EXL|FF_OTHFS)))
|
|
|
|
|
if(has_cachedir_tag(buf_dir->name)) {
|
|
|
|
|
buf_dir->flags |= FF_EXL;
|
|
|
|
|
buf_dir->size = buf_dir->asize = 0;
|
2013-04-12 09:43:01 -08:00
|
|
|
}
|
2013-04-10 06:41:26 -08:00
|
|
|
|
2012-08-26 04:41:25 -08:00
|
|
|
/* Recurse into the dir or output the item */
|
2018-01-23 03:17:06 -09:00
|
|
|
if(buf_dir->flags & FF_DIR && !(buf_dir->flags & (FF_ERR|FF_EXL|FF_OTHFS)))
|
|
|
|
|
fail = dir_scan_recurse(name);
|
|
|
|
|
else if(buf_dir->flags & FF_DIR) {
|
|
|
|
|
if(dir_output.item(buf_dir, name, buf_ext) || dir_output.item(NULL, 0, NULL)) {
|
2012-08-29 00:27:12 -08:00
|
|
|
dir_seterr("Output error: %s", strerror(errno));
|
|
|
|
|
fail = 1;
|
|
|
|
|
}
|
2018-01-23 03:17:06 -09:00
|
|
|
} else if(dir_output.item(buf_dir, name, buf_ext)) {
|
2012-08-29 00:27:12 -08:00
|
|
|
dir_seterr("Output error: %s", strerror(errno));
|
|
|
|
|
fail = 1;
|
|
|
|
|
}
|
2012-08-26 04:41:25 -08:00
|
|
|
|
2012-08-26 06:53:37 -08:00
|
|
|
return fail || input_handle(1);
|
2012-08-26 04:41:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Walks through the directory that we're currently chdir'ed to. *dir contains
|
|
|
|
|
* the filenames as returned by dir_read(), and will be freed automatically by
|
|
|
|
|
* this function. */
|
|
|
|
|
static int dir_walk(char *dir) {
|
|
|
|
|
int fail = 0;
|
|
|
|
|
char *cur;
|
|
|
|
|
|
|
|
|
|
fail = 0;
|
|
|
|
|
for(cur=dir; !fail&&cur&&*cur; cur+=strlen(cur)+1) {
|
|
|
|
|
dir_curpath_enter(cur);
|
2018-01-23 03:17:06 -09:00
|
|
|
memset(buf_dir, 0, offsetof(struct dir, name));
|
|
|
|
|
memset(buf_ext, 0, sizeof(struct dir_ext));
|
|
|
|
|
fail = dir_scan_item(cur);
|
2012-08-26 04:41:25 -08:00
|
|
|
dir_curpath_leave();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(dir);
|
|
|
|
|
return fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2012-09-05 03:52:12 -08:00
|
|
|
static int process() {
|
2012-08-26 04:41:25 -08:00
|
|
|
char *path;
|
|
|
|
|
char *dir;
|
|
|
|
|
int fail = 0;
|
|
|
|
|
struct stat fs;
|
2018-01-23 03:17:06 -09:00
|
|
|
|
|
|
|
|
memset(buf_dir, 0, offsetof(struct dir, name));
|
|
|
|
|
memset(buf_ext, 0, sizeof(struct dir_ext));
|
2012-08-26 04:41:25 -08:00
|
|
|
|
2012-08-26 06:53:37 -08:00
|
|
|
if((path = path_real(dir_curpath)) == NULL)
|
|
|
|
|
dir_seterr("Error obtaining full path: %s", strerror(errno));
|
|
|
|
|
else {
|
|
|
|
|
dir_curpath_set(path);
|
|
|
|
|
free(path);
|
2012-08-26 04:41:25 -08:00
|
|
|
}
|
|
|
|
|
|
2012-08-26 06:53:37 -08:00
|
|
|
if(!dir_fatalerr && path_chdir(dir_curpath) < 0)
|
|
|
|
|
dir_seterr("Error changing directory: %s", strerror(errno));
|
2012-08-26 04:41:25 -08:00
|
|
|
|
|
|
|
|
/* Can these even fail after a chdir? */
|
2012-08-26 06:53:37 -08:00
|
|
|
if(!dir_fatalerr && lstat(".", &fs) != 0)
|
|
|
|
|
dir_seterr("Error obtaining directory information: %s", strerror(errno));
|
|
|
|
|
if(!dir_fatalerr && !S_ISDIR(fs.st_mode))
|
|
|
|
|
dir_seterr("Not a directory");
|
2012-08-26 04:41:25 -08:00
|
|
|
|
2012-08-26 06:53:37 -08:00
|
|
|
if(!dir_fatalerr && !(dir = dir_read(&fail)))
|
|
|
|
|
dir_seterr("Error reading directory: %s", strerror(errno));
|
2012-08-26 04:41:25 -08:00
|
|
|
|
2012-08-26 06:53:37 -08:00
|
|
|
if(!dir_fatalerr) {
|
2012-08-27 11:10:07 -08:00
|
|
|
curdev = (uint64_t)fs.st_dev;
|
2012-08-26 06:53:37 -08:00
|
|
|
if(fail)
|
2018-01-23 03:17:06 -09:00
|
|
|
buf_dir->flags |= FF_ERR;
|
|
|
|
|
stat_to_dir(&fs);
|
2012-08-26 06:53:37 -08:00
|
|
|
|
2018-01-23 03:17:06 -09:00
|
|
|
if(dir_output.item(buf_dir, dir_curpath, buf_ext)) {
|
2012-08-29 00:27:12 -08:00
|
|
|
dir_seterr("Output error: %s", strerror(errno));
|
|
|
|
|
fail = 1;
|
|
|
|
|
}
|
|
|
|
|
if(!fail)
|
|
|
|
|
fail = dir_walk(dir);
|
2018-01-23 03:17:06 -09:00
|
|
|
if(!fail && dir_output.item(NULL, 0, NULL)) {
|
2012-08-29 00:27:12 -08:00
|
|
|
dir_seterr("Output error: %s", strerror(errno));
|
|
|
|
|
fail = 1;
|
|
|
|
|
}
|
2012-08-26 06:53:37 -08:00
|
|
|
}
|
2012-08-26 04:41:25 -08:00
|
|
|
|
2012-08-26 06:53:37 -08:00
|
|
|
while(dir_fatalerr && !input_handle(0))
|
|
|
|
|
;
|
|
|
|
|
return dir_output.final(dir_fatalerr || fail);
|
2012-08-26 04:41:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void dir_scan_init(const char *path) {
|
|
|
|
|
dir_curpath_set(path);
|
|
|
|
|
dir_setlasterr(NULL);
|
2012-08-26 06:53:37 -08:00
|
|
|
dir_seterr(NULL);
|
2012-09-05 03:52:12 -08:00
|
|
|
dir_process = process;
|
2018-01-23 03:17:06 -09:00
|
|
|
buf_dir = malloc(dir_memsize(""));
|
2012-08-26 04:41:25 -08:00
|
|
|
pstate = ST_CALC;
|
|
|
|
|
}
|