Diaspora-common: does 'rm -rf /' on purge
bugs.debian.org
bugs.debian.org
"set -e" is short for "set -o errexit", that is, abort the script if a command returns with a non-zero exit code.
"set -u" is short for "set -o nounset", that is, abort the script if a variable name is dereferenced when the variable hasn't been set.
I also strongly suggest reading http://www.davidpashley.com/articles/writing-robust-shell-sc... for more things that you should be aware of when writing bash scripts.
> Errexit (a.k.a. set -e) is horrible, and you should not be using it in any new shell scripts you write. It exists solely for support of legacy scripts.
http://lists.gnu.org/archive/html/bug-bash/2017-03/msg00170....
edit: I asked after this (for whatever reason, my email isn't yet showing in the archives; maybe it will appear later), and got an interesting response!
http://lists.gnu.org/archive/html/bug-bash/2017-03/msg00171....
More specifically, every shell script should have "set -eu" at its second line, directly after the sh'bang line:
#!...
set -eu
For debugging or logging purposes, sometimes adding -v and/or -x comes handy: #!...
set -euvxDo you have a strong reason to prefer "set" over shebang flags? I have a slight preference for shebang flags so I can deliberately override them from the command line (but it's not a hill I'd die on):
$ cat foo
#!/bin/bash -eu
cd /nowhere
echo 'still here'
$ ./foo
./foo: line 2: cd: nowhere: No such file or directory
$ bash -c ./foo # thinking about system(3)
./foo: line 2: cd: nowhere: No such file or directory
$ bash +e ./foo # override
./foo: line 2: cd: nowhere: No such file or directory
still here
$
EDIT: Google's shell style guide has "Executables must start with #!/bin/bash and a minimum number of flags. Use set to set shell options so that calling your script as bash <script_name> does not break its functionality."https://google.github.io/styleguide/shell.xml?showone=Which_...
So the portable sh'bang line for bash is:
#!/usr/bin/env bash
And there you can't add "-eu" anymore, because only one argument is possible in sh'bang lines. (/usr/bin/env would try to find an executable named "bash -eu")So
#!/usr/bin/env bash
set -eu
is the more portable approach. However, for "/bin/sh" this should work portably: #!/bin/sh -euNitpick: on every BSD system I've used, it's /usr/local/bin/bash. But I definitely agree that using /usr/bin/env bash is the preferred solution for when you want to use bash (although I don't think it's even installed by default on most BSD's); as an Arch user (where /usr/bin/python is symlinked to python3 rather than python2), I appreciate those who go out of their way to use proper, portable shebangs.
#!/bin/sh -eu
Although i'm not sure if there are any advantages/disadvantages set -euo pipefail $ set -eu
$ echo "$(false)"
$ #!/bin/bash
set -eu
export SHELLOPTS echo "$(false)"
swallows the exit code from $(false). $ cat /tmp/test.sh
set -euvx
export SHELLOPTS
echo "$(false)"
echo "echo of string with failed subcommand does not kill script"
$(false)
echo "but consuming exit code does"
$ /tmp/test.sh
export SHELLOPTS
+ export SHELLOPTS
echo "$(false)"
false
++ false
+ echo ''
echo "echo of string with failed subcommand does not kill script"
+ echo 'echo of string with failed subcommand does not kill script'
echo of string with failed subcommand does not kill script
$(false)
false
++ falseI mean, I can always just backtick pipe anything that would be convenient but avoid this nonsense.
Also -C so you don't accidentally clobber existing files. Useful to prevent symlink attacks, in addition to preventing stupid stuff.
My standard non-interactive shell script preamble begins with
set -e # strict error
set -u # don't expand unbound variable
set -f # disable pathname expansion
set -C # noclobber if [ "${1:-}" == "my awesome value" ]; then ...-eu should have been the default from the start, for one.
Hardcore mode.
> It's actually rm -rf /bin.
> Seems to be caused by the same reason as the famous Steam bug - using an undefined variable. Plus the default bash behavior that simply ignores the fact that it doesn't exist and treats it like an empty string.
> tl;dr flamebait: The Unix way™ of "everything is just text" strikes again. (EDIT: yes this a gross over-generalization)
source: https://www.reddit.com/r/programming/comments/610z2f/858521_...
In our capability-based shell scripting language Shill (shill-lang.org) for example, you could prevent this sort of bug by giving the script a capability for just the diaspora_home directory, and deriving the child directories from that capability. (Of course, you still need to make sure you pass in the right directory in the first place.)
That suggestion fails to grasp the basics of Unix shells however: parameter expansion as well as globbing is performed by the shell, rm "sees" nothing but fully-expanded paths.
https://www.gnu.org/software/coreutils/manual/html_node/Trea...
If a command is risky, you should at least GENERATE the variable-expanded list of inputs (e.g. in a separate file) so that the list can be audited. For instance, basic automatic sanity checks can be performed on the expansion like “length > 1”, and you can see every affected item. It also acts as a log of impacted things in case it does run.
Robust code is not always convenient.
+ # safety check
+ [ "${diaspora_user_home}" != "" ] || exit 1
+ [ "${diaspora_home}" != "" ] || exit 1
... but this is key IMO: +# Abort if any unbound variables are used
+#set -u
I often use "set -euo pipefail" in my bash scripts. It does come with some caveats, though.At the top of each file with a brief comment I add:
set -o errexit
set -o nounset
set -o pipefail some_command | head
Then depending on timing, `some_command` _may_ generate all of its output and stuff it into the pipe, exiting successfully. Or `head` may get the lines it needs and close the pipe, causing `some_command` to exit with SIGPIPE. Using pipefail makes the whole pipeline fail, and then `-e` terminates the script.And it only happens sometimes.
I've some general notes on SIGPIPE mishandling at http://www.pixelbeat.org/programming/sigpipe_handling.html
For example, Gentoo's portage is quite capable of running code, since most packages are built from source, but that's done in a separate staging area; my understanding is that the completion of this process is a set of files that the package's build code wants to install. The package manager then installs them, from which it then also learns what to remove (what files belong to that package), and if anything would conflict.
Transitioning some packages took work and thought, but overall the system seemed pretty darn good to me.
If the new operating system has transparent rules about resources and ownership, whether that's files or users or any other system-level entity, that sounds like a great idea.
In the typical Linux model, we build programs from source with makefiles that can do anything or we install programs from package repositories with scripts that can do anything. In many cases, we even do this while running with root privileges.
Sometimes people talk as if this is a great thing, but the reality is that it's crude, opaque, error-prone and dangerous. If someone suggested such a clumsy design as the foundation for almost everything we do in a new operating system today, they'd be laughed out of the room. But we tolerate it in Linux because there's so much already built that way and the cost of changing would be very high.
And /etc is not among the exceptions; it's built as a package, just like nearly everything else. If something goes catastrophically wrong, you can still roll your system back to a previous version. (Through GRUB, if it's completely broken.)
For a concrete example, /etc/passwd is generated by a single NixOS module -- that is, Nix code. That module reads a list of users provided by other modules, and makes sure to e.g. avoid any duplicates. It's impossible to switch the system into a state where two users have the same UID, so while rolling back from that would be doable, you can't get there in the first place and (nonexistent) purge code could not be run.
If the new operating system has transparent rules about resources and ownership, whether that's files or users or any other system-level entity, that sounds like a great idea.
In the typical Linux model, we build programs from source with makefiles that can do anything or we install programs from package repositories with scripts that can do anything. In many cases, we even do this while running with root privileges.
Sometimes people talk as if this is a great thing, but the reality is that it's crude, opaque, error-prone and dangerous. If someone suggested such a clumsy design as the foundation for almost everything we do in a new operating system today, they'd be laughed out of the room. But we tolerate it in Linux because there's so much already built that way and the cost of changing would be very high.
Also, it's the difference between systemd and sysvinit - descriptive configuration vs scripting.
rm -rf "${FOO}/"
ShellCheck helpfully warned me that that could have disastrous consequences when FOO is unset, instead it advised me to use this: rm -rf "${FOO:?}/"
I'd also like to point out that ShellCheck is in the AUR for users of Arch Linux[0], and that there's a handy plugin which I use for the Atom editor[1].[0] https://github.com/SublimeLinter/SublimeLinter-shellcheck
http://redsymbol.net/articles/unofficial-bash-strict-mode/
Looks like it might have prevented this.
It lacks "set -u" which is the first line of defense for this kind of bugs, much more robust than the "if [ -d ...".
Command lines lack proper shell quoting. If $diaspora_home ends up with a trailing space somehow, this will again end in a disaster.
I don't see the point of adding a "test -e".
It seems "rm -rf /var/cache/diaspora /var/log/diaspora" got lost, so these directories are no longer cleaned up after uninstall.