This was only temporary and would be later reverted by the periodic
server.cfg commit, but it's still a grave thing and resulted in add-on
packs not actually being written to the correct files, resulting in
missing or outdated contents for uploads performed since commit
ea4f9a4ba2 was pushed to mainline 1.12.
The cause is a trivial copy-paste error that shouldn't have happened.
As the 2016-10-07~09 downtime incident shows, it is paramount to take
further steps in guaranteeing that the server can't corrupt its own data
files (especially the add-ons database) when receiving
inappropriately-timed signals.
This commit adds and deploys an ostream wrapper that requires callers to
explicitly commit the results to disk when finished writing to the
stream, so that only then the real destination file is overwritten with
the working copy (a temporary in the same dir). This way, there should
never be a situation in which the destination is left in an inconsistent
state due to a signal or exception.
The temporary receives a predictable name right now in the interest of
simplicity, since we are more or less in control of the target directory
anyway. We definitely don't want it to be an unlinked file since that
would make it impossible for admins to inspect and compare the temporary
against the original afterwards.
The code makes some assumptions about the nature of the return value of
filesystem::ostream_file() which will never be broken in this stable
branch, which is why one helper function is in campaignd land rather
than in the global filesystem API for now. This should probably be
rectified when forward-porting to master. Maybe.
Nothing of this will work reliably on Windows but we don't care. There's
only one machine in the world where we support running campaignd at this
time and it runs Linux.
This escapes all strings provided by add-ons server data to guarantee
they can't be used to get extraneous and potentially harmful HTML into
the generated web index.
However, and because I don't have time to look into the dense regex
contained in the relevant code right now, it also removes the hidden
feature of linkifying any URLs found in add-on descriptions. It's a
small price to pay for our safety, really.
Otherwise we run the risk (as seen with the 2016-10-07~09 downtime
incident) of calling exit() while we're in the middle of writing WML
content to disk, in particular the add-ons list and server configuration
file. Trust me, no-one will be very impressed if you make them sort out
the mess that this kind of thing leaves behind.
Ideally we should atomically rename a temporary into the destination
file for each WML write we do here to avoid similar ugliness with
signals we don't/can't/mustn't handle (especially SIGKILL). It's always
best to stay on the safe side and assume that a botched write could kill
people.