Skip to content

Commit ced3ac0

Browse files
committed
wip: unpearl
1 parent 9bd468e commit ced3ac0

File tree

5 files changed

+652
-0
lines changed

5 files changed

+652
-0
lines changed

nix/installer.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070

7171
imports = [
7272
./nix-settings.nix
73+
./un-pearl.nix
7374
];
7475

7576
# Don't add nixpkgs to the image to save space, for our intended use case we don't need it

nix/setup-etc.c

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <string.h>
4+
#include <unistd.h>
5+
#include <sys/stat.h>
6+
#include <sys/types.h>
7+
#include <dirent.h>
8+
#include <pwd.h>
9+
#include <grp.h>
10+
#include <errno.h>
11+
12+
#define MAX_PATH 4096
13+
#define STATIC_PATH "/etc/static"
14+
#define CLEAN_FILE "/etc/.clean"
15+
#define NIXOS_TAG "/etc/NIXOS"
16+
17+
int atomicSymlink(const char *source, const char *target) {
18+
char tmp[MAX_PATH];
19+
snprintf(tmp, MAX_PATH, "%s.tmp", target);
20+
unlink(tmp);
21+
if (symlink(source, tmp) != 0) {
22+
return 0;
23+
}
24+
if (rename(tmp, target) != 0) {
25+
unlink(tmp);
26+
return 0;
27+
}
28+
return 1;
29+
}
30+
31+
int isStatic(const char *path) {
32+
char buf[MAX_PATH];
33+
struct stat st;
34+
if (lstat(path, &st) != 0) {
35+
return 0;
36+
}
37+
if (S_ISLNK(st.st_mode)) {
38+
ssize_t len = readlink(path, buf, MAX_PATH);
39+
if (len < 0 || len >= MAX_PATH) {
40+
return 0;
41+
}
42+
buf[len] = '\0';
43+
return strncmp(buf, STATIC_PATH "/", strlen(STATIC_PATH) + 1) == 0;
44+
}
45+
if (S_ISDIR(st.st_mode)) {
46+
DIR *dir = opendir(path);
47+
if (dir == NULL) {
48+
return 0;
49+
}
50+
struct dirent *entry;
51+
while ((entry = readdir(dir)) != NULL) {
52+
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
53+
continue;
54+
}
55+
char subpath[MAX_PATH];
56+
snprintf(subpath, MAX_PATH, "%s/%s", path, entry->d_name);
57+
if (!isStatic(subpath)) {
58+
closedir(dir);
59+
return 0;
60+
}
61+
}
62+
closedir(dir);
63+
return 1;
64+
}
65+
return 0;
66+
}
67+
68+
void cleanup(const char *path) {
69+
if (strcmp(path, "/etc/nixos") == 0) {
70+
return;
71+
}
72+
struct stat st;
73+
if (lstat(path, &st) != 0) {
74+
return;
75+
}
76+
if (S_ISLNK(st.st_mode)) {
77+
char buf[MAX_PATH];
78+
ssize_t len = readlink(path, buf, MAX_PATH);
79+
if (len < 0 || len >= MAX_PATH) {
80+
return;
81+
}
82+
buf[len] = '\0';
83+
if (strncmp(buf, STATIC_PATH "/", strlen(STATIC_PATH) + 1) == 0) {
84+
char target[MAX_PATH];
85+
snprintf(target, MAX_PATH, "%s/%s", STATIC_PATH, path + strlen("/etc/"));
86+
if (lstat(target, &st) != 0 || !S_ISLNK(st.st_mode)) {
87+
printf("removing obsolete symlink '%s'...\n", path);
88+
unlink(path);
89+
}
90+
}
91+
}
92+
}
93+
94+
void createLink(const char *path, const char *etc_path) {
95+
char fn[MAX_PATH];
96+
snprintf(fn, MAX_PATH, "%s", path + strlen(etc_path) + 1);
97+
98+
if (strcmp(fn, "resolv.conf") == 0 && getenv("IN_NIXOS_ENTER") != NULL) {
99+
return;
100+
}
101+
102+
char target[MAX_PATH];
103+
snprintf(target, MAX_PATH, "/etc/%s", fn);
104+
char *dir = strdup(target);
105+
char *last_slash = strrchr(dir, '/');
106+
*last_slash = '\0';
107+
mkdir(dir, 0755);
108+
free(dir);
109+
110+
char mode_path[MAX_PATH];
111+
snprintf(mode_path, MAX_PATH, "%s.mode", path);
112+
FILE *mode_file = fopen(mode_path, "r");
113+
if (mode_file != NULL) {
114+
char mode[16];
115+
if (fgets(mode, sizeof(mode), mode_file) != NULL) {
116+
mode[strcspn(mode, "\n")] = '\0';
117+
if (strcmp(mode, "direct-symlink") == 0) {
118+
char source[MAX_PATH];
119+
snprintf(source, MAX_PATH, "%s/%s", STATIC_PATH, fn);
120+
char link_target[MAX_PATH];
121+
ssize_t len = readlink(source, link_target, MAX_PATH);
122+
if (len < 0 || len >= MAX_PATH) {
123+
fprintf(stderr, "could not read symlink %s\n", source);
124+
} else {
125+
link_target[len] = '\0';
126+
if (!atomicSymlink(link_target, target)) {
127+
fprintf(stderr, "could not create symlink %s\n", target);
128+
}
129+
}
130+
} else {
131+
char uid_path[MAX_PATH];
132+
snprintf(uid_path, MAX_PATH, "%s.uid", path);
133+
FILE *uid_file = fopen(uid_path, "r");
134+
if (uid_file == NULL) {
135+
fprintf(stderr, "could not open %s\n", uid_path);
136+
fclose(mode_file);
137+
return;
138+
}
139+
char uid_str[32];
140+
if (fgets(uid_str, sizeof(uid_str), uid_file) == NULL) {
141+
fprintf(stderr, "could not read %s\n", uid_path);
142+
fclose(uid_file);
143+
fclose(mode_file);
144+
return;
145+
}
146+
uid_str[strcspn(uid_str, "\n")] = '\0';
147+
uid_t uid = uid_str[0] == '+' ? atoi(uid_str + 1) : getpwnam(uid_str) != NULL ? getpwnam(uid_str)->pw_uid : 0;
148+
fclose(uid_file);
149+
150+
char gid_path[MAX_PATH];
151+
snprintf(gid_path, MAX_PATH, "%s.gid", path);
152+
FILE *gid_file = fopen(gid_path, "r");
153+
if (gid_file == NULL) {
154+
fprintf(stderr, "could not open %s\n", gid_path);
155+
fclose(mode_file);
156+
return;
157+
}
158+
char gid_str[32];
159+
if (fgets(gid_str, sizeof(gid_str), gid_file) == NULL) {
160+
fprintf(stderr, "could not read %s\n", gid_path);
161+
fclose(gid_file);
162+
fclose(mode_file);
163+
return;
164+
}
165+
gid_str[strcspn(gid_str, "\n")] = '\0';
166+
gid_t gid = gid_str[0] == '+' ? atoi(gid_str + 1) : getgrnam(gid_str) != NULL ? getgrnam(gid_str)->gr_gid : 0;
167+
fclose(gid_file);
168+
169+
char tmp_path[MAX_PATH];
170+
snprintf(tmp_path, MAX_PATH, "%s.tmp", target);
171+
char source[MAX_PATH];
172+
snprintf(source, MAX_PATH, "%s/%s", STATIC_PATH, fn);
173+
FILE *source_file = fopen(source, "rb");
174+
if (source_file == NULL) {
175+
fprintf(stderr, "could not open %s\n", source);
176+
fclose(mode_file);
177+
return;
178+
}
179+
FILE *tmp_file = fopen(tmp_path, "wb");
180+
if (tmp_file == NULL) {
181+
fprintf(stderr, "could not create %s\n", tmp_path);
182+
fclose(source_file);
183+
fclose(mode_file);
184+
return;
185+
}
186+
char buf[4096];
187+
size_t n;
188+
while ((n = fread(buf, 1, sizeof(buf), source_file)) > 0) {
189+
fwrite(buf, 1, n, tmp_file);
190+
}
191+
fclose(source_file);
192+
fclose(tmp_file);
193+
chmod(tmp_path, strtol(mode, NULL, 8));
194+
chown(tmp_path, uid, gid);
195+
if (rename(tmp_path, target) != 0) {
196+
fprintf(stderr, "could not create target %s\n", target);
197+
unlink(tmp_path);
198+
}
199+
}
200+
FILE *clean_file = fopen(CLEAN_FILE, "a");
201+
if (clean_file != NULL) {
202+
fprintf(clean_file, "%s\n", fn);
203+
fclose(clean_file);
204+
}
205+
}
206+
fclose(mode_file);
207+
} else {
208+
char source[MAX_PATH];
209+
snprintf(source, MAX_PATH, "%s/%s", STATIC_PATH, fn);
210+
if (!atomicSymlink(source, target)) {
211+
fprintf(stderr, "could not create symlink %s\n", target);
212+
}
213+
}
214+
}
215+
216+
int main(int argc, char *argv[]) {
217+
if (argc != 2) {
218+
fprintf(stderr, "Usage: %s <etc-path>\n", argv[0]);
219+
return 1;
220+
}
221+
222+
char *etc_path = argv[1];
223+
char static_path[MAX_PATH];
224+
snprintf(static_path, MAX_PATH, "%s", STATIC_PATH);
225+
226+
if (!atomicSymlink(etc_path, static_path)) {
227+
fprintf(stderr, "Failed to create symlink %s\n", static_path);
228+
return 1;
229+
}
230+
231+
DIR *dir = opendir("/etc");
232+
if (dir == NULL) {
233+
fprintf(stderr, "could not open /etc\n");
234+
return 1;
235+
}
236+
237+
struct dirent *entry;
238+
while ((entry = readdir(dir)) != NULL) {
239+
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
240+
continue;
241+
}
242+
char path[MAX_PATH];
243+
snprintf(path, MAX_PATH, "/etc/%s", entry->d_name);
244+
cleanup(path);
245+
}
246+
closedir(dir);
247+
248+
char *old_copied[4096];
249+
int old_copied_count = 0;
250+
FILE *clean_file = fopen(CLEAN_FILE, "r");
251+
if (clean_file != NULL) {
252+
char line[MAX_PATH];
253+
while (fgets(line, MAX_PATH, clean_file) != NULL) {
254+
line[strcspn(line, "\n")] = '\0';
255+
old_copied[old_copied_count++] = strdup(line);
256+
}
257+
fclose(clean_file);
258+
}
259+
260+
dir = opendir(etc_path);
261+
if (dir == NULL) {
262+
fprintf(stderr, "could not open %s\n", etc_path);
263+
return 1;
264+
}
265+
266+
while ((entry = readdir(dir)) != NULL) {
267+
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
268+
continue;
269+
}
270+
char path[MAX_PATH];
271+
snprintf(path, MAX_PATH, "%s/%s", etc_path, entry->d_name);
272+
createLink(path, etc_path);
273+
}
274+
closedir(dir);
275+
276+
clean_file = fopen(CLEAN_FILE, "w");
277+
if (clean_file != NULL) {
278+
for (int i = 0; i < old_copied_count; i++) {
279+
char path[MAX_PATH];
280+
snprintf(path, MAX_PATH, "/etc/%s", old_copied[i]);
281+
struct stat st;
282+
if (lstat(path, &st) == 0) {
283+
fprintf(clean_file, "%s\n", old_copied[i]);
284+
} else {
285+
printf("removing obsolete file '%s'...\n", path);
286+
}
287+
free(old_copied[i]);
288+
}
289+
fclose(clean_file);
290+
}
291+
292+
FILE *tag_file = fopen(NIXOS_TAG, "a");
293+
if (tag_file != NULL) {
294+
fclose(tag_file);
295+
}
296+
297+
return 0;
298+
}

nix/un-pearl.nix

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# This module attempts to be a workaround to
2+
# remove the dependency of pearl from the
3+
# crictical path of using kexec images
4+
# in combination with nixos-anywhere
5+
6+
# THIS IS NOT FUNCTIONAL
7+
# POTENTIAL SAVING: ~80MB
8+
9+
# TODO:
10+
# - It's a proof of concept to calculate the saving (with nix-tree)
11+
# - I used some AI to get the initial transcript in bash and c
12+
# - If you, unlike me, know enough C please consider helping to finish the rewrite
13+
14+
{ config, lib, pkgs, utils, ... }: {
15+
nixpkgs.overlays = [
16+
(final: prev: {
17+
# avoid perl ~50MB - dummy
18+
syslinux = prev.coreutils;
19+
})
20+
];
21+
# avoid packages that depend on perl
22+
system.disableInstallerTools = true;
23+
system.switch.enable = false;
24+
# reimplement in c to avoid dependency on perl
25+
system.build.etcActivationCommands = let
26+
setup-etc = pkgs.writeCBin "setup-etc" (builtins.readFile ./setup-etc.c);
27+
etc = config.system.build.etc;
28+
in lib.mkForce
29+
''
30+
# Set up the statically computed bits of /etc.
31+
echo "setting up /etc..."
32+
${setup-etc}/bin/setup-etc ${etc}/etc
33+
'';
34+
system.activationScripts.users.text = let
35+
cfg = config.users;
36+
spec = pkgs.writeText "users-groups.json" (builtins.toJSON {
37+
inherit (cfg) mutableUsers;
38+
users = lib.mapAttrsToList (_: u:
39+
{ inherit (u)
40+
name uid group description home homeMode createHome isSystemUser
41+
password hashedPasswordFile hashedPassword
42+
autoSubUidGidRange subUidRanges subGidRanges
43+
initialPassword initialHashedPassword expires;
44+
shell = utils.toShellPath u.shell;
45+
}) cfg.users;
46+
groups = builtins.attrValues cfg.groups;
47+
});
48+
# update-users-groups = pkgs.writers.writeBashBin "update-users-groups" (builtins.readFile ./update-users-groups.sh);
49+
update-users-groups = pkgs.writeCBin "update-users-groups" (builtins.readFile ./update-users-groups.c);
50+
in lib.mkForce ''
51+
install -m 0700 -d /root
52+
install -m 0755 -d /home
53+
54+
${update-users-groups}/bin/update-users-groups ${spec}
55+
'';
56+
}

0 commit comments

Comments
 (0)