1
1
mirror of https://github.com/NixOS/mobile-nixos.git synced 2024-12-26 01:13:37 +03:00

Merge branch 'feature/stage-2'

This commit is contained in:
Samuel Dionne-Riel 2019-09-21 23:22:57 -04:00
commit 7f5266de24
68 changed files with 2306 additions and 464 deletions

View File

@ -1,38 +1,32 @@
Mobile NixOS
============
An overlay for building stuff.
This is a work-in-progress.
*This is expected to be built against the nixos-unstable for now.*
WIP notes
---------
```
nix-build --argstr device asus-z00t -A all
# Maybe `nix copy ./result --to ssh://another-host`
adb wait-for-device && adb reboot bootloader
fastboot boot result # or full path
# getting adb and fastboot working is left as an exercise to the reader.
```
Alternatively, helpers under `bin` can be used. They mostly pave over
the nix CLI to provide one-liners and one-parameter helpers.
```
# Builds -A all for device_name $1
bin/build asus-z00t
nix-build --argstr device asus-z00t -A build.android-bootimg
```
### Booting qemu
```
bin/build qemu-x86_64
bin/boot-qemu
```
The qemu target has a `vm` build output, which results in a script that will
automatically start the "virtual device".
This currently does not build using 18.03 and may never (18.09 may release before!)
```
nix-build -I --argstr device qemu-x86_64 -A build.vm
./result
```
### `local.nix`

BIN
artwork/splash.stage-0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
artwork/splash.stage-0.xcf Normal file

Binary file not shown.

BIN
artwork/splash.stage-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,50 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -p qemu -p ruby -i ruby
# Boots a given `result` symlink on a remote system.
# This is mainly used to speed-up development where
# an aarch64 host is used to build the system, but the
# target device is plugged into another system
require_relative "../lib/rb/all.rb"
require "fileutils"
include FileUtils
# This assumes `result` is boot.img.
STORE_PATH = File.readlink("result")
# FIXME : read-only doesn't work...
$copy_readwrite = true;
begin
disk_image = Dir.glob(File.join(File.readlink("result"), "system", "sd-image", "*.img")).first
if $copy_readwrite then
DISK_IMAGE = "fs.img"
cp(disk_image, DISK_IMAGE)
chmod("u=rw", DISK_IMAGE)
else
DISK_IMAGE = disk_image
end
end
unless ARGV.count == 0 then
puts "Usage: bin/boot-eqmu"
exit 1
end
KERNEL_INITRD = "result/kernel-initrd"
args = []
args.push("qemu-system-x86_64")
args.push("-kernel", "#{KERNEL_INITRD}/kernel")
args.push("-initrd", "#{KERNEL_INITRD}/initrd")
args.push("-append", File.read("#{KERNEL_INITRD}/cmdline.txt"))
args.push("-m", "#{File.read("#{KERNEL_INITRD}/ram.txt")}M")
args.push("-serial", "stdio")
args.push("-drive", "file=#{DISK_IMAGE},format=raw#{if $copy_readwrite then "" else ",readonly" end}")
args.push("-device", "e1000,netdev=net0")
args.push("-netdev", "user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::2323-:23,net=172.16.42.0/24,dhcpstart=172.16.42.1")
run(*args, exec: true)
# vim: ft=ruby

View File

@ -1,26 +0,0 @@
#!/usr/bin/env nix-shell
#!nix-shell -p ruby -i ruby
require_relative "../lib/rb/all.rb"
# Given a device name, instantiates `nix-build` to build
# everything needed to boot on that device.
if ARGV.count < 1 then
puts "Usage: bin/build <device-name>"
exit 1
end
DEVICE = ARGV.shift
NIXPKGS=File.join(*__dir__.split("/")[0..-2], "nixpkgs")
run(
"env", "-i",
"nix-build", "-A", "all",
"-I", "nixpkgs=#{NIXPKGS}",
"--argstr", "device", DEVICE,
*ARGV,
exec: true
)
# vim: ft=ruby

6
bin/ssh-initrd Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
PS4=" $ "
set -eux
exec ssh -o UserKnownHostsFile=/dev/null root@172.16.42.1

View File

@ -1,31 +1,75 @@
# This entry points allows calling `nix-build -A` with
# anything defined in the overlay (or the host system).
# Selection of the device can be made either through the environment or through
# using `--argstr device [...]`.
let deviceFromEnv = builtins.getEnv "MOBILE_NIXOS_DEVICE"; in
{
device ?
# "a" nixpkgs we're using for its lib.
pkgs' ? import <nixpkgs> {}
# The identifier of the device this should be built for.
, device ?
if deviceFromEnv == ""
then throw "Please pass a device name or set the MOBILE_NIXOS_DEVICE environment variable."
else deviceFromEnv
}:
with import <nixpkgs> {};
let
# Evaluation doesn't actually use the overlay.
# The overlay has been re-defined in the modules system.
eval = import ./lib/eval-config.nix {
inherit (pkgs'.lib) optional;
# Evaluates NixOS, mobile-nixos and the device config with the given
# additional modules.
evalWith = modules: import ./lib/eval-config.nix {
modules = [
(import (./. + "/devices/${device}" ))
]
# TODO : allow loading from elsewhere through ENV
++ lib.optional (builtins.pathExists ./local.nix) (import (./local.nix ))
;
] ++ modules;
};
# Eval with `local.nix` if available.
# This eval is the "WIP" eval. What's usually built with `nix-build`.
eval = evalWith (optional (builtins.pathExists ./local.nix) (import (./local.nix )));
# This is used by the `-A installer` shortcut.
installer-eval = evalWith [
./profiles/installer.nix
];
in
{
inherit (eval.config.system.build) all;
# The build artifacts from the modules system.
inherit (eval.config.system) build;
# The evaluated config
inherit (eval) config;
# The final pkgs set, usable as -A pkgs.[...] on the CLI.
inherit (eval) pkgs;
# The whole (default) eval
inherit eval;
# Shortcut to allow building `nixos` from the same channel revision.
# This is used by `./nixos/default.nix`
# Any time `nix-build nixos` is used upstream, it can be used here.
nixos = (import (path + "/nixos"));
nixos = import <nixpkgs/nixos>;
# `mobile-installer` will, when possible, contain the installer build for the
# given system. It usually is an alias for a disk-image type build.
installer = installer-eval.config.system.build.mobile-installer;
# Evaluating this whole set is counter-productive.
# It'll put a *bunch* of build products from the misc. inherits we added.
# (We're also using `device` to force the other throw to happen first.)
# TODO : We may want to produce an internal list of available outputs, so that
# each platform can document what it makes available. This would allow
# the message to be more user-friendly by displaying a choice.
__please-fail = throw ''
Cannot directly build for ${device}...
Building this whole set is counter-productive, and not likely to be what
is desired.
You can try to build the `installer` attribute (-A installer) if your system
provides an installer.
Please refer to your platform's documentation for usage.
'';
}

View File

@ -14,5 +14,5 @@
};
};
mobile.system.type = "android-bootimg";
mobile.system.type = "android";
}

View File

@ -19,5 +19,5 @@
};
};
mobile.system.type = "android-bootimg";
mobile.system.type = "android";
}

View File

@ -0,0 +1,13 @@
diff --git a/arch/arm64/crypto/Makefile b/arch/arm64/crypto/Makefile
index abb79b3cfcfe..a0f8d8f9e88a 100644
--- a/arch/arm64/crypto/Makefile
+++ b/arch/arm64/crypto/Makefile
@@ -36,7 +36,7 @@ CFLAGS_aes-glue-ce.o := -DUSE_V8_CRYPTO_EXTENSIONS
obj-$(CONFIG_CRYPTO_CRC32_ARM64) += crc32-arm64.o
-CFLAGS_crc32-arm64.o := -mcpu=generic+crc
+CFLAGS_crc32-arm64.o := -march=armv8-a+crc
$(obj)/aes-glue-%.o: $(src)/aes-glue.c FORCE
$(call if_changed_rule,cc_o_c)

View File

@ -21,6 +21,7 @@
patches = [
./0001-Porting-changes-found-in-LineageOS-android_kernel_cy.patch
./01_more_precise_arch.patch
./01_fix_gcc6_errors.patch
./02_mdss_fb_refresh_rate.patch
./05_dtb-fix.patch

View File

@ -38,5 +38,5 @@
};
};
mobile.system.type = "android-bootimg";
mobile.system.type = "android";
}

View File

@ -13,7 +13,7 @@
# This is because motorola ships an armv7l userspace from stock ROM.
#
# in local.nix:
# mobile.system.platform = lib.mkForce "armv7a-linux";
# mobile.system.system = lib.mkForce "armv7l-linux";
#
let

View File

@ -1,7 +1,5 @@
# This file is based on <nixpkgs/nixos/lib/eval-config.nix>.
# From a device configuration, build an initrd.
{ # !!! system can be set modularly, would be nice to remove
system ? builtins.currentSystem
, # !!! is this argument needed any more? The pkgs argument can

View File

@ -0,0 +1,46 @@
{ lib, newScope }:
let
inherit (lib) makeScope;
in
makeScope newScope (self:
let
inherit (self) callPackage;
in
# Note: Prefer using `self.something.deep` rather than making `something` a
# recursive set. Otherwise it won't override as expected.
{
makeFilesystem = callPackage ./makeFilesystem.nix {};
# All known supported filesystems for image generation.
# Use stand-alone (outside of a disk image) is supported.
fileSystem = {
makeExt4 = callPackage ./makeExt4.nix {};
makeFAT32 = callPackage ./makeFAT32.nix {};
# Specialization of `makeFAT32` with (1) filesystemType showing as ESP,
# and (2) the name defaults to ESP.
makeESP = args: self.fileSystem.makeFAT32 ({ name = "ESP"; filesystemType = "ESP"; } // args);
};
gap = length: {
inherit length;
isGap = true;
};
# All supported disk formats for image generation.
diskImage = {
makeMBR = callPackage ./makeMBR.nix {};
makeGPT = callPackage ./makeGPT.nix {};
};
# Don't do maths yourselves, just use the helpers.
# Yes, this is the bibytes family of units.
# (This is fine as rec; it won't be overriden.)
size = rec {
TiB = x: 1024 * (GiB x);
GiB = x: 1024 * (MiB x);
MiB = x: 1024 * (KiB x);
KiB = x: 1024 * x;
};
}
)

View File

@ -0,0 +1,19 @@
In-depth tests
==============
Those are rather bulky integration-type tests. They are not ran by default, but
should be used to better test the image builder infrastructure.
Changes to the infra should be passed through this test suite in addition to the
slimmer usual tests suite.
> **Tip:**
>
> From the image-builder infra directory, run the following.
>
> ```
> nix-build in-depth-tests/[...].nix -I nixpkgs-overlays=$PWD/lib/tests/test-overlay.nix
> ```
>
> This allows you to track the bigger builds more easily.

View File

@ -0,0 +1,96 @@
{ pkgs ? import <nixpkgs> {} }:
let
inherit (pkgs) imageBuilder ubootTools;
configTxt = pkgs.writeText "config.txt" ''
kernel=u-boot-rpi3.bin
# Boot in 64-bit mode.
arm_control=0x200
# Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
# when attempting to show low-voltage or overtemperature warnings.
avoid_warnings=1
'';
scrTxt = pkgs.writeText "uboot.scr.txt" ''
echo
echo
echo
echo
echo " **"
echo " ** Image Builder sanity checks!"
echo " ** This will appear to freeze since the kernel is not built with the VC4 kernel built-in."
echo " ** Stay assured, the kernel should be panicking anyway since there is no initrd, no init, and no useful FS."
echo " **"
echo
load $devtype $devnum:$distro_bootpart $kernel_addr_r boot/kernel
booti $kernel_addr_r
'';
scr = pkgs.runCommandNoCC "uboot-script" {} ''
mkdir -p $out
${ubootTools}/bin/mkimage \
-A arm64 \
-O linux \
-T script \
-C none \
-n ${scrTxt} -d ${scrTxt} \
$out/boot.scr
'';
# Here, we built a fictitious system cloning the AArch64 sd-image setup.
# The chosen derivations are known to build fully when cross-compiled.
pkgsAArch64 = (if pkgs.stdenv.isAarch64 then pkgs else pkgs.pkgsCross.aarch64-multiplatform);
# The kernel for the device.
kernel = pkgsAArch64.linux_rpi;
# TODO: for completeness' sake an initrd with the vc4 driver should be built
# to show that this works as a self-contained demo.
in
with imageBuilder;
/**
* This disk image is built to be functionally compatible with the usual `sd_image`
* from NixOS, but *it is not* an actual `sd_image` compatible system.
*
* The main thing it aims to do is *minimally* create a bootable system.
*/
diskImage.makeMBR {
name = "diskimage";
diskID = "01234567";
partitions = [
(gap (size.MiB 10))
(fileSystem.makeFAT32 {
# Size-less
name = "FIRMWARE";
partitionID = "ABADF00D";
extraPadding = size.MiB 10;
populateCommands = ''
(
src=${pkgsAArch64.raspberrypifw}/share/raspberrypi/boot
cp $src/bootcode.bin $src/fixup*.dat $src/start*.elf ./
cp ${pkgsAArch64.ubootRaspberryPi3_64bit}/u-boot.bin ./u-boot-rpi3.bin
cp ${configTxt} ./config.txt
)
'';
})
(fileSystem.makeExt4 {
bootable = true;
name = "NIXOS";
partitionID = "44444444-4444-4444-8888-888888888888";
populateCommands = ''
mkdir -p ./boot
cp ${kernel}/Image ./boot/kernel
cp ${scr}/boot.scr ./boot/boot.scr
'';
})
];
}

View File

@ -0,0 +1,87 @@
# Ensures we can fit stuff in an ext4 image.
{ pkgs ? import <nixpkgs> {} }:
let
inherit (pkgs) imageBuilder;
makeNull = size: let
filename = "null.img";
filesystemType = "FAT32"; # meh, good enough
in
''
mkdir -p $out
dd if=/dev/zero of=./${toString size}.img bs=${toString size} count=1
'';
in
with imageBuilder;
{
eight = fileSystem.makeExt4 {
name = "eight";
partitionID = "44444444-4444-4444-0000-000000000008";
populateCommands = ''
${makeNull (imageBuilder.size.MiB 8)}
'';
};
eleven = fileSystem.makeExt4 {
name = "eleven";
partitionID = "44444444-4444-4444-0000-000000000011";
populateCommands = ''
${makeNull (imageBuilder.size.MiB 11)}
'';
};
sixteen = fileSystem.makeExt4 {
name = "sixteen";
partitionID = "44444444-4444-4444-0000-000000000016";
populateCommands = ''
${makeNull (imageBuilder.size.MiB 16)}
'';
};
one_twenty_eight = fileSystem.makeExt4 {
name = "one_twenty_eight";
partitionID = "44444444-4444-4444-0000-000000000128";
populateCommands = ''
${makeNull (imageBuilder.size.MiB 128)}
'';
};
two_fifty_six = fileSystem.makeExt4 {
name = "two_fifty_six";
partitionID = "44444444-4444-4444-0000-000000000256";
populateCommands = ''
${makeNull (imageBuilder.size.MiB 256)}
'';
};
five_twelve = fileSystem.makeExt4 {
name = "five_twelve";
partitionID = "44444444-4444-4444-0000-000000000512";
populateCommands = ''
${makeNull (imageBuilder.size.MiB 512)}
'';
};
with_space = fileSystem.makeExt4 {
name = "with_space";
partitionID = "44444444-4444-4444-0000-000000000005";
populateCommands = ''
${makeNull (imageBuilder.size.MiB 5)}
'';
extraPadding = size.MiB 10;
};
# Fills 512 MiB (the downard slump in the high fudge factor) with 512 1MiB
# files so we ensure the filesystem overhead is accounted for.
multiple-files = fileSystem.makeExt4 {
name = "multiple-files";
partitionID = "44444444-4444-4444-0000-000000000512";
populateCommands = ''
for i in {1..512}; do
dd if=/dev/zero of=./$i.img bs=${toString (imageBuilder.size.MiB 1)} count=1
done
'';
};
}

View File

@ -0,0 +1,5 @@
[
(self: super: { imageBuilder = self.callPackage ../. {}; })
# All the software will be upstreamed with NixOS when upstreaming the library.
(import ../../../overlay/overlay.nix)
]

View File

@ -0,0 +1,18 @@
# Adds the imageBuilder overlay.
(import ../overlay.nix) ++
[
# Makes the imageBuilder build impure to force rebuilds to more easily test
# reproducibility of outputs.
(self: super:
let
inherit (self.lib.attrsets) mapAttrs;
in
{
imageBuilder = super.imageBuilder.overrideScope'(self: super: {
makeFilesystem = args: super.makeFilesystem (args // {
REBUILD = "# ${toString builtins.currentTime}";
});
});
}
)
]

View File

@ -0,0 +1,61 @@
#!/usr/bin/env ruby
require "shellwords"
require "open3"
# Test harness to validate results of a `nix-build`.
# This script will `#load()` the given script.
if ARGV.length < 2 then
abort "Usage: verify.rb <script> <result>"
end
$failures = []
$script = ARGV.shift
$result = ARGV.shift
module Helpers
def compare_output(cmd, expected, message: nil)
expected ||= ""
message ||= "Command `#{cmd.shelljoin}` has unexpected output:"
expected = expected.strip
out = `#{cmd.shelljoin}`.strip
unless out == expected then
$failures << "#{message}:\n\tGot: #{out}\n\tExpected #{expected}"
end
end
def sha256sum(filename, expected)
filename = File.join($result, filename)
compare_output(
["nix-hash", "--flat", "--type", "sha256", filename], expected,
message: "File #{filename.shellescape} has unexpected hash",
)
end
def file(filename, expected)
filename = File.join($result, filename)
compare_output(
["file", "--dereference", "--brief", filename], expected,
message: "File #{filename.shellescape} has unexpected file type",
)
end
end
include Helpers
# Executes the script
load($script)
at_exit do
if $failures.length > 0 then
puts "Verification failed:"
$failures.each do |failure|
puts "#{failure}"
end
exit 1
end
exit 0
end

View File

@ -0,0 +1,113 @@
{ lib, imageBuilder, libfaketime, e2fsprogs, make_ext4fs }:
/* */ let scope = { "fileSystem.makeExt4" =
let
inherit (lib.strings) splitString;
inherit (imageBuilder) makeFilesystem;
# Bash doesn't do floating point representations. Multiplications and divisions
# are handled with enough precision that we can multiply and divide to get a precision.
precision = 1000;
first = list: lib.lists.last (lib.lists.reverseList list);
chopDecimal = f: first (splitString "." (toString f));
makeFudge = f: toString (chopDecimal (f * precision));
# This applies only to 256MiB and greater.
# For smaller than 256MiB images the overhead from the FS is much greater.
# This will also let *some* slack space at the end at greater sizes.
# This is the value at 512MiB where it goes slightly down compared to 256MiB.
fudgeFactor = makeFudge 0.05208587646484375;
# This table was built using a script that built an image with `make_ext4fs`
# for the given size in MiB, and recorded the available size according to `df`.
smallFudgeLookup = lib.strings.concatStringsSep "\n" (lib.lists.reverseList(
lib.attrsets.mapAttrsToList (size: factor: ''
elif (( size > ${toString size} )); then
fudgeFactor=${toString factor}
'') {
"${toString (imageBuilder.size.MiB 5)}" = makeFudge 0.84609375;
"${toString (imageBuilder.size.MiB 8)}" = makeFudge 0.5419921875;
"${toString (imageBuilder.size.MiB 16)}" = makeFudge 0.288818359375;
"${toString (imageBuilder.size.MiB 32)}" = makeFudge 0.1622314453125;
"${toString (imageBuilder.size.MiB 64)}" = makeFudge 0.09893798828125;
"${toString (imageBuilder.size.MiB 128)}" = makeFudge 0.067291259765625;
"${toString (imageBuilder.size.MiB 256)}" = makeFudge 0.0518646240234375;
}
));
minimumSize = imageBuilder.size.MiB 5;
in
{ partitionID
, blockSize ? imageBuilder.size.KiB 4
, ... } @ args:
makeFilesystem (args // {
filesystemType = "ext4";
inherit blockSize minimumSize;
nativeBuildInputs = [
e2fsprogs
make_ext4fs
libfaketime
];
filesystemPhase = ''
:
'';
computeMinimalSize = ''
# `local size` is in bytes.
# We don't have a static reserved factor figured out. It is rather hard with
# ext4fs as there are multiple factors increasing the overhead.
local reservedSize=0
local fudgeFactor=${toString fudgeFactor}
# Instead we rely on a lookup table. See how it is built in the derivation file.
if (( size < ${toString (imageBuilder.size.MiB 256)} )); then
echo "$size is smaller than 256MiB; using the lookup table." 1>&2
# A bit of a hack, though allows us to build the lookup table using only
# elifs.
if false; then
:
${smallFudgeLookup}
else
# The data is smaller than 5MiB... The filesystem image size will likely
# not be able to accomodate... here we handle it in another way.
fudgeFactor=0
echo "Fudge factor skipped for extra small partition. Instead increasing by a fixed amount." 1>&2
size=$(( size + ${toString minimumSize}))
fi
fi
local reservedSize=$(( size * $fudgeFactor / ${toString precision} ))
echo "Fudge factor: $fudgeFactor / ${toString precision}" 1>&2
echo -n "Adding reservedSize: $size + $reservedSize = " 1>&2
size=$((size + reservedSize))
echo "$size" 1>&2
'';
copyPhase = ''
faketime -f "1970-01-01 00:00:00" \
make_ext4fs \
-b $blockSize \
-L $partName \
-l $size \
-U $partitionID \
"$img" \
.
'';
checkPhase = ''
'';
# FIXME:
# Padding at end of inode bitmap is not set. Fix? no
# exit code
#EXT2FS_NO_MTAB_OK=yes fsck.ext4 -n -f $img
})
/* */ ;}; in scope."fileSystem.makeExt4"

View File

@ -0,0 +1,102 @@
{ lib, imageBuilder, dosfstools, mtools, libfaketime}:
/* */ let scope = { "fileSystem.makeFAT32" =
let
inherit (lib.strings) splitString;
inherit (imageBuilder) makeFilesystem;
# The default from `mkfs.fat`.
reservedSectors = 32;
# The default from `mkfs.fat`.
hiddenSectors = 0;
# The default from `mkfs.fat`.
numberOfFats = 2;
# Extra padding per FAT, a constant in code
fatPadding = 4;
# I have not been able to validate that it could be different from 1 for FAT32.
# It seems the different values (e.g. 4) are for FAT12 and FAT16.
# This is the only "bad" assumption here.
clusterSize = 1;
# Bash doesn't do floating point representations. Multiplications and divisions
# are handled with enough precision that we can multiply and divide to get a precision.
precision = 1000;
first = list: lib.lists.last (lib.lists.reverseList list);
chopDecimal = f: first (splitString "." (toString f));
in
{ partitionID
# These defaults are assuming small~ish FAT32 filesystems are generated.
, blockSize ? 512
, sectorSize ? 512
, ... } @ args:
makeFilesystem (args // {
# FAT32 can be used for ESP. Let's make this obvious.
filesystemType = if args ? filesystemType then args.filesystemType else "FAT32";
inherit blockSize sectorSize;
minimumSize = imageBuilder.size.KiB 500;
nativeBuildInputs = [
libfaketime
dosfstools
mtools
];
computeMinimalSize = ''
# `local size` is in bytes.
# This amount is a static amount of reserved space.
local static_reserved=${toString ( (reservedSectors + hiddenSectors) * sectorSize )}
# This is a constant representing the relative reserved space ratio.
local relative_reserved=${
chopDecimal (
precision - (
1.0 * sectorSize / ((clusterSize * sectorSize) + (numberOfFats * fatPadding))
# ^ forces floating point
) * precision
)
}
# Rounds up the likely truncated result. At worst it's a bit more space.
(( relative_reserved++ ))
echo "static_reserved=$static_reserved" 1>&2
echo "relative_reserved=$relative_reserved" 1>&2
local reservedSize=$(( (static_reserved + size) * relative_reserved / ${toString precision} + static_reserved ))
echo -n "Adding reservedSize: $size + $reservedSize = " 1>&2
size=$((size + reservedSize))
echo "$size" 1>&2
'';
filesystemPhase = ''
faketime -f "1970-01-01 00:00:00" mkfs.vfat \
-R ${toString reservedSectors} \
-h ${toString hiddenSectors} \
-s ${toString (blockSize / sectorSize)} \
-S ${toString sectorSize} \
-i $partitionID \
-n $partName \
"$img"
'';
copyPhase = ''
for f in ./* ./.*; do
if [[ "$f" != "./." && "$f" != "./.." ]]; then
faketime -f "1970-01-01 00:00:00" \
mcopy -psv -i "$img" "$f" ::
fi
done
'';
checkPhase = ''
# Always verify FS
fsck.vfat -vn "$img"
'';
})
/* */ ;}; in scope."fileSystem.makeFAT32"

View File

@ -0,0 +1,152 @@
{ stdenvNoCC, lib, writeText }:
/* */ let scope = { "fileSystem.makeFilesystem" =
let
inherit (lib) optionals optionalString assertMsg;
in
{
name
# Size (in bytes) the filesystem image will be given.
# When size is not given, it is assumed that `populateCommands` will populate
# the filesystem, and the size will be derived (see computeMinimalSize).
, size ? null
# The populate commands are executed in a subshell. The CWD at the star is the
# public API to know where to add files that will be added to the image.
, populateCommands ? null
# Used with the assumption that files are rounded up to blockSize increments.
, blockSize
# Additional commands to compute a required increase in size to fit files.
, computeMinimalSize ? null
# When automatic sizing is used, additional amount of bytes to pad the image by.
, extraPadding ? 0
, ...
} @ args:
assert lib.asserts.assertMsg
(size !=null || populateCommands != null)
"Either a size or populateCommands needs to be given to build a filesystem.";
let
partName = name;
in
stdenvNoCC.mkDerivation (args // rec {
# Do not inherit `size`; we don't want to accidentally use it. The `size` can
# be dynamic depending on the contents.
inherit partName blockSize;
name = "partition-${partName}";
filename = "${partName}.img";
img = "${placeholder "out"}/${filename}";
nativeBuildInputs = [
] ++ optionals (args ? nativeBuildInputs) args.nativeBuildInputs;
buildCommand = ''
adjust-minimal-size() {
size="$1"
echo "$size"
}
compute-minimal-size() {
local size=0
(
cd files
# Size rounded in blocks. This assumes all files are to be rounded to a
# multiple of blockSize.
# Use of `--apparent-size` is to ensure we don't get the block size of the underlying FS.
# Use of `--block-size` is to get *our* block size.
size=$(find . ! -type d -exec 'du' '--apparent-size' '--block-size' "$blockSize" '{}' ';' | cut -f1 | sum-lines)
echo "Reserving $size sectors for files..." 1>&2
# Adds one blockSize per directory, they do take some place, in the end.
# FIXME: write test to confirm this assumption
local directories=$(find . -type d | wc -l)
echo "Reserving $directories sectors for directories..." 1>&2
size=$(( directories + size ))
size=$((size * blockSize))
${if computeMinimalSize == null then "" else computeMinimalSize}
size=$(( size + ${toString extraPadding} ))
echo "$size"
)
}
sum-lines() {
local acc=0
while read -r number; do
acc=$((acc+number))
done
echo "$acc"
}
# The default stdenv/generic clashes with `runHook`.
# It doesn't override as expected.
unset -f checkPhase
mkdir -p $out
mkdir -p files
${optionalString (populateCommands != null) ''
echo
echo "Populating disk image"
echo
(
cd files
${populateCommands}
)
''}
${optionalString (size == null) ''
size=$(compute-minimal-size)
''}
if (( size < minimumSize )); then
size=$minimumSize
echo "WARNING: the '$partName' partition was too small, size increased to $minimumSize bytes."
fi
echo
echo "Building partition ${partName}"
echo "With ${if size == null
then "automatic size ($size bytes)"
else "$size bytes"
}"
echo
echo " -> Allocating space"
truncate -s $size "$img"
echo " -> Making filesystem"
runHook filesystemPhase
echo " -> Copying files"
(
cd files
runHook copyPhase
)
echo " -> Checking filesystem"
echo "$checkPhase"
runHook checkPhase
'';
})
# mkdir -p $out/nix-support
# cat ${writeText "${name}-metadata" (builtins.toJSON {
# inherit size;
# })} > $out/nix-support/partition-metadata.json
/* */ ;}; in scope."fileSystem.makeFilesystem"

View File

@ -0,0 +1,159 @@
{ stdenvNoCC, lib
, imageBuilder
, utillinux
}:
/* */ let scope = { "diskImage.makeGPT" =
let
inherit (lib) concatMapStringsSep optionalString;
# List of known mappings of GPT partition types to filesystems.
# This is not exhaustive, only used as a default.
types = {
"FAT32" = "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7";
"ESP" = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B";
"ext2" = "0FC63DAF-8483-4772-8E79-3D69D8477DE4";
"ext3" = "0FC63DAF-8483-4772-8E79-3D69D8477DE4";
"ext4" = "0FC63DAF-8483-4772-8E79-3D69D8477DE4";
};
in
{
name
, partitions
, diskID
}:
let
_name = name;
eachPart = partitions: fn: (
concatMapStringsSep "\n" (partition:
fn partition
) partitions);
# Default alignment.
alignment = toString (imageBuilder.size.MiB 1);
image = partition:
if lib.isDerivation partition then
"${partition}/${partition.filename}"
else
partition.filename
;
in
stdenvNoCC.mkDerivation rec {
name = "disk-image-${_name}";
filename = "${_name}.img";
img = "${placeholder "out"}/${filename}";
nativeBuildInputs = [
utillinux
];
buildCommand = let
# This fragment is used to compute the (aligned) size of the partition.
# It is used *only* to track the tally of the space used, thus the starting
# offset of the next partition. The filesystem sizes are untouched.
sizeFragment = partition: ''
start=$totalSize
${
if partition ? length then
''size=$((${toString partition.length}))''
else
''size=$(($(du --apparent-size -B 512 "$input_img" | awk '{ print $1 }') * 512))''
}
size=$(( $(if (($size % ${alignment})); then echo 1; else echo 0; fi ) + size / ${alignment} ))
size=$(( size * ${alignment} ))
totalSize=$(( totalSize + size ))
echo "Partition: start $start | size $size | totalSize $totalSize"
'';
# This fragment is used to add the desired gap to `totalSize`.
# We're setting `start` and `size` only to mirror the information shown
# for partitions.
# Do note that gaps are always aligned, so two gaps sized half the alignment
# would create 2× the space expected.
# What may *instead* be done at one point is always align `start` for partitions.
gapFragment = partition: ''
start=$totalSize
size=${toString partition.length}
size=$(( $(if (($size % ${alignment})); then echo 1; else echo 0; fi ) + size / ${alignment} ))
totalSize=$(( totalSize + size ))
echo "Gap: start $start | size $size | totalSize $totalSize"
'';
in ''
mkdir -p $out
cat <<EOF > script.sfdisk
label: gpt
grain: 1024
label-id: ${diskID}
EOF
totalSize=${alignment}
echo
echo "Gathering information about partitions."
${eachPart partitions (partition:
if partition ? isGap && partition.isGap then
(gapFragment partition)
else
''
input_img="${image partition}"
${sizeFragment partition}
echo " -> ${partition.name}: $size / ${if partition ? filesystemType then partition.filesystemType else ""}"
(
# The size is /1024; otherwise it's in sectors.
echo -n 'start='"$((start/1024))"'KiB'
echo -n ', size='"$((size/1024))"'KiB'
echo -n ', type=${
if partition ? partitionType then
partition.partitionType
else
types.${partition.filesystemType}
}'
${optionalString (partition ? bootable && partition.bootable)
"echo -n ', bootable'"}
echo "" # Finishes the command
) >> script.sfdisk
''
)}
# Allow space for alignment + secondary partition table / header.
totalSize=$(( totalSize + ${alignment} ))
echo "--- script ----"
cat script.sfdisk
echo "--- script ----"
echo
echo "Making image, $totalSize bytes..."
truncate -s $((totalSize)) $img
sfdisk $img < script.sfdisk
totalSize=${alignment}
echo
echo "Writing partitions into image"
${eachPart partitions (partition:
if partition ? isGap && partition.isGap then
(gapFragment partition)
else
''
input_img="${image partition}"
${sizeFragment partition}
echo " -> ${partition.name}: $size / ${if partition ? filesystemType then partition.filesystemType else ""}"
echo "$start / $size"
dd conv=notrunc if=$input_img of=$img seek=$((start/512)) count=$((size/512)) bs=512
''
)}
echo
echo "Information about the image:"
ls -lh $img
sfdisk -V --list $img
'';
}
/* */ ;}; in scope."diskImage.makeGPT"

View File

@ -0,0 +1,139 @@
{ stdenvNoCC, lib
, imageBuilder
, utillinux
}:
/* */ let scope = { "diskImage.makeMBR" =
let
inherit (lib) concatMapStringsSep optionalString;
# List of known mappings of MBR partition types to filesystems.
types = {
"FAT32" = "b";
"ESP" = "ef";
"ext2" = "83";
"ext3" = "83";
"ext4" = "83";
};
in
{
name
, partitions
# Without the prefixed `0x`
, diskID
}:
let
_name = name;
eachPart = partitions: fn: (
concatMapStringsSep "\n" (partition:
fn partition
) partitions);
# Default alignment.
alignment = toString (imageBuilder.size.MiB 1);
in
stdenvNoCC.mkDerivation rec {
name = "disk-image-${_name}";
filename = "${_name}.img";
img = "${placeholder "out"}/${filename}";
nativeBuildInputs = [
utillinux
];
buildCommand = let
# This fragment is used to compute the (aligned) size of the partition.
# It is used *only* to track the tally of the space used, thus the starting
# offset of the next partition. The filesystem sizes are untouched.
sizeFragment = ''
start=$totalSize
size=$(($(du --apparent-size -B 512 "$input_img" | awk '{ print $1 }') * 512))
size=$(( $(if (($size % ${alignment})); then echo 1; else echo 0; fi ) + size / ${alignment} ))
size=$(( size * ${alignment} ))
totalSize=$(( totalSize + size ))
echo "Partition: start $start | size $size | totalSize $totalSize"
'';
# This fragment is used to add the desired gap to `totalSize`.
# We're setting `start` and `size` only to mirror the information shown
# for partitions.
# Do note that gaps are always aligned, so two gaps sized half the alignment
# would create 2× the space expected.
# What may *instead* be done at one point is always align `start` for partitions.
gapFragment = partition: ''
start=$totalSize
size=${toString partition.length}
size=$(( $(if (($size % ${alignment})); then echo 1; else echo 0; fi ) + size / ${alignment} ))
totalSize=$(( totalSize + size ))
echo "Gap: start $start | size $size | totalSize $totalSize"
'';
in ''
mkdir -p $out
cat <<EOF > script.sfdisk
label: dos
grain: 1024
label-id: 0x${diskID}
EOF
totalSize=${alignment}
echo
echo "Gathering information about partitions."
${eachPart partitions (partition:
if partition ? isGap && partition.isGap then
(gapFragment partition)
else
''
input_img="${partition}/${partition.filename}"
${sizeFragment}
echo " -> ${partition.name}: $size / ${partition.filesystemType}"
(
# The size is /1024; otherwise it's in sectors.
echo -n 'start='"$((start/1024))"'KiB'
echo -n ', size='"$((size/1024))"'KiB'
echo -n ', type=${types."${partition.filesystemType}"}'
${optionalString (partition ? bootable && partition.bootable)
"echo -n ', bootable'"}
echo "" # Finishes the command
) >> script.sfdisk
''
)}
echo "--- script ----"
cat script.sfdisk
echo "--- script ----"
echo
echo "Making image, $totalSize bytes..."
truncate -s $((totalSize)) $img
sfdisk $img < script.sfdisk
totalSize=${alignment}
echo
echo "Writing partitions into image"
${eachPart partitions (partition:
if partition ? isGap && partition.isGap then
(gapFragment partition)
else
''
input_img="${partition}/${partition.filename}"
${sizeFragment}
echo " -> ${partition.name}: $size / ${partition.filesystemType}"
echo "$start / $size"
dd conv=notrunc if=$input_img of=$img seek=$((start/512)) count=$((size/512)) bs=512
''
)}
echo
echo "Information about the image:"
ls -lh $img
sfdisk -V --list $img
'';
}
/* */ ;}; in scope."diskImage.makeMBR"

144
lib/image-builder/test.rb Executable file
View File

@ -0,0 +1,144 @@
#!/usr/bin/env nix-shell
# All dependencies `verify` will need too.
#!nix-shell --pure -p nix -p ruby -p file -i ruby
require "tmpdir"
require "open3"
require "json"
# require "fileutils"
prefix = File.join(__dir__, "tests")
NIX_PATH = "nixpkgs-overlays=#{__dir__}/lib/tests/test-overlay.nix:nixpkgs=channel:nixos-19.03"
# Default directives for the test.
DEFAULT_DIRECTIVES = {
# Default is to succeed.
status: 0,
# Nothing to grep for in particular.
# (Caution! Successes are likely not to have logs!)
grep: nil,
}
Env = {
"NIX_PATH" => NIX_PATH,
}
tests =
if ARGV.count > 0 then
ARGV
else
# Assumes all nix files in `./tests` are tests to `nix-build`.
Dir.glob(File.join(prefix, "**/*.nix"))
end
tests.sort!
$exit = 0
$failed_tests = []
puts ""
puts "Tests"
puts "====="
puts ""
tests.each_with_index do |file, index|
short_name = file.sub("#{prefix}/", "")
print "Running test #{(index+1).to_s.rjust(tests.length.to_s.length)}/#{tests.length} '#{short_name}' "
failures = []
# Reads the first line of the file. It may hold a json snippet
# configuring the expected test results.
directives = File.read(file).split("\n").first || ""
directives = DEFAULT_DIRECTIVES.merge(
if directives.match(/^\s*#\s*expect:/i) then
# Parse...
JSON.parse(
# Everything after the first colon as json
directives.split(":", 2).last,
symbolize_names: true,
)
else
{}
end
)
# The result symlink is in a temp directory...
Dir.mktmpdir("image-builder-test") do |dir|
result = File.join(dir, "result")
# TODO : figure out how to keep stdout/stderr synced but logged separately.
log, status = Open3.capture2e(Env, "nix-build", "--show-trace", "--out-link", result, file)
unless status.exitstatus == directives[:status]
failures << "Build exited with status #{status.exitstatus} expected #{directives[:status]}."
end
if directives[:grep] then
unless log.match(directives[:grep])
failures << "Output log did not match `#{directives[:grep]}`."
end
end
# Do we test further with the test scripts?
if failures.length == 0 and status.success? then
script = file.sub(/\.nix$/, ".rb")
if File.exists?(script) then
log, status = Open3.capture2e(Env, File.join(__dir__, "lib/tests/verify.rb"), script, result)
end
unless status.exitstatus == 0
failures << "Verification exited with status #{status.exitstatus} expected 0."
end
store_path = File.readlink(result)
end
if failures.length == 0 then
puts "[Success] (#{store_path || "no output"}) "
else
$exit = 1
$failed_tests << file
puts "[Failed] (#{store_path || "no output"}) "
puts ""
puts "Failures:"
failures.each do |failure|
puts " - #{failure}"
end
puts ""
puts "Directives:"
puts ""
puts "```"
puts "#{directives.inspect}"
puts "```"
puts ""
puts "Output from the test:"
puts ""
puts "````"
puts "#{log}"
puts "````"
end
end
end
puts ""
puts "* * *"
puts ""
puts "Test summary"
puts "============"
puts ""
puts "#{tests.length - $failed_tests.length}/#{tests.length} tests successful."
if $failed_tests.length > 0 then
puts ""
puts "Failed tests:\n"
$failed_tests.each do |filename|
puts " - #{filename.sub(/^#{__dir__}/, ".")}"
end
puts ""
end
exit $exit

View File

@ -0,0 +1,50 @@
# Verifies that filesystems sized to be aligned works.
{ pkgs ? import <nixpkgs> {} }:
let
inherit (pkgs) imageBuilder;
makeNull = size: pkgs.runCommandNoCC "filesystems-test" {
filename = "null.img";
filesystemType = "FAT32"; # meh, good enough
} ''
mkdir -p $out
dd if=/dev/zero of=$out/$filename bs=${toString size} count=1
'';
in
with imageBuilder;
{
one = diskImage.makeMBR {
name = "diskimage";
diskID = "012345678";
partitions = [
(makeNull (size.MiB 1))
(makeNull (size.MiB 1))
];
};
nine = diskImage.makeMBR {
name = "diskimage";
diskID = "012345678";
partitions = [
(makeNull (size.MiB 9))
(makeNull (size.MiB 9))
];
};
ten = diskImage.makeMBR {
name = "diskimage";
diskID = "012345678";
partitions = [
(makeNull (size.MiB 10))
(makeNull (size.MiB 10))
];
};
eleven = diskImage.makeMBR {
name = "diskimage";
diskID = "012345678";
partitions = [
(makeNull (size.MiB 11))
(makeNull (size.MiB 11))
];
};
}

View File

@ -0,0 +1,42 @@
# Verifies that filesystems sized to be unaligned will work.
{ pkgs ? import <nixpkgs> {} }:
let
inherit (pkgs) imageBuilder;
makeNull = size: pkgs.runCommandNoCC "filesystems-test" {
filename = "null.img";
filesystemType = "FAT32"; # meh, good enough
} ''
mkdir -p $out
dd if=/dev/zero of=$out/$filename bs=${toString size} count=1
'';
in
with imageBuilder;
# Through empirical testing, it was found out that the defaults from `sfdisk`
# are not as documented.
# First of all, on small disks no alignment will be made.
# Starting with 3MiB (empirically derived) alignment will be made.
# The alignment is documented as being based on the I/O limits. It seems like
# for files it ends up causing alignments at the 2MiB boundaries.
# Such, `grain: 1024` has to be set to configure sfdisk for the sane default
# documented in its manpage
#
# > grain Specify minimal size in bytes used to calculate partitions alignment.
# > The default is 1MiB and it's strongly recommended to use the default.
# > Do not modify this variable if you're not sure.
#
# The default is *not* 1MiB and will break the generation of images if it tries
# to align a small partition at the very end of the disk, when the disk is sized
# just right to fit.
#
# This is what this test validates.
diskImage.makeMBR {
name = "diskimage";
diskID = "012345678";
partitions = [
(makeNull (size.MiB 3))
(makeNull (size.MiB 1))
];
}

View File

@ -0,0 +1,13 @@
# Expect: { "status": 1, "grep": "Either a size or populateCommands needs to be given to build a filesystem." }
{ pkgs ? import <nixpkgs> {} }:
let
inherit (pkgs) imageBuilder;
in
with imageBuilder;
fileSystem.makeFAT32 {
name = "whatever";
partitionID = "0123456789ABCDEF";
}

View File

@ -0,0 +1,35 @@
# Tests all known filesystems as empty, with defaults.
# This test helps ensure the basic interface stays stable, and works.
{ pkgs ? import <nixpkgs> {} }:
let
inherit (pkgs) imageBuilder;
inherit (pkgs.lib.attrsets) mapAttrsToList;
inherit (pkgs.lib.strings) concatStringsSep removePrefix;
IDs = {
FAT32 = "0123456789ABCDEF";
ESP = "0123456789ABCDEF";
Ext4 = "44444444-4444-4444-1324-123456789098";
};
in
with imageBuilder;
let
cmds =
mapAttrsToList (fn_name: fn:
let
fs = fn rec {
name = removePrefix "make" fn_name;
size = imageBuilder.size.MiB 10;
partitionID = IDs."${name}";
};
in
''
ln -s ${fs}/${fs.filename} $out/
'') fileSystem;
in
pkgs.runCommandNoCC "filesystems-test" {} ''
mkdir -p $out/
${concatStringsSep "\n" cmds}
''

View File

@ -0,0 +1,19 @@
hashes = {
"ESP.img" => "f9b39d98bfb797b050467ca5214671b5b427b7896cae44d27d2fc8dbceaccd88",
"FAT32.img" => "79028d8af97ae4400ea2ab36d34e2a80684c9f8d31ea75e3f54d908c75adc3a4",
"Ext4.img" => "8182df3038f43cdf2aba6f3980d9fa794affab0f040f9c07450ebbf0d3d8c2ad",
}
filetypes = {
"ESP.img" => 'DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", reserved sectors 32, root entries 512, sectors 20480 (volumes <=32 MB), Media descriptor 0xf8, sectors/FAT 80, sectors/track 32, heads 64, serial number 0x89abcdef, label: "ESP ", FAT (16 bit)',
"FAT32.img" => 'DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", reserved sectors 32, root entries 512, sectors 20480 (volumes <=32 MB), Media descriptor 0xf8, sectors/FAT 80, sectors/track 32, heads 64, serial number 0x89abcdef, label: "FAT32 ", FAT (16 bit)',
"Ext4.img" => 'Linux rev 1.0 ext4 filesystem data, UUID=44444444-4444-4444-1324-123456789098, volume name "Ext4" (extents) (large files)'
}
# By globbing on the output, we can validate all built images are verified.
# The builder should have built everything under `fileSystems`.
Dir.glob(File.join($result, "**/*")) do |file|
name = File.basename(file)
sha256sum(name, hashes[name])
file(name, filetypes[name])
end

View File

@ -0,0 +1,39 @@
# Tests all known filesystems with files.
# This test helps ensure the basic interface stays stable, and works.
{ pkgs ? import <nixpkgs> {} }:
let
inherit (pkgs) imageBuilder;
inherit (pkgs.lib.attrsets) mapAttrsToList;
inherit (pkgs.lib.strings) concatStringsSep removePrefix;
IDs = {
FAT32 = "0123456789ABCDEF";
ESP = "0123456789ABCDEF";
Ext4 = "44444444-4444-4444-1324-123456789098";
};
in
with imageBuilder;
let
cmds =
mapAttrsToList (fn_name: fn:
let
fs = fn rec {
name = removePrefix "make" fn_name;
partitionID = IDs."${name}";
populateCommands = ''
echo "I am ${name}." > file
ls -lA
'';
};
in
''
ln -s ${fs}/${fs.filename} $out/
'') fileSystem;
in
pkgs.runCommandNoCC "filesystems-test" {} ''
mkdir -p $out/
${concatStringsSep "\n" cmds}
''

View File

@ -0,0 +1,19 @@
hashes = {
"ESP.img" => "d70b24594f0615b83fddb8608b4819432380359bc5c6763d7c318e0c3233ea64",
"FAT32.img" => "73ab1acd845fd0f7f5ddd30edea074f0b14bf4f2b4137f904a90939f1cb2ac8d",
"Ext4.img" => "56181a43406c4ba731ed14b3ed32ed6c169b1c2abfe385eef989faef55e3cd07",
}
filetypes = {
"ESP.img" => 'DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", reserved sectors 32, root entries 512, sectors 1000 (volumes <=32 MB), Media descriptor 0xf8, sectors/FAT 3, sectors/track 32, heads 64, serial number 0x89abcdef, label: "ESP ", FAT (12 bit)',
"FAT32.img" => 'DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", reserved sectors 32, root entries 512, sectors 1000 (volumes <=32 MB), Media descriptor 0xf8, sectors/FAT 3, sectors/track 32, heads 64, serial number 0x89abcdef, label: "FAT32 ", FAT (12 bit)',
"Ext4.img" => 'Linux rev 1.0 ext4 filesystem data, UUID=44444444-4444-4444-1324-123456789098, volume name "Ext4" (extents) (large files)'
}
# By globbing on the output, we can validate all built images are verified.
# The builder should have built everything under `fileSystems`.
Dir.glob(File.join($result, "**/*")) do |file|
name = File.basename(file)
sha256sum(name, hashes[name])
file(name, filetypes[name])
end

View File

@ -0,0 +1,70 @@
# Ensures we can fit stuff in an ext4 image.
{ pkgs ? import <nixpkgs> {} }:
let
inherit (pkgs) imageBuilder;
makeNull = size: let
filename = "null.img";
filesystemType = "FAT32"; # meh, good enough
in
''
mkdir -p $out
dd if=/dev/zero of=./${toString size}.img bs=${toString size} count=1
'';
in
with imageBuilder;
{
one = fileSystem.makeExt4 {
name = "one";
partitionID = "44444444-4444-4444-0000-000000000001";
populateCommands = ''
${makeNull (imageBuilder.size.MiB 1)}
'';
};
two = fileSystem.makeExt4 {
name = "two";
partitionID = "44444444-4444-4444-0000-000000000002";
populateCommands = ''
${makeNull (imageBuilder.size.MiB 2)}
'';
};
three = fileSystem.makeExt4 {
name = "three";
partitionID = "44444444-4444-4444-0000-000000000003";
populateCommands = ''
${makeNull (imageBuilder.size.MiB 3)}
'';
};
four = fileSystem.makeExt4 {
name = "four";
partitionID = "44444444-4444-4444-0000-000000000004";
populateCommands = ''
${makeNull (imageBuilder.size.MiB 4)}
'';
};
five = fileSystem.makeExt4 {
name = "five";
partitionID = "44444444-4444-4444-0000-000000000005";
populateCommands = ''
${makeNull (imageBuilder.size.MiB 5)}
'';
};
# This is the boundary where otherwise it would begin to fail.
five_plus_one = fileSystem.makeExt4 {
name = "five_plus_one";
partitionID = "44444444-4444-4444-0001-000000000005";
populateCommands = ''
${makeNull ((imageBuilder.size.MiB 5) + 1)}
'';
};
six = fileSystem.makeExt4 {
name = "six";
partitionID = "44444444-4444-4444-0000-000000000006";
populateCommands = ''
${makeNull (imageBuilder.size.MiB 6)}
'';
};
# For bigger tests, see in-depth-tests
}

View File

@ -1,2 +0,0 @@
ROOT = File.join(__dir__, "..", "..")
require_relative "run.rb"

View File

@ -1,12 +0,0 @@
require "shellwords"
def run(*cmd, exec: false)
puts " $ #{cmd.shelljoin}"
if exec then
Kernel.exec(*cmd)
else
system(*cmd)
end
exit $?.exitstatus unless $?.exitstatus == 0
end

View File

@ -0,0 +1,26 @@
# This module, while seemingly having a funny name, has a truthful name.
# It dis(assembles) the integration within the NixOS module system that is
# causing issues for mobile-nixos.
#
# In reality, all fixups made here is a call for a better isolation of the
# modules they target. Let's wait until this is more stable, and add required
# fixes to the NixOS modules system as needed ☺.
{lib, config, ...}:
{
imports = [
./initrd.nix
];
config = lib.mkMerge [
{
# I don't know why, but the documentation has issues with the
# soc options when building other than qemu_x86_64
# And here the installation-device profile is a bit annoying.
# Let's ultra-diable the documentation and nixos manual.
documentation.enable = lib.mkOverride 10 false;
services.nixosManual.showManual = lib.mkOverride 10 false;
}
];
}

View File

@ -0,0 +1,20 @@
{lib, config, ...}:
# FIXME: instead of setting `boot.isContainer`, let's instead disable the whole
# stage-1 module and re-implement the options as needed.
# → Maybe we can even import it ourselves, to get to its `options` attribute?
{
disabledModules = [
<nixpkgs/nixos/modules/tasks/encrypted-devices.nix>
<nixpkgs/nixos/modules/tasks/filesystems/zfs.nix>
];
config = {
# This isn't even used in our initrd...
boot.supportedFilesystems = lib.mkOverride 10 [ ];
boot.initrd.supportedFilesystems = lib.mkOverride 10 [];
# Co-opting this setting to disable the upstream NixOS stage-1.
boot.isContainer = true;
};
}

View File

@ -15,6 +15,6 @@ in
};
config = {
mobile.system.platform = lib.mkIf cfg.generic-x86_64.enable "x86_64-linux";
mobile.system.system = lib.mkIf cfg.generic-x86_64.enable "x86_64-linux";
};
}

View File

@ -27,19 +27,19 @@ in
config = mkMerge [
{
mobile = mkIf cfg.qualcomm-msm8939.enable {
system.platform = "aarch64-linux";
system.system = "aarch64-linux";
quirks.qualcomm.msm-fb-handle.enable = true;
};
}
{
mobile = mkIf cfg.qualcomm-msm8953.enable {
system.platform = "aarch64-linux";
system.system = "aarch64-linux";
quirks.qualcomm.msm-fb-handle.enable = true;
};
}
{
mobile = mkIf cfg.qualcomm-apq8064-1aa.enable {
system.platform = "armv7a-linux";
system.system = "armv7l-linux";
quirks.qualcomm.msm-fb-refresher.enable = true;
};
}

View File

@ -17,7 +17,7 @@ in
config = mkMerge [
{
mobile = mkIf cfg.rockchip-op1.enable {
system.platform = "aarch64-linux";
system.system = "aarch64-linux";
};
}
];

View File

@ -1,8 +1,7 @@
{ config, lib, pkgs, ... }:
with lib;
let
inherit (lib) mkOption types;
cfg = config.mobile.hardware;
in
{

View File

@ -5,6 +5,7 @@ with import ./initrd-order.nix;
let
cfg = config.mobile.boot.stage-1.splash;
image = name: ../artwork + "/${name}.png";
mkSplash = at: name:
{
init = lib.mkOrder at ''
@ -12,7 +13,7 @@ let
'';
contents = [
{
object = (builtins.path { path = ../artwork + "/${name}.png"; });
object = (builtins.path { path = image name; });
symlink = "/${name}.png";
}
];
@ -48,8 +49,18 @@ in
];
}
(mkSplash AFTER_FRAMEBUFFER_INIT "loading")
(mkSplash (READY_INIT - 1) "splash")
# This is as early as we can splash...
(mkSplash AFTER_FRAMEBUFFER_INIT "splash.stage-0")
# Though there's still some setting-up in stage-1,
# This is where "init is ready".
(mkSplash (READY_INIT - 1) "splash.stage-1")
(mkIf cfg.rgb-debug (mkSplash (READY_INIT) "rgb-debug"))
]);
# This happens in stage-2. This is why we're not using `mkSplash`.
# This is the earliest in stage-2 we can show, for vt-less devices, that
# stage-2 is really happening.
config.boot.postBootCommands = ''
${pkgs.ply-image}/bin/ply-image --clear=0x000000 ${image "splash.stage-2"} > /dev/null 2>&1
'';
}

View File

@ -5,6 +5,7 @@ with import ./initrd-order.nix;
let
cfg = config.mobile.boot.stage-1;
system_type = config.mobile.system.type;
in
{
# FIXME Generic USB gadget support to come.
@ -26,7 +27,7 @@ in
};
adbd = mkOption {
type = types.bool;
default = true;
default = system_type == "android";
description = ''
Enables adbd on the device.
'';

9
modules/initrd.nix Normal file
View File

@ -0,0 +1,9 @@
{ config, pkgs, ... }:
let
device_config = config.mobile.device;
stage-1 = config.mobile.boot.stage-1;
in
{
system.build.initrd = pkgs.callPackage ../systems/initrd.nix { inherit device_config stage-1; };
}

View File

@ -1,12 +1,11 @@
# Keep sorted, <nixpkgs> imports first.
let
# This is only used to get the path to nixpkgs.
# This one shouldn't affect cross-compiling.
nixpkgs = (import <nixpkgs> {}).path;
in
# Import the upstream module-list.
# FIXME : This won't allow importing `mobile-nixos` into /etc/configuration.nix
(import <nixpkgs/nixos/modules/module-list.nix>) ++
# Then add our additional modules.
# Keep this list `:sort`ed.
[
(nixpkgs + "/nixos/modules/misc/nixpkgs.nix")
(nixpkgs + "/nixos/modules/misc/assertions.nix")
./_nixos-disintegration
./boot-initrd.nix
./hardware-generic.nix
./hardware-qualcomm.nix
@ -27,11 +26,11 @@ in
./initrd-ssh.nix
./initrd-telnet.nix
./initrd-usb.nix
./initrd.nix
./mobile-device.nix
./nixpkgs.nix
./quirks-qualcomm.nix
./stage-2.nix
./system-build.nix
./system-target.nix
./system-types.nix
]

View File

@ -1,21 +1,36 @@
{ config, lib, pkgs, ... }:
# FIXME: Add hook for mounting, right now it's hardcoded to only mount "/".
# (This'll allow complex schemes like LVM)
# FIXME: Move udev stuff out.
let
rootfs = config.fileSystems."/".device;
in
with import ./initrd-order.nix;
{
mobile.boot.stage-1.init = lib.mkOrder SWITCH_ROOT_INIT ''
mobile.boot.stage-1.init =
lib.mkOrder SWITCH_ROOT_INIT ''
set -x
targetRoot=/mnt
_fs_id() {
blkid | grep ' LABEL="'"$1"'" ' | cut -d':' -f1
}
# FIXME : udev stuff out of here...
systemd-udevd --daemon
udevadm trigger --action=add
udevadm settle
targetRoot=/mnt
_init_path() {
local _system=""
# IF no /nix/var...
if [ -e "$targetRoot/nix/var/nix/profiles/system" ]; then
_system="$targetRoot/nix/var/nix/profiles/system"
# Using -L is required, as the link chain is most likely dangling.
if [ -L "$targetRoot/nix/var/nix/profiles/system" ]; then
# There is a system symlink, use it.
# What's that strange dance? We're canonicalizing one level deep of an
# absolute symlink that we can't easily canonicalize otherwise.
_system=$(cd $targetRoot/nix/var/nix/profiles/; readlink $(readlink system))
elif [ -e "$targetRoot/nix-path-registration" ]; then
# Otherwise, try to find one in nix-path-registration.
_system="$(grep '^/nix/store/[a-z0-9]\+-nixos-system-' $targetRoot/nix-path-registration | head -1)"
else
echo "!!!!!"
@ -29,23 +44,14 @@ with import ./initrd-order.nix;
echo "!!!!!"
echo "!!!!!"
sleep 2m
exit 1
fi
echo "$_system/init"
}
_fs_id NIXOS_SD
_fs_id NIXOS_BOOT
# FIXME : LESS FLIMSY!
mkdir -p $targetRoot
mount $(_fs_id NIXOS_SD) $targetRoot
# mkdir -p $targetRoot/boot
# mount $(_fs_id NIXOS_BOOT) $targetRoot/boot
# mount "$(blkid | grep ' LABEL="'"NIXOS_SD"'" ' | cut -d':' -f1)" /mnt
# mkdir -p /mnt/boot/
# mount "$(blkid | grep ' LABEL="'"NIXOS_BOOT"'" ' | cut -d':' -f1)" /mnt/boot
mount "${rootfs}" $targetRoot
echo ""
echo "***"
@ -55,12 +61,17 @@ with import ./initrd-order.nix;
echo "***"
echo ""
for mp in /proc /sys /dev /run; do
mkdir -m 0755 -p $targetRoot/$mp
mount --move $mp $targetRoot/$mp
done
# TODO : hook "AT" switch root
# FIXME : udev stuff out of here...
# Stop udevd.
udevadm control --exit
exec env -i $(type -P switch_root) $targetRoot $(_init_path)
'';

View File

@ -1,13 +0,0 @@
{ config, lib, pkgs, ... }:
with lib;
{
options.system.build = mkOption {
internal = true;
description = ''
Where the result will be put into.
This ends up building `all`.
'';
};
}

View File

@ -1,24 +1,35 @@
{ config, lib, pkgs, ... }:
# FIXME : current implementation only works for native x86_64 built hosts.
with lib;
let
cfg = config.mobile.system;
target_types = {
aarch64-linux = lib.systems.examples.aarch64-multiplatform;
armv7a-linux = lib.systems.examples.armv7l-hf-multiplatform;
x86_64-linux = { config = "x86_64-unknown-linux-gnu"; };
# Mapping from system types to config types
# A simplified view of <nixpkgs/lib/systems/examples.nix>
config_types = {
aarch64-linux = "aarch64-unknown-linux-gnu";
armv7l-linux = "armv7l-unknown-linux-gnueabihf";
x86_64-linux = "x86_64-unknown-linux-gnu";
};
# Hmmm, this doesn't feel right, but it does work.
host_platform = (import <nixpkgs> {}).buildPackages.hostPlatform;
# Derived from config_types
target_types = lib.attrNames config_types;
# Builds the expected "platform" set for cross-compilation from the given
# system name.
selectPlatform = system: {
inherit system;
platform = lib.systems.platforms.selectBySystem system;
config = config_types.${system};
};
# The platform selected by the configuration
selectedPlatform = selectPlatform cfg.system;
in
{
options.mobile = {
system.platform = mkOption {
type = types.enum (lib.attrNames target_types);
system.system = mkOption {
type = types.enum target_types;
description = ''
Defines the kind of target architecture system the device is.
@ -30,14 +41,25 @@ in
config = {
assertions = [
{
assertion = pkgs.targetPlatform.system == cfg.platform;
message = "pkgs.targetPlatform.system expected to be `${cfg.platform}`, is `${pkgs.targetPlatform.system}`";
assertion = pkgs.targetPlatform.system == cfg.system;
message = "pkgs.targetPlatform.system expected to be `${cfg.system}`, is `${pkgs.targetPlatform.system}`";
}
];
nixpkgs.crossSystem = lib.mkIf
( target_types.${cfg.platform}.config != host_platform.config )
(target_types.${cfg.platform} // { system = cfg.platform; }) # FIXME : WHY? I didn't need to add system before :/
(
let
result = selectedPlatform.system != builtins.currentSystem;
in
builtins.trace
"Building with crossSystem?: ${selectedPlatform.system} != ${builtins.currentSystem} ${if result then "we are" else "we're not"}."
result
)
(
builtins.trace
" crossSystem: config: ${selectedPlatform.config}"
selectedPlatform
)
;
};
}

View File

@ -6,45 +6,27 @@ let
failed = map (x: x.message) (filter (x: !x.assertion) config.assertions);
system_type = config.mobile.system.type;
device_config = config.mobile.device;
hardware_config = config.mobile.hardware;
stage-1 = config.mobile.boot.stage-1;
build_types = {
android-device = pkgs.callPackage ../systems/android-device.nix {
inherit config;
};
android-bootimg = pkgs.callPackage ../systems/bootimg.nix {
inherit device_config;
# XXX : this feels like a hack
initrd = pkgs.callPackage ../systems/initrd.nix { inherit device_config stage-1; };
};
depthcharge = pkgs.callPackage ../systems/depthcharge {
inherit device_config;
initrd = pkgs.callPackage ../systems/initrd.nix { inherit device_config stage-1; };
};
kernel-initrd = pkgs.linkFarm "${device_config.name}-build" [
{
name = "kernel-initrd";
path = pkgs.callPackage ../systems/kernel-initrd.nix {
# FIXME this all feels a bit not enough generic.
inherit device_config hardware_config;
initrd = pkgs.callPackage ../systems/initrd.nix { inherit device_config stage-1; };
};
}
{
name = "system";
# Equivalent to:
# → nix-build nixos -I nixos-config=system-image.nix -A config.system.build.sdImage
path = ((import (pkgs.path + "/nixos")) { configuration = ../system-image.nix; }).config.system.build.sdImage;
}
];
};
known_system_types = config.mobile.system.types;
in
{
imports = [
../systems/rootfs.nix
./system-types/depthcharge.nix
./system-types/kernel-initrd.nix
./system-types/android.nix
];
options.mobile = {
system.types = mkOption {
type = types.listOf types.str;
internal = true;
description = ''
Registry of system types.
'';
};
system.type = mkOption {
type = types.enum (lib.attrNames build_types);
type = types.enum known_system_types;
description = ''
Defines the kind of system the device is.
@ -57,15 +39,11 @@ in
config = {
assertions = [
# While the enum type is enough to implement value safety, this will help
# when implementing new platforms and not implementing them in build_types.
{ assertion = build_types ? ${system_type}; message = "Cannot build unexpected system type: ${system_type}.";}
# when implementing new platforms and not implementing them in known_system_types.
{
assertion = lib.lists.any (x: x == system_type) known_system_types;
message = "Cannot build unexpected system type: ${system_type}.\n Known types: ${lib.concatStringsSep ", " known_system_types}";
}
];
system = {
build =
if failed == [] then
build_types."${system_type}"
else throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: " ${x}") failed)}\n"
;
};
};
}

View File

@ -0,0 +1,32 @@
{ config, pkgs, lib, ... }:
let
device_config = config.mobile.device;
device_name = device_config.name;
enabled = config.mobile.system.type == "android";
inherit (config.system.build) rootfs;
android-bootimg = pkgs.callPackage ../../systems/bootimg.nix {
inherit device_config;
initrd = config.system.build.initrd;
};
android-device = pkgs.runCommandNoCC "android-device-${device_name}" {} ''
mkdir -p $out
ln -s ${rootfs}/${rootfs.filename} $out/system.img
ln -s ${android-bootimg} $out/boot.img
'';
in
{
config = lib.mkMerge [
{ mobile.system.types = [ "android" ]; }
(lib.mkIf enabled {
system.build = {
inherit android-bootimg android-device;
mobile-installer = throw "No installer yet...";
};
})
];
}

View File

@ -0,0 +1,25 @@
{ config, pkgs, lib, ... }:
let
device_config = config.mobile.device;
enabled = config.mobile.system.type == "depthcharge";
disk-image = pkgs.callPackage ../../systems/depthcharge {
inherit device_config;
initrd = config.system.build.initrd;
system = config.system.build.rootfs;
};
in
{
config = lib.mkMerge [
{ mobile.system.types = [ "depthcharge" ]; }
(lib.mkIf enabled {
system.build = {
inherit disk-image;
# installer shortcut; it's a depthcharge disk-image build.
mobile-installer = disk-image;
};
})
];
}

View File

@ -0,0 +1,56 @@
{ config, pkgs, lib, ... }:
let
device_config = config.mobile.device;
device_name = device_config.name;
hardware_config = config.mobile.hardware;
rootfs = config.system.build.rootfs;
enabled = config.mobile.system.type == "kernel-initrd";
kernel-initrd = pkgs.callPackage ../../systems/kernel-initrd.nix {
inherit device_config hardware_config;
initrd = config.system.build.initrd;
};
system = pkgs.linkFarm "${device_config.name}-build" [
{
name = "kernel-initrd";
path = "kernel-initrd";
}
{
name = "system";
path = rootfs;
}
];
in
{
config = lib.mkMerge [
{ mobile.system.types = [ "kernel-initrd" ]; }
(lib.mkIf enabled {
system.build = {
inherit system;
mobile-installer = system;
vm = pkgs.writeScript "run-vm-${device_name}" ''
#!${pkgs.runtimeShell}
PS4=" $ "
set -eux
cp -f ${rootfs}/*.img fs.img
chmod +rw fs.img
qemu-system-x86_64 \
-enable-kvm \
-kernel "${kernel-initrd}/kernel" \
-initrd "${kernel-initrd}/initrd" \
-append "$(cat "${kernel-initrd}/cmdline.txt")" \
-m "$(cat "${kernel-initrd}/ram.txt")M" \
-serial "stdio" \
-drive "file=fs.img,format=raw" \
-device "e1000,netdev=net0" \
-netdev "user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::2323-:23,net=172.16.42.0/24,dhcpstart=172.16.42.1"
'';
};
})
];
}

View File

@ -1 +0,0 @@
((import ../.) { device = "qemu-x86_64"; }).nixos

View File

@ -0,0 +1,82 @@
From 5c2e0b7b5911477dcc4dedfd9910e28e9af65a91 Mon Sep 17 00:00:00 2001
From: Samuel Dionne-Riel <samuel@dionne-riel.com>
Date: Thu, 29 Aug 2019 15:07:36 -0400
Subject: [PATCH 1/2] uuid: add facilities to parse and print UUIDs
Signed-off-by: Samuel Dionne-Riel <samuel@dionne-riel.com>
---
uuid.c | 43 +++++++++++++++++++++++++++++++++++++++++++
uuid.h | 6 ++++++
2 files changed, 49 insertions(+)
diff --git a/uuid.c b/uuid.c
index 7ded43a..47ef097 100644
--- a/uuid.c
+++ b/uuid.c
@@ -59,3 +59,46 @@ void generate_uuid(const char *namespace, const char *name, u8 result[16])
uuid->clk_seq_hi_res &= ~(1 << 6);
uuid->clk_seq_hi_res |= 1 << 7;
}
+
+void parse_uuid(const char *input, uint8_t result [16])
+{
+ int u = 0;
+ if (strlen(input) != UUID_STR_LEN-1) {
+ fprintf(stderr, "UUID MUST be in the format 00112233-4455-6677-8899-AABBCCDDEEFF.\n");
+ abort();
+ }
+ for (int i = 0; i < UUID_STR_LEN - 1; i++) {
+ if (i == 8 || i == 13 || i == 18 || i == 23) {
+ /* Skip hyphens */
+ }
+ else {
+ char pair[3] = "00";
+ strncpy(pair, input + i, 2);
+ result[u] = strtol(pair, NULL, 16);
+ /* This handled a pair, let's move one more further along. */
+ i++;
+ /* Move to the next byte. */
+ u++;
+ }
+ }
+}
+
+void uuid_to_string(const uint8_t uuid [16], char result [UUID_STR_LEN])
+{
+ /* Index in uuid */
+ int u = 0;
+ for (int i = 0; i < UUID_STR_LEN-1; i++) {
+ /* Handles separators */
+ if (i == 8 || i == 13 || i == 18 || i == 23) {
+ strcpy(result+i, "-");
+ }
+ else {
+ /* This writes \0 at the end of every invocation */
+ snprintf(result+i, 3, "%02X", uuid[u]);
+ /* This handled a pair, let's move one more further along. */
+ i++;
+ /* Move to the next byte. */
+ u++;
+ }
+ }
+}
diff --git a/uuid.h b/uuid.h
index ff1b438..21ba165 100644
--- a/uuid.h
+++ b/uuid.h
@@ -19,6 +19,12 @@
#include "ext4_utils.h"
+#define UUID_STR_LEN 36+1
+
void generate_uuid(const char *namespace, const char *name, u8 result[16]);
+void parse_uuid(const char *input, uint8_t result [16]);
+
+void uuid_to_string(const uint8_t uuid [16], char result [UUID_STR_LEN]);
+
#endif
--
2.19.2

View File

@ -0,0 +1,129 @@
From 71bc2e8b772695c00b2ed550565724afddd6fb7e Mon Sep 17 00:00:00 2001
From: Samuel Dionne-Riel <samuel@dionne-riel.com>
Date: Thu, 29 Aug 2019 15:11:04 -0400
Subject: [PATCH 2/2] make_ext4fs: allow setting a specific UUID
The UUID was previously only reproducibly generated using a combination
of a fixed namespace and the label of the partition. This change allows
projects to specify a desired UUID instead.
Signed-off-by: Samuel Dionne-Riel <samuel@dionne-riel.com>
---
ext4_sb.h | 3 +++
ext4_utils.c | 13 ++++++++++++-
make_ext4fs.c | 5 +++++
make_ext4fs_main.c | 9 +++++++--
4 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/ext4_sb.h b/ext4_sb.h
index d6416a7..68f5cb1 100644
--- a/ext4_sb.h
+++ b/ext4_sb.h
@@ -18,6 +18,7 @@
#define _EXT4_UTILS_EXT4_SB_H_
#include "ext4_kernel_headers.h"
+#include <stdbool.h>
#define EXT4_SUPER_MAGIC 0xEF53
@@ -42,6 +43,8 @@ struct fs_info {
uint32_t reserve_pcnt;
const char *label;
uint8_t no_journal;
+ bool with_uuid;
+ uint8_t uuid[16];
};
int ext4_parse_sb(struct ext4_super_block *sb, struct fs_info *info);
diff --git a/ext4_utils.c b/ext4_utils.c
index 1a886d7..e8e60fb 100644
--- a/ext4_utils.c
+++ b/ext4_utils.c
@@ -221,7 +221,13 @@ void ext4_fill_in_sb()
sb->s_feature_compat = info.feat_compat;
sb->s_feature_incompat = info.feat_incompat;
sb->s_feature_ro_compat = info.feat_ro_compat;
- generate_uuid("extandroid/make_ext4fs", info.label, sb->s_uuid);
+ if (info.with_uuid) {
+ memset(sb->s_uuid, 0, sizeof(sb->s_uuid));
+ memcpy(sb->s_uuid, info.uuid, sizeof(sb->s_uuid));
+ }
+ else {
+ generate_uuid("extandroid/make_ext4fs", info.label, sb->s_uuid);
+ }
memset(sb->s_volume_name, 0, sizeof(sb->s_volume_name));
strncpy(sb->s_volume_name, info.label, sizeof(sb->s_volume_name));
memset(sb->s_last_mounted, 0, sizeof(sb->s_last_mounted));
@@ -521,6 +527,11 @@ int read_ext(int fd, int verbose)
printf(" Inodes per group: %d\n", info.inodes_per_group);
printf(" Inode size: %d\n", info.inode_size);
printf(" Label: %s\n", info.label);
+ if (info.with_uuid) {
+ char uuid[UUID_STR_LEN] = "";
+ uuid_to_string(info.uuid, uuid);
+ printf(" UUID: %s\n", uuid);
+ }
printf(" Blocks: %"PRIu64"\n", aux_info.len_blocks);
printf(" Block groups: %d\n", aux_info.groups);
printf(" Reserved block group size: %d\n", info.bg_desc_reserve_blocks);
diff --git a/make_ext4fs.c b/make_ext4fs.c
index 051052b..ac38d15 100644
--- a/make_ext4fs.c
+++ b/make_ext4fs.c
@@ -451,6 +451,11 @@ int make_ext4fs_internal(int fd, const char *_directory,
printf(" Inode size: %d\n", info.inode_size);
printf(" Journal blocks: %d\n", info.journal_blocks);
printf(" Label: %s\n", info.label);
+ if (info.with_uuid) {
+ char uuid[UUID_STR_LEN] = "";
+ uuid_to_string(info.uuid, uuid);
+ printf(" UUID: %s\n", uuid);
+ }
ext4_create_fs_aux_info();
diff --git a/make_ext4fs_main.c b/make_ext4fs_main.c
index 88254c3..e957a03 100644
--- a/make_ext4fs_main.c
+++ b/make_ext4fs_main.c
@@ -27,6 +27,7 @@
#include "ext4_utils.h"
#include "canned_fs_config.h"
+#include "uuid.h"
extern struct fs_info info;
@@ -35,7 +36,7 @@ static void usage(char *path)
{
fprintf(stderr, "%s [ -l <len> ] [ -j <journal size> ] [ -b <block_size> ]\n", basename(path));
fprintf(stderr, " [ -g <blocks per group> ] [ -i <inodes> ] [ -I <inode size> ]\n");
- fprintf(stderr, " [ -m <reserved blocks percent> ] [ -L <label> ] [ -f ]\n");
+ fprintf(stderr, " [ -m <reserved blocks percent> ] [ -L <label> ] [ -U <uuid> ] [ -f ]\n");
fprintf(stderr, " [ -S file_contexts ] [ -C fs_config ] [ -T timestamp ]\n");
fprintf(stderr, " [ -z | -s ] [ -w ] [ -c ] [ -J ] [ -v ] [ -B <block_list_file> ]\n");
fprintf(stderr, " <filename> [<directory>]\n");
@@ -58,7 +59,7 @@ int main(int argc, char **argv)
time_t fixed_time = -1;
FILE* block_list_file = NULL;
- while ((opt = getopt(argc, argv, "l:j:b:g:i:I:L:T:C:B:m:fwzJsctv")) != -1) {
+ while ((opt = getopt(argc, argv, "l:j:b:g:i:I:L:T:C:B:m:U:fwzJsctv")) != -1) {
switch (opt) {
case 'l':
info.len = parse_num(optarg);
@@ -81,6 +82,10 @@ int main(int argc, char **argv)
case 'L':
info.label = optarg;
break;
+ case 'U':
+ info.with_uuid = true;
+ parse_uuid(optarg, info.uuid);
+ break;
case 'f':
force = 1;
break;
--
2.19.2

View File

@ -0,0 +1,34 @@
{ stdenv, fetchgit, zlib }:
stdenv.mkDerivation {
pname = "make_ext4fs";
version = "unstable-2017-05-21";
src = fetchgit {
url = "git://git.openwrt.org/project/make_ext4fs.git";
rev = "eebda1d55d9701ace2700d7ae461697fadf52d1f";
sha256 = "03lqd5qy3nli9mmnlbgxwsplwz8v10cyjyzl1fxcfz8jvzr00c61";
};
patches = [
./0001-uuid-add-facilities-to-parse-and-print-UUIDs.patch
./0002-make_ext4fs-allow-setting-a-specific-UUID.patch
];
buildInputs = [
zlib
];
installPhase = ''
mkdir -p $out/bin
cp make_ext4fs $out/bin
'';
meta = with stdenv.lib; {
homepage = https://git.openwrt.org/?p=project/make_ext4fs.git;
description = "Standalone fork of Android make_ext4fs utility";
license = licenses.asl20;
platforms = platforms.linux;
maintainers = [ maintainers.samueldr ];
};
}

View File

@ -53,6 +53,13 @@ in
p9_fixes
];
#
# New software to upstream
# ------------------------
#
make_ext4fs = callPackage ./make_ext4fs {};
#
# Fixes to upstream
# -----------------
@ -60,33 +67,6 @@ in
# All that follows will have to be cleaned and then upstreamed.
#
fbterm = super.fbterm.overrideDerivation(oldAttrs: with self; {
# Adds missing nativeBuildInputs (they're only buildInputs in nixpkgs).
nativeBuildInputs = [ pkgconfig ncurses binutils ];
# Futhermore, this patch is needed for compilation.
patches = [
(fetchpatch {
name = "0001-fbio.cpp-improxy.cpp-fbterm.cpp-fix-musl-compile.patch";
url = "https://raw.githubusercontent.com/buildroot/buildroot/master/package/fbterm/0001-fbio.cpp-improxy.cpp-fbterm.cpp-fix-musl-compile.patch";
sha256 = "10dgpsym0nhsxzjbi0dbp1y5h2a1b7srsf9l09j9g10ia31ljbs3";
})
]
++ oldAttrs.patches
;
});
freetype = super.freetype.overrideDerivation(oldAttrs: with self;{
# ./configure doesn't detect the native compiler properly.
CC_BUILD = "${buildPackages.stdenv.cc}/bin/cc";
});
libdrm = super.libdrm.overrideAttrs(oldAttrs: {
# valgrind won't build cross.
buildInputs = builtins.filter (
input: input != self.valgrind-light
) oldAttrs.buildInputs;
});
vboot_reference = super.vboot_reference.overrideAttrs(attrs: {
# https://github.com/NixOS/nixpkgs/pull/69039
postPatch = ''
@ -103,4 +83,6 @@ in
stdenv = with self; overrideCC stdenv buildPackages.gcc6;
};
};
imageBuilder = callPackage ../lib/image-builder {};
}

24
profiles/installer.nix Normal file
View File

@ -0,0 +1,24 @@
{ config, lib, pkgs, ... }:
let
ifSystem = type: lib.mkIf (config.mobile.system.type == type);
in
{
imports = [
<nixpkgs/nixos/modules/profiles/installation-device.nix>
];
config = lib.mkMerge [
{
# This seems counter-intuitive to me, but networkmanager requires that
# this is set to false.
networking.wireless.enable = false;
networking.networkmanager.enable = true;
}
(ifSystem "depthcharge" {
environment.systemPackages = with pkgs; [
vboot_reference
];
})
];
}

View File

@ -1,30 +0,0 @@
#
# nix-build nixos -I nixos-config=system-image.nix -A config.system.build.sdImage
#
{ config, lib, pkgs, ... }:
let
inherit (import <nixpkgs> {}) path;
extlinux-conf-builder =
import (path + "/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix") {
inherit pkgs;
};
in
{
imports = [
(path + "/nixos/modules/installer/cd-dvd/sd-image.nix")
(path + "/nixos/modules/profiles/base.nix")
(path + "/nixos/modules/profiles/installation-device.nix")
];
boot.loader.grub.enable = false;
boot.loader.generic-extlinux-compatible.enable = true;
# FIXME: this probably should be in installation-device.nix
sdImage = {
populateBootCommands = ''
${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./boot
'';
};
}

View File

@ -1,46 +0,0 @@
{
config
, pkgs
}:
with pkgs;
let
device_config = config.mobile.device;
hardware_config = config.mobile.hardware;
inherit (config.mobile.boot) stage-1;
inherit (hardware_config) ram;
device_name = device_config.name;
device_info = device_config.info;
android-bootimg = pkgs.callPackage ./bootimg.nix {
inherit device_config;
initrd = pkgs.callPackage ./initrd.nix { inherit device_config stage-1; };
};
android-system =
(
(import (pkgs.path + "/nixos"))
{
#system = "armv7l-linux";
#system = config.nixpkgs.pkgs.targetPlatform.system;
configuration = import (./system-android.nix) { mobile_config = config; };
}
).config.system.build.systemImage
;
in
stdenv.mkDerivation {
name = "nixos-mobile_${device_name}_combined";
src = builtins.filterSource (path: type: false) ./.;
unpackPhase = "true";
buildInputs = [
linux
];
installPhase = ''
mkdir -p $out/
cp ${android-bootimg} $out/boot.img
cp ${android-system} $out/system.img
'';
}

View File

@ -2,6 +2,9 @@
, fetchurl
, runCommandNoCC
, initrd
, system
, imageBuilder
, lib
, dtc
, ubootTools
@ -11,22 +14,48 @@
}:
let
inherit (imageBuilder) size;
inherit (imageBuilder.diskImage) makeGPT;
# https://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
# This doesn't fit into the generic makeGPT, some of those are really specific
# to depthcharge.
GPT_ENTRY_TYPES = {
UNUSED = "00000000-0000-0000-0000-000000000000";
EFI = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B";
CHROMEOS_FIRMWARE = "CAB6E88E-ABF3-4102-A07A-D4BB9BE3C1D3";
CHROMEOS_KERNEL = "FE3A2A5D-4F32-41A7-B725-ACCC3285A309";
CHROMEOS_ROOTFS = "3CB8E202-3B7E-47DD-8A3C-7FF2A13CFCEC";
CHROMEOS_RESERVED = "2E0A753D-9E48-43B0-8337-B15192CB1B5E";
LINUX_DATA = "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7";
LINUX_FS = "0FC63DAF-8483-4772-8E79-3D69D8477DE4";
};
inherit (device_info) arch kernel kernel_cmdline dtbs;
device_info = device_config.info;
device_name = device_config.name;
# Kernel used in kpart.
kernel_file = "${kernel}/${kernel.file}";
# Kernel command line for vbutil_kernel.
kpart_config = writeTextFile {
name = "kpart-config-${device_name}";
text = kernel_cmdline;
};
# Name used for some image file output.
name = "mobile-nixos-${device_name}";
# https://github.com/thefloweringash/kevin-nix/issues/3
make-kernel-its = fetchurl {
url = "https://raw.githubusercontent.com/thefloweringash/kevin-nix/e4156870bdb0a374b92c2291e5061d2c1a6c14b3/modules/make-kernel-its.sh";
sha256 = "05918hcmrgrj71hiq460gpzz8lngz2ccf617m9p4c82s43v4agmg";
};
kpart = runCommandNoCC "depthcharge-${device_name}" {
# The image file containing the kernel and initrd.
kpart = runCommandNoCC "kpart-${device_name}" {
nativeBuildInputs = [
dtc
ubootTools
@ -62,6 +91,42 @@ let
--config ${kpart_config} \
--pack $out/kpart
'';
# An "unfinished" disk image.
# It's missing some minor cgpt magic.
# FIXME : make(MBR|GPT) should have a postBuild hook to manipulate the image.
image = makeGPT {
inherit name;
diskID = "44444444-4444-4444-8888-888888888888";
partitions = [
{
name = "kernel";
filename = "${kpart}/kpart";
partitionType = GPT_ENTRY_TYPES.CHROMEOS_KERNEL;
length = size.MiB 64;
}
system
];
};
in
# FIXME: produce more than the kernel partition.
kpart
# Takes the built image, and do some light editing using `cgpt`.
# This uses some depthcharge-specific fields to make the image bootable.
# FIXME : integrate into the makeGPT call with postBuild or something
runCommandNoCC "depthcharge-${device_name}" { nativeBuildInputs = [ vboot_reference ]; } ''
# Copy the generated image...
# Note that while it's GPT, it's lacking some depthcharge magic attributes
cp ${image}/${name}.img ./
chmod +w ${name}.img
# Which is what we're adding back with cgpt!
cgpt add ${lib.concatStringsSep " " [
"-i 1" # Work on the first partition (instead of adding)
"-S 1" # Mark as successful (so it'll be booted from)
"-T 5" # Tries remaining
"-P 10" # Priority
"${name}.img"
]}
mkdir -p $out
cp ${name}.img $out/
''

View File

@ -11,8 +11,18 @@
lib,
mkExtraUtils,
# FIXME : udev specifics
runCommandNoCC,
udev,
pkgs
}:
# FIXME: get the udev specifics out of here.
# The main issue is how `udevRules` needs a reference to `extraUtils`.
# This means that `extraUtils` should be a build product of stage-1 in
# system.build, that we can refer to when required.
let
inherit (lib) optionals flatten;
@ -24,11 +34,74 @@ let
busybox
]
++ optionals (stage-1 ? extraUtils) stage-1.extraUtils
++ [{
package = runCommandNoCC "empty" {} "mkdir -p $out";
extraCommand =
let
inherit (pkgs) udev;
in
''
# Copy udev.
copy_bin_and_libs ${udev}/lib/systemd/systemd-udevd
copy_bin_and_libs ${udev}/bin/udevadm
for BIN in ${udev}/lib/udev/*_id; do
copy_bin_and_libs $BIN
done
''
;
}]
;
};
shell = "${extraUtils}/bin/ash";
udevRules = runCommandNoCC "udev-rules" {
allowedReferences = [ extraUtils ];
preferLocalBuild = true;
} ''
mkdir -p $out
echo 'ENV{LD_LIBRARY_PATH}="${extraUtils}/lib"' > $out/00-env.rules
cp -v ${udev}/lib/udev/rules.d/60-cdrom_id.rules $out/
cp -v ${udev}/lib/udev/rules.d/60-persistent-storage.rules $out/
cp -v ${udev}/lib/udev/rules.d/80-drivers.rules $out/
cp -v ${pkgs.lvm2}/lib/udev/rules.d/*.rules $out/
for i in $out/*.rules; do
substituteInPlace $i \
--replace ata_id ${extraUtils}/bin/ata_id \
--replace scsi_id ${extraUtils}/bin/scsi_id \
--replace cdrom_id ${extraUtils}/bin/cdrom_id \
--replace ${pkgs.coreutils}/bin/basename ${extraUtils}/bin/basename \
--replace ${pkgs.utillinux}/bin/blkid ${extraUtils}/bin/blkid \
--replace ${pkgs.lvm2}/sbin ${extraUtils}/bin \
--replace ${pkgs.mdadm}/sbin ${extraUtils}/sbin \
--replace ${pkgs.bash}/bin/sh ${extraUtils}/bin/sh \
--replace ${udev}/bin/udevadm ${extraUtils}/bin/udevadm
done
# Work around a bug in QEMU, which doesn't implement the "READ
# DISC INFORMATION" SCSI command:
# https://bugzilla.redhat.com/show_bug.cgi?id=609049
# As a result, `cdrom_id' doesn't print
# ID_CDROM_MEDIA_TRACK_COUNT_DATA, which in turn prevents the
# /dev/disk/by-label symlinks from being created. We need these
# in the NixOS installation CD, so use ID_CDROM_MEDIA in the
# corresponding udev rules for now. This was the behaviour in
# udev <= 154. See also
# http://www.spinics.net/lists/hotplug/msg03935.html
substituteInPlace $out/60-persistent-storage.rules \
--replace ID_CDROM_MEDIA_TRACK_COUNT_DATA ID_CDROM_MEDIA
''; # */
# Just to keep track of this bit.
udevFragment = ''
mkdir -p /etc/udev
ln -sfn ${udevRules} /etc/udev/rules.d
'';
stage1 = writeScript "stage1" ''
#!${shell}
@ -41,6 +114,7 @@ let
ln -sv ${shell} /bin/sh
# ---- stage-1.init START ----
${udevFragment}
${stage-1.init}
# ---- stage-1.init END ----
'';

83
systems/rootfs.nix Normal file
View File

@ -0,0 +1,83 @@
# This builds a rootfs image (ext4) from the current configuration.
{ config, lib, pkgs, ... }:
{
boot.loader.grub.enable = false;
boot.loader.generic-extlinux-compatible.enable = false;
system.build.rootfs =
pkgs.imageBuilder.fileSystem.makeExt4 {
name = "NIXOS_SYSTEM";
partitionID = "44444444-4444-4444-8888-888888888888";
populateCommands =
let
closureInfo = pkgs.buildPackages.closureInfo { rootPaths = config.system.build.toplevel; };
in
''
mkdir -p ./nix/store
echo "Copying system closure..."
while IFS= read -r path; do
echo " Copying $path"
cp -prf "$path" ./nix/store
done < "${closureInfo}/store-paths"
echo "Done copying system closure..."
cp -v ${closureInfo}/registration ./nix-path-registration
'';
# FIXME : fixup the partition autoexpand.
extraPadding = pkgs.imageBuilder.size.MiB 500;
}
;
# FIXME: this is not a rootfs!
system.build.diskImage =
pkgs.imageBuilder.diskImage.makeMBR {
name = "mobile-nixos";
diskID = "01234567";
partitions = [
# FIXME : initrd how?
config.system.build.rootfs
];
}
;
#pkgs.runCommandNoCC "mobile-nixos-rootfs" {} ''
# echo "${config.system.build.toplevel}" > $out
#'';
boot.postBootCommands = ''
# On the first boot do some maintenance tasks
if [ -f /nix-path-registration ]; then
${""
# TODO : optionally resize NIXOS_SYSTEM, depending on the target.
# # Figure out device names for the boot device and root filesystem.
# rootPart=$(readlink -f /dev/disk/by-label/NIXOS_SYSTEM)
# bootDevice=$(lsblk -npo PKNAME $rootPart)
# # Resize the root partition and the filesystem to fit the disk
# echo ",+," | sfdisk -N2 --no-reread $bootDevice
# ${pkgs.parted}/bin/partprobe
# ${pkgs.e2fsprogs}/bin/resize2fs $rootPart
}
# Register the contents of the initial Nix store
${config.nix.package.out}/bin/nix-store --load-db < /nix-path-registration
# nixos-rebuild also requires a "system" profile and an /etc/NIXOS tag.
touch /etc/NIXOS
${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
# Prevents this from running on later boots.
rm -f /nix-path-registration
fi
'';
fileSystems = {
"/" = {
# Expected to be installed as `system` partition on android-like.
# Thus the name.
# TODO: move into the android system type.
device = "/dev/disk/by-label/NIXOS_SYSTEM";
fsType = "ext4";
};
};
}

View File

@ -1,124 +0,0 @@
# This module creates a system.img file built and sized to flash to an android-device.
#
# The derivation for the image will be placed in
# config.system.build.system_img
# FIXME : this is a bit of a hack.
{
mobile_config
}:
{ config, lib, pkgs, ... }:
with lib;
let
rootfsImage = import <nixpkgs/nixos/lib/make-ext4-fs.nix> {
inherit pkgs;
inherit (config.systemImage) storePaths;
volumeLabel = "NIXOS_SD";
};
in
{
options.systemImage = {
imageBaseName = mkOption {
default = "nixos-system";
description = ''
Prefix of the name of the generated image file.
'';
};
storePaths = mkOption {
type = with types; listOf package;
example = literalExample "[ pkgs.stdenv ]";
description = ''
Derivations to be included in the Nix store in the generated SD image.
'';
};
bootSize = mkOption {
type = types.int;
default = 120;
description = ''
Size of the /boot partition, in megabytes.
'';
};
};
config = {
fileSystems = {
"/" = {
device = "/dev/disk/by-label/NIXOS_SD";
fsType = "ext4";
};
};
boot.kernelPackages =
lib.makeExtensible (self: with self; {
inherit (mobile_config.mobile.device.info) kernel;
})
;#pkgs.linuxPackagesFor mobile_config.mobile.device.info.kernel;
systemImage.storePaths = [ config.system.build.toplevel ];
system.build.systemImage = rootfsImage;
#pkgs.stdenv.mkDerivation {
# name = "mobile-nixos_system-android_system.img";
# buildInputs = with pkgs; [ e2fsprogs mtools libfaketime utillinux ];
# buildCommand = ''
# cp "${rootfsImage}" "$img"
# '';
#};
boot.postBootCommands = ''
# On the first boot do some maintenance tasks
if [ -f /nix-path-registration ]; then
# Figure out device names for the boot device and root filesystem.
rootPart=$(readlink -f /dev/disk/by-label/NIXOS_SD)
# bootDevice=$(lsblk -npo PKNAME $rootPart)
#
# # Resize the root partition and the filesystem to fit the disk
# echo ",+," | sfdisk -N2 --no-reread $bootDevice
# ${pkgs.parted}/bin/partprobe
${pkgs.e2fsprogs}/bin/resize2fs $rootPart
# Register the contents of the initial Nix store
${config.nix.package.out}/bin/nix-store --load-db < /nix-path-registration
# nixos-rebuild also requires a "system" profile and an /etc/NIXOS tag.
touch /etc/NIXOS
${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
# Prevents this from running on later boots.
rm -f /nix-path-registration
fi
'';
# FIXME https://github.com/ElvishJerricco/cross-nixos-aarch64/blob/master/configuration.nix
security.polkit.enable = false;
services.udisks2.enable = false;
programs.command-not-found.enable = false;
system.boot.loader.kernelFile = lib.mkForce "Image";
services.nixosManual.enable = lib.mkOverride 0 false;
nix.checkConfig = false;
services.klogd.enable = false;
nixpkgs.crossSystem = mobile_config.nixpkgs.crossSystem;
system.nixos.stateVersion = "18.09";
systemd.services.sshd.wantedBy = lib.mkOverride 0 ["multi-user.target"];
# https://github.com/ElvishJerricco/cross-nixos-aarch64/blob/master/sd-image-aarch64.nix
boot.loader.grub.enable = false;
boot.loader.generic-extlinux-compatible.enable = true;
boot.consoleLogLevel = lib.mkDefault 7;
users.extraUsers.root.initialHashedPassword = "";
# HACK!
# Removing `(isYes "MODULES")` would be preferrable.
system.requiredKernelConfig = lib.mkForce [];
};
}