Merge branch 'standalone' into klibc

This commit is contained in:
Sergey Bolshakov 2008-03-13 19:19:25 +03:00
commit ba911cea3a
12 changed files with 427 additions and 121 deletions

105
Makefile
View File

@ -4,17 +4,14 @@ DESTDIR =
BINDIR ?= /usr/bin
LIBDIR ?= /usr/lib
BINSDIR ?= $(LIBDIR)/$(PACKAGE)/bin
TARGETS = init stage1
all: version.h $(TARGETS)
INSTALL = /usr/bin/install
#---------------------------------------------------------------
L ?= GLIBC
#L = KLIBC
ifeq (KLIBC, $(L))
ifeq ($(L),KLIBC)
CC = klcc
endif
@ -26,31 +23,31 @@ F = STDIO
endif
endif
CFLAGS += -Os -pipe -Wall -D_GNU_SOURCE
CFLAGS += -Os -pipe -Wall
GLIBC_INCLUDES =
KLIBC_INCLUDES =
INCLUDES = -I. $($(L)_INCLUDES)
INCLUDES = $($(L)_INCLUDES)
GCC_LIBDIR=$(shell dirname `gcc -print-libgcc-file-name`)
LIBGCC_A=$(shell gcc -print-libgcc-file-name)
GLIBC_LDFLAGS_STAGE1 = -static
KLIBC_LDFLAGS_STAGE1 =
# $(LIBGCC_A)
LDFLAGS_STAGE1 = $($(L)_LDFLAGS_STAGE1)
GLIBC_LDFLAGS = -static
KLIBC_LDFLAGS =
LDFLAGS = $($(L)_LDFLAGS)
GLIBC_LIBC =
KLIBC_LIBC =
STAGE1_LIBC = $($(L)_LIBC)
LIBC = $($(L)_LIBC)
STRIPCMD = strip -R .note -R .comment
#---------------------------------------------------------------
DEFS =
INIT_DEFS =
STAGE1_DEFS =
#---------------------------------------------------------------
DEFS = -D_GNU_SOURCE
#---------------------------------------------------------------
INITSRC = init.c
INITOBJS = $(addprefix $(L)-,$(subst .c,.o,$(INITSRC)))
#---------------------------------------------------------------
STAGE1_DEFS =
ifneq ($(WITH_SHELL),)
STAGE1_DEFS += -DSPAWN_SHELL
endif
@ -64,14 +61,7 @@ ifneq ($(WITHOUT_USBNET),)
STAGE1_DEFS += -DDISABLE_USBNET
endif
COMPILE = $(CC) $(DEFS) $(CFLAGS)
#- stage1 "loader"
# i386 name is invalid to force this feature off
INITSRC = init.c
INIT_DEFS = $(INCLUDES)
INITOBJS = $(addprefix $(L)-,$(subst .c,.o,$(INITSRC)))
COMPILE = $(CC) $(CFLAGS) $(DEFS)
#- frontends
NEWT_FRONTEND_SRC = newt-frontend.c
@ -87,35 +77,26 @@ GLIBC_STAGE1_OWN_LIBS =
KLIBC_STAGE1_OWN_LIBS =
STAGE1_OWN_LIBS = $($(L)_STAGE1_OWN_LIBS)
ifeq ($(L),KLIBC)
STAGE1_NETWORK_LIBS =
endif
KLIBC_STAGE1_NETWORK_LIBS =
GLIBC_STAGE1_NETWORK_LIBS = $(LIBDIR)/libresolv.a
STAGE1_NETWORK_LIBS = $($(L)_STAGE1_NETWORK_LIBS)
ifeq ($(L),GLIBC)
STAGE1_NETWORK_LIBS = $(LIBDIR)/libresolv.a
endif
###############################################################################
# stage1 itself
STAGE1SRC = stage1.c log.c tools.c modules.c probing.c \
mount.c lomount.c automatic.c frontend-common.c \
cdrom.c disk.c \
network.c nfsmount.c dhcp.c url.c dns.c adsl.c
ALLSRC = $(INITSRC) $(STAGE1SRC)
###############################################################################
network.c dhcp.c url.c dns.c adsl.c
STAGE1OBJS = $(addprefix $(L)-,$(subst .c,.o,$(STAGE1SRC)))
LDFLAGS_INIT = $($(L)_LDFLAGS_STAGE1)
INIT_LIBC = $($(L)_LIBC)
#---------------------------------------------------------------
ALLSRC = $(INITSRC) $(STAGE1SRC)
init: $(INITOBJS) $(INIT_LIBC)
$(CC) -o $@ $^ $(LDFLAGS_INIT)
$(STRIPCMD) $@
TARGETS = init gencpio
stage1: $(STAGE1OBJS) $(STAGE1_OWN_LIBS) $(STAGE1_NETWORK_LIBS) $(FRONTEND_LINK) $(STAGE1_LIBC)
$(CC) -o $@ $^ $(LDFLAGS_STAGE1)
all: version.h $(TARGETS)
init: $(INITOBJS) $(STAGE1OBJS) $(STAGE1_OWN_LIBS) $(STAGE1_NETWORK_LIBS) $(FRONTEND_LINK) $(STAGE1_LIBC)
$(CC) -o $@ $^ $(LDFLAGS)
$(STRIPCMD) $@
$(INITOBJS): $(L)-%.o: %.c
@ -131,27 +112,23 @@ version.h: /etc/altlinux-release
@sed 's|^ALT Linux \([^ ]\+\)\([^(]\+\)(\([^)]\+\))|\
echo \\#define VERSION \\"\2\\";echo \\#define DISTRIB_NAME \\"ALT Linux\2\1 \\(\3\\)\\"|' < $^ |sh > $@
gencpio: gen_init_cpio.c
$(CC) $(CFLAGS) $(DEFS) -o $@ $^
initfs: mkinitfs $(TARGETS)
sh $< $@
install: initfs
$(INSTALL) -D -m0755 gencpio $(DESTDIR)$(BINDIR)/gencpio
$(INSTALL) -m0755 mkmodpack $(DESTDIR)$(BINDIR)/mkmodpack
$(INSTALL) -D -m0644 initfs $(DESTDIR)$(LIBDIR)/$(PACKAGE)/initfs
clean:
rm -f *.o .depend $(TARGETS) version.h
.depend:
.depend: version.h
$(CPP) $(CFLAGS) -M $(ALLSRC) > .depend
ifeq (.depend,$(wildcard .depend))
include .depend
endif
gencpio: gen_init_cpio.c
$(CC) $(CFLAGS) -o $@ $^
install: $(TARGETS)
mkdir -p $(DESTDIR)$(BINDIR) $(DESTDIR)$(BINSDIR)
install -m0755 gencpio $(DESTDIR)$(BINDIR)
install -m0755 init stage1 $(DESTDIR)$(BINSDIR)
initfs: all mkinitfs
sh mkinitfs > $@
test: initfs modpack
cat $^ > $@
qstage -initrd test

45
init.c
View File

@ -33,6 +33,7 @@
#include <sys/wait.h>
#include "config-stage1.h"
#include "lomount.h"
#if defined(__powerpc__)
#define TIOCSCTTY 0x540
@ -53,11 +54,20 @@ char * env[] = {
char ** myenv = NULL;
<<<<<<< HEAD:init.c
char *stage[] = {"/sbin/stage1", NULL};
=======
>>>>>>> e918341c2dd1189e2ef8991eba56480304bead8b:init.c
char *udevd[] = {"/sbin/udevd", NULL};
char *udevtrigger[] = {"/sbin/udevtrigger", NULL};
char *udevsettle[] = {"/sbin/udevsettle", NULL};
<<<<<<< HEAD:init.c
=======
extern void stage1();
>>>>>>> e918341c2dd1189e2ef8991eba56480304bead8b:init.c
/*
* this needs to handle the following cases:
*
@ -130,7 +140,7 @@ pid_t spawn(char *av[])
}
}
void grab_env(int fd)
void take_env(int fd)
{
static char buf[PIPE_BUF];
char *p = buf;
@ -159,24 +169,6 @@ void grab_env(int fd)
*ep = NULL;
}
#define LOOP_CLR_FD 0x4C01
void del_loop(char *device)
{
int fd;
if ((fd = open(device, O_RDONLY, 0)) < 0) {
printf("del_loop open failed\n");
return;
}
if (ioctl(fd, LOOP_CLR_FD, 0) < 0) {
printf("del_loop ioctl failed");
return;
}
close(fd);
}
/*
initramfs cleaner
*/
@ -348,7 +340,10 @@ int main(int argc, char **argv)
struct stat rst, cst, ist;
struct statfs sfs;
pid_t pid, klogpid, udevpid;
<<<<<<< HEAD:init.c
sigset_t sig;
=======
>>>>>>> e918341c2dd1189e2ef8991eba56480304bead8b:init.c
int wait_status;
int fd = -1;
int fds[2];
@ -442,9 +437,13 @@ int main(int argc, char **argv)
fcntl(fds[0], F_SETFD, 1);
fcntl(fds[1], F_SETFD, 0);
pid = spawn(stage);
close(fds[1]);
while (pid != wait(&wait_status));
if ((pid = fork())) {
if (pid < 0) fatal("Failed to spawn stage1");
close(fds[1]);
while (pid != wait(&wait_status));
} else {
stage1();
}
if (!(WIFEXITED(wait_status))) {
/* something went wrong */
@ -472,7 +471,7 @@ int main(int argc, char **argv)
while (1);
}
grab_env(fds[0]);
take_env(fds[0]);
if (waitpid(spawn(udevsettle), &wait_status, 0) < 0 ||
!(WIFEXITED(wait_status)))

View File

@ -114,12 +114,11 @@ set_loop (const char *device, const char *file)
char * loopdev = "/dev/loop3"; /* Ugly. But do I care? */
void
del_loop(void)
void del_loop(char *device)
{
int fd;
if ((fd = open (loopdev, O_RDONLY)) < 0)
if ((fd = open (device, O_RDONLY)) < 0)
return;
if (ioctl (fd, LOOP_CLR_FD, 0) < 0)
@ -148,7 +147,7 @@ lomount(char *loopfile, char *where)
}
if (my_mount(loopdev, where, "iso9660", 0)) {
del_loop();
del_loop(loopdev);
return 1;
}
@ -165,7 +164,7 @@ loumount()
umount(where_mounted);
where_mounted = NULL;
}
del_loop();
del_loop(loopdev);
return 0;
}

View File

@ -17,5 +17,6 @@
int lomount(char *loopfile, char *where);
int loumount(void);
void del_loop(char *device);
#endif

63
mkinitfs Executable file
View File

@ -0,0 +1,63 @@
#!/bin/sh -e
out=$1
shift
[ -n "$out" ] || {
echo "Usage: mkinitfs outfile" >&2
exit 1
}
exit_handler()
{
local rc=$?
trap - EXIT
[ $rc -eq 0 ] || rm -f -- $out
exit $rc
}
trap exit_handler HUP PIPE INT TERM QUIT EXIT
(
cat <<EOF
dir /dev 0755 0 0
dir /image 0755 0 0
dir /proc 0755 0 0
dir /sys 0755 0 0
dir /root 0755 0 0
dir /tmp 0755 0 0
dir /var 0755 0 0
dir /var/lock 0755 0 0
dir /etc 0755 0 0
dir /etc/modprobe.d 0755 0 0
dir /etc/terminfo 0755 0 0
dir /etc/terminfo/l 0755 0 0
file /etc/terminfo/l/linux /lib/terminfo/l/linux 0644 0 0
dir /etc/udev 0755 0 0
dir /etc/udev/rules.d 0755 0 0
dir /lib 0755 0 0
dir /lib/udev 0755 0 0
dir /lib/modules 0755 0 0
dir /bin 0755 0 0
file /bin/sh /lib/mkinitrd/busybox 0755 0 0
file /bin/nfsmount /lib/mkinitrd/klibc/bin/nfsmount 0755 0 0
dir /sbin 0755 0 0
file /sbin/modprobe /lib/mkinitrd/module-init-tools/sbin/modprobe 0755 0 0
file /init init 0755 0 0
EOF
find /lib/mkinitrd/klibc/lib -xtype f|\
sed -e 's,/lib/mkinitrd/klibc,,' -e 's,^.\+$,file\t&\t/lib/mkinitrd/klibc&\t0755\t0 0,g'
find /lib/mkinitrd/udev -type f|\
sed -e 's,/lib/mkinitrd/udev,,' -e 's,^.\+$,file\t&\t/lib/mkinitrd/udev&\t0755\t0 0,g'
find /etc/modprobe.d/ /etc/udev/rules.d -type f|\
sed -e 's,^.\+$,file\t&\t&\t0644\t0 0,g'
) | `pwd`/gencpio - |gzip -c > $out

220
mkmodpack Normal file
View File

@ -0,0 +1,220 @@
#!/bin/sh
PROG=mkmodpack
VERSION=0.1
CATCHED=
Exit()
{
local rc=$?
[ -z "$1" ] || rc="$1"
CATCHED=1
exit $rc
}
Warning()
{
echo "$PROG: warning: $*" >&2
}
Fatal()
{
echo "$PROG: $*" >&2
Exit 1
}
KERNEL=
OUTPUT=
WORKDIR=
TEMPLATE=
pattern=
uname_r="$(uname -r)"
exit_handler()
{
local rc=$?
trap - EXIT
[ -n "$CATCHED" -o $rc -eq 0 ] ||
echo "$PROG: unhandled error, exiting..."
[ -z "$WORKDIR" ] || rm -rf "$WORKDIR"
[ -z "$TEMPLATE" ] || rm -f "$TEMPLATE"
exit $rc
}
signal_handler()
{
echo 'Interrupted!' >&2
Exit 1
}
trap exit_handler EXIT
trap signal_handler SIGHUP SIGPIPE SIGINT SIGTERM SIGQUIT
FIRMWARE_DIRS="/lib/firmware /usr/lib/hotplug/firmware"
Usage()
{
cat >&2 <<EOF
mkmodpack - creates an cpio archive, containing subset of kernel modules.
mkmodpack is free software, covered by the GNU General Public License.
mkmodpack comes with ABSOLUTELY NO WARRANTY, see license for details.
Usage: $PROG [options]
Valid options are:
--version print version number and exit.
-k, --kernel assume given kernel version, instead of uname -r.
-o, --output output filename, stdout if omitted.
-p, --pattern place into archive only modules which match one of pattern from given file.
-h, --help show this text.
Example: $PROG -o modpack -k $uname_r
EOF
[ -n "$1" ] && Exit "$1" || Exit
}
#---------------------------------------------------------------
ListModules()
{
if [ -s "$pattern" ]; then
modprobe --set-version="$KERNEL" --list "$@" 2>/dev/null |grep -wf "$pattern"
else
modprobe --set-version="$KERNEL" --list "$@" 2>/dev/null
fi
}
ListModuleFiles()
{
modprobe --set-version="$KERNEL" --show-depends \
--ignore-all-commands "$@" 2>/dev/null
}
AddUniqueModule()
{
local path="$1" modules=
modules="$MODULES
$path"
modules="$(echo "$modules" |sort)"
if [ -z "$(echo "$modules" |uniq -d)" ]; then
echo "$modules"
fi
}
AddModuleFile()
{
local path="$1" name="$1" modules=
name="${name##*/}"
name="${name%.gz}"
name="${name%.ko}"
modules=$(AddUniqueModule "$path")
if [ -n "$modules" ]; then
MODULES="$modules"
AddModuleFirmware "$name" "$path"
fi
}
AddModuleFirmware()
{
local mod_name="$1" && shift
local mod_path="$1" && shift
local fw_list fw_name fw_dir fw_file
fw_list="$(modinfo -F firmware "$mod_path")" || return
[ -n "$fw_list" ] || return
for fw_name in $fw_list; do
fw_file=
for fw_dir in $FIRMWARE_DIRS; do
if [ -r "$fw_dir/$fw_name" ]; then
fw_file="$fw_dir/$fw_name"
break
fi
done
[ -n "$fw_file" ] || {
Warning "Firmware file \"$fw_name\" for module \"$mod_name\" not found"
continue
}
FILES="$FILES
file /lib/firmware/$fw_name $fw_file 0644 0 0"
done
}
AddModule()
{
local path="$1" name="$1" m list
name="${name##*/}"
name="${name%.gz}"
name="${name%.ko}"
list=`ListModuleFiles "$name"`
for m in $list; do
[ -z "${m##/lib/modules/*}" ] || continue
AddModuleFile "$m"
done
}
TEMP=`getopt -n "$0" -o k:,o:,p:,h -l kernel:,output:,pattern:,help,version -- "$@"` || exit 1
eval set -- "$TEMP"
while :; do
case "$1" in
-k|--kernel)
shift
KERNEL="$1"
;;
-o|--output)
shift
OUTPUT="$1"
;;
-p|--pattern*)
shift
pattern="$1"
;;
-h|--help)
Usage 0
;;
--version)
echo "$PROG: version $VERSION"
exit 0
;;
--) shift; break
;;
*) Fatal "Unrecognized option: $1"
;;
esac
shift
done
[ -n "$KERNEL" ] || KERNEL="$uname_r"
[ -d "/lib/modules/$KERNEL" ] || Fatal "Directory /lib/modules/$KERNEL does not exists."
#---------------------------------------------------------------
MODULES=
FILES=
for m in $(ListModules); do
AddModule "$m"
done
WORKDIR=`mktemp -td mkmodpack.XXXXXXXXXX` || Fatal "Failed to create working directory."
TEMPLATE=`mktemp -t mkmodpack.XXXXXXXXXX` || Fatal "Failed to create temporary file."
echo "$MODULES" |cpio -pmd "$WORKDIR" 2>/dev/null || Fatal "Failed to copy modules to working directory."
depmod -a -F "/boot/System.map-$KERNEL" -b "$WORKDIR" "$KERNEL" || Fatal "Failed to create modules dependencies."
(
cd "$WORKDIR"
find "lib/modules/$KERNEL" -type d |sed -e 's,^.\+$,dir\t/&\t0755\t0 0,g'
find "lib/modules/$KERNEL" -type f |sed -e "s,^.\+$,file\t/&\t$WORKDIR/&\t0644\t0 0,g"
echo "$FILES"
) > "$TEMPLATE"
if [ -n "$OUTPUT" ]; then
gencpio "$TEMPLATE" |gzip -c > "$OUTPUT"
else
gencpio "$TEMPLATE" |gzip -c
fi
Exit 0

68
mount.c
View File

@ -25,11 +25,19 @@
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#ifndef DISABLE_NETWORK
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#include "log.h"
#include "modules.h"
#include "mount.h"
#include "dns.h"
#ifndef DISABLE_MEDIAS
@ -117,6 +125,48 @@ int ensure_dev_exists(char *dev)
}
#endif /* DISABLE_MEDIAS */
#ifndef DISABLE_NETWORK
static int nfsmount(char *dev, char *location)
{
char spec[PATH_MAX + 17], *sep;
struct sockaddr_in saddr;
int n, pid, status;
if ((sep = strchr(dev, ':'))) {
*sep = '\0';
} else {
log_message("nfsmount: directory to mount not in host:dir format");
return -1;
}
saddr.sin_family = AF_INET;
if (!inet_aton(dev, &saddr.sin_addr) &&
mygethostbyname(dev, &saddr.sin_addr)) {
log_message("nfsmount: can't get address for %s", dev);
*sep = ':';
return -1;
}
*sep = ':';
strcpy(spec, inet_ntoa(saddr.sin_addr));
n = strlen(spec);
strncpy(spec + n, sep, sizeof(spec) - n);
log_message("nfsmount %s %s", spec, location);
if (!(pid = fork())) {
char * argv[] = {"/bin/nfsmount", spec, location, NULL};
close(0);
close(1);
close(2);
execve(argv[0], argv, NULL);
exit(1);
}
waitpid(pid, &status, 0);
return (WIFEXITED(status) && !WEXITSTATUS(status)) ? 0 : -1;
}
#endif
/* mounts, creating the device if needed+possible */
int my_mount(char *dev, char *location, char *fs, int force_rw)
@ -155,6 +205,12 @@ int my_mount(char *dev, char *location, char *fs, int force_rw)
}
}
#ifndef DISABLE_NETWORK
if (!strcmp(fs, "nfs")) {
return nfsmount(dev, location);
}
#endif
#ifndef DISABLE_MEDIAS
if (!strcmp(fs, "squashfs"))
my_insmod("squashfs", NULL);
@ -177,16 +233,6 @@ int my_mount(char *dev, char *location, char *fs, int force_rw)
my_insmod("isofs", NULL);
#endif
#ifndef DISABLE_NETWORK
if (!strcmp(fs, "nfs")) {
my_insmod("nfs", NULL);
log_message("preparing nfsmount for %s", dev);
rc = nfsmount_prepare(dev, &opts);
if (rc != 0)
return rc;
}
#endif
rc = mount(dev, location, fs, flags, opts);
if (rc != 0) {
log_perror("mount failed");

View File

@ -22,10 +22,6 @@
#ifndef _MOUNT_H_
#define _MOUNT_H_
#ifndef DISABLE_NETWORK
#include "nfsmount.h"
#endif
int my_mount(char *dev, char *location, char *fs, int force_rw);
#ifndef DISABLE_MEDIAS

View File

@ -33,6 +33,7 @@
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

View File

@ -2,8 +2,8 @@
%def_with splash
Name: propagator
Version: 20070301
Release: alt7
Version: 20080301
Release: alt1
Summary: 'Early userspace' set of binaries
License: GPL
@ -11,7 +11,8 @@ Group: System/Kernel and hardware
Source0: %name-%version-%release.tar
BuildRequires: bzlib-devel-static libnewt-devel-static libslang-devel-static
BuildRequires: libnewt-devel-static libslang-devel-static
BuildRequires: mkinitrd-busybox module-init-tools-initramfs udev-initramfs
%description
%name is a set of binaries useful in 'early userspace' environment,
@ -21,19 +22,20 @@ including init and various helpers for hw probing and bootstrapping.
%setup
%build
make CFLAGS="$RPM_OPT_FLAGS" \
%{?_with_shell:WITH_SHELL=t} \
%{?_with_splash:WITH_SPLASH=t} \
LIBDIR=%_libdir
make %{?_with_shell:WITH_SHELL=t} %{?_with_splash:WITH_SPLASH=t}
%install
%make_install DESTDIR=%buildroot LIBDIR=%_libdir install
%files
%_bindir/gencpio
%_bindir/mkmodpack
%_libdir/%name
%changelog
* Sat Mar 1 2008 Sergey Bolshakov <sbolshakov@altlinux.ru> 20080301-alt1
- use udev from now
* Wed Aug 8 2007 Sergey Bolshakov <sbolshakov@altlinux.ru> 20070301-alt7
- do not show %name build date on tty1, closes \#12491

View File

@ -59,6 +59,7 @@
#include "disk.h"
#endif
extern char *env[];
/************************************************************
* globals */
@ -134,7 +135,7 @@ static void spawn_shell(void)
if (ioctl(0, TIOCSCTTY, NULL))
log_perror("could not set new controlling tty");
execve(shell_name[0], shell_name, grab_env());
execve(shell_name[0], shell_name, env);
log_message("execve of %s failed: %s", shell_name[0], strerror(errno));
exit(-1);
}
@ -167,11 +168,11 @@ static void spawn_splash(void)
dup2(fd, 2);
close(fd);
setsid();
execve(splash_name[0], splash_name, NULL);
execve(splash_name[0], splash_name, env);
log_message("execve of %s failed: %s", splash_name[0], strerror(errno));
exit(-1);
}
close(fd);
#endif
}
@ -318,7 +319,7 @@ void getversion()
fclose(f);
}
int main(int argc, char **argv, char **env)
void stage1()
{
enum return_type ret;
char buf[128];
@ -356,11 +357,11 @@ int main(int argc, char **argv, char **env)
/* all went good */
if (shell_pid != 0)
kill(shell_pid, 9);
if (splash_pid != 0)
kill(splash_pid, 9);
pass_env(4);
return 0; /* shut up compiler (we can't get here anyway!) */
exit(0);
}

View File

@ -32,6 +32,7 @@
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/poll.h>
#include "stage1.h"
#include "log.h"
#include "mount.h"
@ -374,7 +375,7 @@ int update_splash()
return 0;
splashstep++;
progress = (65534 * splashstep ) / splashcount;
log_message("boostplash progress: %i",progress);
log_message("bootsplash progress: %i",progress);
sprintf(data,"show %i\n",progress);
return write(splashfd,data,strlen(data));
}