/release/picobsd/tinyware/oinit/oinit.c
https://bitbucket.org/freebsd/freebsd-head/ · C · 947 lines · 731 code · 72 blank · 144 comment · 118 complexity · daab6c215de086a8d36a77672739e71f MD5 · raw file
- /*-
- * Copyright (c) 1998 Andrzej Bialecki
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * $FreeBSD$
- */
- /*
- * A primitive version of init(8) with simplistic user interface
- */
- #include <sys/types.h>
- #include <sys/param.h>
- #include <sys/mount.h>
- #include <sys/reboot.h>
- #include <sys/time.h>
- #include <sys/resource.h>
- #include <sys/wait.h>
- #include <ctype.h>
- #include <err.h>
- #ifdef USE_HISTORY
- #error "Not yet. But it's quite simple to add - patches are welcome!"
- #endif
- #include <errno.h>
- #include <fcntl.h>
- #include <libutil.h>
- #include <paths.h>
- #include <setjmp.h>
- #include <signal.h>
- #include <stdio.h>
- #include <string.h>
- #include <syslog.h>
- #include <unistd.h>
- #include <varargs.h>
- #define BUFSIZE 1024
- #define MAX_CONS 12
- #define NONE 0
- #define SINGLE 1
- #define MULTI 2
- #define DEATH 3
- #define FALSE 0
- #define TRUE 1
- char cwd[BUFSIZE];
- char vty[]="0123456789abcdef";
- char *progname;
- char *motd=NULL;
- int ncons=MAX_CONS;
- int Reboot=FALSE;
- int transition=MULTI;
- int prevtrans=SINGLE;
- jmp_buf machine;
- char *trans[]={ "NONE", "SINGLE", "MULTI", "DEATH" };
- extern char **environ;
- /* Struct for holding session state */
- struct sess {
- char tty[16]; /* vty device path */
- pid_t pid; /* pid of process running on it */
- int (*func)(int argc, char **argv);
- /* internal function to run on it (after forking) */
- } ttys[MAX_CONS];
- /* Struct for built-in command */
- struct command {
- char *cmd; /* command name */
- char *descr; /* command description */
- char *usage; /* usage */
- char *example; /* example of usage */
- int (*func)(char *); /* callback function */
- };
- /* Prototypes */
- int cd(char *);
- int pwd(char *);
- int echo(char *);
- int xit(char *);
- int set(char *);
- int unset(char *);
- int env(char *);
- int help(char *);
- int sourcer(char *);
- void do_command(int shell, char *cmdline);
- void transition_handler(int);
- /* Table of built-in functions */
- struct command bltins[]={
- {"cd","Change working directory","cd [dir]","cd /etc",cd},
- {"pwd","Print current directory","pwd","pwd",pwd},
- {"exit","Exit from shell()","exit","exit",xit},
- {"set","Set environment variable","set [VAR=value]","set TERM=xterm",set},
- {"unset","Unset environment variable","unset VAR","unset EDITOR",unset},
- {"echo","Echo arguments on stdout","echo arg1 arg2 ...","echo Hello World!",echo},
- {"env","Print all environment variables","env","env",env},
- {".","Source-in a file with commands",". filename",". /etc/rc",sourcer},
- {"?","Print this help :-)","? [command]","? set",help},
- {NULL,NULL,NULL,NULL,NULL}
- };
- /*
- * Built-in 'cd <path>' handler
- */
- int
- cd(char *path)
- {
- if(chdir(path)) return(-1);
- getcwd(cwd,BUFSIZE);
- return(0);
- }
- /*
- * Built-in 'pwd' handler
- */
- int
- pwd(char *dummy)
- {
- if(getcwd(cwd,BUFSIZE)==NULL) return(-1);
- printf("%s\n",cwd);
- return(0);
- }
- /*
- * Built-in 'exit' handler
- */
- int
- xit(char *dummy)
- {
- _exit(0);
- }
- /*
- * Built-in 'echo' handler
- */
- int
- echo(char *args)
- {
- int i=0,j;
- int len;
- char c;
- int s_quote=0,d_quote=0;
- int sep=0,no_lf=0;
- if(args==NULL) {
- printf("\n");
- return;
- }
- len=strlen(args);
- if(len>=2) {
- if(args[0]=='-' && args[1]=='n') {
- no_lf++;
- i=2;
- while(i<len && (args[i]==' ' || args[i]=='\t')) i++;
- }
- }
- while(i<len) {
- c=args[i];
- switch(c) {
- case ' ':
- case '\t':
- if(s_quote||d_quote) {
- putchar(c);
- } else if(!sep) {
- putchar(' ');
- sep=1;
- }
- break;
- case '\\':
- i++;
- c=args[i];
- switch(c) {
- case 'n':
- putchar('\n');
- break;
- case 'b':
- putchar('\b');
- break;
- case 't':
- putchar('\t');
- break;
- case 'r':
- putchar('\r');
- break;
- default:
- putchar(c);
- break;
- }
- break;
- case '"':
- if(!d_quote) {
- d_quote=1;
- for(j=i+1;j<len;j++) {
- if(args[j]=='\\') {
- j++;
- continue;
- }
- if(args[j]=='"') {
- d_quote=2;
- break;
- }
- }
- if(d_quote!=2) {
- printf("\necho(): unmatched \"\n");
- return;
- }
- } else d_quote=0;
- break;
- case '\'':
- if(!s_quote) {
- s_quote=1;
- for(j=i+1;j<len;j++) {
- if(args[j]=='\\') {
- j++;
- continue;
- }
- if(args[j]=='\'') {
- s_quote=2;
- break;
- }
- }
- if(s_quote!=2) {
- printf("\necho(): unmatched '\n");
- return;
- }
- } else s_quote=0;
- break;
- case '`':
- printf("echo(): backquote not implemented yet!\n");
- break;
- default:
- sep=0;
- putchar(c);
- break;
- }
- i++;
- }
- if(!no_lf) putchar('\n');
- fflush(stdout);
- }
- /*
- * Built-in 'set VAR=value' handler
- */
- int
- set(char *var)
- {
- int res;
- if(var==NULL) return(env(NULL));
- res=putenv(var);
- if(res) printf("set: %s\n",strerror(errno));
- return(res);
- }
- /*
- * Built-in 'env' handler
- */
- int
- env(char *dummy)
- {
- char **e;
- e=environ;
- while(*e!=NULL) {
- printf("%s\n",*e++);
- }
- return(0);
- }
- /*
- * Built-in 'unset VAR' handler
- */
- int
- unset(char *var)
- {
- if(var==NULL) {
- printf("%s: parameter required.\n",progname);
- return(-1);
- }
- return(unsetenv(var));
- }
- /*
- * Built-in '?' handler
- */
- int
- help(char *cmd)
- {
- struct command *x;
- int found=0;
- if(cmd==NULL) {
- printf("\nBuilt-in commands:\n");
- printf("-------------------\n");
- x=bltins;
- while(x->cmd!=NULL) {
- printf("%s\t%s\n",x->cmd,x->descr);
- x++;
- }
- printf("\nEnter '? <cmd>' for details.\n\n");
- return(0);
- } else {
- x=bltins;
- while(x->cmd!=NULL) {
- if(strcmp(x->cmd,cmd)==0) {
- found++;
- break;
- }
- x++;
- }
- if(found) {
- printf("\n%s\t%s:\n",x->cmd,x->descr);
- printf("\tUsage:\n\t\t%s\n",x->usage);
- printf("\te.g:\n\t\t%s\n\n",x->example);
- return(0);
- } else {
- printf("\n%s: no such command.\n\n",cmd);
- return(-1);
- }
- }
- }
- /*
- * Signal handler for shell()
- */
- void
- shell_sig(int sig)
- {
- switch(sig) {
- case SIGINT:
- case SIGQUIT:
- case SIGTERM:
- /* ignore ? */
- break;
- default:
- break;
- }
- }
- /*
- * Built-in '.' handler (read-in and execute commands from file)
- */
- int
- sourcer(char *fname)
- {
- FILE *fd;
- char buf[512],*tok,*arg,**av;
- int ac,len,f,res,i;
- pid_t pid;
- char *sep=" \t";
- fd=fopen(fname,"r");
- if(fd==NULL) {
- printf("Couldn't open file '%s'\n",fname);
- return(-1);
- }
- while(!feof(fd)) {
- memset(buf,0,512);
- if(fgets(buf,512,fd)==NULL) continue;
- if((*buf=='#') || (*buf=='\n')) continue;
- len=strlen(buf);
- buf[len-1]='\0';
- if(strncmp(buf,"ncons",5)==0) {
- tok=strtok(buf,sep);
- tok=strtok(NULL,sep);
- ncons=atoi(tok);
- if((ncons<1)||(ncons>MAX_CONS)) {
- syslog(LOG_EMERG,"%s: bad ncons value; defaulting to %d\n",fname,MAX_CONS);
- ncons=MAX_CONS;
- }
- continue;
- } else if(strncmp(buf,"motd",4)==0) {
- tok=strtok(buf,sep);
- motd=strdup(strtok(NULL,sep));
- continue;
- } else {
- do_command(0,buf);
- }
- /* Next command, please. */
- }
- fclose(fd);
- syslog(LOG_EMERG,"Done with %s",fname);
- }
- void
- do_command(int shell, char *cmdline)
- {
- char *tok,*c,*sep=" \t";
- char **av;
- struct command *x;
- int found,len;
- int ac,i,f,res;
- int bg=0;
- pid_t pid;
- len=strlen(cmdline);
- if(cmdline[len-1]=='&') {
- bg++;
- cmdline[len-1]='\0';
- len--;
- } else bg=0;
- tok=strtok(cmdline,sep);
- x=bltins;
- found=0;
- while(x->cmd!=NULL) {
- if(strcmp(x->cmd,tok)==0) {
- found++;
- break;
- }
- x++;
- }
- if(found) {
- tok=cmdline+strlen(x->cmd)+1;
- while(*tok && isblank(*tok) && (tok<(cmdline+len))) tok++;
- if(*tok==NULL) tok=NULL;
- x->func(tok);
- return;
- }
- ac=0;
- av=(char **)calloc(((len+1)/2+1),sizeof(char *));
- av[ac++]=tok;
- while((av[ac++]=strtok(NULL,sep))!=NULL)
- continue;
- switch((pid=fork())) {
- case 0:
- if(shell) {
- signal(SIGINT,SIG_DFL);
- signal(SIGQUIT,SIG_DFL);
- signal(SIGTERM,SIG_DFL);
- signal(SIGHUP,SIG_DFL);
- } else {
- close(0);
- close(1);
- close(2);
- f=open(_PATH_CONSOLE,O_RDWR);
- dup2(f,0);
- dup2(f,1);
- dup2(f,2);
- if(f>2) close(f);
- }
- if(bg) {
- if(daemon(0,0)) {
- printf("do_command(%s): failed to run bg: %s\n",
- av[0],strerror(errno));
- _exit(100);
- }
- }
- execvp(av[0],av);
- /* Something went wrong... */
- printf("do_command(%s): %s\n",av[0],strerror(errno));
- _exit(100);
- break;
- case -1:
- printf("do_command(): %s\n",strerror(errno));
- break;
- default:
- while(waitpid(pid,&res,0)!=pid) continue;
- if(WEXITSTATUS(res)) {
- printf("do_command(%s): exit code=%d\n",
- av[0],WEXITSTATUS(res));
- }
- break;
- }
- free(av);
- return;
- }
- /*
- * This is the user interface. This routine gets executed on each
- * virtual console serviced by init.
- *
- * It works as normal shell does - for each external command it forks
- * and execs, for each internal command just executes a function.
- */
- int
- shell(int argc, char **argv)
- {
- char buf[BUFSIZE];
- char *prompt=" # ";
- int fd;
- int res;
- pid_t mypid;
- if(motd!=NULL) {
- if((fd=open(motd,O_RDONLY))!=-1) {
- do {
- res=read(fd,buf,BUFSIZE);
- res=write(1,buf,res);
- } while(res>0);
- close(fd);
- }
- }
- printf("\n\n+=========================================================+\n");
- printf("| Built-in shell() (enter '?' for short help on commands) |\n");
- printf("+=========================================================+\n\n");
- getcwd(cwd,BUFSIZE);
- mypid=getpid();
- signal(SIGINT,shell_sig);
- signal(SIGQUIT,shell_sig);
- signal(SIGTERM,shell_sig);
- while(!feof(stdin)) {
- memset(buf,0,BUFSIZE);
- printf("(%d)%s%s",mypid,cwd,prompt);
- fflush(stdout);
- if(fgets(buf,BUFSIZE-1,stdin)==NULL) continue;
- buf[strlen(buf)-1]='\0';
- if(strlen(buf)==0) continue;
- do_command(1,buf);
- }
- return(0);
- }
- /*
- * Stub for executing some external program on a console. This is called
- * from previously forked copy of our process, so that exec is ok.
- */
- int
- external_cmd(int argc, char **argv)
- {
- execvp(argv[0],argv);
- }
- /*
- * Acquire vty and properly attach ourselves to it.
- * Also, build basic environment for running user interface.
- */
- int
- start_session(int vty, int argc, char **argv)
- {
- int fd;
- char *t;
- close(0);
- close(1);
- close(2);
- revoke(ttys[vty].tty);
- fd=open(ttys[vty].tty,O_RDWR);
- dup2(fd,0);
- dup2(fd,1);
- dup2(fd,2);
- if(fd>2) close(fd);
- login_tty(fd);
- setpgid(0,getpid());
- putenv("TERM=xterm");
- putenv("HOME=/");
- putenv("PATH=/stand:/bin:/usr/bin:/sbin:.");
- signal(SIGHUP,SIG_DFL);
- signal(SIGINT,SIG_DFL);
- signal(SIGQUIT,SIG_DFL);
- signal(SIGTERM,SIG_DFL);
- chdir("/");
- t=(char *)(rindex(ttys[vty].tty,'/')+1);
- printf("\n\n\nStarting session on %s.\n",t);
- ttys[vty].func(argc,argv);
- _exit(0);
- }
- /*
- * Execute system startup script /etc/rc
- *
- * (Of course if you don't like it - I don't - you can run anything you
- * want here. Perhaps it would be useful just to read some config DB and
- * do these things ourselves, avoiding forking lots of shells and scripts.)
- */
- /* If OINIT_RC is defined, oinit will use it's own configuration file,
- * /etc/oinit.rc. It's format is described below. Otherwise, it will use
- * normal /etc/rc interpreted by Bourne shell.
- */
- #ifndef OINIT_RC
- #ifndef SH_NAME
- #define SH_NAME "-sh"
- #endif
- #ifndef SH_PATH
- #define SH_PATH _PATH_BSHELL
- #endif
- #ifndef SH_ARG
- #define SH_ARG "/etc/rc"
- #endif
- void
- runcom()
- {
- char *argv[3];
- pid_t pid;
- int st;
- int fd;
- if((pid=fork())==0) {
- /* child */
- close(0);
- close(1);
- close(2);
- fd=open(_PATH_CONSOLE,O_RDWR);
- dup2(fd,0);
- dup2(fd,1);
- dup2(fd,2);
- if(fd>2) close(fd);
- argv[0]=SH_NAME;
- argv[1]=SH_ARG;
- argv[2]=0;
- execvp(SH_PATH,argv);
- printf("runcom(): %s\n",strerror(errno));
- _exit(1);
- }
- /* Wait for child to exit */
- while(pid!=waitpid(pid,(int *)0,0)) continue;
- return;
- }
- #else
- /* Alternative /etc/rc - default is /etc/oinit.rc. Its format is as follows:
- * - each empty line or line beginning with a '#' is discarded
- * - any other line must contain a keyword, or a (nonblocking) command to run.
- *
- * Thus far, the following keywords are defined:
- * ncons <number> number of virtual consoles to open
- * motd <pathname> full path to motd file
- *
- * Examples of commands to run:
- *
- * ifconfig lo0 inet 127.0.0.1 netmask 255.0.0.0
- * ifconfig ed0 inet 148.81.168.10 netmask 255.255.255.0
- * kbdcontrol -l /usr/share/syscons/my_map.kbd
- */
- void
- runcom(char *fname)
- {
- int fd;
- close(0);
- close(1);
- close(2);
- fd=open(_PATH_CONSOLE,O_RDWR);
- dup2(fd,0);
- dup2(fd,1);
- dup2(fd,2);
- if(fd>2) close(fd);
- sourcer(fname);
- }
- #endif
- int
- run_multi()
- {
- int i,j;
- pid_t pid;
- int found;
- /* Run /etc/rc if not in single user */
- #ifndef OINIT_RC
- if(prevtrans==SINGLE) runcom();
- #else
- if(prevtrans==SINGLE) runcom(OINIT_RC);
- #endif
- if(transition!=MULTI) return(-1);
- syslog(LOG_EMERG,"*** Starting multi-user mode ***");
- /* Fork shell interface for each console */
- for(i=0;i<ncons;i++) {
- if(ttys[i].pid==0) {
- switch(pid=fork()) {
- case 0:
- start_session(i,0,NULL);
- break;
- case -1:
- printf("%s: %s\n",progname,strerror(errno));
- break;
- default:
- ttys[i].pid=pid;
- break;
- }
- }
- }
- /* Initialize any other services we'll use - most probably this will
- * be a 'telnet' service (some day...).
- */
- /* */
- /* Emulate multi-user */
- while(transition==MULTI) {
- /* XXX Modify this to allow for checking for the input on
- * XXX listening sockets, and forking a 'telnet' service.
- */
- /* */
- /* Wait for any process to exit */
- pid=waitpid(-1,(int *)0,0);
- if(pid==-1) continue;
- found=0;
- j=-1;
- /* search if it's one of our sessions */
- for(i=0;i<ncons;i++) {
- if(ttys[i].pid==pid) {
- found++;
- j=i;
- ttys[j].pid=0;
- break;
- }
- }
- if(!found) {
- /* Just collect the process's status */
- continue;
- } else {
- /* restart shell() on a console, if it died */
- if(transition!=MULTI) return(0);
- switch(pid=fork()) {
- case 0:
- sleep(1);
- start_session(j,0,NULL);
- break;
- case -1:
- printf("%s: %s\n",progname,strerror(errno));
- break;
- default:
- ttys[j].pid=pid;
- break;
- }
- }
- }
- }
- int clang;
- void
- kill_timer(int sig)
- {
- clang=1;
- }
- kill_ttys()
- {
- }
- /*
- * Start a shell on ttyv0 (i.e. the console).
- */
- int
- run_single()
- {
- int i;
- pid_t pid,wpid;
- static int sigs[2]={SIGTERM,SIGKILL};
- syslog(LOG_EMERG,"*** Starting single-user mode ***");
- /* Kill all existing sessions */
- syslog(LOG_EMERG,"Killing all existing sessions...");
- for(i=0;i<MAX_CONS;i++) {
- kill(ttys[i].pid,SIGHUP);
- ttys[i].pid=0;
- }
- for(i=0;i<2;i++) {
- if(kill(-1,sigs[i])==-1 && errno==ESRCH) break;
- clang=0;
- alarm(10);
- do {
- pid=waitpid(-1,(int *)0,WUNTRACED);
- if(errno==EINTR) continue;
- else break;
- } while (clang==0);
- }
- if(errno!=ECHILD) {
- syslog(LOG_EMERG,"Some processes would not die; ps -axl advised");
- }
- /* Single-user */
- switch(pid=fork()) {
- case 0:
- start_session(0,0,NULL);
- break;
- case -1:
- printf("%s: %s\n",progname,strerror(errno));
- printf("The system is seriously hosed. I'm dying...\n");
- transition=DEATH;
- return(-1);
- break;
- default:
- do {
- wpid=waitpid(pid,(int *)0,WUNTRACED);
- } while(wpid!=pid && transition==SINGLE);
- if(transition!=DEATH) {
- prevtrans=transition;
- transition=MULTI;
- }
- break;
- }
- return(0);
- }
- /*
- * Transition handler - installed as signal handler.
- */
- void
- transition_handler(int sig)
- {
- switch(sig) {
- case SIGHUP:
- case SIGTERM:
- prevtrans=transition;
- transition=SINGLE;
- syslog(LOG_EMERG,"*** Going from %s -> %s\n",trans[prevtrans],trans[transition]);
- if(prevtrans!=transition) longjmp(machine,sig);
- break;
- case SIGINT:
- case SIGQUIT:
- prevtrans=transition;
- transition=DEATH;
- syslog(LOG_EMERG,"*** Going from %s -> %s\n",trans[prevtrans],trans[transition]);
- if(prevtrans!=transition) longjmp(machine,sig);
- break;
- default:
- syslog(LOG_EMERG,"pid=%d sig=%s (ignored)\n",getpid(),sys_siglist[sig]);
- break;
- }
- }
- /*
- * Change system state appropriately to the signals
- */
- int
- transition_machine()
- {
- int i;
- while(transition!=DEATH) {
- switch(transition) {
- case MULTI:
- run_multi();
- break;
- case SINGLE:
- run_single();
- break;
- }
- }
- syslog(LOG_EMERG,"Killing all existing sessions...");
- /* Kill all sessions */
- kill(-1,SIGKILL);
- /* Be nice and wait for them */
- while(waitpid(-1,(int *)0,WNOHANG|WUNTRACED)>0) continue;
- unmount("/",0);
- reboot(RB_AUTOBOOT);
- /* NOTREACHED */
- }
- int
- main(int argc, char **argv)
- {
- int devfs=0,c,i;
- /* These are copied from real init(8) */
- if(getuid()!=0)
- errx(1,"%s",strerror(EPERM));
- openlog("init",LOG_CONS|LOG_ODELAY,LOG_AUTH);
- if(setsid()<0)
- warn("initial setsid() failed");
- if(setlogin("root")<0)
- warn("setlogin() failed");
- close(0);
- close(1);
- close(2);
- chdir("/");
- progname=rindex(argv[0],'/');
- if(progname==NULL) {
- progname=argv[0];
- } else progname++;
- transition=MULTI;
- /* We must recognize the same options as real init does */
- while((c=getopt(argc,argv,"dsf"))!=-1) {
- switch(c) {
- case 'd':
- devfs=1;
- break;
- case 's':
- transition=SINGLE;
- break;
- case 'f':
- break;
- default:
- printf("%s: unrecognized flag '-%c'\n",progname,c);
- break;
- }
- }
- if(devfs)
- mount("devfs",_PATH_DEV,MNT_NOEXEC|MNT_RDONLY,0);
- /* Fill in the sess structures. */
- /* XXX Really, should be filled based upon config file. */
- for(i=0;i<MAX_CONS;i++) {
- if(i==0) {
- sprintf(ttys[i].tty,_PATH_CONSOLE);
- } else {
- sprintf(ttys[i].tty,"%sv%c",_PATH_TTY,vty[i]);
- }
- ttys[i].pid=0;
- ttys[i].func=shell;
- }
- getcwd(cwd,BUFSIZE);
- signal(SIGINT,transition_handler);
- signal(SIGQUIT,transition_handler);
- signal(SIGTERM,transition_handler);
- signal(SIGHUP,transition_handler);
- signal(SIGALRM,kill_timer);
- setjmp(machine);
- transition_machine(transition);
- /* NOTREACHED */
- exit(100);
- }