busybox: squashed commit of merging cm-12.1

With fixes to LOCAL_C_INCLUDES for libsepol in M and fixed
some missing includes to enable building for 64 bit devices

Conflicts:
	Android.mk
	android/libc/arch-x86/syscalls/swapoff.S
	android/libc/arch-x86/syscalls/swapon.S
	android/libc/arch-x86/syscalls/sysinfo.S
	android/librpc/pmap_rmt.c
	android/reboot.c
	include-full/copy-current.sh
	include-minimal/copy-current.sh
	include/platform.h
	networking/interface.c
	networking/nslookup.c

Change-Id: If6092fa87f3d21190db1af4f70daa150eb462660
diff --git a/libbb/Kbuild.src b/libbb/Kbuild.src
index 61eec26..a6468f1 100644
--- a/libbb/Kbuild.src
+++ b/libbb/Kbuild.src
@@ -27,8 +27,6 @@
 lib-y += copy_file.o
 lib-y += copyfd.o
 lib-y += crc32.o
-lib-y += create_icmp6_socket.o
-lib-y += create_icmp_socket.o
 lib-y += default_error_retval.o
 lib-y += device_open.o
 lib-y += dump.o
@@ -146,7 +144,7 @@
 lib-$(CONFIG_PASSWD) += pw_encrypt.o update_passwd.o obscure.o
 lib-$(CONFIG_CHPASSWD) += pw_encrypt.o update_passwd.o
 lib-$(CONFIG_CRYPTPW) += pw_encrypt.o
-lib-$(CONFIG_SULOGIN) += pw_encrypt.o
+lib-$(CONFIG_SULOGIN) += pw_encrypt.o correct_password.o
 lib-$(CONFIG_VLOCK) += pw_encrypt.o correct_password.o
 lib-$(CONFIG_SU) += pw_encrypt.o correct_password.o
 lib-$(CONFIG_LOGIN) += pw_encrypt.o correct_password.o
diff --git a/libbb/android.c b/libbb/android.c
deleted file mode 100644
index 8f2c3c4..0000000
--- a/libbb/android.c
+++ /dev/null
@@ -1,96 +0,0 @@
-/* vi: set sw=4 ts=4: */
-/*
- * Android/bionic glue.
- *
- * Copyright (C) 2010 by Dylan Simon <dylan@dylex.net>
- *
- * Licensed under GPLv2, see file LICENSE in this tarball for details.
- */
-
-#include <stdlib.h>
-#include "libbb.h"
-
-/* declared in stdlib.h */
-int clearenv()
-{
-	environ = NULL;
-	return 0;
-}
-
-/* bionic/stubs.c:ttyname not implemented anyway */
-int ttyname_r(int fd, char *name, size_t namesize)
-{
-	char *t = ttyname(fd);
-	if (!t)
-		return -1;
-	strncpy(name, ttyname(fd), namesize);
-	return 0;
-}
-
-/* no /etc/shells anyway */
-char *getusershell() { return NULL; }
-void setusershell() {}
-void endusershell() {}
-
-struct mntent *getmntent_r(FILE *fp, struct mntent *mnt, char *buf, int buflen)
-{
-	char *tokp = NULL, *s;
-
-	do {
-		if (!fgets(buf, buflen, fp))
-			return NULL;
-		tokp = 0;
-		s = strtok_r(buf, " \t\n", &tokp);
-	} while (!s || *s == '#');
-
-	mnt->mnt_fsname = s;
-	mnt->mnt_freq = mnt->mnt_passno = 0;
-	if (!(mnt->mnt_dir = strtok_r(NULL, " \t\n", &tokp)))
-		return NULL;
-	if (!(mnt->mnt_type = strtok_r(NULL, " \t\n", &tokp)))
-		return NULL;
-	if (!(mnt->mnt_opts = strtok_r(NULL, " \t\n", &tokp)))
-		mnt->mnt_opts = "";
-	else if ((s = strtok_r(NULL, " \t\n", &tokp)))
-	{
-		mnt->mnt_freq = atoi(s);
-		if ((s = strtok_r(NULL, " \t\n", &tokp)))
-			mnt->mnt_passno = atoi(s);
-	}
-
-	return mnt;
-}
-
-/* override definition in bionic/stubs.c */
-struct mntent *getmntent(FILE *fp)
-{
-	static struct mntent mnt;
-	static char buf[256];
-	return getmntent_r(fp, &mnt, buf, 256);
-}
-
-/* not used anyway */
-int addmntent(FILE *fp, const struct mntent *mnt)
-{
-	errno = ENOENT;
-	return 1;
-}
-
-const char *hasmntopt(const struct mntent *mnt, const char *opt)
-{
-	const char *o = mnt->mnt_opts;
-	size_t l = strlen(opt);
-
-	while ((o = strstr(o, opt)) && 
-			((o > mnt->mnt_opts && o[-1] != ',') || 
-			 (o[l] != 0 && o[l] != ',' && o[l] != '=')));
-	return o;
-}
-
-/* declared in grp.h, but not necessary */
-#if !ENABLE_USE_BB_PWD_GRP
-int setpwent() { return 0; }
-void setgrent() {}
-void endgrent() {}
-#endif
-
diff --git a/libbb/appletlib.c b/libbb/appletlib.c
index fc1847a..5e4f4a9 100644
--- a/libbb/appletlib.c
+++ b/libbb/appletlib.c
@@ -29,7 +29,7 @@
 #include "busybox.h"
 
 #if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \
-        || defined(__APPLE__) \
+    || defined(__APPLE__) \
     )
 # include <malloc.h> /* for mallopt */
 #endif
@@ -750,7 +750,7 @@
 //TODO: just compare applet_no with APPLET_NO_test
 		if (!ENABLE_TEST || strcmp(applet_name, "test") != 0) {
 			/* If you want "foo --help" to return 0: */
-			/*xfunc_error_retval = 0;*/
+			xfunc_error_retval = 0;
 			bb_show_usage();
 		}
 	}
diff --git a/libbb/bb_askpass.c b/libbb/bb_askpass.c
index fe2b506..77c1bcd 100644
--- a/libbb/bb_askpass.c
+++ b/libbb/bb_askpass.c
@@ -65,7 +65,9 @@
 	i = 0;
 	while (1) {
 		int r = read(fd, &ret[i], 1);
-		if (r < 0) {
+		if ((i == 0 && r == 0) /* EOF (^D) with no password */
+		 || r < 0
+		) {
 			/* read is interrupted by timeout or ^C */
 			ret = NULL;
 			break;
diff --git a/libbb/bb_pwd.c b/libbb/bb_pwd.c
index 4829b72..d5e651c 100644
--- a/libbb/bb_pwd.c
+++ b/libbb/bb_pwd.c
@@ -15,9 +15,31 @@
  * pointers to static data (getpwuid)
  */
 
-struct passwd* FAST_FUNC xgetpwnam(const char *name)
+struct passwd* FAST_FUNC safegetpwnam(const char *name)
 {
 	struct passwd *pw = getpwnam(name);
+#ifdef __BIONIC__
+	if (pw && !pw->pw_passwd) {
+		pw->pw_passwd = "";
+	}
+#endif
+	return pw;
+}
+
+struct passwd* FAST_FUNC safegetpwuid(uid_t uid)
+{
+	struct passwd *pw = getpwuid(uid);
+#ifdef __BIONIC__
+	if (pw && !pw->pw_passwd) {
+		pw->pw_passwd = "";
+	}
+#endif
+	return pw;
+}
+
+struct passwd* FAST_FUNC xgetpwnam(const char *name)
+{
+	struct passwd *pw = safegetpwnam(name);
 	if (!pw)
 		bb_error_msg_and_die("unknown user %s", name);
 	return pw;
@@ -31,10 +53,9 @@
 	return gr;
 }
 
-
 struct passwd* FAST_FUNC xgetpwuid(uid_t uid)
 {
-	struct passwd *pw = getpwuid(uid);
+	struct passwd *pw = safegetpwuid(uid);
 	if (!pw)
 		bb_error_msg_and_die("unknown uid %u", (unsigned)uid);
 	return pw;
@@ -110,3 +131,51 @@
 		return xname2id(s);
 	return r;
 }
+
+/* Experimental "mallocing" API.
+ * The goal is nice: "we want to support a case when "guests" group is very large"
+ * but the code is butt-ugly.
+ */
+#if 0
+static char *find_latest(char last, char *cp)
+{
+	if (!cp)
+		return last;
+	cp += strlen(cp) + 1;
+	if (last < cp)
+		last = cp;
+	return last;
+}
+
+struct group* FAST_FUNC xmalloc_getgrnam(const char *name)
+{
+	struct {
+		struct group gr;
+		// May still be not enough!
+		char buf[64*1024 - sizeof(struct group) - 16];
+	} *s;
+	struct group *grp;
+	int r;
+	char *last;
+	char **gr_mem;
+
+	s = xmalloc(sizeof(*s));
+	r = getgrnam_r(name, &s->gr, s->buf, sizeof(s->buf), &grp);
+	if (!grp) {
+		free(s);
+		return grp;
+	}
+	last = find_latest(s->buf, grp->gr_name);
+	last = find_latest(last, grp->gr_passwd);
+	gr_mem = grp->gr_mem;
+	while (*gr_mem)
+		last = find_latest(last, *gr_mem++);
+	gr_mem++; /* points past NULL */
+	if (last < (char*)gr_mem)
+		last = (char*)gr_mem;
+//FIXME: what if we get not only truncated, but also moved here?
+// grp->gr_name pointer and friends are invalid now!!!
+	s = xrealloc(s, last - (char*)s);
+	return grp;
+}
+#endif
diff --git a/libbb/correct_password.c b/libbb/correct_password.c
index 7cabd33..acadf39 100644
--- a/libbb/correct_password.c
+++ b/libbb/correct_password.c
@@ -31,12 +31,15 @@
 #include "libbb.h"
 
 /* Ask the user for a password.
+ * Return 1 without asking if PW has an empty password.
+ * Return -1 on EOF, error while reading input, or timeout.
  * Return 1 if the user gives the correct password for entry PW,
- * 0 if not.  Return 1 without asking if PW has an empty password.
+ * 0 if not.
  *
- * NULL pw means "just fake it for login with bad username" */
-
-int FAST_FUNC correct_password(const struct passwd *pw)
+ * NULL pw means "just fake it for login with bad username"
+ */
+int FAST_FUNC ask_and_check_password_extended(const struct passwd *pw,
+		int timeout, const char *prompt)
 {
 	char *unencrypted, *encrypted;
 	const char *correct;
@@ -65,13 +68,19 @@
 		return 1;
 
  fake_it:
-	unencrypted = bb_ask_stdin("Password: ");
+	unencrypted = bb_ask(STDIN_FILENO, timeout, prompt);
 	if (!unencrypted) {
-		return 0;
+		/* EOF (such as ^D) or error (such as ^C) or timeout */
+		return -1;
 	}
 	encrypted = pw_encrypt(unencrypted, correct, 1);
 	r = (strcmp(encrypted, correct) == 0);
 	free(encrypted);
-	memset(unencrypted, 0, strlen(unencrypted));
+	nuke_str(unencrypted);
 	return r;
 }
+
+int FAST_FUNC ask_and_check_password(const struct passwd *pw)
+{
+	return ask_and_check_password_extended(pw, 0, "Password: ");
+}
diff --git a/libbb/create_icmp6_socket.c b/libbb/create_icmp6_socket.c
deleted file mode 100644
index bdee7a0..0000000
--- a/libbb/create_icmp6_socket.c
+++ /dev/null
@@ -1,38 +0,0 @@
-/* vi: set sw=4 ts=4: */
-/*
- * Utility routines.
- *
- * create raw socket for icmp (IPv6 version) protocol
- * and drop root privileges if running setuid
- *
- * Licensed under GPLv2, see file LICENSE in this source tree.
- */
-
-#include "libbb.h"
-
-#if ENABLE_FEATURE_IPV6
-int FAST_FUNC create_icmp6_socket(void)
-{
-	int sock;
-#if 0
-	struct protoent *proto;
-	proto = getprotobyname("ipv6-icmp");
-	/* if getprotobyname failed, just silently force
-	 * proto->p_proto to have the correct value for "ipv6-icmp" */
-	sock = socket(AF_INET6, SOCK_RAW,
-			(proto ? proto->p_proto : IPPROTO_ICMPV6));
-#else
-	sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
-#endif
-	if (sock < 0) {
-		if (errno == EPERM)
-			bb_error_msg_and_die("%s", bb_msg_perm_denied_are_you_root);
-		bb_perror_msg_and_die("%s", bb_msg_can_not_create_raw_socket);
-	}
-
-	/* drop root privs if running setuid */
-	xsetuid(getuid());
-
-	return sock;
-}
-#endif
diff --git a/libbb/create_icmp_socket.c b/libbb/create_icmp_socket.c
deleted file mode 100644
index 65eea3b..0000000
--- a/libbb/create_icmp_socket.c
+++ /dev/null
@@ -1,36 +0,0 @@
-/* vi: set sw=4 ts=4: */
-/*
- * Utility routines.
- *
- * create raw socket for icmp protocol
- * and drop root privileges if running setuid
- *
- * Licensed under GPLv2, see file LICENSE in this source tree.
- */
-
-#include "libbb.h"
-
-int FAST_FUNC create_icmp_socket(void)
-{
-	int sock;
-#if 0
-	struct protoent *proto;
-	proto = getprotobyname("icmp");
-	/* if getprotobyname failed, just silently force
-	 * proto->p_proto to have the correct value for "icmp" */
-	sock = socket(AF_INET, SOCK_RAW,
-			(proto ? proto->p_proto : 1)); /* 1 == ICMP */
-#else
-	sock = socket(AF_INET, SOCK_RAW, 1); /* 1 == ICMP */
-#endif
-	if (sock < 0) {
-		if (errno == EPERM)
-			bb_error_msg_and_die("%s", bb_msg_perm_denied_are_you_root);
-		bb_perror_msg_and_die("%s", bb_msg_can_not_create_raw_socket);
-	}
-
-	/* drop root privs if running setuid */
-	xsetuid(getuid());
-
-	return sock;
-}
diff --git a/libbb/dump.c b/libbb/dump.c
index 91efe41..b465c5d 100644
--- a/libbb/dump.c
+++ b/libbb/dump.c
@@ -333,7 +333,7 @@
 			return;
 		}
 	}
-	if (fseek(stdin, dumper->pub.dump_skip, SEEK_SET)) {
+	if (fseeko(stdin, dumper->pub.dump_skip, SEEK_SET)) {
 		bb_simple_perror_msg_and_die(fname);
 	}
 	dumper->address += dumper->pub.dump_skip;
diff --git a/libbb/endofname.c b/libbb/endofname.c
new file mode 100644
index 0000000..305f093
--- /dev/null
+++ b/libbb/endofname.c
@@ -0,0 +1,26 @@
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2013 Denys Vlasenko
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+
+//kbuild:lib-y += endofname.o
+
+#include "libbb.h"
+
+#define is_name(c)      ((c) == '_' || isalpha((unsigned char)(c)))
+#define is_in_name(c)   ((c) == '_' || isalnum((unsigned char)(c)))
+
+const char* FAST_FUNC
+endofname(const char *name)
+{
+	if (!is_name(*name))
+		return name;
+	while (*++name) {
+		if (!is_in_name(*name))
+			break;
+	}
+	return name;
+}
diff --git a/libbb/fclose_nonstdin.c b/libbb/fclose_nonstdin.c
index 5ce9d5b..1b14413 100644
--- a/libbb/fclose_nonstdin.c
+++ b/libbb/fclose_nonstdin.c
@@ -18,7 +18,8 @@
 {
 	/* Some more paranoid applets want ferror() check too */
 	int r = ferror(f); /* NB: does NOT set errno! */
-	if (r) errno = EIO; /* so we'll help it */
+	if (r)
+		errno = EIO; /* so we'll help it */
 	if (f != stdin)
 		return (r | fclose(f)); /* fclose does set errno on error */
 	return r;
diff --git a/libbb/hash_md5_sha.c b/libbb/hash_md5_sha.c
index b4d955e..3f743ac 100644
--- a/libbb/hash_md5_sha.c
+++ b/libbb/hash_md5_sha.c
@@ -84,7 +84,7 @@
 			if (swap_needed)
 				t = bb_bswap_64(t);
 			/* wbuffer is suitably aligned for this */
-			*(uint64_t *) (&ctx->wbuffer[64 - 8]) = t;
+			*(bb__aliased_uint64_t *) (&ctx->wbuffer[64 - 8]) = t;
 		}
 		ctx->process_block(ctx);
 		if (remaining >= 8)
@@ -883,10 +883,10 @@
 			uint64_t t;
 			t = ctx->total64[0] << 3;
 			t = SWAP_BE64(t);
-			*(uint64_t *) (&ctx->wbuffer[128 - 8]) = t;
+			*(bb__aliased_uint64_t *) (&ctx->wbuffer[128 - 8]) = t;
 			t = (ctx->total64[1] << 3) | (ctx->total64[0] >> 61);
 			t = SWAP_BE64(t);
-			*(uint64_t *) (&ctx->wbuffer[128 - 16]) = t;
+			*(bb__aliased_uint64_t *) (&ctx->wbuffer[128 - 16]) = t;
 		}
 		sha512_process_block128(ctx);
 		if (remaining >= 16)
diff --git a/libbb/human_readable.c b/libbb/human_readable.c
index 8b22b0c..0b2eb77 100644
--- a/libbb/human_readable.c
+++ b/libbb/human_readable.c
@@ -94,7 +94,7 @@
 
 /* Convert unsigned long long value into compact 5-char representation.
  * String is not terminated (buf[5] is untouched) */
-void FAST_FUNC smart_ulltoa5(unsigned long long ul, char buf[5], const char *scale)
+char* FAST_FUNC smart_ulltoa5(unsigned long long ul, char buf[5], const char *scale)
 {
 	const char *fmt;
 	char c;
@@ -145,12 +145,13 @@
 		buf[3] = "0123456789"[v];
 		buf[4] = scale[idx]; /* typically scale = " kmgt..." */
 	}
+	return buf + 5;
 }
 
 /* Convert unsigned long long value into compact 4-char
  * representation. Examples: "1234", "1.2k", " 27M", "123T"
  * String is not terminated (buf[4] is untouched) */
-void FAST_FUNC smart_ulltoa4(unsigned long long ul, char buf[4], const char *scale)
+char* FAST_FUNC smart_ulltoa4(unsigned long long ul, char buf[4], const char *scale)
 {
 	const char *fmt;
 	char c;
@@ -194,4 +195,5 @@
 		buf[2] = "0123456789"[v];
 		buf[3] = scale[idx]; /* typically scale = " kmgt..." */
 	}
+	return buf + 4;
 }
diff --git a/libbb/in_ether.c b/libbb/in_ether.c
new file mode 100644
index 0000000..1de383b
--- /dev/null
+++ b/libbb/in_ether.c
@@ -0,0 +1,59 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ */
+
+//kbuild:lib-$(CONFIG_ARP) += in_ether.o
+//kbuild:lib-$(CONFIG_IFCONFIG) += in_ether.o
+//kbuild:lib-$(CONFIG_IFENSLAVE) += in_ether.o
+
+#include "libbb.h"
+#include <net/if_arp.h>
+#include <net/ethernet.h>
+
+/* Convert Ethernet address from "XX[:]XX[:]XX[:]XX[:]XX[:]XX" to sockaddr.
+ * Return nonzero on error.
+ */
+int FAST_FUNC in_ether(const char *bufp, struct sockaddr *sap)
+{
+	char *ptr;
+	int i, j;
+	unsigned char val;
+	unsigned char c;
+
+	sap->sa_family = ARPHRD_ETHER;
+	ptr = (char *) sap->sa_data;
+
+	i = ETH_ALEN;
+	goto first;
+	do {
+		/* We might get a semicolon here */
+		if (*bufp == ':')
+			bufp++;
+ first:
+		j = val = 0;
+		do {
+			c = *bufp;
+			if (((unsigned char)(c - '0')) <= 9) {
+				c -= '0';
+			} else if ((unsigned char)((c|0x20) - 'a') <= 5) {
+				c = (unsigned char)((c|0x20) - 'a') + 10;
+			} else {
+				if (j && (c == ':' || c == '\0'))
+					/* One-digit byte: __:X:__ */
+					break;
+				return -1;
+			}
+			++bufp;
+			val <<= 4;
+			val += c;
+			j ^= 1;
+		} while (j);
+
+		*ptr++ = val;
+
+	} while (--i);
+
+	/* Error if we aren't at end of string */
+	return *bufp;
+}
diff --git a/libbb/inet_common.c b/libbb/inet_common.c
index 0f4fca1..b3e0802 100644
--- a/libbb/inet_common.c
+++ b/libbb/inet_common.c
@@ -175,8 +175,7 @@
 		return -1;
 	}
 	memcpy(sin6, ai->ai_addr, sizeof(*sin6));
-	if (ai)
-		freeaddrinfo(ai);
+	freeaddrinfo(ai);
 	return 0;
 }
 
diff --git a/libbb/inode_hash.c b/libbb/inode_hash.c
index 715535e..377ecbc 100644
--- a/libbb/inode_hash.c
+++ b/libbb/inode_hash.c
@@ -71,7 +71,7 @@
 /* Clear statbuf hash table */
 void FAST_FUNC reset_ino_dev_hashtable(void)
 {
-	int i;
+	unsigned i;
 	ino_dev_hashtable_bucket_t *bucket;
 
 	for (i = 0; ino_dev_hashtable && i < HASH_SIZE; i++) {
diff --git a/libbb/lineedit.c b/libbb/lineedit.c
index 0da625f..f897e96 100644
--- a/libbb/lineedit.c
+++ b/libbb/lineedit.c
@@ -38,6 +38,14 @@
  * and the \] escape to signal the end of such a sequence. Example:
  *
  * PS1='\[\033[01;32m\]\u@\h\[\033[01;34m\] \w \$\[\033[00m\] '
+ *
+ * Unicode in PS1 is not fully supported: prompt length calulation is wrong,
+ * resulting in line wrap problems with long (multi-line) input.
+ *
+ * Multi-line PS1 (e.g. PS1="\n[\w]\n$ ") has problems with history
+ * browsing: up/down arrows result in scrolling.
+ * It stems from simplistic "cmdedit_y = cmdedit_prmt_len / cmdedit_termw"
+ * calculation of how many lines the prompt takes.
  */
 #include "libbb.h"
 #include "unicode.h"
@@ -133,9 +141,6 @@
 	CHAR_T *command_ps;
 
 	const char *cmdedit_prompt;
-#if ENABLE_FEATURE_EDITING_FANCY_PROMPT
-	int num_ok_lines; /* = 1; */
-#endif
 
 #if ENABLE_USERNAME_OR_HOMEDIR
 	char *user_buf;
@@ -172,7 +177,6 @@
 #define command_len      (S.command_len     )
 #define command_ps       (S.command_ps      )
 #define cmdedit_prompt   (S.cmdedit_prompt  )
-#define num_ok_lines     (S.num_ok_lines    )
 #define user_buf         (S.user_buf        )
 #define home_pwd_buf     (S.home_pwd_buf    )
 #define matches          (S.matches         )
@@ -185,8 +189,8 @@
 	(*(struct lineedit_statics**)&lineedit_ptr_to_statics) = xzalloc(sizeof(S)); \
 	barrier(); \
 	cmdedit_termw = 80; \
-	IF_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines = 1;) \
 	IF_USERNAME_OR_HOMEDIR(home_pwd_buf = (char*)null_str;) \
+	IF_FEATURE_EDITING_VI(delptr = delbuf;) \
 } while (0)
 
 static void deinit_S(void)
@@ -1251,14 +1255,16 @@
 {
 	line_input_t *n = xzalloc(sizeof(*n));
 	n->flags = flags;
+#if MAX_HISTORY > 0
 	n->max_history = MAX_HISTORY;
+#endif
 	return n;
 }
 
 
 #if MAX_HISTORY > 0
 
-unsigned size_from_HISTFILESIZE(const char *hp)
+unsigned FAST_FUNC size_from_HISTFILESIZE(const char *hp)
 {
 	int size = MAX_HISTORY;
 	if (hp) {
@@ -1313,6 +1319,17 @@
 	return 0;
 }
 
+/* Lists command history. Used by shell 'history' builtins */
+void FAST_FUNC show_history(const line_input_t *st)
+{
+	unsigned i;
+
+	if (!st)
+		return;
+	for (i = 0; i < st->cnt_history; i++)
+		printf("%4d %s\n", i, st->history[i]);
+}
+
 # if ENABLE_FEATURE_EDITING_SAVEHISTORY
 /* We try to ensure that concurrent additions to the history
  * do not overwrite each other.
@@ -1532,7 +1549,6 @@
 # if ENABLE_FEATURE_EDITING_SAVEHISTORY && !ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
 	save_history(str);
 # endif
-	IF_FEATURE_EDITING_FANCY_PROMPT(num_ok_lines++;)
 }
 
 #else /* MAX_HISTORY == 0 */
@@ -1754,43 +1770,84 @@
 #define ask_terminal() ((void)0)
 #endif
 
+/* Called just once at read_line_input() init time */
 #if !ENABLE_FEATURE_EDITING_FANCY_PROMPT
 static void parse_and_put_prompt(const char *prmt_ptr)
 {
+	const char *p;
 	cmdedit_prompt = prmt_ptr;
-	cmdedit_prmt_len = strlen(prmt_ptr);
+	p = strrchr(prmt_ptr, '\n');
+	cmdedit_prmt_len = unicode_strwidth(p ? p+1 : prmt_ptr);
 	put_prompt();
 }
 #else
 static void parse_and_put_prompt(const char *prmt_ptr)
 {
-	int prmt_len = 0;
-	size_t cur_prmt_len = 0;
-	char flg_not_length = '[';
+	int prmt_size = 0;
 	char *prmt_mem_ptr = xzalloc(1);
-	char *cwd_buf = xrealloc_getcwd_or_warn(NULL);
+# if ENABLE_USERNAME_OR_HOMEDIR
+	char *cwd_buf = NULL;
+# endif
+	char flg_not_length = '[';
 	char cbuf[2];
-	char c;
-	char *pbuf;
 
-	cmdedit_prmt_len = 0;
-
-	if (!cwd_buf) {
-		cwd_buf = (char *)bb_msg_unknown;
-	}
+	/*cmdedit_prmt_len = 0; - already is */
 
 	cbuf[1] = '\0'; /* never changes */
 
 	while (*prmt_ptr) {
+		char timebuf[sizeof("HH:MM:SS")];
 		char *free_me = NULL;
+		char *pbuf;
+		char c;
 
 		pbuf = cbuf;
 		c = *prmt_ptr++;
 		if (c == '\\') {
-			const char *cp = prmt_ptr;
+			const char *cp;
 			int l;
-
-			c = bb_process_escape_sequence(&prmt_ptr);
+/*
+ * Supported via bb_process_escape_sequence:
+ * \a	ASCII bell character (07)
+ * \e	ASCII escape character (033)
+ * \n	newline
+ * \r	carriage return
+ * \\	backslash
+ * \nnn	char with octal code nnn
+ * Supported:
+ * \$	if the effective UID is 0, a #, otherwise a $
+ * \w	current working directory, with $HOME abbreviated with a tilde
+ *	Note: we do not support $PROMPT_DIRTRIM=n feature
+ * \W	basename of the current working directory, with $HOME abbreviated with a tilde
+ * \h	hostname up to the first '.'
+ * \H	hostname
+ * \u	username
+ * \[	begin a sequence of non-printing characters
+ * \]	end a sequence of non-printing characters
+ * \T	current time in 12-hour HH:MM:SS format
+ * \@	current time in 12-hour am/pm format
+ * \A	current time in 24-hour HH:MM format
+ * \t	current time in 24-hour HH:MM:SS format
+ *	(all of the above work as \A)
+ * Not supported:
+ * \!	history number of this command
+ * \#	command number of this command
+ * \j	number of jobs currently managed by the shell
+ * \l	basename of the shell's terminal device name
+ * \s	name of the shell, the basename of $0 (the portion following the final slash)
+ * \V	release of bash, version + patch level (e.g., 2.00.0)
+ * \d	date in "Weekday Month Date" format (e.g., "Tue May 26")
+ * \D{format}
+ *	format is passed to strftime(3).
+ *	An empty format results in a locale-specific time representation.
+ *	The braces are required.
+ * Mishandled by bb_process_escape_sequence:
+ * \v	version of bash (e.g., 2.00)
+ */
+			cp = prmt_ptr;
+			c = *cp;
+			if (c != 't') /* don't treat \t as tab */
+				c = bb_process_escape_sequence(&prmt_ptr);
 			if (prmt_ptr == cp) {
 				if (*cp == '\0')
 					break;
@@ -1802,39 +1859,54 @@
 					pbuf = user_buf ? user_buf : (char*)"";
 					break;
 # endif
+				case 'H':
 				case 'h':
 					pbuf = free_me = safe_gethostname();
-					*strchrnul(pbuf, '.') = '\0';
+					if (c == 'h')
+						strchrnul(pbuf, '.')[0] = '\0';
 					break;
 				case '$':
 					c = (geteuid() == 0 ? '#' : '$');
 					break;
+				case 'T': /* 12-hour HH:MM:SS format */
+				case '@': /* 12-hour am/pm format */
+				case 'A': /* 24-hour HH:MM format */
+				case 't': /* 24-hour HH:MM:SS format */
+					/* We show all of them as 24-hour HH:MM */
+					strftime_HHMMSS(timebuf, sizeof(timebuf), NULL)[-3] = '\0';
+					pbuf = timebuf;
+					break;
 # if ENABLE_USERNAME_OR_HOMEDIR
-				case 'w':
-					/* /home/user[/something] -> ~[/something] */
-					pbuf = cwd_buf;
-					l = strlen(home_pwd_buf);
-					if (l != 0
-					 && strncmp(home_pwd_buf, cwd_buf, l) == 0
-					 && (cwd_buf[l]=='/' || cwd_buf[l]=='\0')
-					 && strlen(cwd_buf + l) < PATH_MAX
-					) {
-						pbuf = free_me = xasprintf("~%s", cwd_buf + l);
+				case 'w': /* current dir */
+				case 'W': /* basename of cur dir */
+					if (!cwd_buf) {
+						cwd_buf = xrealloc_getcwd_or_warn(NULL);
+						if (!cwd_buf)
+							cwd_buf = (char *)bb_msg_unknown;
+						else {
+							/* /home/user[/something] -> ~[/something] */
+							l = strlen(home_pwd_buf);
+							if (l != 0
+							 && strncmp(home_pwd_buf, cwd_buf, l) == 0
+							 && (cwd_buf[l] == '/' || cwd_buf[l] == '\0')
+							) {
+								cwd_buf[0] = '~';
+								overlapping_strcpy(cwd_buf + 1, cwd_buf + l);
+							}
+						}
 					}
+					pbuf = cwd_buf;
+					if (c == 'w')
+						break;
+					cp = strrchr(pbuf, '/');
+					if (cp)
+						pbuf = (char*)cp + 1;
 					break;
 # endif
-				case 'W':
-					pbuf = cwd_buf;
-					cp = strrchr(pbuf, '/');
-					if (cp != NULL && cp != pbuf)
-						pbuf += (cp-pbuf) + 1;
-					break;
-				case '!':
-					pbuf = free_me = xasprintf("%d", num_ok_lines);
-					break;
-				case 'e': case 'E':     /* \e \E = \033 */
-					c = '\033';
-					break;
+// bb_process_escape_sequence does this now:
+//				case 'e': case 'E':     /* \e \E = \033 */
+//					c = '\033';
+//					break;
 				case 'x': case 'X': {
 					char buf2[4];
 					for (l = 0; l < 3;) {
@@ -1856,7 +1928,8 @@
 				}
 				case '[': case ']':
 					if (c == flg_not_length) {
-						flg_not_length = (flg_not_length == '[' ? ']' : '[');
+						/* Toggle '['/']' hex 5b/5d */
+						flg_not_length ^= 6;
 						continue;
 					}
 					break;
@@ -1864,16 +1937,29 @@
 			} /* if */
 		} /* if */
 		cbuf[0] = c;
-		cur_prmt_len = strlen(pbuf);
-		prmt_len += cur_prmt_len;
-		if (flg_not_length != ']')
-			cmdedit_prmt_len += cur_prmt_len;
-		prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf);
+		{
+			int n = strlen(pbuf);
+			prmt_size += n;
+			if (c == '\n')
+				cmdedit_prmt_len = 0;
+			else if (flg_not_length != ']') {
+#if 0 /*ENABLE_UNICODE_SUPPORT*/
+/* Won't work, pbuf is one BYTE string here instead of an one Unicode char string. */
+/* FIXME */
+				cmdedit_prmt_len += unicode_strwidth(pbuf);
+#else
+				cmdedit_prmt_len += n;
+#endif
+			}
+		}
+		prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_size+1), pbuf);
 		free(free_me);
 	} /* while */
 
+# if ENABLE_USERNAME_OR_HOMEDIR
 	if (cwd_buf != (char *)bb_msg_unknown)
 		free(cwd_buf);
+# endif
 	cmdedit_prompt = prmt_mem_ptr;
 	put_prompt();
 }
@@ -1935,7 +2021,15 @@
 			S.sent_ESC_br6n = 0;
 			if (cursor == 0) { /* otherwise it may be bogus */
 				int col = ((ic >> 32) & 0x7fff) - 1;
-				if (col > (int) cmdedit_prmt_len) {
+				/*
+				 * Is col > cmdedit_prmt_len?
+				 * If yes (terminal says cursor is farther to the right
+				 * of where we think it should be),
+				 * the prompt wasn't printed starting at col 1,
+				 * there was additional text before it.
+				 */
+				if ((int)(col - cmdedit_prmt_len) > 0) {
+					/* Fix our understanding of current x position */
 					cmdedit_x += (col - cmdedit_prmt_len);
 					while (cmdedit_x >= cmdedit_termw) {
 						cmdedit_x -= cmdedit_termw;
@@ -2026,6 +2120,7 @@
 	char read_key_buffer[KEYCODE_BUFFER_SIZE];
 	const char *matched_history_line;
 	const char *saved_prompt;
+	unsigned saved_prmt_len;
 	int32_t ic;
 
 	matched_history_line = NULL;
@@ -2034,6 +2129,7 @@
 
 	/* Save and replace the prompt */
 	saved_prompt = cmdedit_prompt;
+	saved_prmt_len = cmdedit_prmt_len;
 	goto set_prompt;
 
 	while (1) {
@@ -2109,7 +2205,7 @@
 					free((char*)cmdedit_prompt);
  set_prompt:
 					cmdedit_prompt = xasprintf("(reverse-i-search)'%s': ", match_buf);
-					cmdedit_prmt_len = strlen(cmdedit_prompt);
+					cmdedit_prmt_len = unicode_strwidth(cmdedit_prompt);
 					goto do_redraw;
 				}
 			}
@@ -2131,7 +2227,7 @@
 
 	free((char*)cmdedit_prompt);
 	cmdedit_prompt = saved_prompt;
-	cmdedit_prmt_len = strlen(cmdedit_prompt);
+	cmdedit_prmt_len = saved_prmt_len;
 	redraw(cmdedit_y, command_len - cursor);
 
 	return ic;
@@ -2699,8 +2795,9 @@
 	free(command_ps);
 #endif
 
-	if (command_len > 0)
+	if (command_len > 0) {
 		remember_in_history(command);
+	}
 
 	if (break_out > 0) {
 		command[command_len++] = '\n';
diff --git a/libbb/login.c b/libbb/login.c
index 8a82c6a..8f080b7 100644
--- a/libbb/login.c
+++ b/libbb/login.c
@@ -16,7 +16,6 @@
 #define LOGIN " login: "
 
 static const char fmtstr_d[] ALIGN1 = "%A, %d %B %Y";
-static const char fmtstr_t[] ALIGN1 = "%H:%M:%S";
 
 void FAST_FUNC print_login_issue(const char *issue_file, const char *tty)
 {
@@ -73,7 +72,7 @@
 				strftime(buf, sizeof(buf), fmtstr_d, localtime(&t));
 				break;
 			case 't':
-				strftime(buf, sizeof(buf), fmtstr_t, localtime(&t));
+				strftime_HHMMSS(buf, sizeof(buf), &t);
 				break;
 			case 'l':
 				outbuf = tty;
diff --git a/libbb/messages.c b/libbb/messages.c
index fad82c9..1ce84cf 100644
--- a/libbb/messages.c
+++ b/libbb/messages.c
@@ -40,7 +40,11 @@
 /* util-linux manpage says /sbin:/bin:/usr/sbin:/usr/bin,
  * but I want to save a few bytes here. Check libbb.h before changing! */
 const char bb_PATH_root_path[] ALIGN1 =
+#ifdef __BIONIC__
+	"PATH=/sbin" BB_ADDITIONAL_PATH; /* platform.h */
+#else
 	"PATH=/sbin:/usr/sbin:/bin:/usr/bin" BB_ADDITIONAL_PATH;
+#endif
 
 
 const int const_int_1 = 1;
diff --git a/libbb/nuke_str.c b/libbb/nuke_str.c
new file mode 100644
index 0000000..56b808b
--- /dev/null
+++ b/libbb/nuke_str.c
@@ -0,0 +1,21 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Utility routines.
+ *
+ * Copyright (C) 2008 Denys Vlasenko
+ *
+ * Licensed under GPLv2, see file LICENSE in this source tree.
+ */
+
+//kbuild:lib-y += nuke_str.o
+
+#include "libbb.h"
+
+void FAST_FUNC nuke_str(char *str)
+{
+        if (str) {
+		while (*str)
+			*str++ = 0;
+		/* or: memset(str, 0, strlen(str)); - not as small as above */
+	}
+}
diff --git a/libbb/platform.c b/libbb/platform.c
index 2bf34f5..5fcd448 100644
--- a/libbb/platform.c
+++ b/libbb/platform.c
@@ -28,14 +28,16 @@
 	r = vsnprintf(buf, 128, format, p);
 	va_end(p);
 
+	/* Note: can't use xstrdup/xmalloc, they call vasprintf (us) on failure! */
+
 	if (r < 128) {
 		va_end(p2);
-		*string_ptr = xstrdup(buf);
-		return r;
+		*string_ptr = strdup(buf);
+		return (*string_ptr ? r : -1);
 	}
 
-	*string_ptr = xmalloc(r+1);
-	r = vsnprintf(*string_ptr, r+1, format, p2);
+	*string_ptr = malloc(r+1);
+	r = (*string_ptr ? vsnprintf(*string_ptr, r+1, format, p2) : -1);
 	va_end(p2);
 
 	return r;
@@ -84,6 +86,9 @@
 #endif
 
 #ifndef HAVE_MKDTEMP
+#ifdef __BIONIC__
+#define mktemp(s) bb_mktemp(s)
+#endif
 /* This is now actually part of POSIX.1, but was only added in 2008 */
 char* FAST_FUNC mkdtemp(char *template)
 {
diff --git a/libbb/printable.c b/libbb/printable.c
index f6ada49..9a42343 100644
--- a/libbb/printable.c
+++ b/libbb/printable.c
@@ -32,3 +32,27 @@
 	}
 	fputc(ch, file);
 }
+
+void FAST_FUNC visible(unsigned ch, char *buf, int flags)
+{
+	if (ch == '\t' && !(flags & VISIBLE_SHOW_TABS)) {
+		goto raw;
+	}
+	if (ch == '\n') {
+		if (flags & VISIBLE_ENDLINE)
+			*buf++ = '$';
+	} else {
+		if (ch >= 128) {
+			ch -= 128;
+			*buf++ = 'M';
+			*buf++ = '-';
+		}
+		if (ch < 32 || ch == 127) {
+			*buf++ = '^';
+			ch ^= 0x40;
+		}
+	}
+ raw:
+	*buf++ = ch;
+	*buf = '\0';
+}
diff --git a/libbb/pw_encrypt_des.c b/libbb/pw_encrypt_des.c
index c8e02dd..35326a5 100644
--- a/libbb/pw_encrypt_des.c
+++ b/libbb/pw_encrypt_des.c
@@ -598,7 +598,7 @@
 	 * l_in, r_in, l_out, and r_out are in pseudo-"big-endian" format.
 	 */
 	uint32_t l, r, *kl, *kr;
-	uint32_t f = f; /* silence gcc */
+	static uint32_t f; /* silence gcc */
 	uint32_t r48l, r48r;
 	int round;
 
diff --git a/libbb/pw_encrypt_md5.c b/libbb/pw_encrypt_md5.c
index 889e09c..1e52eca 100644
--- a/libbb/pw_encrypt_md5.c
+++ b/libbb/pw_encrypt_md5.c
@@ -86,7 +86,7 @@
 
 	/* Get the length of the salt including "$1$" */
 	sl = 3;
-	while (salt[sl] && salt[sl] != '$' && sl < (3 + 8))
+	while (sl < (3 + 8) && salt[sl] && salt[sl] != '$')
 		sl++;
 
 	/* Hash. the password first, since that is what is most unknown */
diff --git a/libbb/pw_encrypt_sha.c b/libbb/pw_encrypt_sha.c
index 8aeaaca..e48b341 100644
--- a/libbb/pw_encrypt_sha.c
+++ b/libbb/pw_encrypt_sha.c
@@ -21,7 +21,7 @@
 	void (*sha_begin)(void *ctx) FAST_FUNC;
 	void (*sha_hash)(void *ctx, const void *buffer, size_t len) FAST_FUNC;
 	void (*sha_end)(void *ctx, void *resbuf) FAST_FUNC;
-	int _32or64;
+	unsigned _32or64;
 
 	char *result, *resptr;
 
@@ -152,7 +152,7 @@
 	/* Start computation of S byte sequence.  */
 	/* For every character in the password add the entire password.  */
 	sha_begin(&alt_ctx);
-	for (cnt = 0; cnt < 16 + alt_result[0]; ++cnt)
+	for (cnt = 0; cnt < 16U + alt_result[0]; ++cnt)
 		sha_hash(&alt_ctx, salt_data, salt_len);
 	sha_end(&alt_ctx, temp_result);
 
diff --git a/libbb/rtc.c b/libbb/rtc.c
index 97455e8..c31f848 100644
--- a/libbb/rtc.c
+++ b/libbb/rtc.c
@@ -61,7 +61,7 @@
 
 time_t FAST_FUNC rtc_tm2time(struct tm *ptm, int utc)
 {
-	char *oldtz = oldtz; /* for compiler */
+	char *oldtz = NULL;
 	time_t t;
 
 	if (utc) {
diff --git a/libbb/run_shell.c b/libbb/run_shell.c
index 4d92c3c..9494f27 100644
--- a/libbb/run_shell.c
+++ b/libbb/run_shell.c
@@ -34,16 +34,18 @@
 #endif
 
 #if ENABLE_SELINUX
-static security_context_t current_sid;
+static security_context_t current_sid = NULL;
 
 void FAST_FUNC renew_current_security_context(void)
 {
-	freecon(current_sid);  /* Release old context  */
+	if (current_sid)
+		freecon(current_sid);  /* Release old context  */
 	getcon(&current_sid);  /* update */
 }
 void FAST_FUNC set_current_security_context(security_context_t sid)
 {
-	freecon(current_sid);  /* Release old context  */
+	if (current_sid)
+		freecon(current_sid);  /* Release old context  */
 	current_sid = sid;
 }
 
@@ -82,6 +84,7 @@
 	args[argno] = NULL;
 
 #if ENABLE_SELINUX
+	renew_current_security_context();
 	if (current_sid)
 		setexeccon(current_sid);
 	if (ENABLE_FEATURE_CLEAN_UP)
diff --git a/libbb/time.c b/libbb/time.c
index 3778a2d..5a64bcb 100644
--- a/libbb/time.c
+++ b/libbb/time.c
@@ -23,14 +23,16 @@
 		if (sscanf(date_str, "%u:%u%c",
 					&ptm->tm_hour,
 					&ptm->tm_min,
-					&end) >= 2) {
+					&end) >= 2
+		) {
 			/* no adjustments needed */
 		} else
 		/* mm.dd-HH:MM */
 		if (sscanf(date_str, "%u.%u-%u:%u%c",
 					&ptm->tm_mon, &ptm->tm_mday,
 					&ptm->tm_hour, &ptm->tm_min,
-					&end) >= 4) {
+					&end) >= 4
+		) {
 			/* Adjust month from 1-12 to 0-11 */
 			ptm->tm_mon -= 1;
 		} else
@@ -38,15 +40,13 @@
 		if (sscanf(date_str, "%u.%u.%u-%u:%u%c", &ptm->tm_year,
 					&ptm->tm_mon, &ptm->tm_mday,
 					&ptm->tm_hour, &ptm->tm_min,
-					&end) >= 5) {
-			ptm->tm_year -= 1900; /* Adjust years */
-			ptm->tm_mon -= 1; /* Adjust month from 1-12 to 0-11 */
-		} else
+					&end) >= 5
 		/* yyyy-mm-dd HH:MM */
-		if (sscanf(date_str, "%u-%u-%u %u:%u%c", &ptm->tm_year,
+		 || sscanf(date_str, "%u-%u-%u %u:%u%c", &ptm->tm_year,
 					&ptm->tm_mon, &ptm->tm_mday,
 					&ptm->tm_hour, &ptm->tm_min,
-					&end) >= 5) {
+					&end) >= 5
+		) {
 			ptm->tm_year -= 1900; /* Adjust years */
 			ptm->tm_mon -= 1; /* Adjust month from 1-12 to 0-11 */
 		} else
@@ -58,7 +58,6 @@
 			return; /* don't fall through to end == ":" check */
 		} else
 #endif
-//TODO: coreutils 6.9 also accepts "yyyy-mm-dd HH" (no minutes)
 		{
 			bb_error_msg_and_die(bb_msg_invalid_date, date_str);
 		}
@@ -68,7 +67,29 @@
 				end = '\0';
 			/* else end != NUL and we error out */
 		}
-	} else if (date_str[0] == '@') {
+	} else
+	if (strchr(date_str, '-')
+	    /* Why strchr('-') check?
+	     * sscanf below will trash ptm->tm_year, this breaks
+	     * if parse_str is "10101010" (iow, "MMddhhmm" form)
+	     * because we destroy year. Do these sscanf
+	     * only if we saw a dash in parse_str.
+	     */
+		/* yyyy-mm-dd HH */
+	 && (sscanf(date_str, "%u-%u-%u %u%c", &ptm->tm_year,
+				&ptm->tm_mon, &ptm->tm_mday,
+				&ptm->tm_hour,
+				&end) >= 4
+		/* yyyy-mm-dd */
+	     || sscanf(date_str, "%u-%u-%u%c", &ptm->tm_year,
+				&ptm->tm_mon, &ptm->tm_mday,
+				&end) >= 3
+	    )
+	) {
+		ptm->tm_year -= 1900; /* Adjust years */
+		ptm->tm_mon -= 1; /* Adjust month from 1-12 to 0-11 */
+	} else
+	if (date_str[0] == '@') {
 		time_t t = bb_strtol(date_str + 1, NULL, 10);
 		if (!errno) {
 			struct tm *lt = localtime(&t);
@@ -187,6 +208,27 @@
 	return t;
 }
 
+static char* strftime_fmt(char *buf, unsigned len, time_t *tp, const char *fmt)
+{
+	time_t t;
+	if (!tp) {
+		tp = &t;
+		time(tp);
+	}
+	/* Returns pointer to NUL */
+	return buf + strftime(buf, len, fmt, localtime(tp));
+}
+
+char* FAST_FUNC strftime_HHMMSS(char *buf, unsigned len, time_t *tp)
+{
+	return strftime_fmt(buf, len, tp, "%H:%M:%S");
+}
+
+char* FAST_FUNC strftime_YYYYMMDDHHMMSS(char *buf, unsigned len, time_t *tp)
+{
+	return strftime_fmt(buf, len, tp, "%Y-%m-%d %H:%M:%S");
+}
+
 #if ENABLE_MONOTONIC_SYSCALL
 
 #include <sys/syscall.h>
diff --git a/libbb/unicode.c b/libbb/unicode.c
index 96eb646..6bdb666 100644
--- a/libbb/unicode.c
+++ b/libbb/unicode.c
@@ -34,19 +34,37 @@
 	static const char unicode_0x394[] = { 0xce, 0x94, 0 };
 	size_t width;
 
+	/* We pass "" instead of "C" because some libc's have
+	 * non-ASCII default locale for setlocale("") call
+	 * (this allows users of such libc to have Unicoded
+	 * system without having to mess with env).
+	 *
+	 * We set LC_CTYPE because (a) we may be called with $LC_CTYPE
+	 * value in LANG, not with $LC_ALL, (b) internationalized
+	 * LC_NUMERIC and LC_TIME are more PITA than benefit
+	 * (for one, some utilities have hard time with comma
+	 * used as a fractional separator).
+	 */
 //TODO: avoid repeated calls by caching last string?
-	setlocale(LC_ALL, (LANG && LANG[0]) ? LANG : "C");
+	setlocale(LC_CTYPE, LANG ? LANG : "");
 
 	/* In unicode, this is a one character string */
-// can use unicode_strlen(string) too, but otherwise unicode_strlen() is unused
-	width = mbstowcs(NULL, unicode_0x394, INT_MAX);
+	width = unicode_strlen(unicode_0x394);
 	unicode_status = (width == 1 ? UNICODE_ON : UNICODE_OFF);
 }
 
 void FAST_FUNC init_unicode(VOID)
 {
-	if (unicode_status == UNICODE_UNKNOWN)
-		reinit_unicode(getenv("LANG"));
+	/* Some people set only $LC_CTYPE, not $LC_ALL, because they want
+	 * only Unicode to be activated on their system, not the whole
+	 * shebang of wrong decimal points, strange date formats and so on.
+	 */
+	if (unicode_status == UNICODE_UNKNOWN) {
+		char *s = getenv("LC_ALL");
+		if (!s) s = getenv("LC_CTYPE");
+		if (!s) s = getenv("LANG");
+		reinit_unicode(s);
+	}
 }
 
 #else
@@ -64,8 +82,12 @@
 
 void FAST_FUNC init_unicode(VOID)
 {
-	if (unicode_status == UNICODE_UNKNOWN)
-		reinit_unicode(getenv("LANG"));
+	if (unicode_status == UNICODE_UNKNOWN) {
+		char *s = getenv("LC_ALL");
+		if (!s) s = getenv("LC_CTYPE");
+		if (!s) s = getenv("LANG");
+		reinit_unicode(s);
+	}
 }
 # endif
 
@@ -971,7 +993,6 @@
 
 /* The rest is mostly same for libc and for "homegrown" support */
 
-#if 0 // UNUSED
 size_t FAST_FUNC unicode_strlen(const char *string)
 {
 	size_t width = mbstowcs(NULL, string, INT_MAX);
@@ -979,7 +1000,6 @@
 		return strlen(string);
 	return width;
 }
-#endif
 
 size_t FAST_FUNC unicode_strwidth(const char *string)
 {
diff --git a/libbb/update_passwd.c b/libbb/update_passwd.c
index a30af6f..b2d0464 100644
--- a/libbb/update_passwd.c
+++ b/libbb/update_passwd.c
@@ -30,7 +30,7 @@
 	if (!seuser)
 		bb_error_msg_and_die("invalid context '%s'", context);
 	if (strcmp(seuser, username) != 0) {
-		if (checkPasswdAccess(PASSWD__PASSWD) != 0)
+		if (selinux_check_passwd_access(PASSWD__PASSWD) != 0)
 			bb_error_msg_and_die("SELinux: access denied");
 	}
 	if (ENABLE_FEATURE_CLEAN_UP)
diff --git a/libbb/xatonum.c b/libbb/xatonum.c
index 62bbe53..6f4e023 100644
--- a/libbb/xatonum.c
+++ b/libbb/xatonum.c
@@ -68,3 +68,10 @@
 {
 	return xatou_range(numstr, 0, 0xffff);
 }
+
+const struct suffix_mult bkm_suffixes[] = {
+	{ "b", 512 },
+	{ "k", 1024 },
+	{ "m", 1024*1024 },
+	{ "", 0 }
+};
diff --git a/libbb/xfuncs_printf.c b/libbb/xfuncs_printf.c
index 29c963f..7fb34e1 100644
--- a/libbb/xfuncs_printf.c
+++ b/libbb/xfuncs_printf.c
@@ -140,15 +140,6 @@
 	return xopen3(pathname, flags, 0666);
 }
 
-/* Die if we can't open an existing file readonly with O_NONBLOCK
- * and return the fd.
- * Note that for ioctl O_RDONLY is sufficient.
- */
-int FAST_FUNC xopen_nonblocking(const char *pathname)
-{
-	return xopen(pathname, O_RDONLY | O_NONBLOCK);
-}
-
 // Warn if we can't open a file and return a fd.
 int FAST_FUNC open3_or_warn(const char *pathname, int flags, int mode)
 {
@@ -167,6 +158,32 @@
 	return open3_or_warn(pathname, flags, 0666);
 }
 
+/* Die if we can't open an existing file readonly with O_NONBLOCK
+ * and return the fd.
+ * Note that for ioctl O_RDONLY is sufficient.
+ */
+int FAST_FUNC xopen_nonblocking(const char *pathname)
+{
+	return xopen(pathname, O_RDONLY | O_NONBLOCK);
+}
+
+int FAST_FUNC xopen_as_uid_gid(const char *pathname, int flags, uid_t u, gid_t g)
+{
+	int fd;
+	uid_t old_euid = geteuid();
+	gid_t old_egid = getegid();
+
+	xsetegid(g);
+	xseteuid(u);
+
+	fd = xopen(pathname, flags);
+
+	xseteuid(old_euid);
+	xsetegid(old_egid);
+
+	return fd;
+}
+
 void FAST_FUNC xunlink(const char *pathname)
 {
 	if (unlink(pathname))
@@ -305,6 +322,11 @@
 
 void FAST_FUNC xsetenv(const char *key, const char *value)
 {
+#ifdef __BIONIC__
+	/* on login, can be NULL, and should not be for bionic */
+	if (environ == NULL)
+		bb_error_msg_and_die("environment is not initialized");
+#endif
 	if (setenv(key, value, 1))
 		bb_error_msg_and_die("%s", bb_msg_memory_exhausted);
 }
@@ -351,6 +373,16 @@
 	if (setuid(uid)) bb_perror_msg_and_die("setuid");
 }
 
+void FAST_FUNC xsetegid(gid_t egid)
+{
+	if (setegid(egid)) bb_perror_msg_and_die("setegid");
+}
+
+void FAST_FUNC xseteuid(uid_t euid)
+{
+	if (seteuid(euid)) bb_perror_msg_and_die("seteuid");
+}
+
 // Die if we can't chdir to a new path.
 void FAST_FUNC xchdir(const char *path)
 {
@@ -541,13 +573,11 @@
 
 char* FAST_FUNC xmalloc_ttyname(int fd)
 {
-	char *buf = xzalloc(128);
-	int r = ttyname_r(fd, buf, 127);
-	if (r) {
-		free(buf);
-		buf = NULL;
-	}
-	return buf;
+	char buf[128];
+	int r = ttyname_r(fd, buf, sizeof(buf) - 1);
+	if (r)
+		return NULL;
+	return xstrdup(buf);
 }
 
 void FAST_FUNC generate_uuid(uint8_t *buf)
diff --git a/libbb/xreadlink.c b/libbb/xreadlink.c
index ec95af2..bb63da0 100644
--- a/libbb/xreadlink.c
+++ b/libbb/xreadlink.c
@@ -8,6 +8,12 @@
 
 #include "libbb.h"
 
+/* some systems (eg Hurd) does not have MAXSYMLINKS definition,
+ * set it to some reasonable value if it isn't defined */
+#ifndef MAXSYMLINKS
+# define MAXSYMLINKS 20
+#endif
+
 /*
  * NOTE: This function returns a malloced char* that you will have to free
  * yourself.
@@ -102,7 +108,8 @@
 
 char* FAST_FUNC xmalloc_realpath(const char *path)
 {
-#if defined(__GLIBC__) && !defined(__UCLIBC__)
+#if defined(__GLIBC__) || \
+    (defined(__UCLIBC__) && UCLIBC_VERSION >= KERNEL_VERSION(0, 9, 31))
 	/* glibc provides a non-standard extension */
 	/* new: POSIX.1-2008 specifies this behavior as well */
 	return realpath(path, NULL);