initscript.sh 10.4 KB
Newer Older
Martin Peres's avatar
Martin Peres committed
1
2
#!/bin/busybox sh

3
4
set -eux

Martin Peres's avatar
Martin Peres committed
5
6
7
8
MODULES_PATH=/usr_mods
CONTAINER_MOUNTPOINT=/container
CONTAINER_ROOTFS="$CONTAINER_MOUNTPOINT/rootfs"
CONTAINER_CACHE="$CONTAINER_MOUNTPOINT/cache"
9
CACHE_PARTITION_LABEL="B2C_CACHE"
Martin Peres's avatar
Martin Peres committed
10
11

function log {
12
    { set +x; } 2>/dev/null
Martin Peres's avatar
Martin Peres committed
13
    echo -e "[$(busybox cut -d ' ' -f1 /proc/uptime)]: $*\n"
14
    set -x
Martin Peres's avatar
Martin Peres committed
15
16
17
18
}

function setup_busybox {
    for cmd in `busybox --list`; do
19
        [ -f "/bin/$cmd" ] || busybox ln -s /bin/busybox /bin/$cmd
Martin Peres's avatar
Martin Peres committed
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    done
    log "Busybox setup: DONE"
}

function setup_mounts {
    mount -t proc none /proc
    mount -t sysfs none /sys
    mount -t devtmpfs none /dev
    mkdir -p /dev/pts
    mount -t devpts devpts /dev/pts

    # Mount cgroups
    mount -t cgroup2 none /sys/fs/cgroup
    cd /sys/fs/cgroup/
    for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do
        mkdir -p $sys
        if ! mount -n -t cgroup -o $sys cgroup $sys; then
            rmdir $sys || true
        fi
    done

    log "Mounts setup: DONE"
}

function setup_env {
    export HOME=/root
}

48
49
ARG_MODULES=""
ARG_CONTAINER=""
50
ARG_POST_CONTAINER=""
51
ARG_CACHE_DEVICE="none"
52
ARG_NTP_PEER="none"
53
ARG_SHUTDOWN_CMD="poweroff -f"
Martin Peres's avatar
Martin Peres committed
54
55
56
function parse_cmdline {
    cmdline=$(busybox cat /proc/cmdline)

57
58
    # Go through the list of options in the commandline, but remove the quotes
    # around multi-words arguments. Awk seems to be doing that best.
59
    OLDIFS=$IFS IFS=$'\n'
60
61
62
63
64
65
    for param in $(echo "$cmdline" | awk -F\" 'BEGIN { OFS = "" } {
        for (i = 1; i <= NF; i += 2) {
            gsub(/[ \t]+/, "\n", $i)
        }
        print
    }'); do
Martin Peres's avatar
Martin Peres committed
66
67
68
69
70
        value="${param#*=}"
        case $param in
            b2c.insmods=*)
                ARG_MODULES=$value
                ;;
71
72
73
            b2c.cache_device=*)
                ARG_CACHE_DEVICE=$value
                ;;
Martin Peres's avatar
Martin Peres committed
74
            b2c.container=*)
75
                ARG_CONTAINER="$ARG_CONTAINER$value\n"
Martin Peres's avatar
Martin Peres committed
76
                ;;
77
78
79
            b2c.post_container=*)
                ARG_POST_CONTAINER="$ARG_POST_CONTAINER$value\n"
                ;;
80
81
82
            b2c.ntp_peer=*)
                ARG_NTP_PEER=$value
                ;;
83
84
85
            b2c.shutdown_cmd=*)
                ARG_SHUTDOWN_CMD="$value"
                ;;
Martin Peres's avatar
Martin Peres committed
86
87
        esac
    done
88
    IFS=$OLDIFS
89
    # TODO: add a parameter to download a volume with firmwares and modules
Martin Peres's avatar
Martin Peres committed
90
91
92
}

function load_modules {
93
94
95
    [ -z "$@" ] && return

    for mod_name in `echo "$@" | busybox tr ',' '\n'`; do
Martin Peres's avatar
Martin Peres committed
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
        path="$MODULES_PATH/$mod_name"
        echo "Load the module: $path"
        insmod "$path"
    done

    log "Loading requested modules: DONE"
}

function connect {
    ip link set eth0 up
    udhcpc -i eth0 -s /etc/uhdcp-default.sh -T 1

    log "Getting IP: DONE"
}

111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
function ntp_set {
    case $1 in
        none)
            log "WARNING: Did not reset the time, use b2c.ntp_peer=auto to set it on boot"
            return 0
            ;;
        auto)
            peer_addr="pool.ntp.org"
            ;;
        *)
            peer_addr=$1
    esac

    # Limit the maximum execution time to prevent the boot sequence to be stuck
    # for too long
    status="DONE"
    time timeout 5 ntpd -dnq -p "$peer_addr" || status="FAILED"

129
    log "Getting the time from the NTP server $peer_addr: $status"
130
131
}

Martin Peres's avatar
Martin Peres committed
132
function find_container_partition {
133
    dev_name=`blkid | grep "LABEL=\"$CACHE_PARTITION_LABEL\"" | head -n 1 | cut -d ':' -f 1`
Martin Peres's avatar
Martin Peres committed
134
135
136
137
138
139
140
141
    if [ -n "$dev_name" ]; then
        echo $dev_name
        return 0
    else
        return 1
    fi
}

142
143
144
145
146
function format_cache_partition {
    log "Formating the partition $CONTAINER_PART_DEV"
    mkfs.ext4 -F -L "$CACHE_PARTITION_LABEL" "$CONTAINER_PART_DEV"
}

147
148
149
150
function format_disk {
    if [ -n "$1" ]; then
        parted --script $1 mklabel gpt
        parted --script $1 mkpart primary ext4 2048s 100%
Martin Peres's avatar
Martin Peres committed
151

152
        CONTAINER_PART_DEV=`lsblk -no PATH $1 | tail -n -1`
153
        format_cache_partition
Martin Peres's avatar
Martin Peres committed
154

155
        return $?
Martin Peres's avatar
Martin Peres committed
156
157
158
159
160
    fi

    return 1
}

161
function find_or_create_cache_partition {
Martin Peres's avatar
Martin Peres committed
162
    # See if we have an existing block device that would work
163
    CONTAINER_PART_DEV=`find_container_partition` && return 0
Martin Peres's avatar
Martin Peres committed
164

165
166
167
168
    # Find a suitable disk
    sr_disks_majors=`grep ' sr' /proc/devices | sed "s/^[ \t]*//" | cut -d ' ' -f 1 | tr '\n' ',' | sed 's/,$//'`
    disk=`lsblk -ndlfp -e "$sr_disks_majors" | head -n 1`

169
170
    log "No existing cache partition found on this machine, create one from the disk $disk"

Martin Peres's avatar
Martin Peres committed
171
    # Find a disk, partition it, then format it as ext4
172
    format_disk $disk || return 1
Martin Peres's avatar
Martin Peres committed
173

174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
    return 0
}

function try_to_use_cache_device {
    # Check if the parameter is a path to a file
    if [ -f "$ARG_CACHE_DEVICE" ]; then
        log "The caching parameter '$ARG_CACHE_DEVICE' is neither 'none', 'auto', or a path to a block device. Defaulting to 'none'"
        return 0
    fi

    # $ARG_CACHE_DEVICE has to be a path to a drive
    # NOTE: Pay attention to the space after $ARG_CACHE_DEVICE, as it
    # makes sure that we don't accidentally match /dev/sda1 when asking
    # for /dev/sda.
    blk_dev=`lsblk -rpno PATH,TYPE,LABEL | grep "$ARG_CACHE_DEVICE "`
    if [ -z "$blk_dev" ]; then
        log "Error: The device '$ARG_CACHE_DEVICE' is neither a block device, nor a partition. Defaulting to no caching."
        return 1
    fi

    path=$(echo "$blk_dev" | cut -d ' ' -f 1)
    type=$(echo "$blk_dev" | cut -d ' ' -f 2)
    label=$(echo "$blk_dev" | cut -d ' ' -f 3)
    case $type in
        part)
199
            CONTAINER_PART_DEV="$path"
200
            if [ -z "$label" ]; then
201
                format_cache_partition
202
203
204
205
206
207
                return $?
            fi
            ;;

        disk)
            # Look for the first partition from the drive $1, that has the right cache
208
209
            CONTAINER_PART_DEV=`lsblk -no PATH,LABEL $path | grep "$CACHE_PARTITION_LABEL" | cut -d ' ' -f 1 | head -n 1`
            if [ -n "$CONTAINER_PART_DEV" ]; then
210
211
212
213
214
215
216
217
218
219
                return 0
            else
                log "No existing cache partition on the drive $path, recreate the partition table and format a partition"
                format_disk $path
                return $?
            fi
            ;;
    esac

    return 0
Martin Peres's avatar
Martin Peres committed
220
221
}

222
CONTAINER_PART_DEV=""
223
function mount_cache_partition {
224
    [ -d "$CONTAINER_MOUNTPOINT" ] || mkdir "$CONTAINER_MOUNTPOINT"
Martin Peres's avatar
Martin Peres committed
225

226
227
228
229
230
231
232
    # Find a suitable cache partition
    case $ARG_CACHE_DEVICE in
        none)
            log "Do not use a partition cache"
            return 0
            ;;
        auto)
233
            find_or_create_cache_partition || return 0
234
235
            ;;
        *)
236
            try_to_use_cache_device "$ARG_CACHE_DEVICE" || return 0
237
238
239
            ;;
    esac

240
    log "Selected the partition $CONTAINER_PART_DEV as a cache"
241

242
    status="DONE"
243
244
    mount "$CONTAINER_PART_DEV" "$CONTAINER_MOUNTPOINT" || status="FAILED"
    log "Mounting the partition $CONTAINER_PART_DEV to $CONTAINER_MOUNTPOINT: $status"
Martin Peres's avatar
Martin Peres committed
245

246
    return 0
Martin Peres's avatar
Martin Peres committed
247
248
249
250
251
252
}

function setup_container_runtime {
    # HACK: I could not find a way to change the right parameter in podman's
    # config, so make a symlink for now
    [ -d "$CONTAINER_CACHE" ] || mkdir "$CONTAINER_CACHE"
253
    [ -f "/var/tmp" ] || ln -s "$CONTAINER_CACHE" /var/tmp
Martin Peres's avatar
Martin Peres committed
254

255
256
257
    # Squash a kernel warning
    echo 1 > /sys/fs/cgroup/memory/memory.use_hierarchy

Martin Peres's avatar
Martin Peres committed
258
259
260
261
262
263
264
265
266
    # Set some configuration files
    touch /etc/hosts
    echo "root:x:0:0:root:/root:/bin/sh" > /etc/passwd
    echo "containers:165536:65537" > /etc/subuid
    echo "containers:165536:65537" > /etc/subgid

    log "Container runtime setup: DONE"
}

267
268
269
270
271
272
function create_container {
    log "Pull, create, and init the container"

    # Podman is not super good at explaining what went wrong when creating a
    # container, so just try multiple times, each time increasing the size of
    # the hammer!
273
    for i in 0 1 2 3 4; do
274
        # Set up the wanted container
275
        container_id=`eval "podman create --rm --privileged --network=host --runtime /bin/crun-no-pivot $@"` \
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
            && podman init "$container_id" && return 0

        # The command failed... Ignore the first 3 times, as we want to check it
        # is not a shortlived-network error
        if [ $i -eq 3 ]; then
            # Try resetting the entire state of podman, before trying again!
            podman system reset -f
        fi

        sleep 1
    done

    return 1
}

Martin Peres's avatar
Martin Peres committed
291
function start_container {
292
293
294
    container_id=""
    create_container $@ || return 1

295
296
297
298
299
300
301
    # HACK: Figure out how to use "podman wait" to wait for the container to be
    # ready for execution. Without this sleep, we sometimes fail to attach the
    # stdout/err to the container. Even a one ms sleep is sufficient in my
    # testing, but let's add a bit more just to be sure
    sleep .1

    log "About to start executing a container"
302
    podman start -a "$container_id"
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322

    return $?
}

function start_containers {
    OLDIFS=$IFS IFS=$'\n'
    for container_params in $(echo -e "$@"); do
        start_container "$container_params"
        exit_code=$?

        if [ $exit_code -eq 0 ] ; then
            log "The container run successfully, load the next one!"
        else
            log "The container exited with error code $?, aborting..."
            return 1
        fi
    done
    IFS=$OLDIFS

    return 0
Martin Peres's avatar
Martin Peres committed
323
324
}

325
326
327
328
329
330
331
332
333
334
335
336
function start_post_containers {
    log "Running the post containers"

    OLDIFS=$IFS IFS=$'\n'
    for container_params in $(echo -e "$@"); do
        start_container "$container_params" || /bin/true
    done
    IFS=$OLDIFS

    return 0
}

337
function container_cleanup {
338
339
340
341
    # HACK: podman has a nice race condition, so let's give a bit of time to
    # settle down... Sleeping isn't a problem here, since we are done testing :)
    sleep 1

342
343
    # Stop and delete all the containers that may still be running.
    # This should be a noop, but I would rather be safe than sorry :)
344
    podman container stop -a
345
346
347
    podman umount -a -f
    podman container prune -f

348
349
    # Remove all the dangling images
    podman image prune -f
350
351
}

352
353
354
# Do not print all the early commands
set +x

Martin Peres's avatar
Martin Peres committed
355
356
357
358
359
# Initial setup
setup_busybox
#setup_mounts  # To be continued to so we could boot without any go commands
setup_env

360
# Parse the kernel command line, in search of the b2c parameters
Martin Peres's avatar
Martin Peres committed
361
362
parse_cmdline

363
364
365
# Now that the early boot is over, let's log every command executed
set -x

366
# Load the user-requested modules
Martin Peres's avatar
Martin Peres committed
367
load_modules $ARG_MODULES
368

369
370
371
# Mount the cache partition
mount_cache_partition

372
# Connect to the network, now that the modules are loaded
Martin Peres's avatar
Martin Peres committed
373
connect
374

375
376
377
# Set the time
ntp_set $ARG_NTP_PEER

378
# Start the containers
379
setup_container_runtime
380
start_containers "$ARG_CONTAINER"
381
start_post_containers "$ARG_POST_CONTAINER"
382
container_cleanup
Martin Peres's avatar
Martin Peres committed
383

384
385
386
# Shutdown command
sync
$ARG_SHUTDOWN_CMD