I use a bash script as my BROWSER which calls another bash script to launch or communicate with my browser that I run inside a container. The script that my BROWSER script calls has some debug output that it prints to stderr.
I use mutt as my email client and urlscan [0] to open URLs inside emails. Urlscan looks at my BROWSER environment variable and thus calls my script to open whatever URL I target. Some time recently, the urlscan author decided to improve the UX by hiding stderr so that it wouldn’t pollute the view, and so attempted to pipe it to `/dev/null`. I guess their original code to do this wasn’t quite correct and it ended up closing the child processes’ stderr.*
I generally use `set -e` (errexit) because I want my scripts to fail if any command fails (I consider that after an unhandled failure the script’s behavior is undefined, some other people disagree and say you should never use `set -e` outside of development, but I digress). My BROWSER scripts are no exception.
While my scripts handle non-zero returns for most things that can go wrong, I never considered that writing log messages to stdout or stderr might fail. But it did, which caused the script to die before it was able to launch my browser. For a few weeks I wasn’t able to use urlscan to open links. I was too lazy to figure out what was wrong, and when I did it took me a while because I looked into every possibility except this one.
Luckily this wasn’t a production app. But I know now it could just as feasibly happen in production, too.
I opened an issue[1] and it was fixed very quickly. I love open source!
*No disrespect to urlscan, it’s an awesome tool and bugs happen to all of us!
I'm not sure return codes are the source of your troubles...
It sounds our sensibilities are similar regarding cli and tool usage. This is a side note, but as someone who used to use "Bash strict mode" in all my scripts, I'm now a bit bearish on `set -e`, mainly due to the subtle caveats. If you're interested, the link below has a nice (and long) list of potentially surprising errexit gotchas:
https://mywiki.wooledge.org/BashFAQ/105
(The list begins below the anecdote.)
I'm really interested. What are their arguments? And how do they handle errors?
I think the idea is you use set -e during development to find where you should catch errors, but in production you may want it off to reduce strange side-effects (or explicitly check for success in the way you expect; so not that the command returned 0 but that the file it made exists and is the right length, etc).
“Hello world” method simply calls an API to a text interface. It uses simple call, to a simple interface that is expected to be ever present. I don’t find any bug there. It won’t work if such interface isn’t available, is blocked or doesn’t exist. It won’t work on my coffee grinder nor on my screwdriver. It won’t work on my Arduino because there is no text interface neither.
Of course, one could argue that user might expect you to handle that error. That’s all about contracts and expectation. How should I deal with that? Is the “Hello world” message such important that the highest escalated scenario should be painted on the sky? I can imagine an awkward social game where we throw each other obscure challenges and call it a bug.
It’s nitpicking that even such simple code might fail and I get it. It will also fail on OOM, faulty hardware or if number of the processes on the machine hit the limit. Maybe some joker replaced bindings and it went straight to 3D printer which is out of material? _My expectations_ were higher based on the title.
Now allow me to excuse myself, I need to write an e-mail to my keyboard manufacturer because it seems like it has a bug which prevents it from working when slightly covered in liquid coffee.
[1]: http://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-...
I still agree with the author though. This is a serious matter and it seems most of the time the vast amount of complexity that exists in seemingly simple functionality is ignored.
Hello world is not "simply" calling a text interface API. It is asking the operating system to write data somewhere. I/O is exactly where "simple" programs meet the real world where useful things happen and it's also where things often get ugly.
Here's all the stuff people need to think about in order to handle the many possible results of a single write system call on Linux:
long result = write(1, "Hello", sizeof("Hello") - 1);
switch (result) {
case -EAGAIN:
/* Occurs only if opened with O_NONBLOCK. */
break;
case -EWOULDBLOCK:
/* Occurs only if opened with O_NONBLOCK. */
break;
case -EBADF:
/* File descriptor is invalid or wasn't opened for writing. */
break;
case -EDQUOT:
/* User's disk quota reached. */
break;
case -EFAULT:
/* Buffer points outside accessible address space. */
break;
case -EFBIG:
/* Maximum file size reached. */
break;
case -EINTR:
/* Write interrupted by signal before writing. */
break;
case -EINVAL:
/* File descriptor unsuitable for writing. */
break;
case -EIO:
/* General output error. */
break;
case -ENOSPC:
/* No space available on device. */
break;
case -EPERM:
/* File seal prevented the file from being written. */
break;
case -EPIPE:
/* The pipe or socket being written to was closed. */
break;
}
Some of these are unlikely. Some of these are irrelevant. Some of these are very important. Virtually all of them seem to be routinely ignored, especially in text APIs.Also I always start to mildly panic in such cases, as lots of software corrupts its on-disk state more when the hard drive is full than any segfault, OOM-kill or hard shutdown is able to. I can understand and empathize on how this happens from a software development perspective, but objectively speaking "our entire field is bad at what we do, and if you rely on us, everybody will die". ( https://xkcd.com/2030/ )
Both Linux man pages and SUS specify some set of possible error situations, but not all of them. In the man pages case the set is not at all fixed and is subject to change and often does not contain some of the more obscure error states. The SUS "Errors" section are explicitly not meant to be complete and the OS can return additional errno values, additionally the OS can even handle some of the error cases as undefined behavior and not return any error code at all (notable example: doing anything to already joined pthread_t on linux, whish is undefined and does not return -ESRCH).
If the requirements of a hello world program include accounting for all error boundaries of the host system, then I am yet to see them written down but would invite anyone to provide them.
The parent comment has made a start in this regard.
> [Hello, world] is the big hurdle. To leap over it you have to be able to
> create the program text somewhere, compile it successfully, load it, run
> it, and find out where your output went.
Those are the goals of "Hello, world!". Create the program, compile it, load it, run it, and find the output. Things that are not goals of "Hello, world!" are handling user input, reusable components (functions), network access, etc etc etc, error handling.It's fine that the error is not handles, just as it is fine that the output went to stdout. Error handling was not a goal of the program.
printf("Hello, World!\n")
Is me saying: "Do a write syscall to stdout. I don't care what the return value is, I don't care if the flush is successfull if stdout happens to be buffered." If that is what I want to do, aka. what the program is specified to do, then it didn't fail.Modern languages do this by default, using exceptions, or force you to check return values using Result<> or alike.
Even in C, when compiled through some more strict linter, this would fail because ignored return value should be prefixed with (void).
In either case I think the main takeaway from the article is that a language where even hello world has such pitfalls, isn’t suitable, given the many other better options today.
IMHO, it doesn't.
hello.c is written in a way that makes it very clear that the program doesn't care about error conditions in any way shape or form; the return value of printf is ignored, the output isn't flushed, the return of flushing isn't checked, noone looks at errno; ...so anything happening that could go wrong will go unreported, unless its something the OS can see (segfault, permission, etc.)
If I expect a program to do something (eg. handle IO errors) that its code says very clearly that it doesn't, that's not the programs fault.
Is there no such thing as a bug then? The program does what the code says so every "misbehavior" and crash is expected behavior.
AFAIK, hello.c doesn't have a spec, so the code is the spec. If I am using it, I have to read the code to know what it does.
If there isn't one however, then the code is all there is.
Sure, we could update it:
// hello_v2.0.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(void) {
printf("Hello, World!\n");
fflush(stdout);
if (errno != 0) {
fprintf(stderr, "error: %s\n", strerror(errno));
return errno;
}
}
But now we have different libraries, a multitude of external identifiers, control structures, blocks, return values, the concept of buffered streams, the concept of file-descriptors, the printf formatting language, program return values, boolean logic & conditionals,...To someone who is already experienced in another language, that may not seem like a big deal, and isn't, but to someone who encounters the language for the first time, this is heavy stuff.
> Unlike other output streams, a PrintStream never throws an IOException; instead, exceptional situations merely set an internal flag that can be tested via the checkError method.
So the correct Hello World would be:
System.out.println("Hello World!");
if (System.out.checkError()) throw new IOException();
While the behaviour of PrintStream cannot be changed (it goes back to Java 1.0, and I'm guessing that the intention was not to require handling exceptions when writing messages to the standard output), adding a method to obtain the underlying, unwrapped OutputStream might be an idea worth considering, as it would allow writing to the standard output just like to any file.[1]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base...
System.out.withErrorChecks().println("Hello World!);
But we can't change the behaviour of the existing PrintStream.If my program writes to the standard output, but you choose to redirect the pipe to a different location, is it my program’s responsibility to check what happens to the bytes AFTER the pipe?
After all: my program did output everything as expected. The part which fucked up was not part of my program.
I can see why some projects decide to not handle this bug.
The output doesn't go into a pipe however, the output goes to /dev/full. Redirection happens before the process is started, so the program is, in fact, writing directly to a file descriptor that returns an error upon write.
I think this is pretty cut and dried - the failure is inside your process’s address space and the programmer error is that you haven’t handled a reported error.
>> what happens to the bytes AFTER the pipe?
There isn’t a pipe involved here, when your process was created it’s stdout was connected to dev/full then your program began executing
Problem is, the error condition is not even that obvious. I tried it, and printf() will happily return the number of bytes written, even when redirecting stdout to /dev/full.
I am not 100% sure, but I think this has to do with the fact that printf uses buffered io, and writing the bytes to the buffer will work. It's only when the buffer is flushed that this will become a problem, but this would need to be handled in the code to show an error message.
Plus the whole point of STDOUT is that it is a file. So it shouldn’t change the developers mental model if that file happens to be a pseudo TTY, a pipe or a traditional persistent file system object. This flexibility is one of the core principles of UNIX and it’s what makes the POSIX command line as flexible as it is.
The fact that there's redirection is a ... misdirection. The redirection is only used to proxy a real-life case that can happen even when no redirection is taking place.
You could do all kinds of things that would cause hello world to "fail". A broken monitor (or even one unplugged) wouldn't show "hello world" or give any indication of an error too, but it's hardly the codes fault. The code does what it's supposed to and ignores all kinds of other things that could go horribly wrong. That's not really a bug, just a known and expected limitation of the program's scope.
No, but it's not "after". Rather, it's your responsibility to handle backpressure by ensuring the bytes were written to the pipe successfully in the first place.
This isn't just about the filesystem being full btw. If you imagine a command like ./foo.py | head -n 10, it only makes sense for the 'head' command to close the pipe when it's done, and foo.py should be able to detect this and stop printing any more output. (This is especially important if you consider that foo.py might produce infinite lines of output, like the 'yes' program.)
I would argue this is not necessarily even an error from a user standpoint, so the return code from food.py should still be zero in many cases—a pipe-is-closed error just means the consumer simply didn't want the rest of the output, which is fine [1], whereas an out-of-disk-space error is probably really an error. Handling these robustly is actually difficult though, because (a) you'd need to figure out why printf() failed (so that you can treat different failures differently—but it's painful), and (b) you need to make sure any side effects in the program flow up to the printf() are semantically correct "prefixes" of the overall side effect, meaning that you'd need to pay careful attention to where you printf(). (Practically speaking, this makes it difficult to even have side effects that respect this, but that's an inherent problem/limitation of the pipeline model...)
FWIW, I would be very curious if anyone has formalized all of these nuances of the pipeline model and come up with a robust & systematic way to handle them. It seems like a complicated problem to me. To give just one example of a problem that I'm thinking of: should stderr and stdout behave the same way with respect to "pipe is closed"? e.g. should the program terminate if either is closed, or if both are closed? The answer is probably "it depends", but on what exactly? What if they're redirected externally? What if they're redirected internally? Is there a pattern you can follow to get it right most of the time? There's a lot of room for analysis of the issues that can come up, especially when you throw buffering/threading/etc. into the mix...
[1] Or maybe it isn't. Maybe the output (say, some archive format like ZIP) has a footer that needs to be read first, and it would be corrupt otherwise. Or maybe that's fine anyway, because the consumer should already understand you're outputting a ZIP, and it's on them if they want partial output. As always, "it depends". But I think a premature stdout closure is usually best treated as not-an-error.
The usual way of handling this is by not (explicitly) handling it. Writes to a closed pipe are special, they do not normally fail with a status that the program then all too often ignores, they result in a SIGPIPE signal that defaults to killing the process. Extra steps are needed to not kill the process. No other kind of write error gets this special treatment that I am aware of.
The pipe is your standard output. Your very program is created with the pipe as its stdout.
> After all: my program did output everything as expected. The part which fucked up was not part of my program.
But you are wrong, your program did not output everything as expected, and it failed to report that information.
I find more modern languages so much less exhausting to use to write correct code.
Modern languages do catch more programmer errors than C/C++, but the more general point is that there are "edge cases" (redirecting to a file isn't an edge case) that developers need to consider that aren't magically caught, and understanding the language you use well enough so as not to write those bugs is important.
The more experience I get as a dev the more I've come to understand that building the functionality required in a feature is actually a very small part of the job. The "happy path" where things go right is often trivial to code. The complexity and effort lies in making sure things don't break when the code is used in a way I didn't anticipate. Essentially experience means anticipating more ways things can go wrong. This article is a good example of that.
But GP’s point is that modern languages can surface those issues and edge cases, and try to behave somewhat sensibly, but even sometimes “magically” report the edge cases in question.
That’s one of the things which is very enjoyable (though sometimes frustrating) in Rust, the APIs were (mostly) designed such that you must acknowledge all possible errors somehow, either handling it or explicitly suppressing it.
The hidden costs are enormous and to this day still not very well accounted for.
There's no garbage collection/reference counting/etc. going on in the background. Objects aren't going to be moved around unless you explicitly move them around (Enjoy your heap fragmentation!). In C, you don't even get exceptions.
Of course, this creates TONS of foot-guns. Buffer overflows, unchecked errors, memory leaks, etc. A modern language won't have these, except for memory leaks, but they're much less likely to happen in trivial to moderate complexity apps.
A modern language could automatically throw an exception if the string cannot be completely written to standard output.
But that has not necessarily helped. The program now has a surprising hidden behavior; it has a way of terminating with a failed status that is not immediately obvious.
If it is used in a script, that could bite someone.
In Unix, there is such an exception mechanism for disconnected pipes: the SIGPIPE error. That can be a nuisance and gets disabled in some programs.
Doing something similar would be a good addition to any non-trivial C program that emits output on stdout and stderr.
In practice I haven't really seen a reason to exhaustively check every write to stdout/stderr as long as standard IO is used, and fflush() etc. is checked.
A much more common pitfall is when dealing with file I/O and forgetting to check the return value of close(). In my experience it's the most common case where code that tries to get it wrong actually gets it wrong, I've even seen code that checked the return value of open(), write() and fsync(), but forgot about the return value of close() before that fsync(). A close() will fail e.g. if the disk is full.
A while ago, I started learning C in my personal time and am curious about this issue. If `close()` fails, I’m guessing there’s not much else the program can do – other than print a message to inform the user (as in the highlighted git code). Also, I would have thought that calling `fsync()` on a file descriptor would also return an error status if the filesystem/block device is full.
This is really more about POSIX and FS semantics than C (although ultimately you end up using the C ABI or kernel system calls, which are closer to C than e.g. Python).
POSIX gives implementations enough leeway to have close() and fsync() do pretty much whatever they want as far as who returns what error goes, as long as not returning an error means your data made it to storage.
But in practice close() is typically 1=1 mapped to the file itself, while fsync() is many=1 (even though both take a "fd"). I.e. many implementations (including the common consumer OS's like Windows, OSX & Linux) have some notion of unrelated outstanding I/O calls being "flushed" by the first process to call fsync().
IIRC on ext3 fsync() was pretty much equivalent to sync(), i.e. it would sync all outstanding I/O writes. I believe that at least Windows and OSX have a notion of doing something similar, but for all outstanding writes to a "leaf" on the filesystem, i.e. an fsync() to a file in a directory will sync all outstanding I/O in that directory implicitly.
Of course none of that is anything you can rely on under POSIX, where you not only have to fsync() each and every file you write, but must not forget to also flush the relevant directory metadata too.
All of which is to say that you might be out of space when close() happens, but by the time you'd fsync() you may no longer be out of space, consider a write filling up the disk and something that frees up data on disk happening concurrently.
If you know your OS and FS semantics you can often get huge speedups by leaning into more lazily syncing data to disk, which depending on your program may be safe, e.g. you write 100 files, fsync() the last one, and know the OS/FS syncs the other 99 implicitly.
But none of that is portable, and you might start losing data on another OS or FS. The only thing that's portable is exhaustively checking errors after every system call, and acting appropriately.
The GNU Hello program produces a familiar, friendly greeting. Yes, this is another implementation of the classic program that prints “Hello, world!” when you run it.
However, unlike the minimal version often seen, GNU Hello processes its argument list to modify its behavior, supports greetings in many languages, and so on. The primary purpose of GNU Hello is to demonstrate how to write other programs that do these things; it serves as a model for GNU coding standards and GNU maintainer practices.
https://git.savannah.gnu.org/cgit/hello.git/tree/src/hello.c
Here's the comment:
/* Even exiting has subtleties. On exit, if any writes failed, change
the exit status. The /dev/full device on GNU/Linux can be used for
testing; for instance, hello >/dev/full should exit unsuccessfully.
This is implemented in the Gnulib module "closeout". */https://www.gnu.org/ghm/2011/paris/slides/jim-meyering-goodb...
It does show that we take such examples a bit too literally: our feeble minds don't consider what's missing, until it's too late. That's a didactic problem. It only matters to certain kinds of software, and when we teach many people to program, most of them won't go beyond a few small programs. But perhaps the "second programming course" should focus a bit less on OOP and introduce error handling.
I’d argue there is little benefit in the latter. Particularly these days where the Hello World of most imperative languages look vaguely similar. Maybe back when LISP, FORTRAN and ALGOL were common it was more useful showing a representation of the kind of syntax one should expect. But that isn’t the case any more.
Plus given the risk of bugs becoming production issues or, worse, security vulnerabilities and the ease and prevalence of which developers now copy and paste code, I think there is now a greater responsibility for examples to make fewer assumptions. Even if that example is just Hello World.
There's a huge benefit in having a program that verifies you have set up the programming environment successfully and can build and execute your programs. Far more than the didactic benefit of any "Hello World" program.
Handling terminal output is just an extra nice-to-have at that point, and one convenient way to verify your tools are working. Correct error handling is definitely out of scope.
It's not, though.
This helloworld is not safe to use as part of something bigger. Like:
echo header > gen.txt && ./helloworld >> gen.txt && ./upload_to_prod gen.txt
That will upload a partial file to prod, if there's any write error.> It's not meant to be part of a shell script
You don't know that. And brittle pieces like this is absolutely not an uncommon source of bugs.
The first piece of C code in introduction section was meant as production software? I've checked it: that section mentions typing "a.out" in the the UNIX shell to see what happens.
Also, what makes the status code handling special compared to, say, - assuming the english language is the preferred language instead of asking the OS for the user's preference - assuming that the output has to be ASCII (or compatible) instead of something like UTF-16
There seems to be a weird obsession with the program's status code over anything else in this whole comment section, and it seems to me that the only reason for that is that back in the stone age of computing, the status code was the only thing that got standardized, while locale and encoding didn't, so properly supporting the latter is hard and therefore assumed to be less important.
So to really do hello world in C right, in addition to fflush, you also need to check the return value from puts. I've never seen any C tutorial do that though.
To me this is an indication that you need to know the context in which the program gets run, and its purpose in that context. Or you'd have to specify every edge case, but I've never seen that really work in practice.
int main(void) { return !(puts("Hello, new world!") == EOF); /* return 0 unless a rare I/O error occurs */ }
Sounds like the program failed its objective, greeting the world. And thus imho shouldn't return 0.
It seems like every single IO thing I can think of can have a relevant error, regardless of whether it's file-system related, network, or anything else.
if (fflush(stdout) != 0 || ferror(stdout) != 0)
{
perror("stdout");
return EXIT_FAILURE;
}
at the end of the program. The same should be done for stderr as well.In GNU programs you can use atexit(close_stdout) to do this automatically.
The article cites an example of writing a YAML file and the dangers of it being half-written. Well, you could imagine outputting a file all in one printf() with lots of %s's in the format string. Some get written, but not all. If printf() decides to return an error message, retrying the printf() later on (after deleting another file, say), will corrupt the data because you'll be duplicating some of the output. But if printf() just returned the number of bytes written, your program will silently miss the error.
So does 'Hello World\n' need to check that printf() succeeded, or does it actually need to go further and check that printf() returned 12? (or is it 13, for \r\n ?) I don't think there's any way to really safely use the function in real life.
> a negative value if an output error occurred
So in your case that's an error and printf returns a negative value. But yes, how many bytes were written is a lost information.
No. According to fprintf(1), when the call succeeds it returns the number of printed characters. If it fails (for example, if it could only print part of the string) then it returns a negative value.
The number of printed characters is useful to know how much space was used on the output file, not to check for success. Success is indicated by a non-negative return value.
Bzzt, no. You can't say that without knowing what the program's requirements are.
Blindly "fixing" a program to indicate failure due to not being able to write to standard output could break something.
Maybe the output is just a diagnostic that's not important, but some other program will reacts to the failed status, causing an issue.
Also, if a program produces output with a well-defined syntax, then the termination status may be superfluous; the truncation of the output can be detected by virtue of that syntax being incomplete.
E.g. JSON hello world fragment:
puts("{\"hello\":\"world\"}");
return 0;
if something is picking up the output and parsing it as JSON, it can deduce from a failed parse that the program didn't complete, rather than going by termination status.This is bad advice. Consider output that might be truncated but can't be detected (mentioned in the article).
The exit status is the only reliable way to detect failures (unless you have a separate communication channel and send a final success message).
I didn't communicate that clearly: syntax can be "well-defined" yet truncatable. I meant some kind of syntax that is invalid if any suffix is missing, including the entire message, or else an object of an unexpected type is produced.
(In the case of JSON, valid JSON could be output which is truncatable, like 3.14 versus 3.14159. If the output is documented and expected to be a dictionary, we declare failure if a number emerges.)
The author covers this (or rather, the possibility that truncation can not be detected).
In the case of file I/O, we do not know that the bits have actually gone to the storage device. A military-grade hello world has to perform a fsync. I think that also requires the right storage hardware to be entirely reliable.
If stdout happens to be a TCP socket, then all we know from a successful flush and close is that the data has gone into the network stack, not that the other side has received it. We need an end-to-end application level ack. (Even just a two-way orderly shutdown: after writing hello, half-close the socket. Then read from it until EOF. If the read fails, the connection was broken and it cannot be assumed that the hello had been received.)
This issue is just a facet of a more general problem: if the goal of the hello world program is to communicate its message to some destination, the only way to be sure is to obtain an acknowledgement from that destination: communication must be validated end-to-end, in other words. If you rely on any success signal of an intermediate agent, you don't have end-to-end validation of success.
The super-robust requirements for hello world therefore call for a protocol: something like this:
puts("Hello, world!");
puts("message received OK? [y/n]")
return (fgets(buffer, sizeof buffer, stdin) != NULL && buffer[0] == 'y')
? EXIT_SUCCESS : EXIT_FAILURE;
Now we can detect failures like that there is no user present at the console who is reading the message. Or that their monitor isn't working so the can't read the question.We can now correctly detect this case of not being able to deliver hello, world, converting it to a failed status:
$ ./hello < /dev/null > /dev/null
We can still be lied to, but there is strong justification in regarding that as not our problem: $ yes | ./hello > /dev/null
We cannot get away from requiring syntax, because the presence of a protocol gives rise to it; the destination has to be able to tell somehow when it has received all of the data, so it can acknowledge it.A super reliable hello world also must not take data integrity for granted; the message should include some kind of checksum to reduce the likelihood of corrupt communication going undetected.
The "program's requirements" can in theory be "to be buggy unusable piece of shit". But when we speak, we don't need to consider that use case.
#[must_use] in Rust is the right idea: Rust doesn't automatically do anything --- there is no policy foisted upon the programmer --- but it will reliably force the programmer to do something about the error explicitly.
https://www.gnu.org/ghm/2011/paris/slides/jim-meyering-goodb...
Most Go in the wild is doing way more than a typical *nix binary, so the use case differs.
If you want a resilient system, you don't die on print and log failures.
Checking the result of log and print is very tedious and not useful most of the time.
begin
WriteLn('Hello World!');
end.
definitely has the bug, at least with the fpc implementation. On the other hand, explicitly trying to write to /dev/full from the Pascal source triggers a beautiful message: Runtime error 101 at $0000000000401104 SYNOPSIS
int dup2(int oldfd, int newfd);
NOTES:
If newfd was open, any errors that would have been reported at close(2) time are lost.
If this is of concern, then the correct approach is not to close newfd before calling dup2(),
because of the race condition described above.
Instead, code something like the following could be used:
/* Obtain a duplicate of 'newfd' that can subsequently
be used to check for close() errors; an EBADF error
means that 'newfd' was not open. */
tmpfd = dup(newfd);
if (tmpfd == -1 && errno != EBADF) {
/* Handle unexpected dup() error */
}
/* Atomically duplicate 'oldfd' on 'newfd' */
if (dup2(oldfd, newfd) == -1) {
/* Handle dup2() error */
}
/* Now check for close() errors on the file originally
referred to by 'newfd' */
if (tmpfd != -1) {
if (close(tmpfd) == -1) {
/* Handle errors from close */
}
}I find the argument that the code obviously ignores the error so that's obviously the program's intent to be completely spurious. The code "obviously" intends to print the string, too, and yet in some cases, it doesn't actually do that. It's clearly a bug. I don't think it's particularly useful to harp on this bug in the most introductory program ever, but it's definitely a bug.
If `puts` were to be used for debug messages, it might be right not to fail so as to not disturb the rest of the program. If the primary purpose is to greet the world, then we might expect it to signal the failure. But each creator or user might have their own expected behaviors.
If a user expects different behavior, then perhaps it is a feature request:
> There's no difference between a bug and a feature request from the user's perspective. (https://blog.codinghorror.com/thats-not-a-bug-its-a-feature-...)
The question is how the behavior can be made more explicit. I think it's a reasonable default to make programs fail often and early. If some failure can be safely ignored, it can always be implemented as an (explicit) feature.
1. Node.js result is out-dated. I run on Node.js v14.15.1 hello world code below on macOS and it reported exit code 1 correctly:
// testlog.js
console.log('hello world')
process.exit(0)
// bash
$ node -v
v14.15.1
$ node testlog.js > /dev/full
-bash: /dev/full: Operation not permitted
$ echo $?
1
2. Node.js is not a language. JavaScript is a language, and Node.js is a JavaScript runtime environment that runs on the V8 engine and executes JavaScript code outside a web browser.3. Missing JavaScript result in the table, which is the most popular language on GitHub: https://octoverse.github.com/#top-languages-over-the-years
Now I'm curious about another interesting question. Should bash be the one that handle the error and exit code in this case? Since it seems to be responsible for handing the piping operation.
This criticism is the wrong way around. All of the author's "languages" are actually language implementations like NodeJS. You can tell because he produced the results by running the code, rather than by reading a spec.
So what I'm proposing is to put JavaScript in the language column (like other languages such as Java) and note the usage of Node.js as the implementation in the second column together with version (similar to Java -> openjdk 11.0.11 2021-04-20).
Would that make sense?
internal/fs/utils.js:332
throw err;
^
Error: ENOSPC: no space left on device, write
at writeSync (fs.js:736:3)> Its main output is the implementation-defined side effect of printing the result to the console.
~ >>> julia -e 'print("Hello world")' > /dev/full
error in running finalizer: Base.SystemError(prefix="close", errnum=28, extrainfo=nothing)
#systemerror#69 at ./error.jl:174
systemerror##kw at ./error.jl:174
systemerror##kw at ./error.jl:174
#systemerror#68 at ./error.jl:173 [inlined]
systemerror at ./error.jl:173 [inlined]
close at ./iostream.jl:63
⋮
~ >>> echo $?
0
`errnum=28` apparently refers to the ENOSPC error: "No space left on device" as defined by POSIX.1; so the information is there, even if not in the most presentable form.Or maybe this about global stdout object. With buffering enabled (by default), printf will not throw any error. The fflush would do. But a final fflush would be done implicitly at the end. But this is again all well documented, so still, this is not really a bug but maybe just bad language design.
I'm not exactly sure what C++ code was used. If this was just the same C code, then the same thing applies. And iostream just behaves exactly as documented.
$ php hello.php > /dev/full
$ echo $?
255
It doesn't exactly print an error, but at least it returns something non-zero.If we accept the idea that the function (non-coding use of the word) of an language's indication of success should - indicate success (or its absence) - of a piece of code, then surely the creators of the languages should make it do just that. That's their job right no? What am I missing?
"Print Hello World and indicate if it succeed or not"
If the requirements were:
"Print Hello World, then return 0"
It's working as intended.
I'd even go so far as to say that print(); return 0; should always return 0, it would be weird for such a program to ever return anything other than 0 (where would that return come from?).
Your second point might be fine, except that it doesn't describe the API that languages actually use to print. For sure, it's trivial to implement the policy you describe, but suggesting that everyone always needs that policy is rather limiting and makes light of the real bugs that failure to handle errors actually results in.
If my program calls your Hello World program, it expects it to print Hello World. That's basically the point of the program.
If your program don't print Hello World for whatever reason, of course you don't need to manage the error if it wasn't specified. But it's probably a bad thing (call it a bug or not) to exit 0 which the caller will interpret by "Hello World have just been printed successfully", I can go on and print ", John".
I agree it's probably not going to be in the requirements, and world will probably not collapse if you don't manage the error, but it's with no doubt an idiom required by most OSes to ensure programs are normally working.
You can also create orphan processes if it's needed by your requirements, but it's probably a bug or a hole in your requirements. Because at some point, non idiomatic programs will be used in situations where they will be creating issues. And we are talking about issues that are very hard to even spot.
Those "non requirements" are exactly how you lately discover that you have no logs from the last two weeks or that your backups aren't complete.
It's not requirement, but it's just hygiene.
tbf, I'm arguing of what should be an idea world, but I probably have myself written those sorts of bugs. Writing idiomatic code is hard and no one is to blame for not doing it perfectly. I just think it's some ideal to aim for.
The point of hello.c is to serve as demonstration to students of the language of what a very basic program looks like, and how to use the toolchain to get it to run.
That's it, that's the requirements specified.
On the other hand, it's kind of depressing that I can't even write to stdout without needing to check for errors. And what are you going to do if that fails? Write to stderr? What if that fails because the program was run with `2>&1` ?
$ ls /dev/null /dev/full
ls: /dev/full: No such file or directory
/dev/null
I guess in theory, you can imitate `/dev/full` by other means.Yes it is. And it specifies the OS as well.
https://gist.github.com/koral--/12a6cdda22ffbd82f28ecc93e0b5...
int main(void)
{
char the_terminal[] = "Hello World!\n"
return 0;
}Unit testing verifies it does what it is supposed to do ideally, and all other tests verify it can do it in non-ideal environments.
int main(void) { if(puts("Hello, World!")!=EOF) { return EXIT_SUCCESS; }else { return EXIT_FAILURE; } }
What worked for me initially was the POSIX write() function:
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
int status;
status = write(1, "Hello World!\n", 13);
if (status < 0) { return EXIT_FAILURE; }
return EXIT_SUCCESS;
}
-----As someone else commented, fflush() gives the desired error response.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int status;
puts("Hello World!");
status = fflush(stdout);
if (status < 0) { return EXIT_FAILURE; }
return EXIT_SUCCESS;
}
-----andreyv probably has the best alternative[1], which is checking fflush() and ferror() at the program's end and calling perror(). It's better because it outputs an actual error message on the current terminal, and you don't need to write a special error checking wrapper.
On my test system (Ubuntu 21.10 on x86_64) the puts() call never fails.
I switched to a raw write() and that successfully catches it, by returning -1 when output is redirected to /dev/full.
Quite interesting, actually.
(And silently returning non-zero would be bad anyway.)
I'm going to have to go back over all the print statements I've ever written now
Would any of the languages report an error?
Maybe they all have bugs.
printf '#include <stdio.h>\nint main() { return printf("Hello world!\\n") && fflush(stdout); }\n' | cc -xc - && ./a.out > /dev/full && echo "Success\!" return (printf("Hello world!\n") < 0) && fflush(stdout);
printf returns the “number of characters transmitted to the output stream or negative value if an output error or an encoding error (for string and character conversion specifiers) occurred”, so it won’t ever return zero for that call (https://en.cppreference.com/w/c/io/fprintf)I also think this optimally should do something like
int x = printf("Hello world!\n");
if(x<0) return x; // maybe fflush here, too, ignoring errors?
return fflush(stdout);
Logging an error message to stderr should be considered, too. I would ignore any errors from that, but attempting to syslog those or to write them to the console could be a better choice.Could have used this knowledge in the past...
10 PRINT "Hell world"
(also, printf is buffered, so close or flush your output)
0. https://www.grammar-monster.com/lessons/commas_with_vocative...