summaryrefslogtreecommitdiffstats
path: root/test/pthread
diff options
context:
space:
mode:
authorCarmelo Amoroso <carmelo.amoroso@st.com>2008-04-25 17:30:17 +0000
committerCarmelo Amoroso <carmelo.amoroso@st.com>2008-04-25 17:30:17 +0000
commitd847043c266e211ceaee0c222928537d40cf9409 (patch)
tree261f12336fb21742a024a2e4c8b8bda458cf7443 /test/pthread
parentffb45327c079ec3d37af737e161ec9e784de0ebf (diff)
downloaduClibc-alpine-d847043c266e211ceaee0c222928537d40cf9409.tar.bz2
uClibc-alpine-d847043c266e211ceaee0c222928537d40cf9409.tar.xz
STEP 12: synch librt directory
Signed-off-by: Carmelo Amoroso <carmelo.amoroso@st.com>
Diffstat (limited to 'test/pthread')
-rw-r--r--test/pthread/Makefile2
-rw-r--r--test/pthread/cancellation-points.c280
-rw-r--r--test/pthread/ex1.c2
-rw-r--r--test/pthread/ex2.c10
-rw-r--r--test/pthread/ex4.c6
-rw-r--r--test/pthread/ex5.c10
-rw-r--r--test/pthread/ex6.c2
-rw-r--r--test/pthread/ex7.c10
-rw-r--r--test/pthread/tst-too-many-cleanups.c104
9 files changed, 406 insertions, 20 deletions
diff --git a/test/pthread/Makefile b/test/pthread/Makefile
index 560a424cb..ef924ad1a 100644
--- a/test/pthread/Makefile
+++ b/test/pthread/Makefile
@@ -4,3 +4,5 @@
include ../Test.mak
EXTRA_LDFLAGS := -lpthread
+
+LDFLAGS_cancellation-points := -lrt
diff --git a/test/pthread/cancellation-points.c b/test/pthread/cancellation-points.c
new file mode 100644
index 000000000..3fe49fcaa
--- /dev/null
+++ b/test/pthread/cancellation-points.c
@@ -0,0 +1,280 @@
+/*
+ * Make sure functions marked as cancellation points actually are.
+ * http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html#tag_02_09_05
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <features.h>
+#include <sys/ipc.h>
+#include <sys/mman.h>
+#include <sys/msg.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <mqueue.h>
+#include <poll.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+/* take care of optional things ... */
+#define STUB(func, args) static void func args { sleep(0); }
+#if !defined(__UCLIBC__) || defined(__UCLIBC_AIO__)
+# include <aio.h>
+#else
+STUB(aio_suspend, (void *p, int n, const void *p2))
+#endif
+#if !defined(__UCLIBC__) || defined(__UCLIBC_STROPTS__)
+# include <stropts.h>
+#else
+STUB(getmsg, (int f, void *p, void *p2, void *p3))
+STUB(getpmsg, (int f, void *p, void *p2, void *p3, void *p4))
+STUB(putmsg, (int f, void *p, void *p2, void *p3))
+STUB(putpmsg, (int f, void *p, void *p2, void *p3, void *p4))
+#endif
+#if defined(__UCLIBC__)
+STUB(clock_nanosleep, (int i, int f, const void *p, void *p2))
+#endif
+
+int cnt;
+bool ready;
+
+void cancel_timeout(int sig)
+{
+ ready = false;
+}
+void cancel_thread_cleanup(void *arg)
+{
+ ready = false;
+}
+
+/* some funcs need some help as they wont take NULL args ... */
+const struct timespec zero_sec = { .tv_sec = 0, .tv_nsec = 0 };
+
+sem_t sem;
+void help_sem_setup(void)
+{
+ if (sem_init(&sem, 0, 1) == -1) {
+ perror("sem_init() failed");
+ exit(-1);
+ }
+}
+
+pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+pthread_mutex_t mutex;
+void help_pthread_setup(void)
+{
+ pthread_mutex_init(&mutex, NULL);
+ pthread_mutex_lock(&mutex);
+}
+
+/* the pthread function that will call the cancellable function over and over */
+#define _MAKE_CANCEL_THREAD_FUNC_EX(func, sysfunc, args, setup) \
+void *cancel_thread_##func(void *arg) \
+{ \
+ if (pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL)) { \
+ perror("unable to set cancel type to deferred; something is seriously broken"); \
+ exit(-1); \
+ } \
+ pthread_cleanup_push(cancel_thread_cleanup, NULL); \
+ setup; \
+ ready = true; \
+ while (ready) \
+ sysfunc args; \
+ pthread_cleanup_pop(1); \
+ return NULL; \
+}
+#define MAKE_CANCEL_THREAD_FUNC_RE(func, sysfunc, args) _MAKE_CANCEL_THREAD_FUNC_EX(func, sysfunc, args, (void)0)
+#define MAKE_CANCEL_THREAD_FUNC_EX(func, args, setup) _MAKE_CANCEL_THREAD_FUNC_EX(func, func, args, setup)
+#define MAKE_CANCEL_THREAD_FUNC(func, args) _MAKE_CANCEL_THREAD_FUNC_EX(func, func, args, (void)0)
+
+MAKE_CANCEL_THREAD_FUNC(accept, (-1, NULL, NULL))
+MAKE_CANCEL_THREAD_FUNC(aio_suspend, (NULL, 0, &zero_sec))
+MAKE_CANCEL_THREAD_FUNC(clock_nanosleep, (0, 0, NULL, NULL))
+MAKE_CANCEL_THREAD_FUNC(close, (-1))
+MAKE_CANCEL_THREAD_FUNC(connect, (-1, NULL, 0))
+MAKE_CANCEL_THREAD_FUNC(creat, ("", 0))
+MAKE_CANCEL_THREAD_FUNC(fcntl, (0, F_SETLKW, NULL))
+MAKE_CANCEL_THREAD_FUNC(fdatasync, (-1))
+MAKE_CANCEL_THREAD_FUNC(fsync, (0))
+MAKE_CANCEL_THREAD_FUNC(getmsg, (-1, NULL, NULL, NULL))
+MAKE_CANCEL_THREAD_FUNC(getpmsg, (-1, NULL, NULL, NULL, NULL))
+MAKE_CANCEL_THREAD_FUNC(lockf, (-1, F_TEST, 0))
+MAKE_CANCEL_THREAD_FUNC(mq_receive, (0, NULL, 0, NULL))
+MAKE_CANCEL_THREAD_FUNC(mq_send, (0, NULL, 0, 0))
+MAKE_CANCEL_THREAD_FUNC(mq_timedreceive, (0, NULL, 0, NULL, NULL))
+MAKE_CANCEL_THREAD_FUNC(mq_timedsend, (0, NULL, 0, 0, NULL))
+MAKE_CANCEL_THREAD_FUNC(msgrcv, (-1, NULL, 0, 0, 0))
+MAKE_CANCEL_THREAD_FUNC(msgsnd, (-1, NULL, 0, 0))
+MAKE_CANCEL_THREAD_FUNC(msync, (NULL, 0, 0))
+MAKE_CANCEL_THREAD_FUNC(nanosleep, (NULL, NULL))
+MAKE_CANCEL_THREAD_FUNC(open, ("", 0))
+MAKE_CANCEL_THREAD_FUNC(pause, ())
+MAKE_CANCEL_THREAD_FUNC(poll, (NULL, 0, 0))
+MAKE_CANCEL_THREAD_FUNC(pread, (-1, NULL, 0, 0))
+MAKE_CANCEL_THREAD_FUNC(pselect, (0, NULL, NULL, NULL, NULL, NULL))
+MAKE_CANCEL_THREAD_FUNC_EX(pthread_cond_timedwait, (&cond, &mutex, &zero_sec), help_pthread_setup())
+MAKE_CANCEL_THREAD_FUNC_EX(pthread_cond_wait, (&cond, &mutex), help_pthread_setup())
+/*MAKE_CANCEL_THREAD_FUNC_EX(pthread_join, (0, NULL))*/
+MAKE_CANCEL_THREAD_FUNC(pthread_testcancel, ())
+MAKE_CANCEL_THREAD_FUNC(putmsg, (-1, NULL, NULL, 0))
+MAKE_CANCEL_THREAD_FUNC(putpmsg, (-1, NULL, NULL, 0, 0))
+MAKE_CANCEL_THREAD_FUNC(pwrite, (-1, NULL, 0, 0))
+MAKE_CANCEL_THREAD_FUNC(read, (-1, NULL, 0))
+MAKE_CANCEL_THREAD_FUNC(readv, (-1, NULL, 0))
+MAKE_CANCEL_THREAD_FUNC(recv, (-1, NULL, 0, 0))
+MAKE_CANCEL_THREAD_FUNC(recvfrom, (-1, NULL, 0, 0, NULL, NULL))
+MAKE_CANCEL_THREAD_FUNC(recvmsg, (-1, NULL, 0))
+MAKE_CANCEL_THREAD_FUNC(select, (0, NULL, NULL, NULL, NULL))
+MAKE_CANCEL_THREAD_FUNC_EX(sem_timedwait, (&sem, &zero_sec), help_sem_setup())
+MAKE_CANCEL_THREAD_FUNC_EX(sem_wait, (&sem), help_sem_setup())
+MAKE_CANCEL_THREAD_FUNC(send, (-1, NULL, 0, 0))
+MAKE_CANCEL_THREAD_FUNC(sendmsg, (-1, NULL, 0))
+MAKE_CANCEL_THREAD_FUNC(sendto, (-1, NULL, 0, 0, NULL, 0))
+MAKE_CANCEL_THREAD_FUNC(sigpause, (0))
+MAKE_CANCEL_THREAD_FUNC(sigsuspend, (NULL))
+MAKE_CANCEL_THREAD_FUNC(sigtimedwait, (NULL, NULL, NULL))
+MAKE_CANCEL_THREAD_FUNC(sigwait, (NULL, NULL))
+MAKE_CANCEL_THREAD_FUNC(sigwaitinfo, (NULL, NULL))
+MAKE_CANCEL_THREAD_FUNC(sleep, (0))
+MAKE_CANCEL_THREAD_FUNC(system, (""))
+MAKE_CANCEL_THREAD_FUNC(tcdrain, (-1))
+MAKE_CANCEL_THREAD_FUNC(usleep, (0))
+MAKE_CANCEL_THREAD_FUNC(wait, (NULL))
+MAKE_CANCEL_THREAD_FUNC(waitid, (0, 0, NULL, 0))
+MAKE_CANCEL_THREAD_FUNC(waitpid, (-1, NULL, 0))
+MAKE_CANCEL_THREAD_FUNC(write, (-1, NULL, 0))
+MAKE_CANCEL_THREAD_FUNC(writev, (-1, NULL, 0))
+
+/* test a few variations that should not cancel ... */
+MAKE_CANCEL_THREAD_FUNC_RE(fcntl_another, fcntl, (0, F_GETFD))
+
+/* main test that creates thread, cancels it, etc... */
+int _test_func(const char *func_name, void *(*func)(void*), const int should_cancel)
+{
+ int ret;
+ pthread_t cancel_thread_id;
+
+ ++cnt;
+
+ printf("testing %-30s ", func_name);
+
+ printf(".");
+ if (signal(SIGALRM, cancel_timeout) == SIG_ERR) {
+ perror("unable to bind SIGALRM");
+ exit(-1);
+ }
+
+ printf(".");
+ ready = false;
+ pthread_create(&cancel_thread_id, NULL, func, NULL);
+
+ printf(".");
+ while (!ready)
+ sched_yield();
+
+ printf(".");
+ if (pthread_cancel(cancel_thread_id)) {
+ perror("unable to cancel thread");
+ exit(-1);
+ }
+
+ printf(".");
+ alarm(5);
+ while (ready)
+ sched_yield();
+
+ printf(".");
+ ret = (!!!alarm(0) == should_cancel);
+
+ if (ret)
+ printf(" failed ;(\n");
+ else
+ printf(" OK!\n");
+
+ return ret;
+}
+#define TEST_FUNC(f) _test_func(#f, cancel_thread_##f, 1)
+#define TEST_FUNC_RE(f) _test_func(#f, cancel_thread_##f, 0)
+
+int main(int argc, char *argv[])
+{
+ int ret = 0;
+ setbuf(stdout, NULL);
+ cnt = 0;
+
+ ret += TEST_FUNC(accept);
+ ret += TEST_FUNC(aio_suspend);
+ ret += TEST_FUNC(clock_nanosleep);
+ ret += TEST_FUNC(close);
+ ret += TEST_FUNC(connect);
+ ret += TEST_FUNC(creat);
+ ret += TEST_FUNC(fcntl);
+ ret += TEST_FUNC(fdatasync);
+ ret += TEST_FUNC(fsync);
+ ret += TEST_FUNC(getmsg);
+ ret += TEST_FUNC(getpmsg);
+ ret += TEST_FUNC(lockf);
+ ret += TEST_FUNC(mq_receive);
+ ret += TEST_FUNC(mq_send);
+ ret += TEST_FUNC(mq_timedreceive);
+ ret += TEST_FUNC(mq_timedsend);
+ ret += TEST_FUNC(msgrcv);
+ ret += TEST_FUNC(msgsnd);
+ ret += TEST_FUNC(msync);
+ ret += TEST_FUNC(nanosleep);
+ ret += TEST_FUNC(open);
+ ret += TEST_FUNC(pause);
+ ret += TEST_FUNC(poll);
+ ret += TEST_FUNC(pread);
+ ret += TEST_FUNC(pselect);
+ ret += TEST_FUNC(pthread_cond_timedwait);
+ ret += TEST_FUNC(pthread_cond_wait);
+ /*ret += TEST_FUNC(pthread_join);*/
+ ret += TEST_FUNC(pthread_testcancel);
+ ret += TEST_FUNC(putmsg);
+ ret += TEST_FUNC(putpmsg);
+ ret += TEST_FUNC(pwrite);
+ ret += TEST_FUNC(read);
+ ret += TEST_FUNC(readv);
+ ret += TEST_FUNC(recv);
+ ret += TEST_FUNC(recvfrom);
+ ret += TEST_FUNC(recvmsg);
+ ret += TEST_FUNC(select);
+ ret += TEST_FUNC(sem_timedwait);
+ ret += TEST_FUNC(sem_wait);
+ ret += TEST_FUNC(send);
+ ret += TEST_FUNC(sendmsg);
+ ret += TEST_FUNC(sendto);
+ ret += TEST_FUNC(sigpause);
+ ret += TEST_FUNC(sigsuspend);
+ ret += TEST_FUNC(sigtimedwait);
+ ret += TEST_FUNC(sigwait);
+ ret += TEST_FUNC(sigwaitinfo);
+ ret += TEST_FUNC(sleep);
+ ret += TEST_FUNC(system);
+ ret += TEST_FUNC(tcdrain);
+ ret += TEST_FUNC(usleep);
+ ret += TEST_FUNC(wait);
+ ret += TEST_FUNC(waitid);
+ ret += TEST_FUNC(waitpid);
+ ret += TEST_FUNC(write);
+ ret += TEST_FUNC(writev);
+
+ ret += TEST_FUNC_RE(fcntl_another);
+
+ if (ret)
+ printf("!!! %i / %i tests failed\n", ret, cnt);
+
+ return ret;
+}
diff --git a/test/pthread/ex1.c b/test/pthread/ex1.c
index a1b24c31a..4d9de03d8 100644
--- a/test/pthread/ex1.c
+++ b/test/pthread/ex1.c
@@ -7,7 +7,7 @@
#include <unistd.h>
#include "pthread.h"
-void *process(void * arg)
+static void *process(void * arg)
{
int i;
printf("Starting process %s\n", (char *)arg);
diff --git a/test/pthread/ex2.c b/test/pthread/ex2.c
index 70cb6b398..98bd4b347 100644
--- a/test/pthread/ex2.c
+++ b/test/pthread/ex2.c
@@ -20,7 +20,7 @@ struct prodcons {
/* Initialize a buffer */
-void init(struct prodcons * b)
+static void init(struct prodcons * b)
{
pthread_mutex_init(&b->lock, NULL);
pthread_cond_init(&b->notempty, NULL);
@@ -31,7 +31,7 @@ void init(struct prodcons * b)
/* Store an integer in the buffer */
-void put(struct prodcons * b, int data)
+static void put(struct prodcons * b, int data)
{
pthread_mutex_lock(&b->lock);
/* Wait until buffer is not full */
@@ -50,7 +50,7 @@ void put(struct prodcons * b, int data)
/* Read and remove an integer from the buffer */
-int get(struct prodcons * b)
+static int get(struct prodcons * b)
{
int data;
pthread_mutex_lock(&b->lock);
@@ -75,7 +75,7 @@ int get(struct prodcons * b)
struct prodcons buffer;
-void * producer(void * data)
+static void * producer(void * data)
{
int n;
for (n = 0; n < 10000; n++) {
@@ -86,7 +86,7 @@ void * producer(void * data)
return NULL;
}
-void * consumer(void * data)
+static void * consumer(void * data)
{
int d;
while (1) {
diff --git a/test/pthread/ex4.c b/test/pthread/ex4.c
index 11a09f013..cf4cf1d69 100644
--- a/test/pthread/ex4.c
+++ b/test/pthread/ex4.c
@@ -14,7 +14,7 @@
#if 0
-char * str_accumulate(char * s)
+static char * str_accumulate(char * s)
{
static char accu[1024] = { 0 };
strcat(accu, s);
@@ -40,7 +40,7 @@ static void str_alloc_destroy_accu(void * accu);
/* Thread-safe version of str_accumulate */
-char * str_accumulate(const char * s)
+static char * str_accumulate(const char * s)
{
char * accu;
@@ -81,7 +81,7 @@ static void str_alloc_destroy_accu(void * accu)
/* Test program */
-void * process(void * arg)
+static void * process(void * arg)
{
char * res;
res = str_accumulate("Result of ");
diff --git a/test/pthread/ex5.c b/test/pthread/ex5.c
index 475de0e0c..7a293eb01 100644
--- a/test/pthread/ex5.c
+++ b/test/pthread/ex5.c
@@ -19,7 +19,7 @@ struct prodcons {
/* Initialize a buffer */
-void init(struct prodcons * b)
+static void init(struct prodcons * b)
{
sem_init(&b->sem_write, 0, BUFFER_SIZE - 1);
sem_init(&b->sem_read, 0, 0);
@@ -29,7 +29,7 @@ void init(struct prodcons * b)
/* Store an integer in the buffer */
-void put(struct prodcons * b, int data)
+static void put(struct prodcons * b, int data)
{
/* Wait until buffer is not full */
sem_wait(&b->sem_write);
@@ -43,7 +43,7 @@ void put(struct prodcons * b, int data)
/* Read and remove an integer from the buffer */
-int get(struct prodcons * b)
+static int get(struct prodcons * b)
{
int data;
/* Wait until buffer is not empty */
@@ -64,7 +64,7 @@ int get(struct prodcons * b)
struct prodcons buffer;
-void * producer(void * data)
+static void * producer(void * data)
{
int n;
for (n = 0; n < 10000; n++) {
@@ -75,7 +75,7 @@ void * producer(void * data)
return NULL;
}
-void * consumer(void * data)
+static void * consumer(void * data)
{
int d;
while (1) {
diff --git a/test/pthread/ex6.c b/test/pthread/ex6.c
index 15914ce85..bb96ca5fa 100644
--- a/test/pthread/ex6.c
+++ b/test/pthread/ex6.c
@@ -4,7 +4,7 @@
#include <pthread.h>
#include <unistd.h>
-void *
+static void *
test_thread (void *v_param)
{
return NULL;
diff --git a/test/pthread/ex7.c b/test/pthread/ex7.c
index bda2ca9eb..9cf30aa19 100644
--- a/test/pthread/ex7.c
+++ b/test/pthread/ex7.c
@@ -22,12 +22,12 @@ typedef struct {
event_t main_event;
-void *
+static void *
test_thread (void *ms_param)
{
unsigned long status = 0;
event_t foo;
- struct timespec time;
+ struct timespec timeout;
struct timeval now;
long ms = (long) ms_param;
@@ -39,13 +39,13 @@ test_thread (void *ms_param)
/* set the time out value */
printf("waiting %ld ms ...\n", ms);
gettimeofday(&now, NULL);
- time.tv_sec = now.tv_sec + ms/1000 + (now.tv_usec + (ms%1000)*1000)/1000000;
- time.tv_nsec = ((now.tv_usec + (ms%1000)*1000) % 1000000) * 1000;
+ timeout.tv_sec = now.tv_sec + ms/1000 + (now.tv_usec + (ms%1000)*1000)/1000000;
+ timeout.tv_nsec = ((now.tv_usec + (ms%1000)*1000) % 1000000) * 1000;
/* Just use this to test the time out. The cond var is never signaled. */
pthread_mutex_lock(&foo.mutex);
while (foo.flag == 0 && status != ETIMEDOUT) {
- status = pthread_cond_timedwait(&foo.cond, &foo.mutex, &time);
+ status = pthread_cond_timedwait(&foo.cond, &foo.mutex, &timeout);
}
pthread_mutex_unlock(&foo.mutex);
diff --git a/test/pthread/tst-too-many-cleanups.c b/test/pthread/tst-too-many-cleanups.c
new file mode 100644
index 000000000..7828c5036
--- /dev/null
+++ b/test/pthread/tst-too-many-cleanups.c
@@ -0,0 +1,104 @@
+/*
+ * This illustrates the bug where the cleanup function
+ * of a thread may be called too many times.
+ *
+ * main thread:
+ * - grab mutex
+ * - spawn thread1
+ * - go to sleep
+ * thread1:
+ * - register cleanup handler via pthread_cleanup_push()
+ * - try to grab mutex and sleep
+ * main:
+ * - kill thread1
+ * - go to sleep
+ * thread1 cleanup handler:
+ * - try to grab mutex and sleep
+ * main:
+ * - kill thread1
+ * - go to sleep
+ * thread1 cleanup handler:
+ * - wrongly called again
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <assert.h>
+#include <unistd.h>
+
+#define warn(fmt, args...) fprintf(stderr, "[%p] " fmt, (void*)pthread_self(), ## args)
+#define warnf(fmt, args...) warn("%s:%i: " fmt, __FUNCTION__, __LINE__, ## args)
+
+int ok_to_kill_thread;
+
+static void thread_killed(void *arg);
+
+static void *KillMeThread(void *thread_par)
+{
+ pthread_t pthread_id;
+
+ warnf("Starting child thread\n");
+
+ pthread_id = pthread_self();
+ pthread_cleanup_push(thread_killed, (void *)pthread_id);
+
+ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
+ /* main code */
+ warnf("please kill me now\n");
+ while (1) {
+ ok_to_kill_thread = 1;
+ sleep(1);
+ }
+
+ pthread_cleanup_pop(0);
+
+ return 0;
+}
+
+static void thread_killed(void *arg)
+{
+ static int num_times_called = 0;
+
+ warnf("killing %p [cnt=%i]\n", arg, ++num_times_called);
+ assert(num_times_called == 1);
+
+ /* pick any cancellation endpoint, sleep() will do just fine */
+ while (1) {
+ warnf("sleeping in cancellation endpoint ...\n");
+ sleep(1);
+ }
+
+ warnf("done cleaning up\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int count = 3;
+ pthread_t app_pthread_id;
+
+ /* need to tweak this test a bit to play nice with signals and LT */
+ return 0;
+
+ ok_to_kill_thread = 0;
+
+ pthread_create(&app_pthread_id, NULL, KillMeThread, NULL);
+
+ warnf("waiting for thread to prepare itself\n");
+ while (!ok_to_kill_thread)
+ sleep(1);
+
+ while (count--) {
+ warnf("killing thread\n");
+ pthread_cancel(app_pthread_id);
+ sleep(3);
+ }
+
+ return 0;
+}