summaryrefslogtreecommitdiff
path: root/src/lamina
diff options
context:
space:
mode:
Diffstat (limited to 'src/lamina')
-rw-r--r--src/lamina/main.c583
1 files changed, 583 insertions, 0 deletions
diff --git a/src/lamina/main.c b/src/lamina/main.c
new file mode 100644
index 0000000..a17462e
--- /dev/null
+++ b/src/lamina/main.c
@@ -0,0 +1,583 @@
+#include "../util.h"
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <signal.h>
+#include <pwd.h>
+
+static char *Root;
+
+NAMED_ENUM(mount_type, layer, devfs, );
+
+struct mount
+{
+ mount_type Type;
+ union
+ {
+ struct
+ {
+ char *Source;
+ } layer;
+ struct
+ {
+ char *Target;
+ } devfs;
+ };
+};
+
+static struct mount *Mounts;
+static size_t MountsCount;
+static bool MountsCleanup;
+static pid_t SlavePID;
+static fd SlaveSocket;
+
+static char const *Arg0;
+
+static void __attribute__((noreturn)) usage(void)
+{
+ fprintf(stderr, "usage: %1$s [recipe-file] [name]\n", Arg0);
+ exit(EX_USAGE);
+}
+
+NAMED_ENUM(message_type, run, chdir, user, );
+
+#define MAX_MESSAGE_SIZE 8192
+#define MAX_SLAVE_COMMAND_ARGUMENT_COUNT 32
+
+struct
+{
+ message_type Type;
+ union
+ {
+ struct
+ {
+ size_t ArgCount;
+ size_t Arg[MAX_SLAVE_COMMAND_ARGUMENT_COUNT];
+ char ArgData[0];
+ } run;
+ struct
+ {
+ char Path[0];
+ } chdir;
+ struct
+ {
+ char Username[0];
+ } user;
+ };
+} typedef message;
+
+static string_array
+Split(char const *Line, size_t LineLen)
+{
+ string_array E;
+ E._ = NULL;
+ E.Count = 0;
+ bool PartStart = true;
+ size_t Mark = 0;
+ for(size_t i = 0; i < LineLen; ++i)
+ {
+ if(Line[i] == ' ')
+ {
+ if(PartStart == false)
+ {
+ PartStart = true;
+ E._[E.Count - 1] = strndup(Line + Mark, i - Mark);
+ }
+ } else if(PartStart == true)
+ {
+ PartStart = false;
+ ++E.Count;
+ Mark = i;
+ E._ = reallocarray(E._, E.Count, sizeof(*E._));
+ if(E._ == NULL)
+ {
+ fprintf(stderr, "fatal: reallocarray failed\n");
+ exit(-1);
+ }
+ }
+ }
+ if(PartStart == false)
+ {
+ PartStart = true;
+ E._[E.Count - 1] = strndup(Line + Mark, LineLen - Mark);
+ }
+ E._ = reallocarray(E._, E.Count + 1, sizeof(*E._));
+ E._[E.Count] = NULL;
+ return E;
+}
+
+static void
+ExpandMounts(void)
+{
+ struct mount *NewMounts = reallocarray(Mounts, MountsCount + 1, sizeof(*NewMounts));
+ if(NewMounts == NULL)
+ {
+ fprintf(stderr, "reallocarray failed: %s\n", strerror(errno));
+ exit(-1);
+ }
+ Mounts = NewMounts;
+}
+
+static int
+command_layer(string_array E)
+{
+ if(E.Count != 1)
+ {
+ fprintf(stderr, "command expects 1 argument\n");
+ return -1;
+ }
+ char const *Source = E._[0];
+ ExpandMounts();
+ char Target[MAXPATHLEN];
+ snprintf(Target, sizeof(Target), "%s", Root);
+ if(mkdir(Target, 755) == -1)
+ {
+ if(errno != EEXIST)
+ {
+ fprintf(stderr, "mkdir %s: %s\n", Target, strerror(errno));
+ return -1;
+ }
+ }
+ int Result = mount_unionfs(Target, Source, true);
+ if(Result == -1) return -1;
+ Mounts[MountsCount++] = (struct mount){
+ .Type = mount_type_layer,
+ .layer = { strdup(Source) },
+ };
+ return 0;
+}
+
+static int
+command_devfs(string_array E)
+{
+ if(E.Count != 1)
+ {
+ fprintf(stderr, "command expects 1 argument\n");
+ return -1;
+ }
+ char const *TargetRelative = E._[0];
+ ExpandMounts();
+ char Target[MAXPATHLEN];
+ snprintf(Target, sizeof(Target), "%s/%s", Root, E._[0]);
+ int Result = mount_devfs(Target);
+ if(Result == -1) return -1;
+ Mounts[MountsCount++] = (struct mount){
+ .Type = mount_type_devfs,
+ .devfs = { strdup(TargetRelative) },
+ };
+ return 0;
+}
+
+static int
+command_copy(string_array E)
+{
+ if(E.Count != 2)
+ {
+ fprintf(stderr, "command expects 2 arguments\n");
+ return -1;
+ }
+ char const *Source = E._[0];
+ char Target[MAXPATHLEN];
+ snprintf(Target, sizeof(Target), "%s/%s", Root, E._[1]);
+ pid_t PID = fork();
+ if(PID == -1) return -1;
+ if(PID == 0)
+ {
+ MountsCleanup = false;
+ close(STDIN_FILENO);
+ dup2(STDERR_FILENO, STDOUT_FILENO);
+ close_all();
+ fprintf(stderr, "rsync --archive --xattrs %s %s\n", Source, Target);
+ execlp("rsync", "rsync", "--archive", "--xattrs", Source, Target, NULL);
+ exit(-1);
+ }
+ int Exit;
+ waitpid(PID, &Exit, 0);
+ if(!WIFEXITED(Exit) || (WEXITSTATUS(Exit) != 0)) return -1;
+ return 0;
+}
+
+static int
+command_run(string_array E)
+{
+ if(E.Count > MAX_SLAVE_COMMAND_ARGUMENT_COUNT)
+ {
+ fprintf(stderr, "too many arguments\n");
+ return -1;
+ }
+ char Buffer[MAX_MESSAGE_SIZE];
+ message *M = (message *)Buffer;
+ M->Type = message_type_run;
+ M->run.ArgCount = E.Count;
+ size_t ArgAt = 0;
+ size_t At = 0;
+ for(size_t i = 0; i < E.Count; ++i)
+ {
+ M->run.Arg[ArgAt] = At;
+ size_t Length = strlen(E._[i]);
+ // XXX: check that we do not go outside
+ memcpy(M->run.ArgData + At, E._[i], Length);
+ M->run.ArgData[At + Length] = 0;
+ At += Length + 1;
+ ++ArgAt;
+ }
+ send(SlaveSocket, M, OffsetOf(message, run.ArgData) + At, 0);
+ int Status;
+ if(recv(SlaveSocket, &Status, sizeof(Status), 0) == -1)
+ {
+ fprintf(stderr, "recv: %s\n", strerror(errno));
+ return -1;
+ }
+ return Status;
+}
+
+static int
+command_chdir(string_array E)
+{
+ if(E.Count != 1)
+ {
+ fprintf(stderr, "requires 1 argument\n");
+ return -1;
+ }
+ char Buffer[MAX_MESSAGE_SIZE];
+ message *M = (message *)Buffer;
+ M->Type = message_type_chdir;
+ size_t Length = strlen(E._[0]);
+ // XXX: check bounds
+ memcpy(M->chdir.Path, E._[0], Length);
+ M->chdir.Path[Length] = 0;
+ send(SlaveSocket, M, OffsetOf(message, chdir.Path) + Length + 1, 0);
+ int Status;
+ if(recv(SlaveSocket, &Status, sizeof(Status), 0) == -1)
+ {
+ fprintf(stderr, "recv: %s\n", strerror(errno));
+ return -1;
+ }
+ return Status;
+}
+
+static int
+command_user(string_array E)
+{
+ if(E.Count != 1)
+ {
+ fprintf(stderr, "requires 1 argument\n");
+ return -1;
+ }
+ char Buffer[MAX_MESSAGE_SIZE];
+ message *M = (message *)Buffer;
+ M->Type = message_type_user;
+ size_t Length = strlen(E._[0]);
+ // XXX: check bounds
+ memcpy(M->user.Username, E._[0], Length);
+ M->user.Username[Length] = 0;
+ send(SlaveSocket, M, OffsetOf(message, user.Username) + Length + 1, 0);
+ int Status;
+ if(recv(SlaveSocket, &Status, sizeof(Status), 0) == -1)
+ {
+ fprintf(stderr, "recv: %s\n", strerror(errno));
+ return -1;
+ }
+ return Status;
+}
+
+static int
+Execute(char const *Line, size_t LineLen)
+{
+ string_array Args = Split(Line, LineLen);
+ if(Args.Count == 0)
+ {
+ Free_string_array(&Args);
+ return 0;
+ }
+ int Result;
+ char *Command = Args._[0];
+ string_array Rest = (string_array){ ._ = Args._ + 1, .Count = Args.Count - 1 };
+ if(strcmp(Command, "RUN") == 0)
+ {
+ Result = command_run(Rest);
+ } else if(strcmp(Command, "LAYER") == 0)
+ {
+ Result = command_layer(Rest);
+ } else if(strcmp(Command, "DEVFS") == 0)
+ {
+ Result = command_devfs(Rest);
+ } else if(strcmp(Command, "CHDIR") == 0)
+ {
+ Result = command_chdir(Rest);
+ } else if(strcmp(Command, "COPY") == 0)
+ {
+ Result = command_copy(Rest);
+ } else if(strcmp(Command, "USER") == 0)
+ {
+ Result = command_user(Rest);
+ } else
+ {
+ fprintf(stderr, "unrecognized command: %s\n", Command);
+ Result = -1;
+ }
+ Free_string_array(&Args);
+ return Result;
+}
+
+static int
+unmount_single(struct mount *E)
+{
+ char const *Target;
+ switch(E->Type)
+ {
+ case mount_type_layer: Target = Root; break;
+ case mount_type_devfs:
+ {
+ char TargetBuffer[MAXPATHLEN];
+ snprintf(TargetBuffer, sizeof(TargetBuffer), "%s/%s", Root, E->devfs.Target);
+ Target = TargetBuffer;
+ break;
+ }
+ case mount_type_COUNT:
+ default: __builtin_unreachable();
+ }
+ while(unmount(Target, MNT_FORCE) == -1)
+ {
+ fprintf(stderr, "unmount %s failed: %s\n", Target, strerror(errno));
+ if(errno != EBUSY) return -1;
+ sleep(1);
+ }
+ return 0;
+}
+
+static void
+unmount_all(void)
+{
+ for(size_t i = MountsCount - 1; i < MountsCount; --i)
+ {
+ unmount_single(Mounts + i);
+ switch(Mounts[i].Type)
+ {
+ case mount_type_layer: free(Mounts[i].layer.Source); break;
+ case mount_type_devfs: free(Mounts[i].devfs.Target); break;
+ case mount_type_COUNT:
+ default: __builtin_unreachable();
+ }
+ }
+ if(Mounts != NULL) free(Mounts);
+}
+
+static int
+slave_command_run(string_array E)
+{
+ if(E.Count == 0)
+ {
+ fprintf(stderr, "run what?\n");
+ return -1;
+ }
+ pid_t PID = fork();
+ if(PID == -1) return -1;
+ if(PID == 0)
+ {
+ MountsCleanup = false;
+ close(STDIN_FILENO);
+ dup2(STDERR_FILENO, STDOUT_FILENO);
+ close_all();
+ execvp(E._[0], E._);
+ fprintf(stderr, "failed to exec '%s': %s\n", E._[0], strerror(errno));
+ exit(-1);
+ }
+ int Exit;
+ waitpid(PID, &Exit, 0);
+ if(!WIFEXITED(Exit) || (WEXITSTATUS(Exit) != 0)) return -1;
+ return 0;
+}
+
+static int
+chuser(char *Username)
+{
+ errno = 0;
+ struct passwd *E = getpwnam(Username);
+ if(E == NULL)
+ {
+ if(errno != 0) fprintf(stderr, "getpwnam(%s): %s\n", Username, strerror(errno));
+ else
+ fprintf(stderr, "user %s does not exist\n", Username);
+ return -1;
+ }
+ uid_t UID = E->pw_uid;
+ gid_t GID = E->pw_gid;
+ if(setgid(GID) == -1)
+ {
+ fprintf(stderr, "setgid(%u): %s\n", GID, strerror(errno));
+ return -1;
+ }
+ if(setuid(UID) == -1)
+ {
+ fprintf(stderr, "setuid(%u): %s\n", UID, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static int
+slave(fd FD)
+{
+ char Buffer[MAX_MESSAGE_SIZE];
+ message *M = (message *)Buffer;
+ ssize_t Size;
+ while((Size = recv(FD, Buffer, sizeof(Buffer), 0)))
+ {
+ if(Size == -1)
+ {
+ if(errno == EINTR) continue;
+ fprintf(stderr, "recv: %s\n", strerror(errno));
+ break;
+ }
+ if(Size == 0) break;
+ int Result;
+ switch(M->Type)
+ {
+ case message_type_run:
+ {
+ string_array E;
+ E._ = NULL;
+ E.Count = M->run.ArgCount;
+ E._ = reallocarray(E._, E.Count + 1, sizeof(*E._));
+ E._[E.Count] = NULL;
+ // XXX: add checking
+ for(size_t i = 0; i < M->run.ArgCount; ++i) E._[i] = M->run.ArgData + M->run.Arg[i];
+ Result = slave_command_run(E);
+ free(E._);
+ break;
+ }
+ case message_type_chdir:
+ {
+ Result = chdir(M->chdir.Path);
+ break;
+ }
+ case message_type_user:
+ {
+ Result = chuser(M->user.Username);
+ break;
+ }
+ case message_type_COUNT:
+ default: __builtin_unreachable();
+ }
+ send(FD, &Result, sizeof(Result), 0);
+ }
+ close(FD);
+ return 0;
+}
+
+static void
+StopSlave(void)
+{
+ kill(SlavePID, SIGINT);
+ int Exit;
+ waitpid(SlavePID, &Exit, 0);
+}
+
+static int
+SpawnSlave(void)
+{
+ fd FD[2];
+ if(socketpair(PF_UNIX, SOCK_DGRAM, 0, FD) == -1)
+ {
+ fprintf(stderr, "socketpair %s\n", strerror(errno));
+ return -1;
+ }
+ pid_t PID = fork();
+ if(PID == -1) return -1;
+ if(PID == 0)
+ {
+ close(FD[1]);
+ close(STDIN_FILENO);
+ dup2(STDERR_FILENO, STDOUT_FILENO);
+ if(chroot(Root) == -1)
+ {
+ fprintf(stderr, "chroot %s: %s\n", Root, strerror(errno));
+ exit(-1);
+ }
+ if(chdir("/") == -1)
+ {
+ fprintf(stderr, "chdir /: %s\n", strerror(errno));
+ exit(-1);
+ }
+ exit(slave(FD[0]));
+ }
+ close(FD[0]);
+ SlaveSocket = FD[1];
+ SlavePID = PID;
+ return 0;
+}
+
+static void
+Cleanup(void)
+{
+ if(SlavePID > 0) StopSlave();
+ if(MountsCleanup == true) unmount_all();
+}
+
+int
+main(int ArgCount, char **Arg)
+{
+ Arg0 = Arg[0];
+ --ArgCount;
+ ++Arg;
+ if(ArgCount != 2) usage();
+ char const *Path = Arg[0];
+ Root = Arg[1];
+ ArgCount -= 2;
+ Arg += 2;
+ if(mkdir(Root, 0755) == -1)
+ {
+ fprintf(stderr, "mkdir %s: %s\n", Root, strerror(errno));
+ exit(-1);
+ }
+ atexit(Cleanup);
+ if(SpawnSlave() == -1) exit(-1);
+ FILE *F = fopen(Path, "r");
+ if(F == NULL)
+ {
+ fprintf(stderr, "fopen(\"%s\", \"r\")\n", Path);
+ exit(-1);
+ }
+ MountsCleanup = true;
+ char *Line = NULL;
+ size_t LineSize = 0;
+ ssize_t LineLength;
+ size_t LineAt = 0;
+ while((LineLength = getline(&Line, &LineSize, F)))
+ {
+ if(LineLength <= 0) break;
+ ++LineAt;
+ if(Line[LineLength - 1] == '\n') Line[--LineLength] = '\0';
+ if(LineLength == 0) continue;
+ if(Line[0] == '#') continue;
+ if(Execute(Line, (size_t)LineLength) == -1)
+ {
+ fprintf(stderr, "(line %zu)\n", LineAt);
+ break;
+ }
+ }
+ printf("LAYER %s\n", Root);
+ for(size_t i = 0; i < MountsCount; ++i)
+ {
+ struct mount *E = Mounts + i;
+ switch(Mounts[i].Type)
+ {
+ case mount_type_layer:
+ {
+ printf("LAYER %s\n", E->layer.Source);
+ break;
+ }
+ case mount_type_devfs:
+ {
+ printf("DEVFS %s\n", E->devfs.Target);
+ break;
+ }
+ case mount_type_COUNT:
+ default: __builtin_unreachable();
+ }
+ }
+ return 0;
+}