aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2015-05-03 17:32:42 -0700
committerElliott Hughes <enh@google.com>2015-05-03 17:32:42 -0700
commitc47e72553eea3038188702c024adaf3148546e1c (patch)
tree25041ff925f1e3b90170d2911b9b216473417414
parentd67730eb9ae4f387fc2d50a8210e789b1509288e (diff)
parent9398f05d34453e9306f7a91993ee4c8fcf1102ec (diff)
downloadtoybox-c47e72553eea3038188702c024adaf3148546e1c.tar.gz
Merge remote-tracking branch 'toybox/master' into HEAD
Change-Id: If8daf7f2f2e1b6d688050c4543d2648320bb21ad
-rw-r--r--lib/lib.c116
-rw-r--r--lib/lib.h5
-rw-r--r--lib/net.c5
-rw-r--r--lib/password.c33
-rw-r--r--lib/portability.h9
-rw-r--r--main.c9
-rwxr-xr-xscripts/genconfig.sh7
-rw-r--r--scripts/mkflags.c9
-rwxr-xr-xtests/truncate.test28
-rw-r--r--toys.h1
-rw-r--r--toys/android/getprop.c64
-rw-r--r--toys/android/setprop.c13
-rw-r--r--toys/lsb/dmesg.c15
-rw-r--r--toys/other/makedevs.c4
-rw-r--r--toys/other/truncate.c33
-rw-r--r--toys/pending/dumpleases.c2
-rw-r--r--toys/pending/hexedit.c250
-rw-r--r--toys/pending/mdev.c74
-rw-r--r--toys/posix/ls.c117
-rwxr-xr-xwww/roadmap.html32
20 files changed, 635 insertions, 191 deletions
diff --git a/lib/lib.c b/lib/lib.c
index 99d2ea60..2f7d28f4 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -280,16 +280,6 @@ long atolx_range(char *numstr, long low, long high)
return val;
}
-int numlen(long l)
-{
- int len = 0;
- while (l) {
- l /= 10;
- len++;
- }
- return len;
-}
-
int stridx(char *haystack, char needle)
{
char *off;
@@ -637,6 +627,40 @@ int terminal_size(unsigned *xx, unsigned *yy)
return x || y;
}
+// Reset terminal to known state, saving copy of old state if old != NULL.
+int set_terminal(int fd, int raw, struct termios *old)
+{
+ struct termios termio;
+
+ // Fetch local copy of old terminfo, and copy struct contents to *old if set
+ if (!tcgetattr(fd, &termio) && old) *old = termio;
+
+ // the following are the bits set for an xterm. Linux text mode TTYs by
+ // default add two additional bits that only matter for serial processing
+ // (turn serial line break into an interrupt, and XON/XOFF flow control)
+
+ // Any key unblocks output, swap CR and NL on input
+ termio.c_iflag = IXANY|ICRNL|INLCR;
+ if (toys.which->flags & TOYFLAG_LOCALE) termio.c_iflag |= IUTF8;
+
+ // Output appends CR to NL, does magic undocumented postprocessing
+ termio.c_oflag = ONLCR|OPOST;
+
+ // Leave serial port speed alone
+ // termio.c_cflag = C_READ|CS8|EXTB;
+
+ // Generate signals, input entire line at once, echo output
+ // erase, line kill, escape control characters with ^
+ // erase line char at a time
+ // "extended" behavior: ctrl-V quotes next char, ctrl-R reprints unread chars,
+ // ctrl-W erases word
+ termio.c_lflag = ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE|IEXTEN;
+
+ if (raw) cfmakeraw(&termio);
+
+ return tcsetattr(fd, TCSANOW, &termio);
+}
+
int yesno(char *prompt, int def)
{
char buf;
@@ -887,3 +911,75 @@ int human_readable(char *buf, unsigned long long num)
return end;
}
+
+// The qsort man page says you can use alphasort, the posix committee
+// disagreed, and doubled down: http://austingroupbugs.net/view.php?id=142
+// So just do our own. (The const is entirely to humor the stupid compiler.)
+int qstrcmp(const void *a, const void *b)
+{
+ return strcmp(*(char **)a, *(char **)b);
+}
+
+int xpoll(struct pollfd *fds, int nfds, int timeout)
+{
+ int i;
+
+ for (;;) {
+ if (0>(i = poll(fds, nfds, timeout))) {
+ if (errno != EINTR && errno != ENOMEM) perror_exit("xpoll");
+ else if (timeout>0) timeout--;
+ } else return i;
+ }
+}
+
+// Scan stdin for a keypress, parsing known escape sequences
+// seq is array of char * strings, ends with NULL ptr
+// Returns: 0-255=literal, -1=EOF, -2=NONE, 256-...=index into seq
+// scratch space is necessary because last char of !seq could start new seq
+// Zero out first byte of scratch before first call to scan_key
+// block=0 allows fetching multiple characters before updating display
+int scan_key(char *scratch, char **seqs, int block)
+{
+ struct pollfd pfd;
+ int maybe, i, j;
+ char *test;
+
+ for (;;) {
+ pfd.fd = 0;
+ pfd.events = POLLIN;
+ pfd.revents = 0;
+
+ // check sequences
+ maybe = 0;
+ if (*scratch) {
+ for (i = maybe = 0; (test = seqs[i]); i++) {
+ for (j = 0; j<*scratch; j++) if (scratch[j+1] != test[j]) break;
+ if (j == *scratch) {
+ maybe = 1;
+ if (!test[j]) {
+ // We recognized current sequence: consume and return
+ *scratch = 0;
+ return 256+i;
+ }
+ }
+ }
+ // If current data can't be a known sequence, return next raw char
+ if (!maybe) break;
+ }
+
+ // Need more data to decide
+
+ // 30 miliseconds is about the gap between characters at 300 baud
+ if (maybe || !block) if (!xpoll(&pfd, 1, 30*maybe)) break;
+
+ if (1 != read(0, scratch+1+*scratch, 1)) return -1;
+ ++*scratch;
+ }
+
+ // Was not a sequence
+ if (!*scratch) return -2;
+ i = scratch[1];
+ if (--*scratch) memmove(scratch+1, scratch+2, *scratch);
+
+ return i;
+}
diff --git a/lib/lib.h b/lib/lib.h
index 3cda7d9f..dc5edd33 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -156,7 +156,6 @@ long estrtol(char *str, char **end, int base);
long xstrtol(char *str, char **end, int base);
long atolx(char *c);
long atolx_range(char *numstr, long low, long high);
-int numlen(long l);
int stridx(char *haystack, char needle);
int unescape(char c);
int strstart(char **a, char *b);
@@ -174,8 +173,12 @@ void replace_tempfile(int fdin, int fdout, char **tempname);
void crc_init(unsigned int *crc_table, int little_endian);
void base64_init(char *p);
int terminal_size(unsigned *x, unsigned *y);
+int set_terminal(int fd, int raw, struct termios *old);
int yesno(char *prompt, int def);
int human_readable(char *buf, unsigned long long num);
+int qstrcmp(const void *a, const void *b);
+int xpoll(struct pollfd *fds, int nfds, int timeout);
+int scan_key(char *scratch, char **seqs, int block);
// net.c
int xsocket(int domain, int type, int protocol);
diff --git a/lib/net.c b/lib/net.c
index c12eff43..5d3ea4a8 100644
--- a/lib/net.c
+++ b/lib/net.c
@@ -7,3 +7,8 @@ int xsocket(int domain, int type, int protocol)
if (fd < 0) perror_exit("socket %x %x", type, protocol);
return fd;
}
+
+void xsetsockopt(int fd, int level, int opt, void *val, socklen_t len)
+{
+ if (-1 == setsockopt(fd, level, opt, val, len)) perror_exit("setsockopt");
+}
diff --git a/lib/password.c b/lib/password.c
index b3cc199e..9654d42d 100644
--- a/lib/password.c
+++ b/lib/password.c
@@ -46,39 +46,6 @@ int get_salt(char *salt, char *algo)
return -1;
}
-// Reset terminal to known state, returning old state if old != NULL.
-int set_terminal(int fd, int raw, struct termios *old)
-{
- struct termios termio;
-
- if (!tcgetattr(fd, &termio) && old) *old = termio;
-
- // the following are the bits set for an xterm. Linux text mode TTYs by
- // default add two additional bits that only matter for serial processing
- // (turn serial line break into an interrupt, and XON/XOFF flow control)
-
- // Any key unblocks output, swap CR and NL on input
- termio.c_iflag = IXANY|ICRNL|INLCR;
- if (toys.which->flags & TOYFLAG_LOCALE) termio.c_iflag |= IUTF8;
-
- // Output appends CR to NL, does magic undocumented postprocessing
- termio.c_oflag = ONLCR|OPOST;
-
- // Leave serial port speed alone
- // termio.c_cflag = C_READ|CS8|EXTB;
-
- // Generate signals, input entire line at once, echo output
- // erase, line kill, escape control characters with ^
- // erase line char at a time
- // "extended" behavior: ctrl-V quotes next char, ctrl-R reprints unread chars,
- // ctrl-W erases word
- termio.c_lflag = ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE|IEXTEN;
-
- if (raw) cfmakeraw(&termio);
-
- return tcsetattr(fd, TCSANOW, &termio);
-}
-
// Prompt with mesg, read password into buf, return 0 for success 1 for fail
int read_password(char *buf, int buflen, char *mesg)
{
diff --git a/lib/portability.h b/lib/portability.h
index 740a4ee5..7bab646d 100644
--- a/lib/portability.h
+++ b/lib/portability.h
@@ -28,6 +28,9 @@
#include <features.h>
+// Types various replacement prototypes need
+#include <sys/types.h>
+
// Various constants old build environments might not have even if kernel does
#ifndef AT_FDCWD
@@ -226,6 +229,10 @@ ssize_t getline(char **lineptr, size_t *n, FILE *stream);
#define O_CLOEXEC 02000000
#endif
+#ifndef O_PATH
+#define O_PATH 010000000
+#endif
+
#if defined(__SIZEOF_DOUBLE__) && defined(__SIZEOF_LONG__) \
&& __SIZEOF_DOUBLE__ <= __SIZEOF_LONG__
typedef double FLOAT;
@@ -257,5 +264,7 @@ int getcon(void* con);
#define smack_set_label_for_self(...) (-1)
#define XATTR_NAME_SMACK ""
#define SMACK_LABEL_LEN (1) /* for just ? */
+
+ssize_t fgetxattr (int fd, char *name, void *value, size_t size);
#endif
diff --git a/main.c b/main.c
index 57022442..2fd22d4b 100644
--- a/main.c
+++ b/main.c
@@ -183,8 +183,17 @@ void toybox_main(void)
xputc('\n');
}
+static void shutup_sigpipe(int i)
+{
+ // exit success from sigpipe to mollify overzealous crash reporting.
+ _exit(0);
+}
+
int main(int argc, char *argv[])
{
+ if (CFG_TOYBOX_ON_ANDROID) signal(SIGPIPE, shutup_sigpipe);
+ else signal(SIGPIPE, SIG_IGN);
+
if (CFG_TOYBOX) {
// Trim path off of command name
*argv = basename(*argv);
diff --git a/scripts/genconfig.sh b/scripts/genconfig.sh
index ead5b8a2..b8dc3c79 100755
--- a/scripts/genconfig.sh
+++ b/scripts/genconfig.sh
@@ -65,6 +65,13 @@ EOF
struct spwd *a = getspnam("root"); return 0;
}
EOF
+
+ # Some commands are android-specific
+ probesymbol TOYBOX_ON_ANDROID -c << EOF
+ #ifndef __ANDROID__
+ #error nope
+ #endif
+EOF
}
genconfig()
diff --git a/scripts/mkflags.c b/scripts/mkflags.c
index 228cd2f8..d87087bc 100644
--- a/scripts/mkflags.c
+++ b/scripts/mkflags.c
@@ -122,10 +122,14 @@ int main(int argc, char *argv[])
char *gaps, *mgaps, c;
unsigned bit;
- *command = 0;
+ *command = *flags = *allflags = 0;
bit = fscanf(stdin, "%255s \"%1023[^\"]\" \"%1023[^\"]\"\n",
command, flags, allflags);
+ if (getenv("DEBUG"))
+ fprintf(stderr, "command=%s, flags=%s, allflags=%s\n",
+ command, flags, allflags);
+
if (!*command) break;
if (bit != 3) {
fprintf(stderr, "\nError in %s (duplicate command?)\n", command);
@@ -182,8 +186,7 @@ int main(int argc, char *argv[])
if (flist) flist = flist->next;
}
} else if (aflist->command) {
- if (flist && (!aflist->command || *aflist->command == *flist->command))
- {
+ if (flist && (!flist->command || *aflist->command == *flist->command)) {
if (aflist->command)
sprintf(out, "#define FLAG_%c (1<<%d)\n", *aflist->command, bit);
flist = flist->next;
diff --git a/tests/truncate.test b/tests/truncate.test
new file mode 100755
index 00000000..ee2b2bb8
--- /dev/null
+++ b/tests/truncate.test
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . testing.sh
+
+#testing "name" "command" "result" "infile" "stdin"
+
+SIZE='&& stat -c %s freep'
+testing "truncate 0" "truncate -s 0 freep $SIZE" "0\n" "" ""
+testing "truncate 12345" "truncate -s 12345 freep $SIZE" "12345\n" "" ""
+testing "truncate 1m" "truncate -s 1m freep $SIZE" "1048576\n" "" ""
+testing "truncate is sparse" "truncate -s 1g freep && stat -c %b freep" \
+ "0\n" "" ""
+testing "truncate +" "truncate -s 1k freep && truncate -s +1k freep $SIZE" \
+ "2048\n" "" ""
+testing "truncate -" "truncate -s 4k freep && truncate -s -1k freep $SIZE" \
+ "3072\n" "" ""
+testing "truncate < hit" \
+ "truncate -s 5k freep && truncate -s \<4k freep $SIZE" "4096\n" "" ""
+testing "truncate < miss" \
+ "truncate -s 4k freep && truncate -s \<6k freep $SIZE" "4096\n" "" ""
+testing "truncate > hit" \
+ "truncate -s 3k freep && truncate -s \>4k freep $SIZE" "4096\n" "" ""
+testing "truncate > miss" \
+ "truncate -s 4k freep && truncate -s \>2k freep $SIZE" "4096\n" "" ""
+testing "truncate /" "truncate -s 7k freep && truncate -s /3k freep $SIZE" \
+ "6144\n" "" ""
+testing "truncate %" "truncate -s 7k freep && truncate -s %3k freep $SIZE" \
+ "9216\n" "" ""
diff --git a/toys.h b/toys.h
index aa4381c5..4b2d3736 100644
--- a/toys.h
+++ b/toys.h
@@ -36,7 +36,6 @@
#include <sys/statvfs.h>
#include <sys/time.h>
#include <sys/times.h>
-#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <syslog.h>
diff --git a/toys/android/getprop.c b/toys/android/getprop.c
index 66530419..4afac1a5 100644
--- a/toys/android/getprop.c
+++ b/toys/android/getprop.c
@@ -7,6 +7,7 @@ USE_GETPROP(NEWTOY(getprop, ">2", TOYFLAG_USR|TOYFLAG_SBIN))
config GETPROP
bool "getprop"
default y
+ depends on TOYBOX_ON_ANDROID
help
usage: getprop [NAME [DEFAULT]]
@@ -22,70 +23,29 @@ config GETPROP
GLOBALS(
size_t size;
- size_t capacity;
+ char **nv; // name/value pairs: even=name, odd=value
)
-struct property_info {
- char *name;
- char *value;
-};
-
-static struct property_info **properties;
-
-static void add_property(const char *name, const char *value, void *unused)
-{
- struct property_info *new = xmalloc(sizeof(struct property_info));
-
- if (TT.size >= TT.capacity) {
- TT.capacity += 32;
- properties = xrealloc(properties,
- TT.capacity * sizeof(struct property_info *));
- }
-
- // TODO: fix xstrdup signature so we can remove these bogus casts.
- new->name = xstrdup((char *) name);
- new->value = xstrdup((char *) value);
- properties[TT.size++] = new;
-}
-
-static void free_properties()
+static void add_property(char *name, char *value, void *unused)
{
- size_t i;
+ if (!(TT.size&31)) TT.nv = xrealloc(TT.nv, (TT.size+32)*2*sizeof(char *));
- for (i = 0; i < TT.size; ++i) {
- free(properties[i]->name);
- free(properties[i]->value);
- free(properties[i]);
- }
- free(properties);
-}
-
-static int property_cmp(const void *a, const void *b)
-{
- struct property_info *pa = *((struct property_info **)a);
- struct property_info *pb = *((struct property_info **)b);
-
- return strcmp(pa->name, pb->name);
+ TT.nv[2*TT.size] = xstrdup(name);
+ TT.nv[1+2*TT.size++] = xstrdup(value);
}
void getprop_main(void)
{
if (*toys.optargs) {
- char value[PROPERTY_VALUE_MAX];
- const char *default_value = "";
-
- if (toys.optargs[1]) default_value = toys.optargs[1];
- property_get(*toys.optargs, value, default_value);
- puts(value);
+ property_get(*toys.optargs, toybuf, toys.optargs[1] ? toys.optargs[1] : "");
+ puts(toybuf);
} else {
size_t i;
- if (property_list(add_property, NULL))
- error_exit("property_list failed");
- qsort(properties, TT.size, sizeof(struct property_info *), property_cmp);
- for (i = 0; i < TT.size; ++i)
- printf("[%s]: [%s]\n", properties[i]->name, properties[i]->value);
- if (CFG_TOYBOX_FREE) free_properties();
+ if (property_list((void *)add_property, 0)) error_exit("property_list");
+ qsort(TT.nv, TT.size, 2*sizeof(char *), qstrcmp);
+ for (i = 0; i<TT.size; i++) printf("[%s]: [%s]\n", TT.nv[i*2],TT.nv[1+i*2]);
+ if (CFG_TOYBOX_FREE) free(TT.nv);
}
}
diff --git a/toys/android/setprop.c b/toys/android/setprop.c
index ef24c9ad..34e0de45 100644
--- a/toys/android/setprop.c
+++ b/toys/android/setprop.c
@@ -7,6 +7,7 @@ USE_SETPROP(NEWTOY(setprop, "<2>2", TOYFLAG_USR|TOYFLAG_SBIN))
config SETPROP
bool "setprop"
default y
+ depends on TOYBOX_ON_ANDROID
help
usage: setprop NAME VALUE
@@ -17,12 +18,11 @@ config SETPROP
#include "toys.h"
#if defined(__ANDROID__)
+
#include <cutils/properties.h>
-#endif
void setprop_main(void)
{
-#if defined(__ANDROID__)
char *name = toys.optargs[0], *value = toys.optargs[1];
char *p;
size_t name_len = strlen(name), value_len = strlen(value);
@@ -48,5 +48,12 @@ void setprop_main(void)
if (property_set(name, value))
error_msg("failed to set property '%s' to '%s'", name, value);
-#endif
}
+
+#else
+
+void setprop_main(void)
+{
+}
+
+#endif
diff --git a/toys/lsb/dmesg.c b/toys/lsb/dmesg.c
index c8876757..aac638d0 100644
--- a/toys/lsb/dmesg.c
+++ b/toys/lsb/dmesg.c
@@ -5,13 +5,13 @@
* http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/dmesg.html
// We care that FLAG_c is 1, so keep c at the end.
-USE_DMESG(NEWTOY(dmesg, "rs#<1n#c", TOYFLAG_BIN))
+USE_DMESG(NEWTOY(dmesg, "trs#<1n#c[!tr]", TOYFLAG_BIN))
config DMESG
bool "dmesg"
default y
help
- usage: dmesg [-n LEVEL] [-s SIZE] | -c
+ usage: dmesg [-c] [-r|-t] [-n LEVEL] [-s SIZE]
Print or control the kernel ring buffer.
@@ -19,6 +19,7 @@ config DMESG
-n Set kernel logging LEVEL (1-9)
-r Raw output (with <level markers>)
-s Show the last SIZE many bytes
+ -t Don't print kernel's timestamps
*/
#define FOR_dmesg
@@ -47,12 +48,14 @@ void dmesg_main(void)
if (size < 0) error_exit("klogctl");
data[size] = 0;
- // Filter out level markers.
+ // Filter out level markers and optionally time markers
if (!(toys.optflags & FLAG_r)) while ((from - data) < size) {
- if ((from == data || from[-1] == '\n') && *from == '<') {
- int i = stridx(from, '>');
+ if (from == data || from[-1] == '\n') {
+ char *to;
- if (i>0) from += i+1;
+ if (*from == '<' && (to = strchr(from, '>'))) from = ++to;
+ if ((toys.optflags&FLAG_t) && *from == '[' && (to = strchr(from, ']')))
+ from = to+1+(to[1]==' ');
}
*(to++) = *(from++);
} else to = data+size;
diff --git a/toys/other/makedevs.c b/toys/other/makedevs.c
index 0d20a57d..0f0a6615 100644
--- a/toys/other/makedevs.c
+++ b/toys/other/makedevs.c
@@ -84,8 +84,8 @@ void makedevs_main()
while (*node == '/') node++; // using relative path
for (i = 0; (!cnt && !i) || i < cnt; i++) {
- if (cnt) {
- snprintf(toybuf, sizeof(toybuf), "%s%u", node, st_val + i);
+ if (cnt>1) {
+ snprintf(toybuf, sizeof(toybuf), "%.999s%u", node, st_val + i);
ptr = toybuf;
} else ptr = node;
diff --git a/toys/other/truncate.c b/toys/other/truncate.c
index d09818f3..bfe1f10c 100644
--- a/toys/other/truncate.c
+++ b/toys/other/truncate.c
@@ -2,37 +2,62 @@
*
* Copyright 2011 Rob Landley <rob@landley.net>
-USE_TRUNCATE(NEWTOY(truncate, "<1s#|c", TOYFLAG_BIN))
+USE_TRUNCATE(NEWTOY(truncate, "<1s:|c", TOYFLAG_BIN))
config TRUNCATE
bool "truncate"
default y
help
- usage: truncate [-c] -s file...
+ usage: truncate [-c] -s SIZE file...
Set length of file(s), extending sparsely if necessary.
-c Don't create file if it doesn't exist.
- -s New size
+ -s New size (with optional prefix and suffix)
+
+ SIZE prefix: + add, - subtract, < shrink to, > expand to,
+ / multiple rounding down, % multiple rounding up
+ SIZE suffix: k=1024, m=1024^2, g=1024^3, t=1024^4, p=1024^5, e=1024^6
*/
#define FOR_truncate
#include "toys.h"
GLOBALS(
+ char *s;
+
long size;
+ int type;
)
static void do_truncate(int fd, char *name)
{
+ long long size;
+
if (fd<0) return;
- if (ftruncate(fd, TT.size)) perror_msg("'%s' to '%ld'", name, TT.size);
+
+ if (TT.type == -1) size = TT.size;
+ else {
+ size = fdlength(fd);
+ if (TT.type<2) size += TT.size*(1-(2*TT.type));
+ else if (TT.type<4) {
+ if ((TT.type==2) ? (size <= TT.size) : (size >= TT.size)) return;
+ size = TT.size;
+ } else {
+ size = (size+(TT.type-4)*(TT.size-1))/TT.size;
+ size *= TT.size;
+ }
+ }
+ if (ftruncate(fd, size)) perror_msg("'%s' to '%lld'", name, size);
}
void truncate_main(void)
{
int cr = !(toys.optflags&1);
+ if (-1 != (TT.type = stridx("+-<>/%", *TT.s))) TT.s++;
+ TT.size = atolx(TT.s);
+
// Create files with mask rwrwrw.
// Nonexistent files are only an error if we're supposed to create them.
loopfiles_rw(toys.optargs, O_WRONLY|O_CLOEXEC|(cr ? O_CREAT : 0), 0666, cr,
diff --git a/toys/pending/dumpleases.c b/toys/pending/dumpleases.c
index fec39559..86cb4e76 100644
--- a/toys/pending/dumpleases.c
+++ b/toys/pending/dumpleases.c
@@ -41,7 +41,7 @@ void dumpleases_main(void)
int64_t written_time , current_time, exp;
int i, fd;
- if(!(toys.optflags & FLAG_f)) TT.file = "/var/lib/misc/udhcpd.leases"; //DEF_LEASE_FILE
+ if(!(toys.optflags & FLAG_f)) TT.file = "/var/lib/misc/dhcpd.leases"; //DEF_LEASE_FILE
fd = xopen(TT.file, O_RDONLY);
xprintf("Mac Address IP Address Host Name Expires %s\n", (toys.optflags & FLAG_a) ? "at" : "in");
xread(fd, &written_time, sizeof(written_time));
diff --git a/toys/pending/hexedit.c b/toys/pending/hexedit.c
new file mode 100644
index 00000000..518755bb
--- /dev/null
+++ b/toys/pending/hexedit.c
@@ -0,0 +1,250 @@
+/* hexedit.c - Hexadecimal file editor
+ *
+ * Copyright 2015 Rob Landley <rob@landley.net>
+ *
+ * No standard
+
+USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN))
+
+config HEXEDIT
+ bool "hexedit"
+ default y
+ help
+ usage: hexedit FILENAME
+
+ Hexadecimal file editor.
+
+ -r Read only (display but don't edit)
+*/
+
+#define FOR_hexedit
+#include "toys.h"
+
+GLOBALS(
+ char *data;
+ long long len, base;
+ int numlen;
+ unsigned height;
+)
+
+static void esc(char *s)
+{
+ printf("\033[%s", s);
+}
+
+static void jump(int x, int y)
+{
+ char s[32];
+
+ sprintf(s, "%d;%dH", y+1, x+1);
+ esc(s);
+}
+
+static void fix_terminal(void)
+{
+ set_terminal(1, 0, 0);
+ esc("?25h");
+ esc("0m");
+ jump(0, 999);
+}
+
+static void sigttyreset(int i)
+{
+ fix_terminal();
+ // how do I re-raise the signal so it dies with right signal info for wait()?
+ _exit(127);
+}
+
+// Render all characters printable, using color to distinguish.
+static void draw_char(char broiled)
+{
+ if (broiled<32 || broiled>=127) {
+ if (broiled>127) {
+ esc("2m");
+ broiled &= 127;
+ }
+ if (broiled<32 || broiled==127) {
+ esc("7m");
+ if (broiled==127) broiled = 32;
+ else broiled += 64;
+ }
+ printf("%c", broiled);
+ esc("0m");
+ } else printf("%c", broiled);
+}
+
+static void draw_line(long long yy)
+{
+ int x;
+
+ yy = (TT.base+yy)*16;
+
+ if (yy<TT.len) {
+ printf("\r%0*llX ", TT.numlen, yy);
+ for (x=0; x<16; x++) {
+ if (yy+x<TT.len) printf(" %02X", TT.data[yy+x]);
+ else printf(" ");
+ }
+ printf(" ");
+ for (x=0; x<16; x++) draw_char(TT.data[yy+x]);
+ }
+ esc("K");
+}
+
+static void draw_page(void)
+{
+ int y;
+
+ jump(0, 0);
+ for (y = 0; y<TT.height; y++) {
+ if (y) printf("\r\n");
+ draw_line(y);
+ }
+}
+
+// side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only
+static void highlight(int xx, int yy, int side)
+{
+ char cc = TT.data[16*(TT.base+yy)+xx];
+ int i;
+
+ // Display cursor
+ jump(2+TT.numlen+3*xx, yy);
+ esc("0m");
+ if (side!=2) esc("7m");
+ if (side>1) printf("%02X", cc);
+ else for (i=0; i<2;) {
+ if (side==i) esc("32m");
+ printf("%X", (cc>>(4*(1&++i)))&15);
+ }
+ esc("0m");
+ jump(TT.numlen+17*3+xx, yy);
+ draw_char(cc);
+}
+
+#define KEY_UP 256
+#define KEY_DOWN 257
+#define KEY_RIGHT 258
+#define KEY_LEFT 259
+#define KEY_PGUP 260
+#define KEY_PGDN 261
+#define KEY_HOME 262
+#define KEY_END 263
+#define KEY_INSERT 264
+
+void hexedit_main(void)
+{
+ // up down right left pgup pgdn home end ins
+ char *keys[] = {"\033[A", "\033[B", "\033[C", "\033[D", "\033[5~", "\033[6~",
+ "\033OH", "\033OF", "\033[2~", 0};
+ long long pos;
+ int x, y, i, side = 0, key, ro = toys.optflags&FLAG_r,
+ fd = xopen(*toys.optargs, ro ? O_RDONLY : O_RDWR);
+
+ TT.height = 25;
+ terminal_size(0, &TT.height);
+ if (TT.height) TT.height--;
+ sigatexit(sigttyreset);
+ esc("0m");
+ esc("?25l");
+ fflush(0);
+ set_terminal(1, 1, 0);
+
+ if ((TT.len = fdlength(fd))<0) error_exit("bad length");
+ if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX;
+ // count file length hex digits, rounded up to multiple of 4
+ for (pos = TT.len, TT.numlen = 0; pos; pos >>= 4, TT.numlen++);
+ TT.numlen += (4-TT.numlen)&3;
+
+ TT.data = mmap(0, TT.len, PROT_READ|(PROT_WRITE*!ro), MAP_SHARED, fd, 0);
+
+ draw_page();
+
+ y = x = 0;
+ for (;;) {
+ // Get position within file, trimming if we overshot end.
+ pos = 16*(TT.base+y)+x;
+ if (pos>=TT.len) {
+ pos = TT.len-1;
+ x = (TT.len-1)%15;
+ }
+
+ // Display cursor
+ highlight(x, y, ro ? 3 : side);
+ fflush(0);
+
+ // Wait for next key
+ key = scan_key(toybuf, keys, 1);
+ // Exit for q, ctrl-c, ctrl-d, escape, or EOF
+ if (key==-1 || key==3 || key==4 || key==27 || key=='q') break;
+ highlight(x, y, 2);
+
+ if (key>='a' && key<='f') key-=32;
+ if (!ro && ((key>='0' && key<='9') || (key>='A' && key<='F'))) {
+ i = key - '0';
+ if (i>9) i -= 7;
+ TT.data[pos] &= 15<<(4*side);
+ TT.data[pos] |= i<<(4*!side);
+
+ highlight(x, y, ++side);
+ if (side==2) {
+ side = 0;
+ if (++pos<TT.len && ++x==16) {
+ x = 0;
+ if (++y == TT.height) {
+ --y;
+ goto down;
+ }
+ }
+ }
+ }
+ if (key>255) side = 0;
+ if (key==KEY_UP) {
+ if (--y<0) {
+ if (TT.base) {
+ TT.base--;
+ esc("1T");
+ jump(0, TT.height);
+ esc("K");
+ jump(0, 0);
+ draw_line(0);
+ }
+ y = 0;
+ }
+ } else if (key==KEY_DOWN) {
+ if (y == TT.height-1 && pos+32<TT.len) {
+down:
+ TT.base++;
+ esc("1S");
+ jump(0, TT.height-1);
+ draw_line(TT.height-1);
+ }
+ if (pos+16<TT.len && ++y>=TT.height) y--;
+ } else if (key==KEY_RIGHT) {
+ if (x<15 && pos+1<TT.len) x++;
+ } else if (key==KEY_LEFT) {
+ if (x) x--;
+ } else if (key==KEY_PGUP) {
+ TT.base -= TT.height;
+ if (TT.base<0) TT.base = 0;
+ draw_page();
+ } else if (key==KEY_PGDN) {
+ TT.base += TT.height;
+ if ((TT.base*16)>=TT.len) TT.base=(TT.len-1)/16;
+ while ((TT.base+y)*16>=TT.len) y--;
+ if (16*(TT.base+y)+x>=TT.len) x = (TT.len-1)&15;
+ draw_page();
+ } else if (key==KEY_HOME) {
+ TT.base = 0;
+ x = 0;
+ draw_page();
+ } else if (key==KEY_END) {
+ TT.base=(TT.len-1)/16;
+ x = (TT.len-1)&15;
+ draw_page();
+ }
+ }
+ munmap(TT.data, TT.len);
+ close(fd);
+ fix_terminal();
+}
diff --git a/toys/pending/mdev.c b/toys/pending/mdev.c
index 2d98c257..0c496336 100644
--- a/toys/pending/mdev.c
+++ b/toys/pending/mdev.c
@@ -31,34 +31,53 @@ config MDEV_CONF
#include "toys.h"
-// todo, open() block devices to trigger partition scanning.
-
// mknod in /dev based on a path like "/sys/block/hda/hda1"
static void make_device(char *path)
{
- char *device_name, *s, *temp;
+ char *device_name = NULL, *s, *temp;
int major, minor, type, len, fd;
int mode = 0660;
uid_t uid = 0;
gid_t gid = 0;
- // Try to read major/minor string
-
- temp = strrchr(path, '/');
- fd = open(path, O_RDONLY);
- *temp=0;
- temp = toybuf;
- len = read(fd, temp, 64);
- close(fd);
- if (len<1) return;
- temp[len] = 0;
-
- // Determine device name, type, major and minor
-
- device_name = strrchr(path, '/') + 1;
- type = path[5]=='c' ? S_IFCHR : S_IFBLK;
- major = minor = 0;
- sscanf(temp, "%u:%u", &major, &minor);
+ if (path) {
+ // Try to read major/minor string
+
+ temp = strrchr(path, '/');
+ fd = open(path, O_RDONLY);
+ *temp=0;
+ temp = toybuf;
+ len = read(fd, temp, 64);
+ close(fd);
+ if (len<1) return;
+ temp[len] = 0;
+
+ // Determine device type, major and minor
+
+ type = path[5]=='c' ? S_IFCHR : S_IFBLK;
+ major = minor = 0;
+ sscanf(temp, "%u:%u", &major, &minor);
+ } else {
+ // if (!path), do hotplug
+
+ if (!(temp = getenv("SUBSYSTEM")))
+ return;
+ type = strcmp(temp, "block") ? S_IFCHR : S_IFBLK;
+ major = minor = 0;
+ if (!(temp = getenv("MAJOR")))
+ return;
+ sscanf(temp, "%u", &major);
+ if (!(temp = getenv("MINOR")))
+ return;
+ sscanf(temp, "%u", &minor);
+ path = getenv("DEVPATH");
+ device_name = getenv("DEVNAME");
+ if (!path)
+ return;
+ temp = toybuf;
+ }
+ if (!device_name)
+ device_name = strrchr(path, '/') + 1;
// If we have a config file, look up permissions for this device
@@ -167,9 +186,20 @@ found_device:
}
sprintf(temp, "/dev/%s", device_name);
+
+ if (getenv("ACTION") && !strcmp(getenv("ACTION"), "remove")) {
+ unlink(temp);
+ return;
+ }
+
+ if (strchr(device_name, '/'))
+ mkpathat(AT_FDCWD, temp, 0, 2);
if (mknod(temp, mode | type, makedev(major, minor)) && errno != EEXIST)
perror_exit("mknod %s failed", temp);
+
+ if (type == S_IFBLK) close(open(temp, O_RDONLY)); // scan for partitions
+
if (CFG_MDEV_CONF) mode=chown(temp, uid, gid);
}
@@ -202,7 +232,7 @@ void mdev_main(void)
if (toys.optflags) {
dirtree_read("/sys/class", callback);
dirtree_read("/sys/block", callback);
+ } else { // hotplug support
+ make_device(NULL);
}
-
- // hotplug support goes here
}
diff --git a/toys/posix/ls.c b/toys/posix/ls.c
index a9241379..9c4d6d3b 100644
--- a/toys/posix/ls.c
+++ b/toys/posix/ls.c
@@ -5,7 +5,7 @@
*
* See http://opengroup.org/onlinepubs/9699919799/utilities/ls.html
-USE_LS(NEWTOY(ls, USE_LS_COLOR("(color):;")"goACFHLRSacdfiklmnpqrstux1[-1Cglmnox][-cu][-ftS][-HL]", TOYFLAG_BIN|TOYFLAG_LOCALE))
+USE_LS(NEWTOY(ls, USE_LS_COLOR("(color):;")USE_LS_SMACK("Z")"goACFHLRSacdfiklmnpqrstux1[-Cxm1][-Cxml][-Cxmo][-Cxmg][-cu][-ftS][-HL]", TOYFLAG_BIN|TOYFLAG_LOCALE))
config LS
bool "ls"
@@ -32,6 +32,15 @@ config LS
sorting (default is alphabetical):
-f unsorted -r reverse -t timestamp -S size
+config LS_SMACK
+ bool
+ default y
+ depends on LS && TOYBOX_SMACK
+ help
+ usage: ls [-Z]
+
+ -Z security context
+
config LS_COLOR
bool "ls --color"
default y
@@ -128,6 +137,31 @@ static char *getgroupname(gid_t gid)
return gr ? gr->gr_name : TT.gid_buf;
}
+static int numlen(long long ll)
+{
+ return snprintf(0, 0, "%llu", ll);
+}
+
+// measure/print smack attributes. (If pad=0, just measure.)
+static unsigned zmack(struct dirtree *dt, int pad)
+{
+ char buf[SMACK_LABEL_LEN+1];
+ int fd = openat(dirtree_parentfd(dt), dt->name, O_PATH|O_NOFOLLOW);
+ ssize_t len = 1;
+
+ strcpy(buf, "?");
+ if (fd != -1) {
+ len = fgetxattr(fd, XATTR_NAME_SMACK, pad?buf:0, pad?SMACK_LABEL_LEN:0);
+ close(fd);
+
+ if (len<1 || len>SMACK_LABEL_LEN) len = 0;
+ else buf[len] = 0;
+ }
+ if (pad) printf(" %*s "+(pad>0), pad, buf);
+
+ return len;
+}
+
// Figure out size of printable entry fields for display indent/wrap
static void entrylen(struct dirtree *dt, unsigned *len)
@@ -139,21 +173,22 @@ static void entrylen(struct dirtree *dt, unsigned *len)
if (endtype(st)) ++*len;
if (flags & FLAG_m) ++*len;
- if (flags & FLAG_i) *len += (len[1] = numlen(st->st_ino));
+ len[1] = (flags & FLAG_i) ? numlen(st->st_ino) : 0;
if (flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g)) {
unsigned fn = flags & FLAG_n;
len[2] = numlen(st->st_nlink);
- len[3] = fn ? snprintf(0, 0, "%u", (unsigned)st->st_uid)
- : strwidth(getusername(st->st_uid));
- len[4] = fn ? snprintf(0, 0, "%u", (unsigned)st->st_gid)
- : strwidth(getgroupname(st->st_gid));
+ len[3] = fn ? numlen(st->st_uid) : strwidth(getusername(st->st_uid));
+ len[4] = fn ? numlen(st->st_gid) : strwidth(getgroupname(st->st_gid));
if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) {
// cheating slightly here: assuming minor is always 3 digits to avoid
// tracking another column
len[5] = numlen(major(st->st_rdev))+5;
} else len[5] = numlen(st->st_size);
}
- if (flags & FLAG_s) *len += (len[6] = numlen(st->st_blocks));
+
+ len[6] = (flags & FLAG_s) ? numlen(st->st_blocks) : 0;
+
+ if (CFG_LS_SMACK && (flags & FLAG_Z)) len[7] = zmack(dt, 0);
}
static int compare(void *a, void *b)
@@ -257,9 +292,9 @@ int color_from_mode(mode_t mode)
static void listfiles(int dirfd, struct dirtree *indir)
{
- struct dirtree *dt, **sort = 0;
- unsigned long dtlen = 0, ul = 0;
- unsigned width, flags = toys.optflags, totals[7], len[7],
+ struct dirtree *dt, **sort;
+ unsigned long dtlen, ul = 0;
+ unsigned width, flags = toys.optflags, totals[8], len[8], totpad = 0,
*colsizes = (unsigned *)(toybuf+260), columns = (sizeof(toybuf)-260)/4;
memset(totals, 0, sizeof(totals));
@@ -280,17 +315,12 @@ static void listfiles(int dirfd, struct dirtree *indir)
}
// Copy linked list to array and sort it. Directories go in array because
- // we visit them in sorted order.
-
- for (;;) {
- for (dt = indir->child; dt; dt = dt->next) {
+ // we visit them in sorted order too. (The nested loops let us measure and
+ // fill with the same inner loop.)
+ for (sort = 0;;sort = xmalloc(dtlen * sizeof(void *))) {
+ for (dtlen = 0, dt = indir->child; dt; dt = dt->next, dtlen++)
if (sort) sort[dtlen] = dt;
- dtlen++;
- }
if (sort) break;
- sort = xmalloc(dtlen * sizeof(void *));
- dtlen = 0;
- continue;
}
// Label directory if not top of tree, or if -R
@@ -303,7 +333,21 @@ static void listfiles(int dirfd, struct dirtree *indir)
free(path);
}
- if (!(flags & FLAG_f)) qsort(sort, dtlen, sizeof(void *), (void *)compare);
+ // Measure each entry to work out whitespace padding and total blocks
+ if (!(flags & FLAG_f)) {
+ unsigned long long blocks = 0;
+
+ qsort(sort, dtlen, sizeof(void *), (void *)compare);
+ for (ul = 0; ul<dtlen; ul++) {
+ entrylen(sort[ul], len);
+ for (width = 0; width<8; width++)
+ if (len[width]>totals[width]) totals[width] = len[width];
+ blocks += sort[ul]->st.st_blocks;
+ }
+ totpad = totals[1]+!!totals[1]+totals[6]+!!totals[6]+totals[7]+!!totals[7];
+ if (flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g|FLAG_s) && indir->parent)
+ xprintf("total %llu\n", blocks);
+ }
// Find largest entry in each field for display alignment
if (flags & (FLAG_C|FLAG_x)) {
@@ -320,29 +364,18 @@ static void listfiles(int dirfd, struct dirtree *indir)
memset(colsizes, 0, columns*sizeof(unsigned));
for (ul=0; ul<dtlen; ul++) {
entrylen(sort[next_column(ul, dtlen, columns, &c)], len);
+ *len += totpad;
if (c == columns) break;
- // Does this put us over budget?
+ // Expand this column if necessary, break if that puts us over budget
if (*len > colsizes[c]) {
- totlen += *len-colsizes[c];
+ totlen += (*len)-colsizes[c];
colsizes[c] = *len;
if (totlen > TT.screen_width) break;
}
}
- // If it fit, stop here
+ // If everything fit, stop here
if (ul == dtlen) break;
}
- } else if (flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g|FLAG_s)) {
- unsigned long blocks = 0;
-
- for (ul = 0; ul<dtlen; ul++)
- {
- entrylen(sort[ul], len);
- for (width=0; width<6; width++)
- if (len[width] > totals[width]) totals[width] = len[width];
- blocks += sort[ul]->st.st_blocks;
- }
-
- if (indir->parent) xprintf("total %lu\n", blocks);
}
// Loop through again to produce output.
@@ -375,8 +408,10 @@ static void listfiles(int dirfd, struct dirtree *indir)
}
width += *len;
- if (flags & FLAG_i) xprintf("%*lu ", len[1], (unsigned long)st->st_ino);
- if (flags & FLAG_s) xprintf("%*lu ", len[6], (unsigned long)st->st_blocks);
+ if (flags & FLAG_i)
+ xprintf("%*lu ", totals[1], (unsigned long)st->st_ino);
+ if (flags & FLAG_s)
+ xprintf("%*lu ", totals[6], (unsigned long)st->st_blocks);
if (flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g)) {
struct tm *tm;
@@ -402,14 +437,16 @@ static void listfiles(int dirfd, struct dirtree *indir)
printf("%s% *ld %s%s%s%s", perm, totals[2]+1, (long)st->st_nlink,
usr, upad, grp, grpad);
+ if (CFG_LS_SMACK && (flags & FLAG_Z)) zmack(sort[next], -(int)totals[7]);
+
if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
printf("% *d,% 4d", totals[5]-4, major(st->st_rdev),minor(st->st_rdev));
- else printf("% *"PRId64, totals[5]+1, (int64_t)st->st_size);
+ else printf("% *lld", totals[5]+1, (long long)st->st_size);
tm = localtime(&(st->st_mtime));
strftime(thyme, sizeof(thyme), "%F %H:%M", tm);
xprintf(" %s ", thyme);
- }
+ } else if (CFG_LS_SMACK && (flags & FLAG_Z)) zmack(sort[next], totals[7]);
if (flags & FLAG_color) {
color = color_from_mode(st->st_mode);
@@ -441,7 +478,7 @@ static void listfiles(int dirfd, struct dirtree *indir)
// Pad columns
if (flags & (FLAG_C|FLAG_x)) {
- curcol = colsizes[curcol] - *len;
+ curcol = colsizes[curcol]-(*len)-totpad;
if (curcol < 255) xprintf("%s", toybuf+255-curcol);
}
}
diff --git a/www/roadmap.html b/www/roadmap.html
index 26146e42..bba08b1b 100755
--- a/www/roadmap.html
+++ b/www/roadmap.html
@@ -231,13 +231,14 @@ git repository</a>.</p>
<h3>Toolbox commands:</h3>
-<p>According to system/core/toolbox/Android.mk the toolbox directory builds
-the following commands:</p>
+<p>According to <a href=https://android.googlesource.com/platform/system/core/+/master/toolbox/Android.mk>
+system/core/toolbox/Android.mk</a> the toolbox directory builds the
+following commands:</p>
<blockquote><b>
-dd du df getevent getprop iftop ioctl ionice load_policy log ls
-lsof mount nandread newfs_msdos ps prlimit renice restorecon
-sendevent setprop start stop top uptime watchprops
+dd du df getevent iftop ioctl ionice log ls
+lsof mount nandread newfs_msdos ps prlimit renice
+sendevent start stop top uptime watchprops
</b></blockquote>
<h3>Other Android core commands</h3>
@@ -269,14 +270,15 @@ bespoke code to install itself.</p>
<p>For reference, combining everything listed above, we get:</p>
<blockquote><b>
-dd du df getevent getprop gpttool iftop init ioctl ionice
+dd du df getevent gpttool iftop init ioctl ionice
log logcat logwrapper ls lsof mkbootimg mount nandread
-newfs_msdos ps prlimit reboot renice restorecon run-as
-sendevent setprop start stop top uptime watchprops
+newfs_msdos ps prlimit reboot renice run-as
+sendevent start stop top uptime watchprops
</b></blockquote>
<p>We may eventually implement all of that, but for toybox 1.0 we need to
-focus a bit. For our first pass, let's ignore selinux,
+focus a bit. For our first pass, let's ignore selinux [note: the android
+guys submitted selinux code to us and we merged it],
and grab just logcat and logwrapper from the "core"
commands (since the rest have some full/standard version providing that
functionality, which we can implement a shim interface for later).</p>
@@ -284,9 +286,9 @@ functionality, which we can implement a shim interface for later).</p>
<p>This means toybox should implement (or finish implementing):</p>
<blockquote><b>
<span id=toolbox>
-dd du df getevent getprop iftop ioctl ionice log logcat logwrapper ls lsof
+dd du df getevent iftop ioctl ionice log logcat logwrapper ls lsof
mount nandread newfs_msdos ps prlimit renice schedtop sendevent
-setprop smd start stop top uptime watchprops
+smd start stop top uptime watchprops
</span>
</b></blockquote>
@@ -305,7 +307,7 @@ arch base64 users dir vdir unexpand shred join csplit
hostid nproc runcon sha224 sha256 sha384 sha512 sha3 mkfs.vfat fsck.vfat
dosfslabel uname stdbuf pinky diff3 sdiff zcmp zdiff zegrep zfgrep zless zmore
</span>
-</blockquote>
+</b></blockquote>
<p>In addition, they'd like to use several commands currently in pending:</p>
@@ -315,6 +317,10 @@ tar diff printf wget rsync fdisk vi less tr test stty fold expr dd
</span>
</b></blockquote>
+<p>Also, tizen uses a different Linux Security Module called SMACK, so
+many of the SELinux options ala ls -Z need smack alternatives in an
+if/else setup.</p>
+
<hr /><a name=klibc />
<h2>klibc:</h2>
@@ -378,7 +384,7 @@ from it into a /proc file, something the kernel is capable of doing itself.
<a href=http://www.zytor.com/pipermail/klibc/2006-June/001748.html>attempted
to remove</a> that capability from the kernel, current kernel/power/hibernate.c
still parses "resume=" on the command line). And yet various distros seem to
-make use of klibc for this>
+make use of klibc for this.
Given the history of swsusp/hibernate (and
<a href=http://lwn.net/Articles/333007>TuxOnIce</a>
and <a href=http://lwn.net/Articles/242107>kexec jump</a>) I've lost track