]> git.ipfire.org Git - thirdparty/dracut.git/blame - dracut
dracut: fix arithemtic ${#VAR[@]} tests
[thirdparty/dracut.git] / dracut
CommitLineData
c0815e4e 1#!/bin/bash
cc02093d
HH
2# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
3# ex: ts=8 sw=4 sts=4 et filetype=sh
641cc356
JK
4#
5# Generator script for a dracut initramfs
6# Tries to retain some degree of compatibility with the command line
7# of the various mkinitrd implementations out there
8#
70c26b7f 9
cc02093d 10# Copyright 2005-2010 Red Hat, Inc. All rights reserved.
4f18fe82
HH
11#
12# This program is free software; you can redistribute it and/or modify
13# it under the terms of the GNU General Public License as published by
14# the Free Software Foundation; either version 2 of the License, or
15# (at your option) any later version.
16#
17# This program is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License
23# along with this program. If not, see <http://www.gnu.org/licenses/>.
24#
ec9315e5 25
58dad702
HH
26# store for logging
27dracut_args="$@"
5616feb0
AT
28
29usage() {
30# 80x25 linebreak here ^
cc02093d
HH
31 cat << EOF
32Usage: $0 [OPTION]... <initramfs> <kernel-version>
5616feb0
AT
33Creates initial ramdisk images for preloading modules
34
39ff0682 35 -f, --force Overwrite existing initramfs file.
5616feb0
AT
36 -m, --modules [LIST] Specify a space-separated list of dracut modules to
37 call when building the initramfs. Modules are located
73198d53 38 in /usr/share/dracut/modules.d.
39ff0682 39 -o, --omit [LIST] Omit a space-separated list of dracut modules.
3e17f33b 40 -a, --add [LIST] Add a space-separated list of dracut modules.
5616feb0 41 -d, --drivers [LIST] Specify a space-separated list of kernel modules to
cb47caf7
HH
42 exclusively include in the initramfs.
43 --add-drivers [LIST] Specify a space-separated list of kernel
44 modules to add to the initramfs.
8fa510d4
DH
45 --filesystems [LIST] Specify a space-separated list of kernel filesystem
46 modules to exclusively include in the generic
47 initramfs.
2b9dfbbe
HH
48 -k, --kmoddir [DIR] Specify the directory, where to look for kernel
49 modules
33ee031c
HH
50 --fwdir [DIR] Specify additional directories, where to look for
51 firmwares, separated by :
52 --kernel-only Only install kernel drivers and firmware files
53 --no-kernel Do not install kernel drivers and firmware files
5be225d2
HH
54 --strip Strip binaries in the initramfs
55 --nostrip Do not strip binaries in the initramfs (default)
2f02ae9d
HH
56 --mdadmconf Include local /etc/mdadm.conf
57 --nomdadmconf Do not include local /etc/mdadm.conf
7a34efa5 58 --lvmconf Include local /etc/lvm/lvm.conf
cc02093d 59 --nolvmconf Do not include local /etc/lvm/lvm.conf
5616feb0 60 -h, --help This message
00531568 61 --debug Output debug information of the build process
e103615b
62 -L, --stdlog [0-6] Specify logging level (to standard error)
63 0 - suppress any messages
64 1 - only fatal errors
65 2 - all errors
66 3 - warnings
67 4 - info (default)
68 5 - debug info (here starts lots of output)
69 6 - trace info (and even more)
432196ae
70 -v, --verbose Increase verbosity level (default is info(4))
71 -q, --quiet Decrease verbosity level (default is info(4))
5616feb0
AT
72 -c, --conf [FILE] Specify configuration file to use.
73 Default: /etc/dracut.conf
cc02093d
HH
74 --confdir [DIR] Specify configuration directory to use *.conf files
75 from. Default: /etc/dracut.conf.d
5616feb0
AT
76 -l, --local Local mode. Use modules from the current working
77 directory instead of the system-wide installed in
73198d53 78 /usr/share/dracut/modules.d.
5616feb0 79 Useful when running dracut from a git checkout.
7c179686 80 -H, --hostonly Host-Only mode: Install only what is needed for
5616feb0 81 booting the local host instead of a generic host.
7c179686 82 --fstab Use /etc/fstab to determine the root device.
5616feb0 83 -i, --include [SOURCE] [TARGET]
39ff0682
AT
84 Include the files in the SOURCE directory into the
85 Target directory in the final initramfs.
4fea3ea6
HH
86 If SOURCE is a file, it will be installed to TARGET
87 in the final initramfs.
39ff0682
AT
88 -I, --install [LIST] Install the space separated list of files into the
89 initramfs.
5b158ad3 90 --gzip Compress the generated initramfs using gzip.
cc02093d
HH
91 This will be done by default, unless another
92 compression option or --no-compress is passed.
5b158ad3 93 --bzip2 Compress the generated initramfs using bzip2.
cc02093d
HH
94 Make sure your kernel has bzip2 decompression support
95 compiled in, otherwise you will not be able to boot.
96 --lzma Compress the generated initramfs using lzma.
97 Make sure your kernel has lzma support compiled in,
98 otherwise you will not be able to boot.
5b158ad3 99 --no-compress Do not compress the generated initramfs. This will
cc02093d 100 override any other compression options.
5b11bb73 101 --list-modules List all available dracut modules.
cc02093d 102EOF
5616feb0
AT
103}
104
8466db96
HH
105# function push()
106# push values to a stack
107# $1 = stack variable
108# $2.. values
109# example:
110# push stack 1 2 "3 4"
111push() {
112 local __stack=$1; shift
113 for i in "$@"; do
114 eval ${__stack}'[${#'${__stack}'[@]}]="$i"'
115 done
116}
117
118# function pop()
119# pops the last value from a stack
120# assigns value to second argument variable
121# or echo to stdout, if no second argument
122# $1 = stack variable
123# $2 = optional variable to store the value
124# example:
125# pop stack val
126# val=$(pop stack)
127pop() {
128 local __stack=$1; shift
129 local __resultvar=$1
130 local myresult;
131 # check for empty stack
132 eval '[[ ${#'${__stack}'[@]} -eq 0 ]] && return 1'
133
134 eval myresult='${'${__stack}'[${#'${__stack}'[@]}-1]}'
135
136 if [[ "$__resultvar" ]]; then
137 eval $__resultvar="'$myresult'"
138 else
139 echo "$myresult"
140 fi
141 eval unset ${__stack}'[${#'${__stack}'[@]}-1]'
142 return 0
143}
144
5bc545ed
VL
145# Little helper function for reading args from the commandline.
146# it automatically handles -a b and -a=b variants, and returns 1 if
147# we need to shift $3.
148read_arg() {
149 # $1 = arg name
150 # $2 = arg value
151 # $3 = arg parameter
152 local rematch='^[^=]*=(.*)$'
153 if [[ $2 =~ $rematch ]]; then
cc02093d 154 read "$1" <<< "${BASH_REMATCH[1]}"
5bc545ed 155 else
cc02093d
HH
156 read "$1" <<< "$3"
157 # There is no way to shift our callers args, so
158 # return 1 to indicate they should do it instead.
159 return 1
5bc545ed
VL
160 fi
161}
162
661f9a34
HH
163# Little helper function for reading args from the commandline to a stack.
164# it automatically handles -a b and -a=b variants, and returns 1 if
165# we need to shift $3.
166push_arg() {
167 # $1 = arg name
168 # $2 = arg value
169 # $3 = arg parameter
170 local rematch='^[^=]*=(.*)$'
171 if [[ $2 =~ $rematch ]]; then
172 push "$1" "${BASH_REMATCH[1]}"
173 else
174 push "$1" "$3"
175 # There is no way to shift our callers args, so
176 # return 1 to indicate they should do it instead.
177 return 1
178 fi
179}
180
486a1b93 181kernel="unset"
432196ae 182verbosity_mod_l=0
486a1b93 183
b368a5f3 184while (($# > 0)); do
5bc545ed 185 case ${1%%=*} in
661f9a34
HH
186 -a|--add) push_arg add_dracutmodules_l "$@" || shift;;
187 --add-drivers) push_arg add_drivers_l "$@" || shift;;
188 -m|--modules) push_arg dracutmodules_l "$@" || shift;;
189 -o|--omit) push_arg omit_dracutmodules_l "$@" || shift;;
190 -d|--drivers) push_arg drivers_l "$@" || shift;;
191 --filesystems) push_arg filesystems_l "$@" || shift;;
192 -I|--install) push_arg install_items "$@" || shift;;
193 --fwdir) push_arg fw_dir_l "$@" || shift;;
cc02093d
HH
194 -k|--kmoddir) read_arg drivers_dir_l "$@" || shift;;
195 -c|--conf) read_arg conffile "$@" || shift;;
196 --confdir) read_arg confdir "$@" || shift;;
e103615b 197 -L|--stdlog) read_arg stdloglvl_l "$@" || shift;;
cc02093d
HH
198 -f|--force) force=yes;;
199 --kernel-only) kernel_only="yes"; no_kernel="no";;
200 --no-kernel) kernel_only="no"; no_kernel="yes";;
201 --strip) do_strip_l="yes";;
202 --nostrip) do_strip_l="no";;
690396a5
VL
203 --mdadmconf) mdadmconf_l="yes";;
204 --nomdadmconf) mdadmconf_l="no";;
205 --lvmconf) lvmconf_l="yes";;
206 --nolvmconf) lvmconf_l="no";;
cc02093d 207 --debug) debug="yes";;
432196ae
208 -v|--verbose) ((verbosity_mod_l++));;
209 -q|--quiet) ((verbosity_mod_l--));;
cc02093d
HH
210 -l|--local) allowlocal="yes" ;;
211 -H|--hostonly) hostonly_l="yes" ;;
212 --fstab) use_fstab_l="yes" ;;
213 -h|--help) usage; exit 1 ;;
661f9a34 214 -i|--include) push include_src "$2"; push include_target "$3"; shift 2;;
690396a5 215 --bzip2) [[ $compress != cat ]] && compress="bzip2 -9";;
d486e8f6 216 --lzma) [[ $compress != cat ]] && compress="lzma -9";;
e7b1b342 217 --xz) [[ $compress != cat ]] && compress="xz --check=crc32";;
690396a5 218 --no-compress) compress="cat";;
b2ff4317
VL
219 --gzip) if [[ $compress != cat ]]; then
220 type pigz > /dev/null 2>&1 && compress="pigz -9" || \
221 compress="gzip -9"
222 fi;;
5b11bb73
HH
223 --list-modules)
224 do_list="yes";
225 ;;
cc02093d 226 -*) printf "\nUnknown option: %s\n\n" "$1" >&2; usage; exit 1;;
486a1b93
HH
227 *)
228 if ! [[ $outfile ]]; then
229 outfile=$1
230 elif [[ $kernel = "unset" ]]; then
231 kernel=$1
232 else
233 usage; exit 1;
234 fi
235 ;;
641cc356 236 esac
b368a5f3 237 shift
641cc356 238done
486a1b93
HH
239if ! [[ $kernel ]] || [[ $kernel = "unset" ]]; then
240 kernel=$(uname -r)
241fi
242[[ $outfile ]] || outfile="/boot/initramfs-$kernel.img"
4cba351e 243
f8545d04
HH
244PATH=/sbin:/bin:/usr/sbin:/usr/bin
245export PATH
a55711cd 246
c36ce334
VL
247[[ $debug ]] && {
248 export PS4='${BASH_SOURCE}@${LINENO}(${FUNCNAME[0]}): ';
249 set -x
250}
251
5d791c0e
HH
252[[ $dracutbasedir ]] || dracutbasedir=/usr/share/dracut
253
cc02093d
HH
254[[ $allowlocal && -f "$(readlink -f ${0%/*})/dracut-functions" ]] && \
255 dracutbasedir="${0%/*}"
42c71947 256
f1336ac7 257# if we were not passed a config file, try the default one
42c71947 258if [[ ! -f $conffile ]]; then
eebc929a
VL
259 [[ $allowlocal ]] && conffile="$dracutbasedir/dracut.conf" || \
260 conffile="/etc/dracut.conf"
42c71947 261fi
f1336ac7 262
2c2c4580 263if [[ ! -d $confdir ]]; then
ae24b114 264 [[ $allowlocal ]] && confdir="$dracutbasedir/dracut.conf.d" || \
eebc929a 265 confdir="/etc/dracut.conf.d"
2c2c4580
HH
266fi
267
2245f372
AB
268# source our config file
269[[ -f $conffile ]] && . "$conffile"
270
2c2c4580 271# source our config dir
6438b4fe 272if [[ $confdir && -d $confdir ]]; then
2c2c4580 273 for f in "$confdir"/*.conf; do
cc02093d 274 [[ -e $f ]] && . "$f"
2c2c4580
HH
275 done
276fi
277
d87c2708 278# these optins add to the stuff in the config file
e5e5c895 279if (( ${#add_dracutmodules_l[@]} )); then
661f9a34
HH
280 while pop add_dracutmodules_l val; do
281 add_dracutmodules+=" $val "
282 done
283fi
284
e5e5c895 285if (( ${#add_drivers_l[@]} )); then
661f9a34
HH
286 while pop add_drivers_l val; do
287 add_drivers+=" $val "
288 done
289fi
d87c2708 290
f1336ac7 291# these options override the stuff in the config file
e5e5c895 292if (( ${#dracutmodules_l[@]} )); then
661f9a34
HH
293 dracutmodules=''
294 while pop dracutmodules_l val; do
295 dracutmodules+="$val "
296 done
297fi
298
e5e5c895 299if (( ${#omit_dracutmodules_l[@]} )); then
661f9a34
HH
300 omit_dracutmodules=''
301 while pop omit_dracutmodules_l val; do
302 omit_dracutmodules+="$val "
303 done
304fi
305
e5e5c895 306if (( ${#drivers_l[@]} )); then
661f9a34
HH
307 drivers=''
308 while pop drivers_l val; do
309 drivers+="$val "
310 done
311fi
312
e5e5c895 313if (( ${#filesystems_l[@]} )); then
661f9a34
HH
314 filesystems=''
315 while pop filesystems_l val; do
316 filesystems+="$val "
317 done
318fi
319
e5e5c895 320if (( ${#fw_dir_l[@]} )); then
661f9a34
HH
321 fw_dir=''
322 while pop fw_dir_l val; do
323 fw_dir+="$val "
324 done
325fi
326
e103615b 327[[ $stdloglvl_l ]] && stdloglvl=$stdloglvl_l
432196ae
328stdloglvl=$((stdloglvl + verbosity_mod_l))
329((stdloglvl > 6)) && stdloglvl=6
330((stdloglvl < 0)) && stdloglvl=0
331
26537a5b 332[[ $drivers_dir_l ]] && drivers_dir=$drivers_dir_l
31f7db66 333[[ $do_strip_l ]] && do_strip=$do_strip_l
ba726310 334[[ $hostonly_l ]] && hostonly=$hostonly_l
7c179686 335[[ $use_fstab_l ]] && use_fstab=$use_fstab_l
2f02ae9d 336[[ $mdadmconf_l ]] && mdadmconf=$mdadmconf_l
7a34efa5 337[[ $lvmconf_l ]] && lvmconf=$lvmconf_l
73198d53 338[[ $dracutbasedir ]] || dracutbasedir=/usr/share/dracut
33ee031c 339[[ $fw_dir ]] || fw_dir=/lib/firmware
5be225d2 340[[ $do_strip ]] || do_strip=no
ddfd1d10
VL
341# eliminate IFS hackery when messing with fw_dir
342fw_dir=${fw_dir//:/ }
9a8a00cf 343
ba726310 344[[ $hostonly = yes ]] && hostonly="-h"
ba67b923 345[[ $hostonly != "-h" ]] && unset hostonly
5b158ad3 346[[ $compress ]] || compress="gzip -9"
ba726310 347
5d791c0e 348if [[ -f $dracutbasedir/dracut-functions ]]; then
cc02093d 349 . $dracutbasedir/dracut-functions
adbc8a42 350else
6f590cd1
HH
351 echo "Cannot find $dracutbasedir/dracut-functions." >&2
352 echo "Are you running from a git checkout?" >&2
353 echo "Try passing -l as an argument to $0" >&2
cc02093d 354 exit 1
adbc8a42 355fi
22fd1627 356
5d791c0e 357dracutfunctions=$dracutbasedir/dracut-functions
5cad5bb5 358export dracutfunctions
9a8a00cf 359
432196ae 360ddebug "Executing $0 $dracut_args"
58dad702 361
5b11bb73
HH
362[[ $do_list = yes ]] && {
363 for mod in $dracutbasedir/modules.d/*; do
364 [[ -d $mod ]] || continue;
d8e8e14e
365 [[ -e $mod/install || -e $mod/installkernel || \
366 -e $mod/module-setup.sh ]] || continue
5b11bb73
HH
367 echo ${mod##*/??}
368 done
369 exit 0
370}
371
fa5cd2bf
372# Detect lib paths
373[[ $libdir ]] || for libdir in /lib64 /lib; do
374 [[ -d $libdir ]] && break
375done || {
432196ae 376 dfatal 'No lib directory?!!!'
fa5cd2bf
377 exit 1
378}
432196ae 379
fa5cd2bf
380[[ $usrlibdir ]] || for usrlibdir in /usr/lib64 /usr/lib; do
381 [[ -d $usrlibdir ]] && break
432196ae 382done || dwarn 'No usr/lib directory!'
fa5cd2bf 383
66ac3cd1 384# This is kinda legacy -- eventually it should go away.
f1336ac7 385case $dracutmodules in
66ac3cd1 386 ""|auto) dracutmodules="all" ;;
f1336ac7 387esac
e12aac5e 388
454771cd 389abs_outfile=$(readlink -f "$outfile") && outfile="$abs_outfile"
ec9315e5 390
f24a2d46 391srcmods="/lib/modules/$kernel/"
ecf42850 392[[ $drivers_dir ]] && {
22ecea45 393 if vercmp $(modprobe --version | cut -d' ' -f3) lt 3.7; then
432196ae 394 dfatal 'To use --kmoddir option module-init-tools >= 3.7 is required.'
ecf42850
395 exit 1
396 fi
397 srcmods="$drivers_dir"
398}
f24a2d46
HH
399export srcmods
400
c8937ec4 401if [[ -f $outfile && ! $force ]]; then
432196ae 402 dfatal "Will not override existing initramfs ($outfile) without --force"
ec9315e5
JK
403 exit 1
404fi
405
ebfdb219
VL
406outdir=${outfile%/*}
407if [[ ! -d "$outdir" ]]; then
432196ae 408 dfatal "Can't write $outfile: Directory $outdir does not exist."
454771cd 409 exit 1
ebfdb219 410elif [[ ! -w "$outdir" ]]; then
432196ae 411 dfatal "No permission to write $outdir."
454771cd 412 exit 1
0a325a91 413elif [[ -f "$outfile" && ! -w "$outfile" ]]; then
432196ae 414 dfatal "No permission to write $outfile."
734a0d9e
HH
415 exit 1
416fi
417
cc02093d
HH
418hookdirs="cmdline pre-udev pre-trigger netroot pre-mount"
419hookdirs+=" pre-pivot mount emergency"
49c68fa4 420
b2ff4317 421[[ $TMPDIR && ! -w $TMPDIR ]] && unset TMPDIR
49c68fa4 422readonly initdir=$(mktemp -d -t initramfs.XXXXXX)
734a0d9e 423
cc02093d
HH
424# clean up after ourselves no matter how we die.
425trap 'ret=$?;rm -rf "$initdir";exit $ret;' EXIT
426# clean up after ourselves no matter how we die.
427trap 'exit 1;' SIGINT
ec9315e5 428
f6f74096
DD
429# Need to be able to have non-root users read stuff (rpcbind etc)
430chmod 755 "$initdir"
431
5d791c0e 432export initdir hookdirs dracutbasedir dracutmodules drivers \
e103615b 433 fw_dir drivers_dir debug no_kernel kernel_only \
9d1015b6 434 add_drivers mdadmconf lvmconf filesystems \
e103615b 435 use_fstab libdir usrlibdir \
1e64e493
HH
436 stdloglvl sysloglvl fileloglvl kmsgloglvl logfile \
437 debug
f4fff04e 438
98adb06e 439if [[ $kernel_only != yes ]]; then
33ee031c 440 # Create some directory structure first
cc02093d
HH
441 for d in bin sbin usr/bin usr/sbin usr/lib etc \
442 proc sys sysroot tmp dev/pts var/run; do
443 inst_dir "/$d";
33ee031c
HH
444 done
445fi
0f86847d 446
66ac3cd1
VL
447# check all our modules to see if they should be sourced.
448# This builds a list of modules that we will install next.
95d2dabc 449check_module_dir
cc02093d 450
95bde758 451# source our modules.
5d791c0e 452for moddir in "$dracutbasedir/modules.d"/[0-9][0-9]*; do
0c2e3d12 453 mod=${moddir##*/}; mod=${mod#[0-9][0-9]}
66ac3cd1 454 if strstr "$mods_to_load" " $mod "; then
432196ae 455 dinfo "*** Including module: $mod ***"
cc02093d 456 if [[ $kernel_only = yes ]]; then
95d2dabc 457 module_installkernel $mod
cc02093d 458 else
95d2dabc
HH
459 module_install $mod
460 if [[ $no_kernel != yes ]]; then
461 module_installkernel $mod
cc02093d
HH
462 fi
463 fi
464 mods_to_load=${mods_to_load// $mod /}
66ac3cd1 465 fi
15136762 466done
20abd914 467unset moddir
432196ae 468dinfo "*** Including modules' done ***"
aabc0553 469
0f86847d 470## final stuff that has to happen
bc6b0dec 471
0f86847d 472# generate module dependencies for the initrd
8a474569
VL
473if [[ -d $initdir/lib/modules/$kernel ]] && \
474 ! depmod -a -b "$initdir" $kernel; then
432196ae 475 dfatal "\"depmod -a $kernel\" failed."
8a474569 476 exit 1
f1336ac7 477fi
ec9315e5 478
661f9a34
HH
479while pop include_src src && pop include_target tgt; do
480 if [[ $src && $tgt ]]; then
481 if [[ -f $src ]]; then
482 inst $src $tgt
483 else
432196ae 484 ddebug "Including directory: $src"
661f9a34
HH
485 mkdir -p "${initdir}/${tgt}"
486 cp -a -t "${initdir}/${tgt}" "$src"/*
487 fi
4fea3ea6 488 fi
661f9a34 489done
88ffd2df 490
661f9a34
HH
491while pop install_items items; do
492 for item in $items; do
493 dracut_install "$item"
494 done
39ff0682
AT
495done
496unset item
497
fdc421db 498# make sure that library links are correct and up to date
19530529 499dracut_install /etc/ld.so.conf /etc/ld.so.conf.d/*
432196ae
500if ! ldconfig -r "$initdir"; then
501 if [[ $UID = 0 ]]; then
502 derror "ldconfig exited ungracefully"
503 else
504 derror "ldconfig might need uid=0 (root) for chroot()"
505 fi
506fi
fdc421db 507
432196ae
508if (($maxloglvl >= 5)); then
509 ddebug "Listing sizes of included files:"
510 du -c "$initdir" | sort -n | ddebug
511fi
c4ad7fff 512
31f7db66 513# strip binaries
98adb06e 514if [[ $do_strip = yes ]] ; then
d0ced35f 515 for p in strip grep find; do
cc02093d
HH
516 if ! type -P $p >/dev/null; then
517 derror "Could not find '$p'. You should run $0 with '--nostrip'."
518 do_strip=no
519 fi
31f7db66
HH
520 done
521fi
522
98adb06e 523if [[ $do_strip = yes ]] ; then
cc02093d
HH
524 for f in $(find "$initdir" -type f \
525 \( -perm -0100 -or -perm -0010 -or -perm -0001 \
526 -or -path '*/lib/modules/*.ko' \) ); do
527 dinfo "Stripping $f"
528 strip -g "$f" 2>/dev/null|| :
31f7db66
HH
529 done
530fi
531
6292ee9d 532type hardlink &>/dev/null && {
cc02093d 533 hardlink "$initdir" 2>&1
6292ee9d
HH
534}
535
937f678e
VL
536if ! ( cd "$initdir"; find . |cpio -R 0:0 -H newc -o --quiet | \
537 $compress > "$outfile"; ); then
432196ae 538 dfatal "dracut: creation of $outfile failed"
734a0d9e
HH
539 exit 1
540fi
432196ae
541
542dinfo "Wrote $outfile:"
543dinfo "$(ls -l "$outfile")"
1faecdc1 544
3da58569 545exit 0