aboutsummaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rw-r--r--contrib/argv0.c13
l---------contrib/bootlog.c1
-rw-r--r--contrib/bootlog.h100
-rw-r--r--contrib/bootlog_mmap.c96
-rw-r--r--contrib/bootlog_sbrk.c73
-rw-r--r--contrib/conditional-init.c47
-rw-r--r--contrib/env.c68
-rw-r--r--contrib/put_env.h27
-rw-r--r--contrib/serdo.c236
-rw-r--r--contrib/sleeprun.c65
10 files changed, 726 insertions, 0 deletions
diff --git a/contrib/argv0.c b/contrib/argv0.c
new file mode 100644
index 0000000..e2eeb81
--- /dev/null
+++ b/contrib/argv0.c
@@ -0,0 +1,13 @@
+#include "../ninitfeatures.h"
+#include "../error_table.h"
+
+int main(int argc,char **argv,char **envp) {
+ errmsg_iam("argv0");
+ if (argc < 3) {
+ carp("usage: argv0", " realname program [ arg ... ]");
+ return 100;
+ }
+ pathexec_run(argv[1],argv + 2,envp);
+ carp("unable to run ",argv[1],": ",error_string(table,errno));
+ return 111;
+}
diff --git a/contrib/bootlog.c b/contrib/bootlog.c
new file mode 120000
index 0000000..f2f9083
--- /dev/null
+++ b/contrib/bootlog.c
@@ -0,0 +1 @@
+bootlog_sbrk.c \ No newline at end of file
diff --git a/contrib/bootlog.h b/contrib/bootlog.h
new file mode 100644
index 0000000..967ed6d
--- /dev/null
+++ b/contrib/bootlog.h
@@ -0,0 +1,100 @@
+/* return -1 on error */
+static int xx_write(int fd, void *buf, size_t len) {
+ char *x = buf;
+ ssize_t w;
+ while (len) {
+ w = write(fd, buf, len);
+ if (w <= 0) {
+ if (w < 0 && errno == EINTR) continue;
+ return -1;
+ }
+ x += w;
+ len -= w;
+ }
+ return 0;
+}
+
+/* return 0 if closed or error, -1 temporary error */
+static int do_io(void *buf, int len) {
+ int r = read(0,buf,len);
+ if (r<0)
+ if (errno != EINTR) r = 0;
+ if (r>0) xx_write(1,buf,r);
+ return r;
+}
+
+static int mk_backup() {
+ if (flag_rename) {
+ if (rename(name, flag_rename) && errno != ENOENT) return -1;
+ flag_rename = 0;
+ }
+ return 0;
+}
+
+static void write2(char *s) { write(2,s,str_len(s)); }
+
+int main(int argc, char **argv) {
+ unsigned long len=0;
+ int pid, pi[2];
+
+ for (;;) {
+ char *p;
+ argc--;
+ argv++;
+ if ((p=argv[0]) == 0 || *p != '-') break;
+ while (*++p)
+ switch (*p) {
+ case 'a': m |= O_APPEND; break;
+ case 't': m |= O_TRUNC; break;
+ case 'c': m |= O_CREAT; break;
+ case 'r': ++flag_rename; break;
+ case '2': ++flagstderr; break;
+ case '1': ++flagstdout; break;
+ default:
+ goto usage;
+ }
+ }
+
+ if (argc<3) {
+ usage:
+ write2("usage: bootlog [-12ctar] size logfile program args...\n");
+ _exit(1);
+ }
+ if (scan_ulong(argv[0], &len) == 0) goto usage;
+ if ((flagstderr | flagstdout) == 0) {
+ ++flagstdout;
+ ++flagstderr;
+ }
+ name=argv[1];
+
+ for (pid=0; pid<3; pid++)
+ if (fcntl(pid,F_GETFL,0) == -1) goto do_it;
+
+ if (pipe(pi)) goto do_it;
+ while ((pid=fork()) < 0);
+
+ if (pid==0) {
+ close(pi[1]);
+ while ((pid=fork()) < 0);
+ if (pid==0) {
+ dup2(pi[0],0);
+ close(pi[0]);
+ loop(len);
+ }
+ _exit(0);
+ } else {
+ close(pi[0]);
+ waitpid(pid, 0, 0);
+ if (flagstdout) { dup2(pi[1],1); }
+ if (flagstderr) { dup2(pi[1],2); }
+ close(pi[1]);
+ }
+
+ do_it:
+ argv += 2;
+ pathexec_run(argv[0], argv, environ);
+ write2("bootlog: ");
+ write2(argv[0]);
+ write2(": exec error\n");
+ _exit(127);
+}
diff --git a/contrib/bootlog_mmap.c b/contrib/bootlog_mmap.c
new file mode 100644
index 0000000..7bd9583
--- /dev/null
+++ b/contrib/bootlog_mmap.c
@@ -0,0 +1,96 @@
+/* bootlog.c */
+/* diet -Os gcc -o bootlog bootlog.c -Wall -W */
+
+#include <unistd.h>
+#include <alloca.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h> /* rename */
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include "../ninitfeatures.h"
+
+static int xx_write(int fd, void *buf, size_t len);
+static int do_io(void *buf, int len);
+static int mk_backup();
+
+struct mem {
+ struct mem *x;
+ unsigned int p;
+};
+
+static struct mem *last, *root;
+static char *flag_rename, *name;
+static int flagstderr, flagstdout, m;
+int fd = -1;
+
+#include "../pagesize_defs.h"
+#define mem_size sizeof(struct mem)
+#define alloc_size (page_size - mem_size)
+
+void *mmap_alloc() {
+ struct mem *m;
+ m=mmap(0,page_size,PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_PRIVATE,-1,0);
+ if (m==(struct mem*)-1) return 0;
+ /* kernel must zeroed m->p and m->x */
+
+ if (last) last->x = m;
+ else root = m;
+ last = m;
+ return ((void *)m) + mem_size;
+}
+
+static void flush_root() {
+ if (mk_backup()) return;
+ if (fd < 0) fd = open(name, O_WRONLY | m, 0644);
+ if (fd < 0) return;
+ while (root) {
+ void *x = root->x;
+ xx_write(fd, ((void *)root) + mem_size, root->p);
+ root->p = 0;
+ if (root == last) break;
+ munmap(root, page_size);
+ root = x;
+ }
+}
+
+static void loop(unsigned long len) {
+ char *buf = 0;
+ int r;
+ if (flag_rename) {
+ char *d, *s = name;
+ d = flag_rename = alloca(str_len(name) + 5);
+ while (*s) *d++ = *s++;
+ d[0] = '~';
+ d[1] = 0;
+ }
+ while (len) {
+ if (buf == 0 && (buf=mmap_alloc()) == 0) break;
+
+ if (last->p >= alloc_size) {
+ flush_root();
+ if (last->p) { buf = 0; continue; }
+ }
+
+ r = do_io(buf + last->p, alloc_size - last->p);
+ if (r==0) break;
+ if (r<0) continue;
+
+ if ((unsigned long)r > len) r = len;
+ last->p += r;
+ len -= r;
+ }
+
+ if (buf==0 || len==0) {
+ char tmp[1024];
+ while (do_io(tmp,sizeof(tmp)));
+ }
+
+ mk_backup();
+ flag_rename = 0;
+ flush_root();
+ fsync(fd);
+ close(fd);
+}
+
+#include "bootlog.h"
diff --git a/contrib/bootlog_sbrk.c b/contrib/bootlog_sbrk.c
new file mode 100644
index 0000000..525fcdc
--- /dev/null
+++ b/contrib/bootlog_sbrk.c
@@ -0,0 +1,73 @@
+/* bootlog.c */
+/* diet -Os gcc -o bootlog bootlog.c -Wall -W */
+
+#include <unistd.h>
+#include <alloca.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h> /* rename */
+#include <sys/wait.h>
+#include "../ninitfeatures.h"
+#define BRKINCR 1024
+
+static int xx_write(int fd, void *buf, size_t len);
+static int do_io(void *buf, int len);
+static int mk_backup();
+
+static char *root, *last, *end;
+static char *flag_rename, *name;
+static int flagstderr, flagstdout, m;
+int fd = -1;
+
+static void *setbrk(ssize_t incr) {
+ void *x = sbrk(incr);
+ if ((void *)-1 == x) return x;
+ if (root == 0) root = last = x;
+ end = x + incr;
+ return 0;
+}
+
+static void flush_root() {
+ if (mk_backup()) return;
+ if (fd < 0) fd = open(name, O_WRONLY | m, 0644);
+ if (fd < 0 || root==0) return;
+ xx_write(fd, root, last-root);
+ last = root;
+ setbrk((ssize_t)BRKINCR - (end-root));
+}
+
+static void loop(unsigned long len) {
+ int r;
+ if (flag_rename) {
+ char *d, *s = name;
+ d = flag_rename = alloca(str_len(name) + 5);
+ while (*s) *d++ = *s++;
+ d[0] = '~';
+ d[1] = 0;
+ }
+ while (len) {
+ if (last >= end) flush_root();
+ if (last >= end && setbrk(BRKINCR)) break;
+
+ r = do_io(last, end - last);
+ if (r==0) break;
+ if (r<0) continue;
+
+ if ((unsigned long)r > len) r = len;
+ last += r;
+ len -= r;
+ }
+
+ if (last >= end || len==0) {
+ char tmp[1024];
+ while (do_io(tmp,sizeof(tmp)));
+ }
+
+ mk_backup();
+ flag_rename = 0;
+ flush_root();
+ fsync(fd);
+ close(fd);
+}
+
+#include "bootlog.h"
diff --git a/contrib/conditional-init.c b/contrib/conditional-init.c
new file mode 100644
index 0000000..10c69cc
--- /dev/null
+++ b/contrib/conditional-init.c
@@ -0,0 +1,47 @@
+#include <unistd.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/reboot.h>
+#include "../ninitfeatures.h"
+#include "../uid.h"
+
+#define TM_OUT 1200
+
+int main(int argc, char **argv) {
+ unsigned long ul;
+ char *stable, *test;
+
+ if (argc < 4) {
+ carp("usage: conditional-init now /stable/init /test/init [args...]\n"
+ "\tnow must be the output of: date +%s");
+ return 1;
+ }
+
+ errmsg_argv0 = "conditional-init";
+ stable=argv[2];
+ test=argv[3];
+
+ if (scan_ulong(argv[1], &ul)==0 || ul + TM_OUT < (unsigned long)time(0)) {
+ carp("booting (stable): ", stable);
+ argv += 3;
+ argv[0] = stable;
+ execve(stable, argv, environ);
+ return 0;
+ }
+
+ if (fork()==0) {
+ unsigned long deadline = (unsigned long)time(0) + TM_OUT;
+ while ((unsigned long)time(0) < deadline ) nano_sleep(1,0);
+ sync();
+ set_reboot(RB_AUTOBOOT);
+ return 2;
+ }
+
+ argv += 3;
+ argv[0]=test;
+ carp("booting (test): ", test);
+ execve(test, argv, environ);
+ nano_sleep(TM_OUT,0); /* only if test fails */
+ set_reboot(RB_AUTOBOOT);
+ return 1;
+}
diff --git a/contrib/env.c b/contrib/env.c
new file mode 100644
index 0000000..5bc44bd
--- /dev/null
+++ b/contrib/env.c
@@ -0,0 +1,68 @@
+#include <unistd.h>
+#include <alloca.h>
+#include "../ninitfeatures.h"
+#include "../djb/buffer.h"
+#include "../error_table.h"
+
+static unsigned long env_free;
+#include "put_env.h"
+
+#define BUFFER_1_SIZE 1200
+buffer b = BUFFER_INIT(write, 2, 0, BUFFER_1_SIZE);
+
+#undef die
+#define die(n,...) err_b(&b,__VA_ARGS__,0); buffer_flush(&b); _exit(n)
+
+int main(int argc,char *argv[]) {
+ int i;
+ char *nenv, **env;
+
+ b.x=alloca(BUFFER_1_SIZE); /* XXX if it fails bummer */
+ errmsg_iam("env");
+ if (environ==0) environ = argv+argc;
+ for (env=environ; *env; ++env) env_free++;
+
+ env=alloca((argc+env_free+3) * sizeof (char*));
+ if (env==0) { die(1, "Out of memory"); }
+
+ byte_copy(env, (env_free+1) * sizeof(char *), environ);
+ environ = env;
+ env_free = argc;
+
+ for (i=1; i<argc; ++i) {
+ char *v=argv[i];
+ if (v[0]=='-') {
+ if (v[1]==0) {
+ environ[0]=0;
+ } else {
+ for (++v; *v; ++v)
+ switch (*v) {
+ case 'i': environ[0]=0; break;
+ case 'u':
+ if (v[1]) { nenv = v+1; goto do_it; }
+ else if (argv[++i]) { nenv = argv[i]; goto do_it; }
+ default:
+ die(1, "usage: env [-] [-i] [-u] [NAME=VALUE...] "
+ "[program args...]");
+ }
+ }
+ } else {
+ if (v[str_chr(v,'=')]) { nenv = v; goto do_it; }
+ else {
+ pathexec_run(v,argv+i,environ);
+ die(127, "unable ro run: ",v,": ",error_string(table,errno));
+ }
+ }
+ continue;
+
+ do_it:
+ put_env(nenv);
+ }
+
+ for (b.fd = 1; *environ; ++environ) {
+ buffer_puts(&b, *environ);
+ buffer_puts(&b, "\n");
+ }
+ buffer_flush(&b);
+ return 0;
+}
diff --git a/contrib/put_env.h b/contrib/put_env.h
new file mode 100644
index 0000000..0d817b3
--- /dev/null
+++ b/contrib/put_env.h
@@ -0,0 +1,27 @@
+int put_env(const char *string) {
+ unsigned int len, envc, remove=0;
+ char **ep;
+
+ len=str_chr(string,'=');
+ if (string[len]==0) remove=1;
+ for (envc=0, ep=environ; *ep; ++ep) {
+ if (!byte_diff(string, len, *ep) && (*ep)[len]=='=') {
+ if (remove) {
+ for (; ep[1]; ++ep) ep[0]=ep[1];
+ ep[0]=0;
+ ++env_free;
+ return 0;
+ }
+ *ep=(char *)string;
+ return 0;
+ }
+ ++envc;
+ }
+ if (remove==0) {
+ if (env_free==0) return -1;
+ environ[envc++]=(char*)string;
+ environ[envc]=0;
+ --env_free;
+ }
+ return 0;
+}
diff --git a/contrib/serdo.c b/contrib/serdo.c
new file mode 100644
index 0000000..e45fb9a
--- /dev/null
+++ b/contrib/serdo.c
@@ -0,0 +1,236 @@
+/* serdo.c
+ modified by Nikola Vladov
+ unset VAR
+ exit n
+ exec file
+ # comment
+ ps -ax # comment ps
+ long line \
+ continues here
+ echo something
+ . file
+ VAR=val ... command ...
+ killall5 -signumber
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <time.h>
+#include "../ninitfeatures.h"
+#include "../error_table.h"
+
+struct cmd {
+ char *pos;
+ char *x;
+ char last;
+ char eof;
+};
+
+static unsigned int env_free, last_cmd;
+static char continueonerror;
+static int batch(char *s);
+
+#include "put_env.h"
+
+static char *e() { return error_string(table, errno); }
+
+#define str_equal(A,B) !str_diff(A,B)
+#define byte_equal(A,l,B) !byte_diff(A,l,B)
+#define is_space(c) *c==' ' || (unsigned int)(char)(*c - 9)<5
+#define carpsys(...) err(2,__VA_ARGS__,": ",e(),(char*)0)
+#define diesys(n,...) do { err(2,__VA_ARGS__,": ",e(),(char*)0); _exit(n); } while(0)
+
+#define DQUOTE '"'
+#define SQUOTE '\''
+
+static void pr_argv(int fd, char **argv) {
+ char *s;
+ while ((s=*argv++)) {
+ unsigned int len = str_len(s);
+ if (*argv) s[len++] = ' ';
+ errmsg_put(fd,s,len);
+ }
+ errmsg_puts(fd,"\n");
+ errmsg_puts(fd,0);
+}
+
+static int pr_fail(char **argv) {
+ carpsys(argv[0]);
+ if (argv[1]) pr_argv(2, argv);
+ return -1;
+}
+
+static void put__env(char *s) { if (put_env(s)) die(1, "Out of memory"); }
+
+static int spawn(char **argv) {
+ int i;
+ char *s0=argv[0], *s1=argv[1], cfg_exec=0, ignore=0;
+
+ if (str_equal(s0,"cd")) {
+ if (chdir(s1)) return pr_fail(argv);
+ return 0;
+ } else if (str_equal(s0,"export") || str_equal(s0,"unset")) {
+ while (*++argv) put__env(*argv);
+ return 0;
+#ifdef SERDO_WANT_echo
+ } else if (str_equal(s0,"echo")) {
+ pr_argv(1, argv+1);
+ return 0;
+#endif
+ } else if (s1) {
+ i = x_atoi(s1);
+ if (str_equal(s0,"exit")) { _exit(i); }
+ else if (str_equal(s0,"exec")) { cfg_exec++; ++argv; }
+ else if (s0[0]=='.' && s0[1]==0) { return batch(s1); }
+#ifdef SERDO_WANT_killall5
+ else if (str_equal(s0,"killall5")) { return kill(-1,-i); }
+#endif
+ }
+
+ if (argv[0][0] == '-') { argv[0] += 1; ignore = 1; }
+
+ if (last_cmd && !cfg_exec) {
+ struct timespec ts = { 0, 500000000 };
+ while ((i=fork()) < 0) nanosleep(&ts, 0);
+ } else i=-1; /* don't fork */
+
+ if (i <= 0) {
+#ifdef SERDO_WANT_environ_in_command
+ for (; argv[1]; put__env(*argv++)) {
+ s0 = argv[0];
+ if (!s0[str_chr(s0,'=')]) break;
+ }
+#endif
+ pathexec_run(*argv,argv,environ);
+ pr_fail(argv);
+ if (!i) _exit(-1); /* child */
+ return (ignore) ? 0 : -1;
+ }
+
+ if (waitpid(i,&i,0)==-1) diesys(1,"waitpid failed");
+ if (ignore) return 0;
+ if (!WIFEXITED(i)) return -1;
+ return WEXITSTATUS(i);
+}
+
+static void get_word(struct cmd *c) {
+ char ch, *x, *s = c->pos;
+
+ while (is_space(s) || (*s=='\\' && s[1]=='\n')) ++s;
+ if (!*s) { c->eof |= 1; return; }
+ c->last=0;
+ c->x=x=s;
+ if (*x == '#') c->last |= 2;
+
+ while ((ch=*s)) {
+ if (ch==DQUOTE || ch==SQUOTE) {
+ while (*++s) { /* unterminated quoted string <==> *s == 0 */
+ if (*s != ch) *x++ = *s;
+ else {
+ if (s[-1] == '\\') x[-1] = ch;
+ else { s++; break; }
+ }
+ }
+ if (!*s) die(2, "syntax error: unexpected EOF");
+ }
+ else if (ch==' ' || ch=='\n' || ch=='\t') break;
+ else if (ch=='\\' && s[1]=='\n') { s += 2; continue; }
+ else if (ch=='#') { for (; *s && *s != '\n';) s++; break; }
+ else *x++ = *s++;
+ }
+ while (*s==' ' || *s=='\t') ++s;
+ if (*s=='\n') { ++s; c->last |= 1; }
+
+ c->pos = s;
+ if (!*s) c->last |= 1;
+ *x = 0;
+}
+
+static char *get_argv0(struct cmd *c) {
+ while (1) {
+ get_word(c);
+ if (c->eof) break;
+ if (c->last & 2) continue;
+ return c->x;
+ }
+ return 0;
+}
+
+static int get_argv(struct cmd *c) {
+ unsigned int k=0, len=16;
+ char **argv, first=0;
+
+ argv = alloca((len+2)*sizeof(char*));
+ do {
+ if (first) get_word(c);
+ else first |= 1;
+ if (c->eof) break;
+ if (c->last < 2) { /* comment */
+ if (k >= len) {
+ char **tmp = argv;
+ len *= 2;
+ argv = alloca((len+2) * sizeof(char*));
+ byte_copy(argv, k*sizeof(char*), tmp);
+ }
+ argv[k++] = c->x;
+ }
+ } while (!c->last);
+ argv[k] = 0;
+
+ if (!get_argv0(c)) --last_cmd;
+ return spawn(argv);
+}
+
+static int execute(struct cmd *c) {
+ int r = 0;
+ if (get_argv0(c)) {
+ ++last_cmd;
+
+ while (1) {
+ r = get_argv(c);
+ if (r!=0 && !continueonerror) break;
+ if (c->eof) break;
+ }
+ }
+ return r;
+}
+
+static int batch(char *s) {
+ struct cmd c;
+ int len, fd=open(s,O_RDONLY);
+ if (fd==-1 || GLOBAL_READ(fd,c.pos, len,32768)) {
+ carpsys("could not open ",s);
+ return 1;
+ }
+ close(fd);
+ c.pos[len] = 0;
+ c.eof = 0;
+ return execute(&c);
+}
+
+int main(int argc,char* argv[],char* env[]) {
+ if (argc<2) {
+#ifdef SERDO_EXEC_script
+ if (!access("script",O_RDONLY)) *argv-- = "script"; else
+#endif
+ { ops: die(1,"usage: serdo [-c] file"); }
+ }
+
+ if (str_equal(argv[1],"-c")) {
+ ++continueonerror;
+ ++argv;
+ }
+ if (*++argv == 0) goto ops;
+ errmsg_iam("serdo");
+
+ for (env_free=0; env[env_free];) ++env_free;
+ ++env_free;
+ environ = alloca((env_free + SERDO_MAX_NEW_ENVIRON) * sizeof(char *));
+ byte_copy(environ, env_free * sizeof(char *), env);
+ env_free = SERDO_MAX_NEW_ENVIRON;
+
+ return batch(*argv);
+}
diff --git a/contrib/sleeprun.c b/contrib/sleeprun.c
new file mode 100644
index 0000000..01ea5c9
--- /dev/null
+++ b/contrib/sleeprun.c
@@ -0,0 +1,65 @@
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <signal.h>
+#include "../ninitfeatures.h"
+#include "../error_table.h"
+
+int main(int argc, char **argv) {
+ int fd;
+ unsigned long now, interval, last, ul[2] = { 0, 0 };
+ char tmp[32],*p;
+ if (argc<3)
+ die(1, "usage: sleeprun SleepFile interval [program args...]\n"
+ "usage: sleeprun -aNumber program [args...]");
+ errmsg_iam(argv[0]);
+
+ p = argv[1];
+ if (p[0] == '-' && p[1] == 'a') {
+ scan_ulong(p+2, &interval);
+ if (interval) alarm(interval);
+ argv += 2;
+ goto do_it;
+ }
+
+ if (read_ulongs(argv[1], ul, 2) > 0) {
+ errno=0;
+ if (kill(ul[0],0)==0 || errno != ESRCH) {
+ tmp[fmt_ulong(tmp, ul[0])] = 0;
+ carp("WARNING: a program with PID ",tmp," is running now");
+ }
+ }
+
+ last=ul[1];
+ scan_ulong(argv[2], &interval);
+ now = (unsigned long)time(0);
+
+ last += interval;
+ if (last > now) {
+ nano_sleep(last-now, 0);
+ now = (unsigned long)time(0);
+ }
+
+ if ((fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644)) >=0) {
+ p = tmp;
+ p += fmt_ulong(tmp, getpid()); *p++ = ':';
+ p += fmt_ulong(p, now); *p++ = 0;
+ write(fd,tmp,p-tmp);
+ close(fd);
+ }
+
+ argv+=3;
+ do_it:
+ if (argv[0]) {
+ char *argv_0 = argv[0];
+ argv[0] = argv_0 + str_rchr(argv_0,'/');
+ if (argv[0][0]) argv[0]++;
+ else argv[0]=argv_0;
+
+ pathexec_run(argv_0,argv,environ);
+ carp("unable to run: ",argv_0,": ",error_string(table, errno));
+ return 127;
+ }
+ return 0;
+}