My upgrade to Fedora 27, Secure Boot, and a mistake made somewhere
I'm usually slow about updating to new versions of Fedora; I like to let other people find the problems and then it's generally a hassle in various ways, so I keep putting it off. This week I decided that I'd been sitting on the Fedora 27 upgrade for long enough (or too long), and today it was the turn of my work laptop. It didn't entirely go well, but after the dust settled I think it's due to an innocent looking mistake I made and my specific laptop configuration.
This is a new laptop, a Dell XPS 13, and this is the first Fedora
upgrade I've done on it (I installed Fedora 26 when we got it in
mid-August). As I usually do, I did the Fedora 26 to 27 upgrade
with the officially unsupported method of a live upgrade with
based on the traditional documentation for it,
which I've been doing on multiple machines for many years. After I
finished the upgrade process, I rebooted and the laptop failed to
come up in Linux; instead it booted into the Windows 10 installation
that I have on the other half of its drive. My Linux install (now
with Fedora 27) was intact, but it wouldn't boot at all.
I will start with the summary. If your system boots using UEFI,
you almost certainly shouldn't ever run
portions of the Fedora wiki (like the Fedora page on Grub2) will tell you this pretty
loudly, but the 'upgrade with package manager' page
still says to use
grub2-install without any qualifications, and that's
what I did during my Fedora 27 upgrade.
What caused my issue is that I have Secure Boot
enabled on my laptop, and at some point during the upgrade my Fedora
UEFI boot entry wound up pointing to the EFI image image
EFI/fedora/grubx64.efi, which isn't correctly signed and so won't
boot under Secure Boot. The XPS UEFI firmware doesn't report any
error message when this happens; instead it silently goes on to the
next UEFI boot entry (if there is one), which in my case was Windows'
In order to boot my laptop with Secure Boot enabled, the UEFI boot
entry for Fedora 27 needs to point to
grubx64.efi. This shim loader is signed and passes the UEFI
firmware's Secure Boot verification, and once it starts it hands
things off to
grubx64.efi for regular GRUB2 UEFI booting.
(If I disabled Secure Boot, I could use the
boot entry. Otherwise, only the
shimx64.efi entry worked.)
At this point I don't know what my Fedora 26 UEFI boot entry looked
like, but I suspect that it pointed to the Fedora 26 version of the
shim (which appears to be called
EFI/fedora/shim.efi). My best
guess for what happened during my Fedora 27 upgrade is that when I
grub2-install at the end, one of the things it did was
run efibootmgr and reset
where the 'fedora' UEFI boot entry pointed. I don't remember seeing
any message reporting this, but I didn't run
any flag to make it verbose and the code to run efibootmgr
appears to be in the Grub2 source.
(And changing the UEFI boot entry is sort of reasonable. After all,
I told Grub2 to install itself, and that logically includes making
the UEFI boot entry point to it, just as
grub2-install on a
non-UEFI system will update the MBR boot record to point to itself.)
PS: I consider all of this a valuable learning experience, since I got to shoot myself in the foot and learn a bunch of things about UEFI on a machine I could live without. I'm planning to set up my future desktops as pure UEFI machines, and making this mistake on one of them would have been much more painful. For that matter, simply knowing how to set up UEFI boot entries is going to come in handy when I migrate my current disks over to the new machines.
(I'm up in the air about whether or not I'll use Secure Boot on the desktops. If they come that way, well, maybe.)
Sidebar: How I fixed this
In theory you can boot a Fedora 27 live image from a USB stick and
fiddle around with
efibootmgr. In practice, I went in to the
laptop's UEFI 'BIOS' interface and told it to add another UEFI boot
entry, because this had a reasonably simple and obvious interface.
The resulting entry is a bit different from what I think
would make, but it works (as well it should, since it was set up by
the very thing that's interpreting it).
(In the course of this experience I was not pleased to discover that the Dell XPS 13's UEFI interface will let you delete UEFI boot entries with immediate effect and no confirmation or saving needed. Click the wrong button at the wrong time, and your entry is irretrievably gone on the spot.)
In practice, Go's slices are two different data structures in one
As I've seen them in Go code and used them myself, Go's slices are generally used in two pretty distinctly separate situations. As a result, I believe that many people have two different conceptual models of slices and their behavior, depending on which situation they're using slices in.
The first model and use of slices is as views into a concrete array
(or string) that you already have in your code. You're taking an
efficient reference to some portion of the array and saying 'here,
deal with this chunk' to some piece of code. This is the use of
slices that is initially presented in A Tour of Go here and that is implicitly used
in, for example,
io.Writer, both of which
are given a reference to an underlying concrete byte array.
The second model and use of slices is as dynamically resizable
arrays. This is the usage where, for example, you start with '
accum string', and then add things to it with '
accum = append(accum,
...)'. In general, any code using
append() is using slices this
way, as is code that uses explicit slice literals ('
b, ..}'). Dynamically resizable arrays are a very convenient thing,
so this sort of slice shows up in lots of Go code.
(Part of this is that Go's type system strongly encourages you to use slices instead of arrays, especially in arguments and return values.)
Slices as dynamically resizable arrays actually have an anonymous backing store behind them, but you don't normally think about it; it's materialized, managed, and deallocated for you by the runtime and you can't get a direct reference to it. As we've seen, it's easy to not remember that the second usage of slices is actually a funny, GC-driven special case of the first sort of use. This can lead to leaking memory or corrupting other slices.
(It's not quite fair to call the anonymous backing array an implementation detail, because Go explicitly documents it in the language specification. But I think people are often going to wind up working that way, with the slice as the real thing they deal with and the backing array just an implementation detail. This is especially tempting since it works almost all of the time.)
This distinct split in usage and conceptual model (and the glitches that result at the edges of it) are why I've wound up feeling that in practice, Go's slices are two different data structures in one. The two concepts may be implemented with the same language features and runtime mechanisms, but people treat them differently and have different expectations and beliefs about them.