aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/lib.c14
-rw-r--r--lib/lib.h6
-rw-r--r--toys/posix/ps.c410
3 files changed, 255 insertions, 175 deletions
diff --git a/lib/lib.c b/lib/lib.c
index b3703052..a16439e7 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -411,14 +411,16 @@ off_t fdlength(int fd)
}
// Read contents of file as a single nul-terminated string.
-// malloc new one if buf=len=0
-char *readfileat(int dirfd, char *name, char *ibuf, off_t len)
+// measure file size if !len, allocate buffer if !buf
+// note: for existing buffers use len = size-1, will set buf[len] = 0
+char *readfileat(int dirfd, char *name, char *ibuf, off_t *plen)
{
+ off_t len = *plen-!!ibuf;
int fd;
char *buf;
if (-1 == (fd = openat(dirfd, name, O_RDONLY))) return 0;
- if (len<1) {
+ if (!len) {
len = fdlength(fd);
// proc files don't report a length, so try 1 page minimum.
if (len<4096) len = 4096;
@@ -426,11 +428,11 @@ char *readfileat(int dirfd, char *name, char *ibuf, off_t len)
if (!ibuf) buf = xmalloc(len+1);
else buf = ibuf;
- len = readall(fd, buf, len-1);
+ *plen = len = readall(fd, buf, len);
close(fd);
if (len<0) {
if (ibuf != buf) free(buf);
- buf = 0;
+ buf = 0;
} else buf[len] = 0;
return buf;
@@ -438,7 +440,7 @@ char *readfileat(int dirfd, char *name, char *ibuf, off_t len)
char *readfile(char *name, char *ibuf, off_t len)
{
- return readfileat(AT_FDCWD, name, ibuf, len);
+ return readfileat(AT_FDCWD, name, ibuf, &len);
}
// Sleep for this many thousandths of a second
diff --git a/lib/lib.h b/lib/lib.h
index 301c50f4..3047d379 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -154,7 +154,7 @@ ssize_t writeall(int fd, void *buf, size_t len);
off_t lskip(int fd, off_t offset);
int mkpathat(int atfd, char *dir, mode_t lastmode, int flags);
struct string_list **splitpath(char *path, struct string_list **list);
-char *readfileat(int dirfd, char *name, char *buf, off_t len);
+char *readfileat(int dirfd, char *name, char *buf, off_t *len);
char *readfile(char *name, char *buf, off_t len);
void msleep(long miliseconds);
int64_t peek_le(void *ptr, unsigned size);
@@ -256,14 +256,14 @@ void names_to_pid(char **names, int (*callback)(pid_t pid, char *name));
pid_t xvforkwrap(pid_t pid);
#define XVFORK() xvforkwrap(vfork())
-#define WOULD_EXIT(y, x) { jmp_buf _noexit; \
+#define WOULD_EXIT(y, x) do { jmp_buf _noexit; \
int _noexit_res; \
toys.rebound = &_noexit; \
_noexit_res = setjmp(_noexit); \
if (!_noexit_res) do {x;} while(0); \
toys.rebound = 0; \
y = _noexit_res; \
-}
+} while(0);
#define NOEXIT(x) WOULD_EXIT(_noexit_res, x)
diff --git a/toys/posix/ps.c b/toys/posix/ps.c
index e42e7ec4..e5fcc5c5 100644
--- a/toys/posix/ps.c
+++ b/toys/posix/ps.c
@@ -35,13 +35,13 @@
* significant. The array index is used in strawberry->which (consumed
* in do_ps()) and in the bitmasks enabling default fields in ps_main().
-USE_PS(NEWTOY(ps, "P(ppid)*aAdeflno*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_PS(NEWTOY(ps, "k(sort)P(ppid)*aAdeflno*p(pid)*s*t*u*U*g*G*wZ[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN))
config PS
bool "ps"
default y
help
- usage: ps [-AadeflnwZ] [-gG GROUP] [-o FIELD] [-p PID] [-t TTY] [-uU USER]
+ usage: ps [-AadeflnwZ] [-gG GROUP,] [-k FIELD,] [-o FIELD,] [-p PID,] [-t TTY,] [-uU USER,]
List processes.
@@ -62,6 +62,7 @@ config PS
Output modifiers:
+ -k Sort FIELDs in +increasing or -decreasting order (--sort)
-n Show numeric USER and GROUP
-w Wide output (don't truncate at terminal width)
@@ -75,9 +76,9 @@ config PS
Available -o FIELDs:
ADDR Instruction pointer
- CMD Command line (from /proc/pid/cmdline, including args)
- CMDLINE Command line (from /proc/pid/cmdline, no args)
- COMM Command name (from /proc/pid/stat, no args)
+ CMD Command name (original)
+ CMDLINE Command name (current argv[0])
+ COMM Command line (with arguments)
ETIME Elapsed time since process start
F Process flags (PF_*) from linux source file include/sched.h
(in octal rather than hex because posix)
@@ -126,14 +127,15 @@ GLOBALS(
struct arg_list *p;
struct arg_list *o;
struct arg_list *P;
+ struct arg_list *k;
struct ptr_len gg, GG, pp, PP, ss, tt, uu, UU, *parsing;
unsigned width;
dev_t tty;
void *fields;
- long bits;
- long long ticks;
+ long long ticks, bits;
size_t header_len;
+ int kcount;
)
struct strawberry {
@@ -143,16 +145,7 @@ struct strawberry {
char forever[];
};
-static time_t get_uptime(void)
-{
- struct sysinfo si;
-
- sysinfo(&si);
-
- return si.uptime;
-}
-
-// Return 1 to display, 0 to skip
+// Return 1 to keep, 0 to discard
static int match_process(long long *slot)
{
struct ptr_len *match[] = {
@@ -181,68 +174,32 @@ static int match_process(long long *slot)
return 1;
}
-// dirtree callback.
-// toybuf used as: 1024 /proc/$PID/stat, 1024 slot[], 2048 /proc/$PID/cmdline
-static int do_ps(struct dirtree *new)
+// Display process data that get_ps() read from /proc, formatting with TT.fields
+static void show_ps(long long *slot)
{
+ char state, *s, *buf, *strslot[5]; // name, tty, wchan, attr, cmdline
struct strawberry *field;
- long long *slot = (void *)(toybuf+1024), ll;
- char *name, *s, state;
- int nlen, i, fd, len, width = TT.width;
-
- if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP;
- if (!(*slot = atol(new->name))) return 0;
-
- // name field limited to 256 bytes by VFS, plus 40 fields * max 20 chars:
- // 1000-ish total, but some forced zero so actually there's headroom.
- sprintf(toybuf, "%lld/stat", *slot);
- if (!readfileat(dirtree_parentfd(new), toybuf, toybuf, 1024)) return 0;
-
- // parse oddball fields (name and state)
- if (!(s = strchr(toybuf, '('))) return 0;
- for (name = ++s; *s != ')'; s++) if (!*s) return 0;
- nlen = s++-name;
- if (1>sscanf(++s, " %c%n", &state, &i)) return 0;
-
- // parse numeric fields (PID = 0, skip 2, then 4th field goes in slot[1])
- for (len = 1; len<100; len++)
- if (1>sscanf(s += i, " %lld%n", slot+len, &i)) break;
-
- // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch
- // or numeric wchan, and the remaining two are always zero).
- slot[31] = new->st.st_uid;
- slot[33] = new->st.st_gid;
-
- // If RGROUP RUSER STAT RUID RGID
- // Save ruid in slot[34] and rgid in slot[35], which are otherwise zero,
- // and vmlck into slot[18] (it_real_value, also always zero).
- if ((TT.bits & 0x38300000) || TT.GG.len || TT.UU.len) {
- char *out = toybuf+2048;
-
- sprintf(out, "%lld/status", *slot);
- if (!readfileat(dirtree_parentfd(new), out, out, 2048)) *out = 0;
- s = strstr(out, "\nUid:");
- slot[32] = s ? atol(s+5) : new->st.st_uid;
- s = strstr(out, "\nGid:");
- slot[34] = s ? atol(s+5) : new->st.st_gid;
- s = strstr(out, "\nVmLck:");
- if (s) slot[18] = atoll(s+5);
+ long long ll;
+ int i, len, width = TT.width;
+
+ // Carve toybuf up into chunks to extract our incoming data
+ s = (char *)(slot+50);
+ state = *s++;
+ for (i=0; i<5; i++) {
+ strslot[i] = s;
+ s += strlen(s)+1;
}
+ // 64 byte sscratch space reserved for sprintf to output data to
+ buf = toybuf+sizeof(toybuf)-64;
- // skip processes we don't care about.
- if (!match_process(slot)) return 0;
-
- // At this point 512 bytes at toybuf+512 are free (already parsed).
- // Start of toybuf still has name in it.
-
- // Loop through fields
+ // Loop through fields to display
for (field = TT.fields; field; field = field->next) {
- char *out = toybuf+2048, *scratch = toybuf+512;
+ char *out = buf;
// Default: unsupported (5 "C")
sprintf(out, "-");
- // PID, PPID, PRI, NI, ADDR, SZ, RSS, PGID, VSS, MAJFL, MINFL
+ // stat#s: PID, PPID, PRI, NI, ADDR, SZ, RSS, PGID, VSS, MAJFL, MINFL
if (-1!=(i = stridx((char[]){3,4,6,7,8,9,24,19,23,25,30,0}, field->which)))
{
char *fmt = "%lld";
@@ -254,7 +211,7 @@ static int do_ps(struct dirtree *new)
else if (i==6) ll <<= 2;
else if (i==8) ll >>= 10;
sprintf(out, fmt, ll);
- // UID USER RUID RUSER GID GROUP RGID RGROUP
+ // user/group: UID USER RUID RUSER GID GROUP RGID RGROUP
} else if (-1!=(i = stridx((char[]){2,22,28,21,26,17,29,20}, field->which)))
{
int id = slot[31+i/2]; // uid, ruid, gid, rgid
@@ -272,97 +229,43 @@ static int do_ps(struct dirtree *new)
if (pw) out = pw->pw_name;
}
}
+ // strslot: CMD TTY WCHAN LABEL (plus CMDLINE handled elsewhere)
+ } else if (-1!=(i = stridx((char[]){15,12,10,31}, field->which)))
+ out = strslot[i];
// F (also assignment of i used by later tests)
// Posix doesn't specify what flags should say. Man page says
// 1 for PF_FORKNOEXEC and 4 for PF_SUPERPRIV from linux/sched.h
- } else if (!(i = field->which)) sprintf(out, "%llo", (slot[6]>>6)&5);
+ else if (!(i = field->which)) sprintf(out, "%llo", (slot[6]>>6)&5);
// S STAT
else if (i==1 || i==27) {
- sprintf(out, "%c", state);
+ s = out;
+ *s++ = state;
if (i==27) {
// TODO l = multithreaded
- s = out+1;
if (slot[16]<0) *s++ = '<';
else if (slot[16]>0) *s++ = 'N';
if (slot[3]==*slot) *s++ = 's';
if (slot[18]) *s++ = 'L';
if (slot[5]==*slot) *s++ = '+';
- *s = 0;
}
- // WCHAN
- } else if (i==10) {
- sprintf(scratch, "%lld/wchan", *slot);
- readfileat(dirtree_parentfd(new), scratch, out, 2047);
-
- // LABEL
- } else if (i==31) {
- sprintf(scratch, "%lld/attr/current", *slot);
- readfileat(dirtree_parentfd(new), scratch, out, 2047);
- chomp(out);
-
+ *s = 0;
// STIME
} else if (i==11) {
- time_t t = time(0) - get_uptime() + slot[19]/sysconf(_SC_CLK_TCK);
+ time_t t = time(0)-slot[46]+slot[19]/sysconf(_SC_CLK_TCK);
// Padding behavior's a bit odd: default field size is just hh:mm.
- // Increasing stime:size reveals more data at left until full
- // yyyy-mm-dd hh:mm revealed at :16, then adds :ss at end for :19. But
- // expanding last field just adds :ss.
- strftime(scratch, 512, "%F %T", localtime(&t));
- out = scratch+strlen(scratch)-3-abs(field->len);
- if (out<scratch) out = scratch;
-
- // TTY
- } else if (i==12) {
- int rdev = slot[4];
- struct stat st;
-
- // Call no tty "?" rather than "0:0".
- if (!rdev) strcpy(out, "?");
- else {
- // Can we readlink() our way to a name?
- for (i=0; i<3; i++) {
- sprintf(scratch, "%lld/fd/%i", *slot, i);
- fd = dirtree_parentfd(new);
- if (!fstatat(fd, scratch, &st, 0) && S_ISCHR(st.st_mode)
- && st.st_rdev == rdev
- && 0<(len = readlinkat(fd, scratch, out, 2047)))
- {
- out[len] = 0;
- break;
- }
- }
-
- // Couldn't find it, try all the tty drivers.
- if (i == 3) {
- FILE *fp = fopen("/proc/tty/drivers", "r");
- int tty_major = 0, maj = major(rdev), min = minor(rdev);
-
- if (fp) {
- while (fscanf(fp, "%*s %256s %d %*s %*s", out, &tty_major) == 2) {
- // TODO: we could parse the minor range too.
- if (tty_major == maj) {
- sprintf(out + strlen(out), "%d", min);
- if (!stat(out, &st) && S_ISCHR(st.st_mode) && st.st_rdev==rdev)
- break;
- }
- tty_major = 0;
- }
- fclose(fp);
- }
-
- // Really couldn't find it, so just show major:minor.
- if (!tty_major) sprintf(out, "%d:%d", maj, min);
- }
-
- strstart(&out, "/dev/");
- }
+ // Increasing stime:size reveals more data at left until full,
+ // so move start address so yyyy-mm-dd hh:mm revealed on left at :16,
+ // then add :ss on right for :19.
+ strftime(out, 64, "%F %T", localtime(&t));
+ out = out+strlen(out)-3-abs(field->len);
+ if (out<buf) out = buf;
// TIME ELAPSED
} else if (i==13 || i==16) {
int unit = 60*60*24, j = sysconf(_SC_CLK_TCK);
- time_t seconds = (i==16) ? (get_uptime()*j)-slot[19] : slot[11]+slot[12];
+ time_t seconds = (i==16) ? (slot[46]*j)-slot[19] : slot[11]+slot[12];
seconds /= j;
for (s = 0, j = 0; j<4; j++) {
@@ -377,35 +280,19 @@ static int do_ps(struct dirtree *new)
}
// COMM - command line including arguments
- // Command line limited to 2k displayable. We could dynamically malloc, but
- // it'd almost never get used, querying length of a proc file is awkward,
- // fixed buffer is nommu friendly... Wait for somebody to complain. :)
- // CMDLINE - command line from /proc/pid/cmdline without arguments
+ // CMDLINE - command name from /proc/pid/cmdline (no arguments)
} else if (i==14 || i==32) {
- int fd;
-
- len = 0;
- sprintf(out, "%lld/cmdline", *slot);
- fd = openat(dirtree_parentfd(new), out, O_RDONLY);
-
- if (fd != -1) {
- if (0<(len = read(fd, out, 2047))) {
- if (!out[len-1]) len--;
- else out[len] = 0;
- if (i==14) for (i = 0; i<len; i++) if (out[i] < ' ') out[i] = ' ';
- }
- close(fd);
+ if (slot[47]<1) {
+ out = toybuf+sizeof(toybuf)-300;
+ sprintf(out, "[%s]", *strslot); // kernel thread, use real name
+ } else {
+ out = strslot[4];
+ if (slot[47]!=INT_MAX) out[slot[47]] = ' '*(i==14);
}
- if (len<1) sprintf(out, "[%.*s]", nlen, name);
-
- // CMD - command name (without arguments)
- } else if (i==15) {
- sprintf(out, "%.*s", nlen, name);
-
// %CPU
} else if (i==18) {
- ll = (get_uptime()*sysconf(_SC_CLK_TCK)-slot[19]);
+ ll = (slot[46]*sysconf(_SC_CLK_TCK)-slot[19]);
len = ((slot[11]+slot[12])*1000)/ll;
sprintf(out, "%d.%d", len/10, len%10);
}
@@ -423,8 +310,172 @@ static int do_ps(struct dirtree *new)
if (!width) break;
}
xputc('\n');
+}
- return 0;
+// dirtree callback: read data about process to display, store, or discard it.
+// Collects slot[50] plus stat, name, tty, wchan, attr, cmdline
+static int get_ps(struct dirtree *new)
+{
+ struct {
+ char *name;
+ long long bits;
+ } fetch[] = {{"fd/", 1<<12}, {"wchan", 1<<10}, {"attr/current", 1<<31},
+ {"cmdline", (1<<14)|(1LL<<32)}};
+ long long *slot = (void *)toybuf;
+ char *name, *s, *buf = (char *)(slot+50), *end = 0;
+ int i, j, fd, ksave = DIRTREE_SAVE*!!(toys.optflags&FLAG_k);
+ off_t len;
+
+ // Recurse one level into /proc children, skip non-numeric entries
+ if (!new->parent) return DIRTREE_RECURSE|DIRTREE_SHUTUP|ksave;
+ if (!(*slot = atol(new->name))) return 0;
+ fd = dirtree_parentfd(new);
+
+ len = 2048;
+ sprintf(buf, "%lld/stat", *slot);
+ if (!readfileat(fd, buf, buf, &len)) return 0;
+
+ // parse oddball fields (name and state). Name can have embedded ')' so match
+ // _last_ ')' in stat (although VFS limits filenames to 255 bytes max).
+ // All remaining fields should be numeric.
+ if (!(name = strchr(buf, '('))) return 0;
+ for (s = ++name; *s; s++) if (*s == ')') end = s;
+ if (!end || end-name>255) return 0;
+
+ // Put status in first byte, and copy name after with low chars spaced.
+ if (1>sscanf(s = end, ") %c%n", buf++, &i)) return 0;
+ for (i = 0; i<end-name; i++) if ((buf[i] = name[i]) < ' ') buf[i] = ' ';
+ buf += i;
+ *buf++ = 0;
+ i = 3;
+
+ // parse numeric fields (PID = 0, skip 2, then 4th field goes in slot[1])
+ for (j = 1; j<50; j++) if (1>sscanf(s += i, " %lld%n", slot+j, &i)) break;
+
+ // save uid, ruid, gid, gid, and rgid int slots 31-34 (we don't use sigcatch
+ // or numeric wchan, and the remaining two are always zero), and vmlck into
+ // 18 (which is "obsolete, always 0")
+ slot[31] = new->st.st_uid;
+ slot[33] = new->st.st_gid;
+
+ // If RGROUP RUSER STAT RUID RGID happening, or -G or -U, parse "status"
+ // Save ruid in slot[34] and rgid in slot[35], which are otherwise zero,
+ // and vmlck into slot[18] (it_real_value, also always zero).
+ if ((TT.bits & 0x38300000) || TT.GG.len || TT.UU.len) {
+ len = sizeof(toybuf)-(buf-toybuf);
+ sprintf(buf, "%lld/status", *slot);
+ if (!readfileat(fd, buf, buf, &len)) *buf = 0;
+ s = strstr(buf, "\nUid:");
+ slot[32] = s ? atol(s+5) : new->st.st_uid;
+ s = strstr(buf, "\nGid:");
+ slot[34] = s ? atol(s+5) : new->st.st_gid;
+ s = strstr(buf, "\nVmLck:");
+ if (s) slot[18] = atoll(s+5);
+ }
+
+ // We now know enough to skip processes we don't care about.
+ if (!match_process(slot)) return 0;
+
+ // /proc data is generated as it's read, so for maximum accuracy on slow
+ // systems (or ps | more) we re-fetch uptime as we fetch each /proc line.
+ sysinfo((void *)(toybuf+2048));
+ slot[46] = ((struct sysinfo *)toybuf)->uptime;
+
+ // fetch remaining data while parentfd still available, appending to buf.
+ // (There's well over 3k of toybuf left. We could dynamically malloc, but
+ // it'd almost never get used, querying length of a proc file is awkward,
+ // fixed buffer is nommu friendly... Wait for somebody to complain. :)
+ for (j = 0; j<ARRAY_LEN(fetch); j++) {
+ if (!(TT.bits&fetch[j].bits)) {
+ *buf++ = 0;
+ continue;
+ }
+
+ // Determine remaining space, reserving minimum of 256 bytes/field and
+ // 64 bytes scratch space at the end (for output conversion later).
+ len = sizeof(toybuf)-(buf-toybuf)-64-256*(ARRAY_LEN(fetch)-j);
+ sprintf(buf, "%lld/%s", *slot, fetch[j].name);
+
+ // If it's not the TTY field, data we want is in a file.
+ // Last length saved in slot[] is command line (which has embedded NULs)
+ if (j) {
+ readfileat(fd, buf, buf, &len);
+
+ // When command has no arguments, don't space over the NUL
+ if (len>0) {
+ int temp = 0;
+
+ if (buf[len-1]=='\n') buf[--len] = 0;
+
+ // Always escape spaces because an executable named esc[0m would be bad.
+ // Escaping low ascii does not affect utf8.
+ for (i=0; i<len; i++) {
+ if (!temp && !buf[i]) temp = i;
+ if (buf[i]<' ') buf[i] = ' ';
+ }
+ if (temp) len = temp; // position of _first_ NUL
+ else len = INT_MAX;
+ } else *buf = len = 0;
+ // Store end of argv[0] so COMM and CMDLINE can differ.
+ slot[47] = len;
+ } else {
+ int rdev = slot[4];
+ struct stat st;
+
+ // Call no tty "?" rather than "0:0".
+ strcpy(buf, "?");
+ if (rdev) {
+ // Can we readlink() our way to a name?
+ for (i = 0; i<3; i++) {
+ sprintf(buf, "%lld/fd/%i", *slot, i);
+ if (!fstatat(fd, buf, &st, 0) && S_ISCHR(st.st_mode)
+ && st.st_rdev == rdev && 0<(len = readlinkat(fd, buf, buf, len)))
+ {
+ buf[len] = 0;
+ break;
+ }
+ }
+
+ // Couldn't find it, try all the tty drivers.
+ if (i == 3) {
+ FILE *fp = fopen("/proc/tty/drivers", "r");
+ int tty_major = 0, maj = major(rdev), min = minor(rdev);
+
+ if (fp) {
+ while (fscanf(fp, "%*s %256s %d %*s %*s", buf, &tty_major) == 2) {
+ // TODO: we could parse the minor range too.
+ if (tty_major == maj) {
+ sprintf(buf+strlen(buf), "%d", min);
+ if (!stat(buf, &st) && S_ISCHR(st.st_mode) && st.st_rdev==rdev)
+ break;
+ }
+ tty_major = 0;
+ }
+ fclose(fp);
+ }
+
+ // Really couldn't find it, so just show major:minor.
+ if (!tty_major) sprintf(buf, "%d:%d", maj, min);
+ }
+
+ s = buf;
+ if (strstart(&s, "/dev/")) memmove(buf, s, strlen(s)+1);;
+ }
+ }
+ buf += strlen(buf)+1;
+ }
+
+ // If we need to sort the output, add it to the list and return.
+ if (ksave) {
+ s = xmalloc(buf-toybuf);
+ new->extra = (long)s;
+ memcpy(s, toybuf, buf-toybuf);
+ TT.kcount++;
+
+ // Otherwise display it now
+ } else show_ps(slot);
+
+ return ksave;
}
// Traverse arg_list of csv, calling callback on each value
@@ -597,8 +648,16 @@ static char *parse_rest(char *str, int len)
return str;
}
+// sort for -k
+static int ksort(void *aa, void *bb)
+{
+ // TODO: sort!
+ return 1;
+}
+
void ps_main(void)
{
+ struct dirtree *dt;
int i;
TT.width = 99999;
@@ -658,7 +717,26 @@ void ps_main(void)
dlist_terminate(TT.fields);
printf("%s\n", toybuf);
- dirtree_read("/proc", do_ps);
+ dt = dirtree_read("/proc", get_ps);
+
+ if (toys.optflags&FLAG_k) {
+ struct dirtree **dtsort = xmalloc(TT.kcount*sizeof(struct dirtree *));
+
+ // descend into child list
+ *dtsort = dt;
+ dt = dt->child;
+ free(*dtsort);
+
+ // populate array
+ i = 0;
+ for (i = 0; dt; dt = dt->next) dtsort[i++] = dt;
+ qsort(dtsort, TT.kcount, sizeof(struct dirtree *), (void *)ksort);
+ for (i = 0; i<TT.kcount; i++) {
+ show_ps((void *)dtsort[i]->extra);
+ free((void *)dtsort[i]->extra);
+ free(dtsort[i]);
+ }
+ }
if (CFG_TOYBOX_FREE) {
free(TT.gg.ptr);