/* Copyright (C) 2020 C. McEnroe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7: * * If you modify this Program, or any covered work, by linking or * combining it with OpenSSL (or a modified version of that library), * containing parts covered by the terms of the OpenSSL License and the * original SSLeay license, the licensors of this Program grant you * additional permission to convey the resulting work. Corresponding * Source for a non-source form of such a combination shall include the * source code for the parts of OpenSSL used as well as that of the * covered work. */ #include #include #include #include #include #include #include #include #include #include "chat.h" uint replies[ReplyCap]; static const char *CapNames[] = { #define X(name, id) [id##Bit] = name, ENUM_CAP #undef X }; static enum Cap capParse(const char *list) { enum Cap caps = 0; while (*list) { enum Cap cap = 0; size_t len = strcspn(list, " "); for (size_t i = 0; i < ARRAY_LEN(CapNames); ++i) { if (len != strlen(CapNames[i])) continue; if (strncmp(list, CapNames[i], len)) continue; cap = 1 << i; break; } caps |= cap; list += len; if (*list) list++; } return caps; } static const char *capList(struct Cat *cat, enum Cap caps) { for (size_t i = 0; i < ARRAY_LEN(CapNames); ++i) { if (caps & (1 << i)) { catf(cat, "%s%s", (cat->len ? " " : ""), CapNames[i]); } } return cat->buf; } static void require(struct Message *msg, bool origin, uint len) { if (origin) { if (!msg->nick) msg->nick = "*.*"; if (!msg->user) msg->user = msg->nick; if (!msg->host) msg->host = msg->user; } for (uint i = 0; i < len; ++i) { if (msg->params[i]) continue; errx(EX_PROTOCOL, "%s missing parameter %u", msg->cmd, 1 + i); } } static const time_t *tagTime(const struct Message *msg) { static time_t time; struct tm tm; if (!msg->tags[TagTime]) return NULL; if (!strptime(msg->tags[TagTime], "%FT%T", &tm)) return NULL; time = timegm(&tm); return &time; } typedef void Handler(struct Message *msg); static void handleStandardReply(struct Message *msg) { require(msg, false, 3); for (uint i = 2; i < ParamCap - 1; ++i) { if (msg->params[i + 1]) continue; uiFormat( Network, Warm, tagTime(msg), "%s", msg->params[i] ); break; } } static void handleErrorGeneric(struct Message *msg) { require(msg, false, 2); if (msg->params[2]) { size_t len = strlen(msg->params[2]); if (msg->params[2][len - 1] == '.') msg->params[2][len - 1] = '\0'; uiFormat( Network, Warm, tagTime(msg), "%s: %s", msg->params[2], msg->params[1] ); } else { uiFormat( Network, Warm, tagTime(msg), "%s", msg->params[1] ); } } static void handleErrorNicknameInUse(struct Message *msg) { require(msg, false, 2); if (!strcmp(self.nick, "*")) { ircFormat("NICK :%s_\r\n", msg->params[1]); } else { handleErrorGeneric(msg); } } static void handleErrorErroneousNickname(struct Message *msg) { require(msg, false, 3); if (!strcmp(self.nick, "*")) { errx(EX_CONFIG, "%s: %s", msg->params[1], msg->params[2]); } else { handleErrorGeneric(msg); } } static void handleCap(struct Message *msg) { require(msg, false, 3); enum Cap caps = capParse(msg->params[2]); if (!strcmp(msg->params[1], "LS")) { caps &= ~CapSASL; if (caps & CapConsumer && self.pos) { ircFormat("CAP REQ %s=%zu\r\n", CapNames[CapConsumerBit], self.pos); caps &= ~CapConsumer; } if (caps) { char buf[512] = ""; struct Cat cat = { buf, sizeof(buf), 0 }; ircFormat("CAP REQ :%s\r\n", capList(&cat, caps)); } else { if (!(self.caps & CapSASL)) ircFormat("CAP END\r\n"); } } else if (!strcmp(msg->params[1], "ACK")) { self.caps |= caps; if (caps & CapSASL) { ircFormat("AUTHENTICATE %s\r\n", (self.plain ? "PLAIN" : "EXTERNAL")); } if (!(self.caps & CapSASL)) ircFormat("CAP END\r\n"); } else if (!strcmp(msg->params[1], "NAK")) { errx(EX_CONFIG, "server does not support %s", msg->params[2]); } } #define BASE64_SIZE(len) (1 + ((len) + 2) / 3 * 4) static void base64(char *dst, const byte *src, size_t len) { static const char Base64[64] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" }; size_t i = 0; while (len > 2) { dst[i++] = Base64[0x3F & (src[0] >> 2)]; dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)]; dst[i++] = Base64[0x3F & (src[1] << 2 | src[2] >> 6)]; dst[i++] = Base64[0x3F & src[2]]; src += 3; len -= 3; } if (len) { dst[i++] = Base64[0x3F & (src[0] >> 2)]; if (len > 1) { dst[i++] = Base64[0x3F & (src[0] << 4 | src[1] >> 4)]; dst[i++] = Base64[0x3F & (src[1] << 2)]; } else { dst[i++] = Base64[0x3F & (src[0] << 4)]; dst[i++] = '='; } dst[i++] = '='; } dst[i] = '\0'; } static void handleAuthenticate(struct Message *msg) { (void)msg; if (!self.plain) { ircFormat("AUTHENTICATE +\r\n"); return; } byte buf[299]; size_t len = 1 + strlen(self.plain); if (sizeof(buf) < len) errx(EX_CONFIG, "SASL PLAIN is too long"); buf[0] = 0; for (size_t i = 0; self.plain[i]; ++i) { buf[1 + i] = (self.plain[i] == ':' ? 0 : self.plain[i]); } char b64[BASE64_SIZE(sizeof(buf))]; base64(b64, buf, len); ircFormat("AUTHENTICATE "); ircSend(b64, BASE64_SIZE(len)); ircFormat("\r\n"); explicit_bzero(b64, sizeof(b64)); explicit_bzero(buf, sizeof(buf)); explicit_bzero(self.plain, strlen(self.plain)); } static void handleReplyLoggedIn(struct Message *msg) { (void)msg; ircFormat("CAP END\r\n"); } static void handleErrorSASLFail(struct Message *msg) { require(msg, false, 2); errx(EX_CONFIG, "%s", msg->params[1]); } static void handleReplyWelcome(struct Message *msg) { require(msg, false, 1); set(&self.nick, msg->params[0]); completeTouch(Network, self.nick, Default); if (self.join) { uint count = 1; for (const char *ch = self.join; *ch && *ch != ' '; ++ch) { if (*ch == ',') count++; } ircFormat("JOIN %s\r\n", self.join); replies[ReplyJoin] += count; replies[ReplyTopic] += count; replies[ReplyNames] += count; } } static void handleReplyISupport(struct Message *msg) { for (uint i = 1; i < ParamCap; ++i) { if (!msg->params[i]) break; char *key = strsep(&msg->params[i], "="); if (!strcmp(key, "NETWORK")) { if (!msg->params[i]) continue; set(&network.name, msg->params[i]); uiFormat( Network, Cold, tagTime(msg), "You arrive in %s", msg->params[i] ); } else if (!strcmp(key, "USERLEN")) { if (!msg->params[i]) continue; network.userLen = strtoul(msg->params[i], NULL, 10); } else if (!strcmp(key, "HOSTLEN")) { if (!msg->params[i]) continue; network.hostLen = strtoul(msg->params[i], NULL, 10); } else if (!strcmp(key, "CHANTYPES")) { if (!msg->params[i]) continue; set(&network.chanTypes, msg->params[i]); } else if (!strcmp(key, "PREFIX")) { strsep(&msg->params[i], "("); char *modes = strsep(&msg->params[i], ")"); char *prefixes = msg->params[i]; if (!modes || !prefixes || strlen(modes) != strlen(prefixes)) { errx(EX_PROTOCOL, "invalid PREFIX value"); } set(&network.prefixModes, modes); set(&network.prefixes, prefixes); } else if (!strcmp(key, "CHANMODES")) { char *list = strsep(&msg->params[i], ","); char *param = strsep(&msg->params[i], ","); char *setParam = strsep(&msg->params[i], ","); char *channel = strsep(&msg->params[i], ","); if (!list || !param || !setParam || !channel) { errx(EX_PROTOCOL, "invalid CHANMODES value"); } set(&network.listModes, list); set(&network.paramModes, param); set(&network.setParamModes, setParam); set(&network.channelModes, channel); } else if (!strcmp(key, "EXCEPTS")) { network.excepts = (msg->params[i] ?: "e")[0]; } else if (!strcmp(key, "INVEX")) { network.invex = (msg->params[i] ?: "I")[0]; } } } static void handleReplyMOTD(struct Message *msg) { require(msg, false, 2); char *line = msg->params[1]; urlScan(Network, msg->nick, line); if (!strncmp(line, "- ", 2)) { uiFormat(Network, Cold, tagTime(msg), "\3%d-\3\t%s", Gray, &line[2]); } else { uiFormat(Network, Cold, tagTime(msg), "%s", line); } } static void handleErrorNoMOTD(struct Message *msg) { (void)msg; } static void handleReplyHelp(struct Message *msg) { require(msg, false, 3); urlScan(Network, msg->nick, msg->params[2]); uiWrite(Network, Warm, tagTime(msg), msg->params[2]); } static void handleJoin(struct Message *msg) { require(msg, true, 1); uint id = idFor(msg->params[0]); if (!strcmp(msg->nick, self.nick)) { if (!self.user || strcmp(self.user, msg->user)) { set(&self.user, msg->user); self.color = hash(msg->user); } if (!self.host || strcmp(self.host, msg->host)) { set(&self.host, msg->host); } idColors[id] = hash(msg->params[0]); completeTouch(None, msg->params[0], idColors[id]); if (replies[ReplyJoin]) { uiShowID(id); replies[ReplyJoin]--; } } completeTouch(id, msg->nick, hash(msg->user)); if (msg->params[2] && !strcasecmp(msg->params[2], msg->nick)) { msg->params[2] = NULL; } uiFormat( id, ignoreCheck(Cold, id, msg), tagTime(msg), "\3%02d%s\3\t%s%s%sarrives in \3%02d%s\3", hash(msg->user), msg->nick, (msg->params[2] ? "(" : ""), (msg->params[2] ?: ""), (msg->params[2] ? ") " : ""), hash(msg->params[0]), msg->params[0] ); logFormat(id, tagTime(msg), "%s arrives in %s", msg->nick, msg->params[0]); } static void handleChghost(struct Message *msg) { require(msg, true, 2); if (strcmp(msg->nick, self.nick)) return; if (!self.user || strcmp(self.user, msg->params[0])) { set(&self.usHerbert Xu 2016-09-23expand - Fix dangling left square brackets in patternsHerbert Xu When there is an unmatched left square bracket in patterns, pmatch will behave strangely and exhibit undefined behaviour. This patch (based on Harld van Dijk's original) fixes this by treating it as a literal left square bracket. Reported-by: Olof Johansson <olof@ethup.se> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2016-09-02builtin: Fix echo -n early terminationHerbert Xu The commit 7a784244625d5489c0fc779201c349555dc5f8bc ("[BUILTIN] Simplify echo command") broke echo -n by making it always terminate after printing the first argument. This patch fixes this by only terminating when we have reached the end of the arguments. Fixes: 7a784244625d ("[BUILTIN] Simplify echo command") Reported-by: Luigi Tarenga <luigi.tarenga@gmail.com> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2016-09-02builtin: Fix handling of trailing IFS white spacesHerbert Xu The read built-in does not handle trailing IFS white spaces in the right way, when there are more fields than variables. Part of the problem is that this case is handled outside of ifsbreakup. Harald van Dijk wrote a patch to fix this by moving the magic into ifsbreakup itself. This patch further reorganises the ifsbreakup loop by having only one loop over the whole string. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Tested-by: Harald van Dijk <harald@gigawatt.nl> 2016-09-02eval: Return status in eval functionsHerbert Xu The exit status is currently clobbered too early for case statements and loops. This patch fixes it by making the eval functions return the current exit status and setting them in one place -- evaltree. Harald van Dijk pointed out a number of bugs in the original patch. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2016-09-02jobs: Handle string-based job descriptorsStephen Kitt When looking for a job using a string descriptor, e.g. fg %man the relevant loop in src/jobs.c only ever exits to the err label. With this patch, when the end condition is reached, we check whether a job was found, and if so, set things up to exit correctly via gotit. Multiple matches are already caught using the test in the match block. Signed-off-by: Stephen Kitt <steve@sk2.org> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2016-09-02trap: Implement POSIX.1-2008 trap reset behaviourHerbert Xu Jonathan Perkin submitted a patch to fix the behaviour of trap when the first argument is an integer. Currently it is treated as a command while POSIX requires it to be treated as a signal. This patch is based on his idea but instead of adding an extra argument to decode_signal I have added a new decode_signum helper. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2016-06-07eval: Fix exit status when calling eval/dot with no commandsHarald van Dijk On 17/11/2015 03:18, Gioele Barabucci wrote: > Hello, > > a bug has been filed in the Debian BTS about dash not resetting the exit > status after sourcing an empty file with the dot command. [1] > > The following test echoes "OK" with bash and "fail" with dash > > #!/bin/sh > > echo > ./empty > false > > . ./empty && echo "OK" || echo "fail" > > A similar bug in dash has been discussed and addressed in 2011 [2], but > it looks like the solution has been only partial. > > The version of dash I tested is the current git master branch, commit > 2e58422. > > [1] https://bugs.debian.org/777262 > [2] http://article.gmane.org/gmane.comp.shells.dash/531 The bug described there was about empty files. While the fix has been applied and does make dash handle empty files properly, your test doesn't use an empty file, it uses a file containing a single blank line. Unfortunately, the single blank line gets parsed by dash as a null command, null commands don't (and shouldn't) reset the exit status, and the fix you link to doesn't handle this because it sees a command has been executed and saves the exit status after executing that command as the exit status to be used by ".". I think the easiest way to fix this is to prevent null commands from affecting status in cmdloop, as attached. An alternative could be to change the outer if condition to exclude n == NULL, but I didn't do that because the change of job_warning and clearing of numeof make sense to me even for null commands. Besides, when debug tracing is enabled, null commands have a visible effect that should remain. Note that this fixes the problem with . but the same problem can be present in other locations. For example, false eval " " && echo OK || echo Fail used to print Fail, and needed the same modification in the evalstring function to make that print OK (included in the attached patch). There may be other similar bugs lurking. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2016-06-06man: Document ulimit -vGioele Barabucci Document that `ulimit` can set the `RLIMIT_AS` limit (virtual memory) with the `-v` flag. Fixes: https://bugs.debian.org/78556 Reported-by: Vincent Lefevre <vincent@vinc17.net> Signed-off-by: Gioele Barabucci <gioele@svario.it> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2016-06-06shell: Fix build on Solaris 9Jonathan Perkin Ensure dash can build in a default Solaris 9 or older environment: - Execute scripts with $SHELL rather than /bin/sh, the latter does not support e.g. "if ! .." used by mkbuiltins. - /bin/awk does not support ?: syntax, use explicit statements instead. - /bin/nl requires no spaces between options and arguments. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2016-06-06jobs: Don't attempt to access job table for job %0Tobias Klauser If job %0 is (mistakenly) specified, an out-of-bounds access to the jobtab occurs in function getjob() if num = 0: jp = jobtab + 0 - 1 Fix this by checking that the job number is larger than 0 before accessing the jobtab. Signed-off-by: Tobias Klauser <tklauser@distanz.ch> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2016-06-06builtin: Reject malformed printf specifications with digits after '*'Patrick Brown Dash doesn't notice when a format string has digits following a * width specifier. $ dash -c 'printf "%*0s " 1 2 && echo FAIL || echo OK' %10s FAIL $ bash -c 'printf "%*0s " 1 2 && echo FAIL || echo OK' bash: line 0: printf: `0': invalid format character OK $ mksh -c 'printf "%*0s " 1 2 && echo FAIL || echo OK' printf: %*0: invalid conversion specification OK With this patch dash complains about the malformed specifications. $ ./src/dash -c 'printf "%*0s " 1 2 && echo FAIL || echo OK' ./src/dash: 1: printf: %*0: invalid directive OK Fixes: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=779618 Originally-by: Patrick Brown <opensource@whoopdedo.org> Forwarded-by: Gioele Barabucci <gioele@svario.it> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2016-06-06Release 0.5.9.Herbert Xu 2015-08-13builtin: Reset t_wp_op in testcmdHerbert Xu The global variable t_wp_op needs to be reset every time testcmd is called or it may cause incorrect parsing of the arguments. Reported-by: Martijn Dekker <martijn@inlv.org> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2015-06-11man: Fix description of getopts when last argument reachedFelix Dietrich The description of getops in the manual incorrectly states that var will be set to "--" when no arguments remain. In fact it will be set to "?". Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2015-06-11builtin: Clear LC_ALL in mkbuiltinsFredrik Fornwall In mkbuiltins LC_COLLATE is set, but since "The value of the LC_ALL environment variable has precedence over any of the other environment variables starting with LC_" (http://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html), this has no effect when LC_ALL is set. This breaks when having e.g. LC_ALL=en_US.UTF-8 during make, which causes the test case dash -c : to fail, probably due to broken ordering in builtins.c. The patch corrects that by clearing LC_ALL. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2015-01-05input: Allow two consecutive calls to pungetcHerbert Xu The commit ef91d3d6a4c39421fd3a391e02cd82f9f3aee4a8 ([PARSER] Handle backslash newlines properly after dollar sign) created cases where we make two consecutive calls to pungetc. As we don't explicitly support that there are corner cases where you end up with garbage input leading to undefined behaviour. This patch adds explicit support for two consecutive calls to pungetc. Reported-by: Jilles Tjoelker <jilles@stack.nl> Reported-by: Juergen Daubert <jue@jue.li> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2015-01-05input: Move all input state into parsefileHerbert Xu Currently we maintain a copy of the input state outside of parsefile. This is redundant and makes reentrancy difficult. This patch kills the duplicate global states and now everyone simply uses parsefile. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2015-01-05input: Remove HETIOHerbert Xu It hasn't been possible to build HETIO for over ten years. So let's just kill it. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2015-01-05input: Make preadbuffer staticHerbert Xu The function preadbuffer should be static as it's only used in input.c. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2015-01-05expand: Fixed "$@" expansion when EXP_FULL is falseHerbert Xu The commit 3c06acdac0b1ba0e0acdda513a57ee6e31385dce ([EXPAND] Split unquoted $@/$* correctly when IFS is set but empty) broke the case where $@ is in quotes and EXP_FULL is false. In that case we should still emit IFS as field splitting is not performed. Reported-by: Juergen Daubert <jue@jue.li> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-12-26builtin: create builtins.c properly on old cppkabe@sra-tohoku.co.jp Encontered this on ancient gcc-2.95.3 environment; src/builtins.def.in -> src/builtins.def generation emitted ^ $ lines (likely by /* */), which where NOT ignored by src/mkbuiltins and generating bogus builtins.c. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-12-26man: Clarify two redirection mechanismsStéphane Aulery Close Debian Bug #501566 Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-11-17[BUILTIN] Fix "test -x" as root on FreeBSD 8Jonathan Nieder POSIX.1-2008 §4.4 "File Access Permission" sayeth: If execute permission is requested, access shall be granted if execute permission is granted to at least one user by the file permission bits or by an alternate access control mechanism; otherwise, access shall be denied. For historical reasons, POSIX unfortunately also allows access() and faccessat() to return success for X_OK if the current process is privileged, even when the above condition is not fulfilled and actual execution would fail. On the affected platforms, "test -x <path>" as root started returning true on nonexecutable files when dash switched from its own emulation to the true faccessat in v0.5.7~54 (2010-04-02). Work around this by checking the permissions bits when mode == X_OK and geteuid() == 0 on such platforms. Unfortunately the behavior seems to vary from one kernel version to another, so we cannot just check the behavior at compile time and rely on that. A survey of some affected kernels: - NetBSD's kernel moved to the sane semantics in 1997 - OpenBSD's kernel made the same change in version 4.4, three years ago - FreeBSD 9's kernel fixes this but hasn't been released yet It seems safe to only apply the workaround on systems using the FreeBSD kernel for now, and to push for standardization on the expected access()/faccessat() semantics so we can drop the workaround altogether in a few years. To try it on other platforms, use "./configure --enable-test-workaround". Reported-by: Christoph Egger <christoph@debian.org> Analysis-by: Petr Salinger <Petr.Salinger@seznam.cz> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-11-17[MAN] Document redirection file descriptor limitationStéphane Aulery Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-11-17[MAN] Correct typo in manual pageStéphane Aulery Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-10-30[PARSER] Catch variable length expansions on non-existant specialsHerbert Xu Currently we only check special variable names that follow directly after $ or ${. So errors such as ${#&} are not caught. This patch fixes that by moving the is_special check to just before we print out the special variable name. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-10-28[PARSER] Simplify EOF/newline handling in list parserHerbert Xu This patch simplifies the EOF and new handling in the list parser. In particular, it eliminates a case where we may leave here-documents unfinished upon EOF. It also removes special EOF/newline handling from parsecmd. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-10-28[PARSER] Removed unnecessary pungetc on EOF from parserHerbert Xu Doing a pungetc on an EOF is a noop and is only useful when we don't know what character we're putting back. This patch removes an unnecessary pungetc when we know it's EOF. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-10-27[BUILTIN] Handle -- in dotcmdHerbert Xu This patch adds a nextopt call in dotcmd in order to handle --. Reported-by: Stephane Chazelas <stephane_chazelas@yahoo.fr> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-10-27[BUILTIN] Simplify echo commandHerbert Xu Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-10-27[BUILTIN] Optimise handling of backslash octals in printfHerbert Xu This patch removes the duplicate octal handling for %b by reusing the existing code in conv_escape. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-10-27[BUILTIN] Use error instead of warnx for fatal errors in printfHerbert Xu This patch replaces uses of warnx where we abort with error since the effect is the same. The exit status however changes from 1 to 2. Non-fatal errors where we continue are unchanged. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-10-27[BUILTIN] Remove getintmax in printfHerbert Xu This patch removes getintmax and moves its functionality into getuintmax in order to reduce code duplication. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-10-27[BUILTIN] Remove unnecessary restoration of format string in printfHerbert Xu Currently we try to preserve the format string which is stored in argv after temporarily modifying it. This is unnecessary as it's only ever used once. This patch gets rid of it. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-10-27[OUTPUT] Add ifdefs around MEM_OUT handling in outmemHerbert Xu MEM_OUT is only used by forkless backtick processing which we do not currently support. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-10-27[OUTPUT] Add likely tag in outmemHerbert Xu The branch in outmem where the string fits in the buffer is the common case and is now marked as likely. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-10-27[INPUT] Replace open-coded flushall in preadbufferHerbert Xu Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> 2014-10-27[BUILTIN] Handle embedded NULs correctly in printfHerbert Xu https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=379227 On Sat, Jul 22, 2006 at 12:48:38PM +0200, A Mennucc wrote: > Package: dash > Version: 0.5.3-3 > Severity: normal > > hi > > here are the examples > > $ bash -c 'echo -n -e "A\0102C\00D\0E" | hexdump -c' > 0000000 A B C \0 D \0 E > 0000007 > > $ /bin/echo -n -e "A\0102C\00D\0E" | hexdump -c > 0000000 A B C \0 D \0 E > 0000007 > > $ zsh -c 'echo