Problem Exists Between Keyboard ...
Today I started getting "Sorry, try again" from sudo after a supposedly unrelated Fedora update. After some PAM debug fun, the problem turned out to be ... the keyboard. Old laptop, one of the keys became fiddly.
Today I started getting "Sorry, try again" from sudo after a supposedly unrelated Fedora update. After some PAM debug fun, the problem turned out to be ... the keyboard. Old laptop, one of the keys became fiddly.
errno brought the example code to new depths of ugliness. It is about time to introduce some relief.fprintf, which can be addressed by using a simple wrapper function. Even the simplest function, however, introduces a new word in the vocabulary used to describe the solution to the problem implemented by the program - to describe it to a fellow programmer (which might be your future self), that is: the compiler is just as happy with the code as it stands. In other words, each function introduces an abstraction and as such it is essential to pick its name judiciously, so that it is not always necessary to reach for the actual source of the function in order to understand what it does.
/* Report an error on stderr */
void err_printf(const char *format, ...) {
va_list args;
va_start(args, format);
(void) vfprintf(stderr, format, args);
va_end(args);
}
err_printf() might not appeal old UNIX hands, but this is my best effort at conveying what the function is supposed to do. I once read that the inability to come up with a name describing what a function does is a telltale sign that said function is attempting to do too many things at once; hopefully, the above function does not.err_printf() embeds a decision: we are not interested in the return value of vfprintf, which is documented to be the same value that would be returned by the equivalent fprintf. In other words, the caller will get no information about the success or failure of the function; as briefly suggested last time, should he choose to invoke it, it will either be because he's genuinely uninterested in the outcome, or because he's confident that whatever the outcome subsequent execution of the program will satisfy his requirements.err_printf() which of the above was the actual intent, i.e. if the programmer was merely uninterested in the outcome or actually confident in its non-relevance. The latter gets problematic if code maintenance adds code afterwards which depends on the successful execution of err_printf, such as a followup message with more details about the problem.err_printf brings some measurable improvement to the example code:
/*
popen-test: experiment with popen(), Part 5.
Copyright © 2006 Davide Bolcioni <dbolcion@libero.it>
Distributed under the GPL,
see http://www.gnu.org/copyleft/gpl.html.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/wait.h>
void err_printf(const char* format, ...)
{
va_list args;
va_start(args, format);
(void) vfprintf(stderr, format, args);
va_end(args);
}
int main(int argc, char* argv[])
{
int rc = EXIT_FAILURE; /* Optimist */
if (argc > 1) {
FILE* sink;
if (fflush(NULL) != EOF) { /* all open files */
sink = popen(argv[1], "w");
if (sink) {
int status;
int i;
/* NB: deliberately allow to test for no output sent */
for (i = 2; i < argc; i++) {
if (fputs(argv[i], sink) == EOF) {
err_printf("%s: fputs(\"%s\") failed: ",
argv[0], argv[i]);
perror("");
break;
}
}
status = pclose(sink);
/* The return value is funny */
if (status == -1) {
err_printf("%s: pclose() failed: ",
argv[0]);
perror("");
}
else if (WIFEXITED(status)) {
rc = WEXITSTATUS(status);
err_printf("%s: child did an exit(%d).\n",
argv[0], rc);
}
else if (WIFSIGNALED(status)) {
err_printf("%s: child terminated by signal %d.\n",
argv[0], WTERMSIG(status));
}
else if (WIFSTOPPED(status)) {
err_printf("%s: child stopped with signal %d.\n",
argv[0], WSTOPSIG(status));
}
else {
err_printf(
"%s: I don't think we're in Kansas anymore, Toto.\n",
argv[0]
);
}
}
else {
err_printf("%s: popen(\"%s\") failed: ",
argv[0], argv[1]);
perror("");
}
}
else {
err_printf("%s: fflush(NULL) failed: ",
argv[0]);
perror("");
}
}
else {
err_printf("Usage: %s <command> [<arg>]...\n",
argv[0]);
}
return rc;
}
/*
vim:sw=2:nowrap
*/
...
else {
err_printf("%s: popen(\"%s\") failed: ",
argv[0], argv[1]);
perror("");
}
...
errno within err_printf(). This second problem is easily fixed1:
/* Report an error on stderr */
void err_printf(const char *format, ...) {
int saved_errno = errno;
va_list args;
va_start(args, format);
(void) vfprintf(stderr, format, args);
va_end(args);
errno = saved_errno;
}
errno all over the program. It might be tempting to add perror() to the mix also:
/* Report an error on stderr */
void err_printf(const char *format, ...) {
int saved_errno = errno;
va_list args;
va_start(args, format);
(void) vfprintf(stderr, format, args);
va_end(args);
if (saved_errno) {
errno = saved_errno;
perror("");
}
errno = saved_errno;
}
perror() is going to print in case of no error, the above code studiously avoids to trigger that. The next installment will attempt to discuss if the above is really an improvement.err_printf() and all its clients are compiled with the same threading flags.errno.errno; when a function returns failure, it sets the global variable1 errno to some numeric value which is supposed to give more details about what went wrong. When it returns success, you should not look at the value of errno because it might have changed anyway. Suppose we write a mightily uninteresting frobble() function:
/* The frobble() function returns 0 on success and -1 on failure */
int frobble() {
return -1;
}
frobble() is documented to fail but there is no sign of it affecting errno in any way; the handling of errno is a convention, which dates back to the beginnings of C and UNIX, so functions have to be explicitly written to follow this convention and should be documented accordingly: system calls and library functions often do, but the only way to tell is to read the documentation.errno lies in a problem with our toy program. Suppose we had the following run:
[db@centaur ~] ./popen-test /bin/true
[db@centaur ~] ./popen-test: popen("/bin/true") failed.
popen() to fail; whatever information errno might have conveyed is not made available. All too often, when programs fail they do not bother to tell you why, which makes the life of your system administrator friends either very simple (just blame you, the developer) or very complicated.errno on failure, just make sure its value is included in the error message. Since the value is just an integer, which is not very user-friendly, we might want to carry this a step further and provide some kind of explanatory message using perror() (we'll get more sophisticated later). Note that we're not referencing errno in our code, just invoking perror().
/*
popen-test: experiment with popen(), Part 4.
Copyright © 2006 Davide Bolcioni <dbolcion@libero.it>
Distributed under the GPL,
see http://www.gnu.org/copyleft/gpl.html.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char* argv[])
{
int rc = EXIT_FAILURE; /* Optimist */
if (argc > 1) {
FILE* sink;
if (fflush(NULL) != EOF) { /* all open files */
sink = popen(argv[1], "w");
if (sink) {
int status;
int i;
/* NB: deliberately allow to test for no output sent */
for (i = 2; i < argc; i++) {
(void)fputs(argv[i], sink);
}
status = pclose(sink);
/* The return value is funny */
if (status == -1) {
(void)fprintf(stderr,
"%s: pclose() failed: ",
argv[0]);
perror("");
}
else if (WIFEXITED(status)) {
rc = WEXITSTATUS(status);
(void)fprintf(stderr,
"%s: child did an exit(%d).\n",
argv[0], rc);
}
else if (WIFSIGNALED(status)) {
(void)fprintf(stderr,
"%s: child terminated by signal %d.\n",
argv[0], WTERMSIG(status));
}
else if (WIFSTOPPED(status)) {
(void)fprintf(stderr,
"%s: child stopped with signal %d.\n",
argv[0], WSTOPSIG(status));
}
else {
(void)fprintf(stderr,
"%s: I don't think we're in Kansas anymore, Toto.\n",
argv[0]);
}
}
else {
(void)fprintf(stderr,
"%s: popen(\"%s\") failed: ",
argv[0], argv[1]);
perror("");
}
}
else {
(void)fprintf(stderr,
"%s: fflush(NULL) failed: ",
argv[0]);
perror("");
}
}
else {
(void)fprintf(stderr,
"Usage: %s <command> [<arg>]...\n", argv[0]);
}
return rc;
}
/*
vim:sw=2:nowrap
*/
errno, resulting from the invocations of fputs() in the for loop. This illustrates why casting away error handling to void is slightly better than silently ignoring error conditions: it is easier to spot even after some time. In the code above, we should probably leave the loop as soon as the first fputs() fails and report the value of errno:
...
/* NB: deliberately allow to test for no output sent */
for (i = 2; i < argc; i++) {
if (fputs(argv[i], sink) == EOF) {
int errno_from_fputs = errno;
(void)fprintf(stderr, "%s: fputs(\"%s\") failed: ",
argv[0],
argv[i]);
errno = errno_from_fputs;
perror("");
break;
}
}
status = pclose(sink);
...
errno have a head start: because fprintf() might affect errno even when not failing, we have to save it and then restore it back for the use of perror().\n in our fprintf() format strings to a colon followed by a space, so that perror() output follows the message on the same line; if supplied an non-empty argument perror() would do just that on its own, but this would require us to format the message as a string, with all the attendant complications.errno is documented as an lvalue and might be a macro which expands to some funny construct intended to carry over all the above in the world of POSIX threads without too much pain. For this reason, errno should be accessed only after explicitly including <errno.h> even if perror() in all likelyhood already includes it.fprintf() to standard error an example of why not checking return codes is bad is admittedly somewhat contrived, but this installment is going to amend for that by addressing another problem in the original code.popen(), you may notice, the code invokes (void)fflush(NULL) - ignoring the result. The reason for this is somewhat involved, but easily explained: C standard I/O is buffered, so you might output a few characters, so few that they would sit in your stdout buffer waiting for more, invoke through popen() a program which sends output to standard output on its own, and this output would appear before the output your program produced before the call, which would be still sitting in its buffer. Calling fflush(NULL) flushes all open streams, so it's a kind of blanket prevention for the problem with output; when reading from standard input and invoking a program which itself reads from standard input (an input filter), the problem gets harder and will not be discussed here, and the same goes for similar problems associated with file descriptors.popen() does not perform the equivalent operation by itself; the only reason I can offer you is that the standard says it does not need to. In a few specific cases, such as ours by the way, fflush() is superfluous: at the point in code where it's called, no output has been produced yet so no output could possibly be sitting in a buffer - but in this case fflush() should complete with little fuss anyway.fflush(NULL) and ignore the result; an error would mean that some data that you thought you output might have failed to make it from the buffer to its intended destination. Can you say data loss ? I thought you could.fflush() on it failed; this might include charging along into popen(), but chances are you would want to do more than that, and fflush(NULL) does not even tell you which stream had a problem, nor can help you address the case when more than one had a problem, and they were not the same problem.fflush(NULL) should not do anything, much less fail. In part 2, two strategies for handling "should not happen" circumstances have been shown: cast to void or assert(). Note that our toy program already handles the case of popen() not providing a stream explicitly; the focus here is on handling the "should not happen" case instead.void results in the program charging onerrno). The writer is making a statement to the effect thatfprintf() at most once in its operation, and then exits. If the invocation fails, there is little we can do (writing an error message just failed, after all) so carrying on as if the problem did not occur is appropriate: there is no interest in the circumstance, and a fair amount of confidence that it will not impact the program exiting shortly thereafter.fflush(NULL), however, there is no such confidence that popen() will be guaranteed to function as advertised; on the contrary, such an unlikely failure would suggest nasty problems with the execution environment, so carrying along as if nothing happened does not cut it. The acknowledged intent of assert(), however, is guarding against inconsistencies in "our" code, not in the execution environment: on failure assert() aborts execution, which is an operation tied to the execution environment if there is one. So this is a circumstance that must be handled explicitly:
/*
popen-test: experiment with popen(), Part 3.
Copyright © 2006 Davide Bolcioni <dbolcion@libero.it>
Distributed under the GPL,
see http://www.gnu.org/copyleft/gpl.html.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char* argv[])
{
int rc = EXIT_FAILURE; /* Optimist */
if (argc > 1) {
FILE* sink;
if (fflush(NULL) != EOF) { /* all open files */
sink = popen(argv[1], "w");
if (sink) {
int status;
int i;
/* NB: deliberately allow to test for no output sent */
for (i = 2; i < argc; i++) {
(void)fputs(argv[i], sink);
}
status = pclose(sink);
/* The return value is funny */
if (status == -1) {
(void)fprintf(stderr,
"%s: pclose() failed.\n",
argv[0]);
}
else if (WIFEXITED(status)) {
rc = WEXITSTATUS(status);
(void)fprintf(stderr,
"%s: child did an exit(%d).\n",
argv[0], rc);
}
else if (WIFSIGNALED(status)) {
(void)fprintf(stderr,
"%s: child terminated by signal %d.\n",
argv[0], WTERMSIG(status));
}
else if (WIFSTOPPED(status)) {
(void)fprintf(stderr,
"%s: child stopped with signal %d.\n",
argv[0], WSTOPSIG(status));
}
else {
(void)fprintf(stderr,
"%s: I don't think we're in Kansas anymore, Toto.\n",
argv[0]);
}
}
else {
(void)fprintf(stderr,
"%s: popen(\"%s\") failed.\n",
argv[0], argv[1]);
}
}
else {
(void)fprintf(stderr,
"%s: fflush(NULL) failed.\n",
argv[0]);
}
}
else {
(void)fprintf(stderr,
"Usage: %s <command> [<arg>]...\n", argv[0]);
}
return rc;
}
/*
vim:sw=2:nowrap
*/
errno and at programs that don't tell you why they fail.