Add support for reboot parameter #3353

Merged
merged 3 commits into from Jun 5, 2017

Conversation

Projects
None yet
8 participants
Contributor

alfonsosanchezbeato commented May 19, 2017

Add support for reboot parameter in system-shutdown program, and use it for the android bootloader.

I looked at the C parts mostly. CC @chipaca

+{
+ FILE *f;
+
+ *arg = '\0';
@zyga

zyga May 19, 2017

Contributor

Ugh

This doesn't look at max_size. Perhaps I'm skewed towards security sensitive code (from snap-confine) but I prefer defensive coding.

+
+ *arg = '\0';
+
+ f = fopen("/run/systemd/reboot-param", "r");
@zyga

zyga May 19, 2017

Contributor

Where is this file name coming from? Is this our invention? If so, can we call it something like snapd.reboot-mode

@morphis

morphis May 19, 2017

Contributor

Can we get this properly documented with a link to the systemd source (a tagged version)? Also needs to be added in the go portion of this PR.

@@ -29,4 +29,8 @@ void die(const char *msg);
__attribute__ ((format(printf, 1, 2)))
void kmsg(const char *fmt, ...);
+// Reads a possible argument for reboot syscall in /run/systemd/reboot-param,
+// which is the place where systemd stores it.
+int read_reboot_arg(char *arg, int max_size);
@zyga

zyga May 19, 2017

Contributor

Please prefix this with sc_, the second argument should really be size_t

@chipaca

chipaca May 19, 2017

Member

why the prefix? it's got nothing to do with snap-confine (and nothing else in system-shutdown-utils.h has that prefix)

@alfonsosanchezbeato

alfonsosanchezbeato May 19, 2017

Contributor

Agreed, don't really see the need for the prefix...

@alfonsosanchezbeato

alfonsosanchezbeato May 19, 2017

Contributor

Re: size_t I always have mixed feelings wrt using natural integer size vs array sizes, it is a C language issue in the end. In fact, fgets uses "int", but in this case the advantage of using size_t is that you make sure that max_size is non-negative, so I will change the type.

@zyga

zyga May 19, 2017

Contributor

The prefix is used on all non-private functions simply because you can just move code around from place to place without thinking about it too hard. It is also free to do it now.

cmd/system-shutdown/system-shutdown.c
#include "system-shutdown-utils.h"
#include "../libsnap-confine-private/string-utils.h"
int main(int argc, char *argv[])
{
+ char reboot_arg[LINE_MAX] = { 0 };
@zyga

zyga May 19, 2017

Contributor

I think we can reasonably say the size is something smaller, you don't need all of that space.

cmd/system-shutdown/system-shutdown.c
errno = 0;
if (getpid() != 1) {
fprintf(stderr,
"This is a shutdown helper program; don't call it directly.\n");
exit(1);
}
- kmsg("started.");
+ kmsg("started. Hi there!");
@zyga

zyga May 19, 2017

Contributor

Can you please remove this?

@chipaca

chipaca May 19, 2017

Member

by "this" you mean the "hi there!", yes? :-)

@alfonsosanchezbeato

alfonsosanchezbeato May 19, 2017

Contributor

Lol, some debugging leftover

@zyga

zyga May 19, 2017

Contributor

Yes, I meant this change. :-)

cmd/system-shutdown/system-shutdown.c
- reboot(cmd);
+ // We are reading a file from /run and need to do this before unmounting
+ if (cmd == RB_AUTOBOOT)
+ read_reboot_arg(reboot_arg, sizeof reboot_arg);
@zyga

zyga May 19, 2017

Contributor

Can you just always read this and not change the flow of the code?

@alfonsosanchezbeato

alfonsosanchezbeato May 19, 2017

Contributor

Ok, in fact I oscillated between the two options...

cmd/system-shutdown/system-shutdown.c
+ LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
+ LINUX_REBOOT_CMD_RESTART2, reboot_arg);
+ else
+ reboot(cmd);
@zyga

zyga May 19, 2017

Contributor

Can you add error handling to both cases please. It doesn't have to be very sophisticated but I'd like to see it.

partition/androidboot.go
+func (a *androidboot) Reboot() error {
+ // Write argument so we reboot to recovery partition
+ param := []byte("recovery\n")
+ if err := ioutil.WriteFile("/run/systemd/reboot-param", param, 0644); err != nil {
@pedronis

pedronis May 19, 2017

Contributor

if this is not systemd predefined shouldn't it go to /run/snapd/ instead?

@pedronis

pedronis May 19, 2017

Contributor

should this use osutil.AtomicWrite ? are we writing over a systemd undocumented file, that sounds odd? can't we use our own file ? given that we are consuming it from our own code?

@alfonsosanchezbeato

alfonsosanchezbeato May 19, 2017

Contributor

I prefer to use the same file because this way system-shutdown works properly not only for snapd, but also for the "reboot" command (there are those two ways to generate the file, so better to use the same interface, otherwise we would need to support two different ways of receiving the information in system-shutdown). Note also that we would not need the file if "shutdown +x -r" worked the same way as "reboot", which is a systemd inconsistency.

@alfonsosanchezbeato

alfonsosanchezbeato May 19, 2017

Contributor

Re: using AtomicWrite this is an ephemeral archive, so no, it does not make much sense imo.

partition/bootloader.go
@@ -146,3 +155,13 @@ func MarkBootSuccessful(bootloader Bootloader) error {
return bootloader.SetBootVars(m)
}
+
+func reboot() error {
+ cmd := exec.Command("shutdown", "+10", "-r", shutdownMsg)
@pedronis

pedronis May 19, 2017

Contributor

given we are doing this, can we instead of hard coding +10 here, pass in a afterMins params to Reboot(afterMins int)

daemon/daemon.go
+ bootloader, err := partition.FindBootloader()
+ if err != nil {
+ logger.Noticef("cannot get bootloader: %s", err)
+ break
@morphis

morphis May 19, 2017

Contributor

Shouldn't we have a proper fallback here when no bootloader is found and still call "shutdown"? Not sure if we can otherwise supports environments like docker or LXD.

@pedronis

pedronis May 19, 2017

Contributor

that's a much bigger issue than this place in the code (there various places assuming that if we are core system we will get a reboot in some situations), if we have such issues we need to think through them, this is probably not the right PR for that

partition/androidboot.go
@@ -75,3 +76,13 @@ func (a *androidboot) SetBootVars(values map[string]string) error {
}
return env.Save()
}
+
+func (a *androidboot) Reboot() error {
@morphis

morphis May 19, 2017

Contributor

Should have a unit test.

@alfonsosanchezbeato

alfonsosanchezbeato May 19, 2017

Contributor

This one is tricky as it is writing in a global folder (/run/systemd) for which root permissions are needed, and I do not see an equivalent of MockCommand for this.

@pedronis

pedronis May 19, 2017

Contributor

don't hardcode the folder here but in dirs/dirs.go and follow the patterns there, then you can use dirs.SetRootDir in tests to have an alternate root dir

@alfonsosanchezbeato

alfonsosanchezbeato May 19, 2017

Contributor

Not sure if we should do that, as this is not a snapd folder, but a systemd one.

@pedronis

pedronis May 19, 2017

Contributor

I'm telling you to do that! there's a lot of system directories already listed there because of similar reasons, for example

LocaleDir = filepath.Join(rootdir, "/usr/share/locale")

otoh see my comment about being a bit strange to write over a systemd file

partition/bootloader.go
@@ -146,3 +155,13 @@ func MarkBootSuccessful(bootloader Bootloader) error {
return bootloader.SetBootVars(m)
}
+
+func reboot() error {
@morphis

morphis May 19, 2017

Contributor

Needs a unit test

@morphis

morphis May 19, 2017

Contributor

Also I think we should give this a better name. like regularSystemReboot() so it name expresses a bit more that this is the std. way of doing a reboot.

@alfonsosanchezbeato

alfonsosanchezbeato May 19, 2017

Contributor

I thought about that, but how do you unit-test something that reboots the system (or fails if not root)?...

@morphis

morphis May 19, 2017

Contributor

By mocking the actual exec call :-)

We do this elsewhere in the snapd code base already. https://github.com/snapcore/snapd/blob/master/systemd/systemd_test.go is a good reference.

@pedronis

pedronis May 19, 2017

Contributor

testutil.MockCommand there

Contributor

ogra1 commented May 19, 2017

as i mentioned on IRC, i'd prefer to do all this on a lower level simply through systemd configuration so that every reboot gets us into recovery regardless ...

that said, i dont mind if you add a super complex function to snapd like above instead, if it fulfills the same purpose as hardcoding "reboot recovery" on a systemd level for all reboot calls, i just think it is overkill.

Contributor

alfonsosanchezbeato commented May 19, 2017

PR refreshed, thanks for all the comments from the different reviewers!

partition/bootloader.go
+func reboot(afterMins int) error {
+ cmd := exec.Command("shutdown", fmt.Sprintf("+%d", afterMins), "-r", shutdownMsg)
+ if out, err := cmd.CombinedOutput(); err != nil {
+ logger.Noticef("%s", osutil.OutputErr(out, err))
@pedronis

pedronis May 19, 2017

Contributor

this should return osutil.OutputErr directly and the logging moved back to daemon.go I think

partition/bootloader_test.go
@@ -157,3 +161,23 @@ func (s *PartitionTestSuite) TestInstallBootloaderConfig(c *C) {
c.Assert(osutil.FileExists(fn), Equals, true)
}
}
+
+func (s *PartitionTestSuite) TestRebootNoError(c *C) {
@pedronis

pedronis May 19, 2017

Contributor

can we test the argument to shutdown as well here ? we should have examples how to do that

Thanks for doing this work! It looks good, I have some comments inline

+ fclose(f);
+ return -1;
+ }
+ arg[strcspn(arg, "\n")] = '\0';
@mvo5

mvo5 May 22, 2017

Collaborator

Curious why strcspn oder a simple strchr()?

@alfonsosanchezbeato

alfonsosanchezbeato May 23, 2017

Contributor

strchr() would need a check for NULL pointer on failure to find '\n', which is not needed by strcspn()

daemon/daemon.go
}
+ // Reboot in 10 minutes
+ bootloader.RebootForUpdate(10)
@mvo5

mvo5 May 22, 2017

Collaborator

I would prefer it bootloader.RebootForUpdate() takes a time.Time - this way it is more clear what the parameter means and the code is easier to read (even without a comment).

partition/bootloader_test.go
+ defer systemdCmd.Restore()
+
+ err := reboot(10)
+ c.Assert(err, IsNil)
@mvo5

mvo5 May 22, 2017

Collaborator

I think we also want to c.Check(systemdCmd.Calls(), DeepEquals, [][]string{ {"shutdown", "+10"} }) (or similar) here just to ensure that a) shutdown got really called b) it was called with the right parameters.

partition/bootloader_test.go
+ defer systemdCmd.Restore()
+
+ err := reboot(10)
+ c.Assert(err, NotNil)
@mvo5

mvo5 May 22, 2017

Collaborator

If we could use c.Assert(err, ErrorMatches, "the actual error") that would be great.

partition/grub.go
@@ -81,3 +81,7 @@ func (g *grub) SetBootVars(values map[string]string) error {
}
return env.Save()
}
+
+func (g *grub) RebootForUpdate(afterMins int) error {
+ return reboot(afterMins)
@mvo5

mvo5 May 22, 2017

Collaborator

This (and the one for uboot) are untested currently and this will reduce the test-coverage. Not a big deal, we can do a followup branch but if we can get a test for this cheaply that would be great.

Contributor

alfonsosanchezbeato commented May 22, 2017

Please consider this branch on hold, as I have added a comment in the android support thread in the forum and the way to do this might change: https://forum.snapcraft.io/t/android-support-in-snapd/327/34

@pedronis pedronis added the Blocked label May 22, 2017

Contributor

alfonsosanchezbeato commented May 23, 2017

After some discussion in the forum thread, we are probably not going to use the snapd parts of this PR. I have repushed just the changes to system-shutdown, which are good to have anyway so we fulfill systemd interface for the reboot argument.

@pedronis please consider the PR unblocked and ready for review again.

codecov-io commented May 23, 2017

Codecov Report

Merging #3353 into master will decrease coverage by <.01%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3353      +/-   ##
==========================================
- Coverage   77.56%   77.56%   -0.01%     
==========================================
  Files         371      371              
  Lines       25519    25519              
==========================================
- Hits        19794    19793       -1     
- Misses       3975     3976       +1     
  Partials     1750     1750
Impacted Files Coverage Δ
interfaces/sorting.go 93.33% <0%> (-3.34%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3d65d03...fbb8107. Read the comment docs.

branch has been reduced since

cmd/system-shutdown/system-shutdown.c
@@ -56,6 +62,8 @@ int main(int argc, char *argv[])
if (mkdir("/writable", 0755) < 0) {
die("cannot create directory /writable");
}
+ // We are reading a file from /run and need to do this before unmounting
+ sc_read_reboot_arg(reboot_arg, sizeof reboot_arg);
@zyga

zyga May 24, 2017

Contributor

This is ignoring the error code.

@alfonsosanchezbeato

alfonsosanchezbeato May 24, 2017

Contributor

Yes, because no action would be taken if there is a failure to read the file, we will simply have reboot_arg untouched, which is checked later when choosing how to reboot. If you want we can print something here, but just an info log, as the file not being present would be actually quite frequent.

cmd/system-shutdown/system-shutdown.c
+ ret = reboot(cmd);
+
+ if (ret == -1)
+ kmsg("Error calling reboot! : %s (%d)", strerror(errno), errno);
@zyga

zyga May 24, 2017

Contributor

Nitpick, can you please reword this: kmsg(cannot reboot the system: %s", strerror(errno));

@@ -93,7 +101,19 @@ int main(int argc, char *argv[])
}
}
- reboot(cmd);
+ // glibc reboot wrapper does not expose the optional reboot syscall
+ // parameter
@chipaca

chipaca May 24, 2017

Member

you could also use reboot(2) from linux/reboot.h instead of sys/reboot.h, but not both of these at the same time.

@alfonsosanchezbeato

alfonsosanchezbeato May 24, 2017

Contributor

Not sure what you mean, /usr/include/linux/reboot.h only shows some macros, and does not have any include directive.

@chipaca

chipaca May 24, 2017

Member

ah, I misread reboot(2), then.

Contributor

alfonsosanchezbeato commented May 24, 2017

MP refreshed after addressing comments.

@pedronis pedronis removed the Blocked label May 25, 2017

mvo5 approved these changes May 29, 2017

Looks good to me now, thanks for your work on this PR!

zyga approved these changes Jun 1, 2017

alfonsosanchezbeato added some commits May 18, 2017

Add support to reboot param in system-shutdown
So now the syntax "reboot <parameter>" works.

@zyga zyga merged commit 4543861 into snapcore:master Jun 5, 2017

7 checks passed

artful-amd64 autopkgtest finished (success)
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
xenial-amd64 autopkgtest finished (success)
Details
xenial-i386 autopkgtest finished (success)
Details
xenial-ppc64el autopkgtest finished (success)
Details
yakkety-amd64 autopkgtest finished (success)
Details
zesty-amd64 autopkgtest finished (success)
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment