Skip to content

Commit

Permalink
psh/du: implement 'du' command
Browse files Browse the repository at this point in the history
Implements tool that estimates file space usage

JIRA: RTOS-745
  • Loading branch information
gerard5 committed Jan 24, 2024
1 parent 7b6b834 commit 5a996a8
Show file tree
Hide file tree
Showing 2 changed files with 312 additions and 3 deletions.
6 changes: 3 additions & 3 deletions psh/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ LOCAL_CFLAGS += -DPSH_SYSEXECWL='"$(PSH_SYSEXECWL)"'
LOCAL_LDFLAGS := -z stack-size=4096 -z noexecstack

# TODO: search for dirs?
PSH_ALLCOMMANDS := bind cat cd cp date dd df dmesg echo edit exec hm ifconfig kill \
ln ls mem mkdir mount nc nslookup ntpclient perf ping pm printenv ps pwd reboot rm \
rmdir runfile sync sysexec top touch tty umount uptime wget
PSH_ALLCOMMANDS := bind cat cd cp date dd df du dmesg echo edit exec hm ifconfig \
kill ln ls mem mkdir mount nc nslookup ntpclient perf ping pm printenv ps pwd reboot \
rm rmdir runfile sync sysexec top touch tty umount uptime wget
PSH_COMMANDS ?= $(PSH_ALLCOMMANDS)
PSH_INTERNAL_APPLETS := pshapp help $(filter $(PSH_ALLCOMMANDS), $(PSH_COMMANDS))

Expand Down
309 changes: 309 additions & 0 deletions psh/du/du.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
/*
* Phoenix-RTOS
*
* du - estimates file space usage
*
* Copyright 2024 Phoenix Systems
* Author: Gerard Swiderski
*
* This file is part of Phoenix-RTOS.
*
* %LICENSE%
*/

#include <errno.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h> /* memset */
#include <unistd.h> /* getopt */
#include <limits.h> /* PATH_MAX */
#include <dirent.h> /* readdir */
#include <sys/stat.h> /* lstat */

#include "../psh.h"

#define ALREADY_MAX 64
#define LEVELS_MAX 64


struct already {
struct already *next;
nlink_t nlink;
ino_t inum;
int dev;
};


static struct {
struct already *already[ALREADY_MAX];
char dent[PATH_MAX];
/* options */
int levels;
int8_t ki;
bool silent;
bool all;
bool tot;
bool crosschk;
} du_common;


/*
* makePath - make the pathname from the directory name, and the directory entry,
* placing it in out. If this would overflow return 0, otherwise length of path.
*/
static size_t makePath(size_t dlen, const char *f)
{
char *cp = &du_common.dent[dlen];
size_t length = strlen(f);

if ((dlen + length) > (sizeof(du_common.dent) - 2u)) {
return 0;
}

if ((dlen != 0u) && (*(cp - 1) != '/')) {
*(cp++) = '/';
}

while (length-- != 0u) {
*(cp++) = *(f++);
}
*cp = '\0';

return cp - du_common.dent;
}


/*
* isDone - have we encountered (dev, inum) before? Returns 1 for yes, 0 for no
* and remembers (dev, inum, nlink).
*/
static int isDone(dev_t dev, ino_t inum, nlink_t nlink)
{
struct already *ap;
struct already **pap = &du_common.already[(int)inum % ALREADY_MAX];

for (;;) {
ap = *pap;
if (ap == NULL) {
break;
}

if ((ap->inum == inum) && (ap->dev == dev)) {
ap->nlink--;
if (ap->nlink == (nlink_t)0) {
*pap = ap->next;
free(ap);
}
return 1;
}

pap = &ap->next;
}

ap = (struct already *)malloc(sizeof(*ap));
if (ap == NULL) {
fprintf(stderr, "du: Out of memory\n");
return -1;
}

ap->next = NULL;
ap->inum = inum;
ap->dev = dev;
ap->nlink = nlink - 1;
*pap = ap;

return 0;
}


static void freeAlreadyList(void)
{
for (int i = 0; i < ALREADY_MAX; ++i) {
struct already *list = du_common.already[i];
while (list != NULL) {
struct already *temp = list;
list = list->next;
free(temp);
}
}
}


static size_t roundPrefix(size_t v)
{
switch (du_common.ki) {
case 1: return (v + (size_t)999) / (size_t)1000; /* kilo */
case 2: return (v + (size_t)1023) / (size_t)1024; /* Kibi */
default: break;
}
return v;
}


/*
* doDirectory - process the directory.
* Return the size (in bytes) of the directory and its descendants.
*/
static size_t doDirectory(size_t startPos, int curLevel, dev_t dev)
{
DIR *dp;
bool maybePrint;
struct stat stbuf;
size_t total = 0;
struct dirent *entry;
char *d = du_common.dent;

if (lstat(d, &stbuf) < 0) {
fprintf(stderr, "du: %s: %s\n", d, strerror(errno));
return (size_t)0;
}

if ((stbuf.st_dev != dev) && (dev != 0) && (du_common.crosschk)) {
return (size_t)0;
}

if (stbuf.st_size > (off_t)0) {
total = (size_t)stbuf.st_size;
}

switch (stbuf.st_mode & S_IFMT) {
case S_IFDIR:
/*
* Directories should not be linked except to "." and "..", so this
* directory should not already have been done.
*/
maybePrint = !du_common.silent;
dp = opendir(d);
if (dp == NULL) {
break;
}

for (;;) {
du_common.dent[startPos] = '\0';
entry = readdir(dp);
if (entry == NULL) {
break;
}

if (!((entry->d_name[0] == '.') && ((entry->d_name[1] == '\0') || ((entry->d_name[1] == '.') && (entry->d_name[2] == '\0'))))) {
const size_t nextPos = makePath(startPos, entry->d_name);
if (nextPos > 0u) {
total += doDirectory(nextPos, curLevel - 1, stbuf.st_dev);
}
}
}

closedir(dp);
break;

case S_IFBLK:
case S_IFCHR:
/* st_size for special files is not related to blocks used. */
total = (size_t)0;
/* fall-through */

default:
if ((stbuf.st_nlink > (nlink_t)1) && (isDone(stbuf.st_dev, stbuf.st_ino, stbuf.st_nlink) != 0)) {
return (size_t)0;
}
maybePrint = du_common.all;
break;
}


if ((curLevel >= du_common.levels) || ((maybePrint) && (curLevel >= 0))) {
printf("%zu\t%s\n", roundPrefix(total), d);
}

return total;
}


static void psh_du_info(void)
{
printf("estimates file space usage");
}


static void psh_du_usage(void)
{
fprintf(stderr, "Usage: du [-acsxkK] [-d depth] [startdir]\n");
}


static int psh_du(int argc, char **argv)
{
int opt;
size_t total = 0;
const char *startdir = ".";

memset(&du_common, 0, sizeof(du_common));
du_common.levels = LEVELS_MAX;

for (;;) {
opt = getopt(argc, argv, "acsxd:kK");
if (opt < 0) {
break;
}

switch (opt) {
case 'a':
du_common.all = 1;
break;

case 'c':
du_common.tot = 1;
break;

case 's':
du_common.silent = 1;
break;

case 'x':
du_common.crosschk = 1;
break;

case 'd':
du_common.levels = atoi(optarg);
break;

case 'k':
du_common.ki = 1; /* kilo */
break;

case 'K':
du_common.ki = 2; /* kibi */
break;

default:
psh_du_usage();
return EXIT_FAILURE;
}
}

do {
if (optind < argc) {
startdir = argv[optind++];
}
const size_t startPos = makePath(0, startdir);
if (startPos > 0u) {
total += doDirectory(startPos, du_common.levels, 0);
}
} while (optind < argc);

freeAlreadyList();

if (du_common.tot) {
printf("%zu\ttotal\n", roundPrefix(total));
}

return EXIT_SUCCESS;
}


static void __attribute__((constructor)) du_registerapp(void)
{
static psh_appentry_t app = { .name = "du", .run = psh_du, .info = psh_du_info };
psh_registerapp(&app);
}

0 comments on commit 5a996a8

Please sign in to comment.