ls: make readlink error to not disrupt output (try ls -l /proc/self/fd).
libbb: make xmalloc_readlink_or_warn warning more specific.

function                                             old     new   delta
xmalloc_readlink_or_warn                              33      61     +28
showfiles                                           1495    1460     -35
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 1/1 up/down: 28/-35)             Total: -7 bytes

diff --git a/coreutils/ls.c b/coreutils/ls.c
index 8007f2a..85c6729 100644
--- a/coreutils/ls.c
+++ b/coreutils/ls.c
@@ -76,7 +76,7 @@
 LIST_ID_NUMERIC = 1 << 5,
 LIST_CONTEXT    = 1 << 6,
 LIST_SIZE       = 1 << 7,
-LIST_DEV        = 1 << 8,
+//LIST_DEV        = 1 << 8, - unused, synonym to LIST_SIZE
 LIST_DATE_TIME  = 1 << 9,
 LIST_FULLTIME   = 1 << 10,
 LIST_FILENAME   = 1 << 11,
@@ -741,8 +741,8 @@
 
 static int list_single(const struct dnode *dn)
 {
-	int i, column = 0;
-
+	int column = 0;
+	char *lpath;
 #if ENABLE_FEATURE_LS_TIMESTAMPS
 	char *filetime;
 	time_t ttime, age;
@@ -767,127 +767,123 @@
 	append = append_char(dn->dstat.st_mode);
 #endif
 
-	for (i = 0; i <= 31; i++) {
-		switch (all_fmt & (1 << i)) {
-		case LIST_INO:
-			column += printf("%7lu ", (long) dn->dstat.st_ino);
-			break;
-		case LIST_BLOCKS:
-			column += printf("%4"OFF_FMT"u ", (off_t) dn->dstat.st_blocks >> 1);
-			break;
-		case LIST_MODEBITS:
-			column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
-			break;
-		case LIST_NLINKS:
-			column += printf("%4lu ", (long) dn->dstat.st_nlink);
-			break;
-		case LIST_ID_NAME:
+	/* Do readlink early, so that if it fails, error message
+	 * does not appear *inside* of the "ls -l" line */
+	if (all_fmt & LIST_SYMLINK)
+		if (S_ISLNK(dn->dstat.st_mode))
+			lpath = xmalloc_readlink_or_warn(dn->fullname);
+
+	if (all_fmt & LIST_INO)
+		column += printf("%7lu ", (long) dn->dstat.st_ino);
+	if (all_fmt & LIST_BLOCKS)
+		column += printf("%4"OFF_FMT"u ", (off_t) dn->dstat.st_blocks >> 1);
+	if (all_fmt & LIST_MODEBITS)
+		column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
+	if (all_fmt & LIST_NLINKS)
+		column += printf("%4lu ", (long) dn->dstat.st_nlink);
 #if ENABLE_FEATURE_LS_USERNAME
-			if (option_mask32 & OPT_g) {
-				printf("%-8.8s",
-					get_cached_username(dn->dstat.st_uid));
-				column += 9;
-				break;
-			}
-			printf("%-8.8s %-8.8s",
+	if (all_fmt & LIST_ID_NAME) {
+		if (option_mask32 & OPT_g) {
+			column += printf("%-8.8s",
+				get_cached_username(dn->dstat.st_uid));
+		} else {
+			column += printf("%-8.8s %-8.8s",
 				get_cached_username(dn->dstat.st_uid),
 				get_cached_groupname(dn->dstat.st_gid));
-			column += 17;
-			break;
+		}
+	}
 #endif
-		case LIST_ID_NUMERIC:
-			column += printf("%-8u %-8u", dn->dstat.st_uid, dn->dstat.st_gid);
-			break;
-		case LIST_SIZE:
-		case LIST_DEV:
-			if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
-				column += printf("%4u, %3u ", (int) major(dn->dstat.st_rdev),
-					   (int) minor(dn->dstat.st_rdev));
+	if (all_fmt & LIST_ID_NUMERIC) {
+		if (option_mask32 & OPT_g)
+			column += printf("%-8u", (int) dn->dstat.st_uid);
+		else
+			column += printf("%-8u %-8u",
+					(int) dn->dstat.st_uid,
+					(int) dn->dstat.st_gid);
+	}
+	if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
+		if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
+			column += printf("%4u, %3u ",
+					(int) major(dn->dstat.st_rdev),
+					(int) minor(dn->dstat.st_rdev));
+		} else {
+			if (all_fmt & LS_DISP_HR) {
+				column += printf("%9s ",
+					make_human_readable_str(dn->dstat.st_size, 1, 0));
 			} else {
-				if (all_fmt & LS_DISP_HR) {
-					column += printf("%9s ",
-						make_human_readable_str(dn->dstat.st_size, 1, 0));
-				} else {
-					column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
-				}
+				column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
 			}
-			break;
+		}
+	}
 #if ENABLE_FEATURE_LS_TIMESTAMPS
-		case LIST_FULLTIME:
-			printf("%24.24s ", filetime);
-			column += 25;
-			break;
-		case LIST_DATE_TIME:
-			if ((all_fmt & LIST_FULLTIME) == 0) {
-				/* current_time_t ~== time(NULL) */
-				age = current_time_t - ttime;
-				printf("%6.6s ", filetime + 4);
-				if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
-					/* hh:mm if less than 6 months old */
-					printf("%5.5s ", filetime + 11);
-				} else {
-					printf(" %4.4s ", filetime + 20);
-				}
-				column += 13;
+	if (all_fmt & LIST_FULLTIME)
+		column += printf("%24.24s ", filetime);
+	if (all_fmt & LIST_DATE_TIME)
+		if ((all_fmt & LIST_FULLTIME) == 0) {
+			/* current_time_t ~== time(NULL) */
+			age = current_time_t - ttime;
+			printf("%6.6s ", filetime + 4);
+			if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
+				/* hh:mm if less than 6 months old */
+				printf("%5.5s ", filetime + 11);
+			} else {
+				printf(" %4.4s ", filetime + 20);
 			}
-			break;
+			column += 13;
+		}
 #endif
 #if ENABLE_SELINUX
-		case LIST_CONTEXT:
-			column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
-			freecon(dn->sid);
-			break;
+	if (all_fmt & LIST_CONTEXT) {
+		column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
+		freecon(dn->sid);
+	}
 #endif
-		case LIST_FILENAME:
+	if (all_fmt & LIST_FILENAME) {
 #if ENABLE_FEATURE_LS_COLOR
-			if (show_color) {
-				info.st_mode = 0; /* for fgcolor() */
-				lstat(dn->fullname, &info);
-				printf("\033[%u;%um", bold(info.st_mode),
-						fgcolor(info.st_mode));
+		if (show_color) {
+			info.st_mode = 0; /* for fgcolor() */
+			lstat(dn->fullname, &info);
+			printf("\033[%u;%um", bold(info.st_mode),
+					fgcolor(info.st_mode));
+		}
+#endif
+		column += print_name(dn->name);
+		if (show_color) {
+			printf("\033[0m");
+		}
+	}
+	if (all_fmt & LIST_SYMLINK) {
+		if (S_ISLNK(dn->dstat.st_mode) && lpath) {
+			printf(" -> ");
+#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
+#if ENABLE_FEATURE_LS_COLOR
+			info.st_mode = 0; /* for fgcolor() */
+#endif
+			if (stat(dn->fullname, &info) == 0) {
+				append = append_char(info.st_mode);
 			}
 #endif
-			column += print_name(dn->name);
+#if ENABLE_FEATURE_LS_COLOR
+			if (show_color) {
+				printf("\033[%u;%um", bold(info.st_mode),
+					   fgcolor(info.st_mode));
+			}
+#endif
+			column += print_name(lpath) + 4;
 			if (show_color) {
 				printf("\033[0m");
 			}
-			break;
-		case LIST_SYMLINK:
-			if (S_ISLNK(dn->dstat.st_mode)) {
-				char *lpath = xmalloc_readlink_or_warn(dn->fullname);
-				if (!lpath) break;
-				printf(" -> ");
-#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
-#if ENABLE_FEATURE_LS_COLOR
-				info.st_mode = 0; /* for fgcolor() */
-#endif
-				if (stat(dn->fullname, &info) == 0) {
-					append = append_char(info.st_mode);
-				}
-#endif
-#if ENABLE_FEATURE_LS_COLOR
-				if (show_color) {
-					printf("\033[%u;%um", bold(info.st_mode),
-						   fgcolor(info.st_mode));
-				}
-#endif
-				column += print_name(lpath) + 4;
-				if (show_color) {
-					printf("\033[0m");
-				}
-				free(lpath);
-			}
-			break;
-#if ENABLE_FEATURE_LS_FILETYPES
-		case LIST_FILETYPE:
-			if (append) {
-				putchar(append);
-				column++;
-			}
-			break;
-#endif
+			free(lpath);
 		}
 	}
+#if ENABLE_FEATURE_LS_FILETYPES
+	if (all_fmt & LIST_FILETYPE) {
+		if (append) {
+			putchar(append);
+			column++;
+		}
+	}
+#endif
 
 	return column;
 }
diff --git a/libbb/xreadlink.c b/libbb/xreadlink.c
index 6bff4be..8d232f1 100644
--- a/libbb/xreadlink.c
+++ b/libbb/xreadlink.c
@@ -91,7 +91,11 @@
 	char *buf = xmalloc_readlink(path);
 	if (!buf) {
 		/* EINVAL => "file: Invalid argument" => puzzled user */
-		bb_error_msg("%s: cannot read link (not a symlink?)", path);
+		const char *errmsg = "not a symlink";
+		int err = errno;
+		if (err != EINVAL)
+			errmsg = strerror(err);
+		bb_error_msg("%s: cannot read link: %s", path, errmsg);
 	}
 	return buf;
 }