-
Notifications
You must be signed in to change notification settings - Fork 582
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
partition,snap: add support for android boot #3324
Conversation
Rename fastboot bootloader to android-boot, as this is a more accurate description (fastboot is a protocol). android-boot is chosen because what we support is booting from devices that are configured in the usual way for Android devices, with recovery and boot partitions and boot images with format as defined in https://android.googlesource.com/platform/system/core/+/master/mkbootimg/bootimg.h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments / early feedback.
partition/androidboot.go
Outdated
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
|
||
/* | ||
* Copyright (C) 2014-2017 Canonical Ltd |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: was this written in 2014?
partition/androidboot.go
Outdated
} | ||
|
||
func (a *androidboot) GetBootVars(names ...string) (map[string]string, error) { | ||
out := make(map[string]string) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: move this closer to the loop that populates it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
furthermore: you know how big it needs to be (len(names)
), so make
it with that size already
partition/androidboot_test.go
Outdated
. "gopkg.in/check.v1" | ||
) | ||
|
||
func mockAndroidbootFile(c *C, newPath string, mode os.FileMode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we not have a SetUpTest
function that calls dirs.SetRootDir(c.MkDir())
? This should be paired with TearDownTest
that calls dirs.SetRootDir("")
partition/androidboot_test.go
Outdated
func (s *PartitionTestSuite) TestNewAndroidbootNoAndroidbootReturnsNil(c *C) { | ||
s.makeFakeAndroidbootConfig(c) | ||
|
||
dirs.GlobalRootDir = "/something/not/there" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't want this, see my comment above
partition/androidboot_test.go
Outdated
c.Assert(err, IsNil) | ||
} | ||
|
||
func (s *PartitionTestSuite) makeFakeAndroidbootConfig(c *C) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this happens to be done in each test feel free to do it in SetUpTest
return err | ||
} | ||
|
||
rawEnv := bytes.Split(buf, []byte("\n")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be nicer with a bufio.Scanner
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, this whole method needs restructuring around that. Please.
l := bytes.SplitN(env, []byte("="), 2) | ||
// be liberal in what you accept | ||
if len(l) < 2 { | ||
continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: please log weird stuff with logger.Noticef
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(or logger.Debugf
if it's weird but harmless)
return err | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the place to use our atomic write helpers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, one nice thing about bytes.Buffer
is that you can just declare it and start using it; NewBuffer
is only needed when you want to preload the buffer with something. Otherwise, just
var w bytes.Buffer
and you're done (although some things will need you to pass a pointer to this, e.g. things expecting an io.Writer
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice, thank you!
A bunch of small things to fix, mostly idiomatic and not conceptual.
partition/androidboot.go
Outdated
} | ||
|
||
func (a *androidboot) GetBootVars(names ...string) (map[string]string, error) { | ||
out := make(map[string]string) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
furthermore: you know how big it needs to be (len(names)
), so make
it with that size already
l := bytes.SplitN(env, []byte("="), 2) | ||
// be liberal in what you accept | ||
if len(l) < 2 { | ||
continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(or logger.Debugf
if it's weird but harmless)
return err | ||
} | ||
|
||
rawEnv := bytes.Split(buf, []byte("\n")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, this whole method needs restructuring around that. Please.
return err | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep :-)
partition/androidboot_test.go
Outdated
* | ||
*/ | ||
|
||
package partition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you make this package partition_test
, please? (maybe use export_test.go
to get to the private bits you need for testing)
partition/androidboot_test.go
Outdated
|
||
a := newAndroidboot() | ||
bootVars := map[string]string{} | ||
bootVars["snap_mode"] = "try" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could directly
bootVars := map[string]string{"snap_mode": "try"}
return err | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, one nice thing about bytes.Buffer
is that you can just declare it and start using it; NewBuffer
is only needed when you want to preload the buffer with something. Otherwise, just
var w bytes.Buffer
and you're done (although some things will need you to pass a pointer to this, e.g. things expecting an io.Writer
).
|
||
func (a *androidbootenvTestSuite) TestSaveAndLoad(c *C) { | ||
env := androidbootenv.NewEnv(a.envPath) | ||
c.Check(env, NotNil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here this should probably be an Assert
(otherwise all the other tests will panic)
|
||
func (a *androidbootenvTestSuite) TestSet(c *C) { | ||
env := androidbootenv.NewEnv(a.envPath) | ||
c.Check(env, NotNil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... Assert :-)
snap/gadget.go
Outdated
foundBootloader = true | ||
default: | ||
return nil, fmt.Errorf(errorFormat, "bootloader must be either grub or u-boot") | ||
return nil, fmt.Errorf(errorFormat, "bootloader must be either grub, u-boot or android-boot") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/either/one of/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let us know if there's anything we can do to help.
b3d8bc3
to
bdbce34
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks very nice, thanks for doing this work. I have some comments inside that might be worth considering. But nothing major, it looks pretty nice.
partition/export_test.go
Outdated
) | ||
|
||
// creates a new Androidboot bootloader object | ||
func MockNewAndroidboot() Bootloader { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nitpick) this can probably just be NewAndroidboot()
nothing is mocked/faked here its just exported for the tests.
partition/export_test.go
Outdated
func MockAndroidbootFile(c *C, mode os.FileMode) { | ||
f := &androidboot{} | ||
newpath := filepath.Join(dirs.GlobalRootDir, "/boot/androidboot") | ||
os.MkdirAll(newpath, os.ModePerm) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nitpick) I think it makes sense to get the err from os.MkdirAll() and c.Assert(err, IsNil).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, os.ModePerm is 0777, even in its a test I would use 0755
here instead (just in case umask is funny etc).
partition/export_test.go
Outdated
|
||
func MockAndroidbootFile(c *C, mode os.FileMode) { | ||
f := &androidboot{} | ||
newpath := filepath.Join(dirs.GlobalRootDir, "/boot/androidboot") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nitpick) might be easier to do: newpath := filepath.Dir(f.ConfigFile())
(or f.Dir()), then you don't need to encode the knowledge of the exact path into this test.
file, err := os.Open(a.path) | ||
if err != nil { | ||
return err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defer file.Close()
is missing here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(fixed, below)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much nicer, thank you! A few more suggestions.
|
||
out := make(map[string]string, len(names)) | ||
for _, name := range names { | ||
out[name] = env.Get(name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can there be any duplicates in names
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really, it would be a bug either in snapd or in the bootloader if that happens.
if err := env.Load(); err != nil && !os.IsNotExist(err) { | ||
return err | ||
} | ||
for k, v := range values { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to worry about overwriting values here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, values should not be duplicated
partition/androidboot_test.go
Outdated
func (g *androidbootTestSuite) SetUpTest(c *C) { | ||
dirs.SetRootDir(c.MkDir()) | ||
|
||
// the file needs to exist |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be more useful to explain why the file has to exist.
partition/androidboot_test.go
Outdated
} | ||
|
||
func (s *androidbootTestSuite) TestNewAndroidbootNoAndroidbootReturnsNil(c *C) { | ||
dirs.SetRootDir("") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Except when ran on android. I would suggest you to actually keep using the temporary random directory and populate it (or not populate it perhaps) so that it has the right data for the test. This will just run it on the real system which is not what unit tests are about.
partition/androidboot_test.go
Outdated
} | ||
|
||
func (s *androidbootTestSuite) TestSetGetBootVar(c *C) { | ||
a := partition.MockNewAndroidboot() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can move this to the SetUpTest
function as it seems to happen in all the tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TestNewAndroidbootNoAndroidbootReturnsNil is unfortunately an exception.
"github.com/snapcore/snapd/osutil" | ||
) | ||
|
||
type Env struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some documentation (tip: run golint
in the directory) would help here.
l := strings.SplitN(scanner.Text(), "=", 2) | ||
// be liberal in what you accept | ||
if len(l) < 2 { | ||
logger.Noticef("WARNING: bad value while parsing %v", a.path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be invaluable to actually log scanner.Text()
with %q
here.
} | ||
|
||
func (a *androidbootenvTestSuite) TestSet(c *C) { | ||
env := androidbootenv.NewEnv(a.envPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
env := ...
can be moved to SetUpTest
partition/export_test.go
Outdated
) | ||
|
||
// creates a new Androidboot bootloader object | ||
func MockNewAndroidboot() Bootloader { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a mock, it's doing the real thing.
Instead do this:
var NewAndroidBoot = newAndroidBoot
(Note that I also changed the capitalization of androidboot
to AndroidBoot
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed but to NewAndroidBoot instead
partition/export_test.go
Outdated
return newAndroidboot() | ||
} | ||
|
||
func MockAndroidbootFile(c *C, mode os.FileMode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: spell it as androidBoot
everywhere
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nearly there! marking approved by me as my concerns are raised by zyga and mvo already :-)
Thanks again for the reviews, PR refreshed after addressing comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One last set of nitpicks. LGTM then
} | ||
a.env[l[0]] = l[1] | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need to use the scanner.Err
here. See https://golang.org/src/bufio/example_test.go for examples.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, done
func (a *Env) Save() error { | ||
var w bytes.Buffer | ||
|
||
for k, v := range a.env { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we care about deterministic output here? If so please sort the keys, and write in order.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really, neither the booloader or snapd cares about the order in which the variables are stored.
a.env.Set("key3", "value3") | ||
|
||
err := a.env.Save() | ||
c.Assert(err, IsNil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a trivial test that ensures the output is as you expect, as in the actual string is what we expect (deterministic)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See previous comment
PR repushed after addressing comments |
Codecov Report
@@ Coverage Diff @@
## master #3324 +/- ##
==========================================
- Coverage 77.6% 77.59% -0.02%
==========================================
Files 364 366 +2
Lines 24956 25014 +58
==========================================
+ Hits 19368 19409 +41
- Misses 3865 3876 +11
- Partials 1723 1729 +6
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, there are some tweaks we could do but I won't mind doing it in a follow-up.
Support for android style bootloading