Skip to content

Commit 8c5f531

Browse files
author
Kathryn Baldauf
committed
Use kmod library to load modules
- Update Makefile to use libkmod when building init and remove static compilation Signed-off-by: Kathryn Baldauf <[email protected]>
1 parent 7458e58 commit 8c5f531

5 files changed

Lines changed: 138 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,11 @@ jobs:
657657
go.sum
658658
test/go.sum
659659
660+
- name: Install kmod
661+
shell: bash
662+
run: |
663+
sudo apt-get install -y libkmod-dev
664+
660665
- name: Set version info
661666
shell: pwsh
662667
run: |

.github/workflows/codeql.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ jobs:
9292
with:
9393
languages: ${{matrix.language}}
9494

95+
- name: Install kmod
96+
shell: bash
97+
run: |
98+
sudo apt-get install -y libkmod-dev
99+
95100
# build binaries
96101
- name: Build go binaries
97102
shell: pwsh

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ CGO_ENABLED:=0
77
GOMODVENDOR:=
88

99
CFLAGS:=-O2 -Wall
10-
LDFLAGS:=-static -s # strip C binaries
10+
LDFLAGS:= -s # strip C binaries
11+
LDLIBS:= -lkmod
1112

1213
GO_FLAGS_EXTRA:=
1314
ifeq "$(GOMODVENDOR)" "1"
@@ -181,7 +182,7 @@ bin/vsockexec: vsockexec/vsockexec.o vsockexec/vsock.o
181182

182183
bin/init: init/init.o vsockexec/vsock.o
183184
@mkdir -p bin
184-
$(CC) $(LDFLAGS) -o $@ $^
185+
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
185186

186187
%.o: %.c
187188
@mkdir -p $(dir $@)

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ It is primarily used in the [Moby](https://github.com/moby/moby) and [Containerd
1010

1111
While this repository can be used as a library of sorts to call the HCS apis, there are a couple binaries built out of the repository as well. The main ones being the Linux guest agent, and an implementation of the [runtime v2 containerd shim api](https://github.com/containerd/containerd/blob/master/runtime/v2/README.md).
1212

13+
### Install dependencies
14+
To build the init binary used to launch Utility VMs, we rely on "libkmod". To install libkmod, run the following in a linux environment:
15+
16+
```sh
17+
> sudo apt-get update
18+
> sudo apt-get install -y libkmod2 libkmod-dev
19+
```
20+
1321
### Linux Hyper-V Container Guest Agent
1422

1523
To build the Linux guest agent itself all that's needed is to set your GOOS to "Linux" and build out of ./cmd/gcs.

init/init.c

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#define _GNU_SOURCE
22
#include <errno.h>
33
#include <fcntl.h>
4+
#include <ftw.h>
45
#include <getopt.h>
6+
#include <libkmod.h>
57
#include <net/if.h>
68
#include <netinet/ip.h>
79
#include <signal.h>
@@ -14,6 +16,7 @@
1416
#include <sys/stat.h>
1517
#include <sys/sysmacros.h>
1618
#include <sys/types.h>
19+
#include <sys/utsname.h>
1720
#include <sys/wait.h>
1821
#include <unistd.h>
1922
#include "../vsockexec/vsock.h"
@@ -57,15 +60,20 @@ static int opentcp(unsigned short port)
5760
#define RNDADDENTROPY _IOW( 'R', 0x03, int [2] )
5861

5962
#define DEFAULT_PATH_ENV "PATH=/sbin:/usr/sbin:/bin:/usr/bin"
63+
#define OPEN_FDS 15
6064

6165
const char *const default_envp[] = {
6266
DEFAULT_PATH_ENV,
6367
NULL,
6468
};
6569

70+
// global kmod k_ctx so we can access it in the file tree traversal
71+
struct kmod_ctx *k_ctx;
72+
6673
// When nothing is passed, default to the LCOWv1 behavior.
6774
const char *const default_argv[] = { "/bin/gcs", "-loglevel", "debug", "-logfile=/run/gcs/gcs.log" };
6875
const char *const default_shell = "/bin/sh";
76+
const char *const lib_modules = "/lib/modules";
6977

7078
struct Mount {
7179
const char *source, *target, *type;
@@ -403,6 +411,110 @@ int reap_until(pid_t until_pid) {
403411
}
404412
}
405413

414+
// load_module gets the module from the absolute path to the module and then
415+
// inserts into the kernel.
416+
int load_module(struct kmod_ctx *ctx, const char *module_path) {
417+
struct kmod_module *mod = NULL;
418+
int err;
419+
420+
#ifdef DEBUG
421+
printf("loading module: %s\n", module_path);
422+
#endif
423+
424+
err = kmod_module_new_from_path(ctx, module_path, &mod);
425+
if (err < 0) {
426+
return err;
427+
}
428+
429+
err = kmod_module_probe_insert_module(mod, 0, NULL, NULL, NULL, NULL);
430+
if (err < 0) {
431+
kmod_module_unref(mod);
432+
return err;
433+
}
434+
435+
kmod_module_unref(mod);
436+
return 0;
437+
}
438+
439+
// parse_tree_entry is called by ftw for each directory and file in the file tree.
440+
// If this entry is a file and has a .ko file extension, attempt to load into kernel.
441+
int parse_tree_entry(const char *fpath, const struct stat *sb, int typeflag) {
442+
int result;
443+
const char *ext;
444+
445+
if (typeflag != FTW_F) {
446+
// do nothing if this isn't a file
447+
return 0;
448+
}
449+
450+
ext = strrchr(fpath, '.');
451+
if (!ext || ext == fpath) {
452+
// no file extension found in the filepath
453+
return 0;
454+
}
455+
456+
if ((result = strcmp(ext, ".ko")) != 0) {
457+
// file does not have .ko extension so it is not a kernel module
458+
return 0;
459+
}
460+
461+
// print warning if we fail to load the module, but don't fail fn so
462+
// we keep trying to load the rest of the modules.
463+
result = load_module(k_ctx, fpath);
464+
if (result != 0) {
465+
warn2("failed to load module", fpath);
466+
}
467+
return 0;
468+
}
469+
470+
// load_all_modules finds the modules in the image and loads them using kmod,
471+
// which accounts for ordering requirements.
472+
void load_all_modules() {
473+
int max_path = 256;
474+
char modules_dir[max_path];
475+
struct utsname uname_data;
476+
int ret;
477+
478+
// get information on the running kernel
479+
ret = uname(&uname_data);
480+
if (ret != 0) {
481+
die("failed to get kernel information");
482+
}
483+
484+
// create the absolute path of the modules directory this looks
485+
// like /lib/modules/<uname.release>
486+
ret = snprintf(modules_dir, max_path, "%s/%s", lib_modules, uname_data.release);
487+
if (ret < 0) {
488+
die("failed to create the modules directory path");
489+
} else if (ret > max_path) {
490+
die("modules directory buffer larger than expected");
491+
}
492+
493+
if (k_ctx == NULL) {
494+
k_ctx = kmod_new(NULL, NULL);
495+
if (k_ctx == NULL) {
496+
die("failed to create kmod context");
497+
}
498+
}
499+
500+
kmod_load_resources(k_ctx);
501+
ret = ftw(modules_dir, parse_tree_entry, OPEN_FDS);
502+
if (ret < 0) {
503+
kmod_unref(k_ctx);
504+
die("failed to load kmod resources");
505+
} else if (ret != 0) {
506+
// Don't fail on error from walking the file tree and loading modules right now.
507+
// ftw may return an error if the modules directory doesn't exist, which
508+
// may be the case for some images. Additionally, we don't currently support
509+
// using a denylist when loading modules, so we may try to load modules
510+
// that cannot be loaded until later, such as nvidia modules which fail to
511+
// load if no device is present.
512+
warn("error adding modules");
513+
}
514+
515+
kmod_unref(k_ctx);
516+
}
517+
406518
#ifdef DEBUG
407519
int debug_main(int argc, char **argv) {
408520
unsigned int ports[3] = {2056, 2056, 2056};
@@ -533,6 +645,11 @@ int main(int argc, char **argv) {
533645
init_entropy(entropy_port);
534646
}
535647

648+
#ifdef DEBUG
649+
printf("loading modules\n");
650+
#endif
651+
load_all_modules();
652+
536653
pid_t pid = launch(child_argc, child_argv);
537654
if (debug_shell != NULL) {
538655
// The debug shell takes over as the primary child.

0 commit comments

Comments
 (0)