Update.
authorUlrich Drepper <drepper@redhat.com>
Tue, 10 Apr 2001 21:46:48 +0000 (21:46 +0000)
committerUlrich Drepper <drepper@redhat.com>
Tue, 10 Apr 2001 21:46:48 +0000 (21:46 +0000)
2001-04-10  Martin Schwidefsky  <schwidefsky@de.ibm.com>

* sysdeps/unix/sysv/linux/s390/s390-32/getcontext.S: Fix return
value of getcontext.
* sysdeps/unix/sysv/linux/s390/s390-64/getcontext.S: Likewise.
* sysdeps/unix/sysv/linux/s390/s390-32/setcontext.S: Fix return
value of setcontext.
* sysdeps/unix/sysv/linux/s390/s390-64/setcontext.S: Likewise.
* sysdeps/unix/sysv/linux/s390/swapcontext.c: Skip setcontext
call by changing the saved context.

18 files changed:
ChangeLog
linuxthreads/ChangeLog
linuxthreads/Makefile
linuxthreads/cancel.c
linuxthreads/condvar.c
linuxthreads/internals.h
linuxthreads/join.c
linuxthreads/manager.c
linuxthreads/oldsemaphore.c
linuxthreads/pthread.c
linuxthreads/ptlongjmp.c
linuxthreads/semaphore.c
linuxthreads/tst-cancel.c [new file with mode: 0644]
sysdeps/unix/sysv/linux/s390/s390-32/getcontext.S
sysdeps/unix/sysv/linux/s390/s390-32/setcontext.S
sysdeps/unix/sysv/linux/s390/s390-64/getcontext.S
sysdeps/unix/sysv/linux/s390/s390-64/setcontext.S
sysdeps/unix/sysv/linux/s390/swapcontext.c

index 3c3d8a9..2522b10 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2001-04-10  Martin Schwidefsky  <schwidefsky@de.ibm.com>
+
+       * sysdeps/unix/sysv/linux/s390/s390-32/getcontext.S: Fix return
+       value of getcontext.
+       * sysdeps/unix/sysv/linux/s390/s390-64/getcontext.S: Likewise.
+       * sysdeps/unix/sysv/linux/s390/s390-32/setcontext.S: Fix return
+       value of setcontext.
+       * sysdeps/unix/sysv/linux/s390/s390-64/setcontext.S: Likewise.
+       * sysdeps/unix/sysv/linux/s390/swapcontext.c: Skip setcontext
+       call by changing the saved context.
+
 2001-04-10  Ulrich Drepper  <drepper@redhat.com>
 
        * sysdeps/alpha/stackinfo.h: New file.
index de4047c..917ab71 100644 (file)
@@ -1,3 +1,36 @@
+2001-04-10  Ulrich Drepper  <drepper@redhat.com>
+
+       * join.c (pthread_exit): Move code to new function __pthread_do_exit
+       which takes an extra parameter with the current frame pointer.
+       Call new function with CURRENT_STACK_FRAME.
+       (__pthread_do_exit): New function.  Call __pthread_perform_cleanup
+       with the new parameter.
+       (pthread_join): Call __pthread_do_exit instead of pthread_exit.
+       * cancel.c (__pthread_perform_cleanup): Takes extra parameter.  Use
+       this parameter as the initial value the cleanup handler records are
+       compared against.  No active cleanup handler record must have an
+       address lower than the previous one and the initial record must be
+       above (below on PA) the frame address passed in.
+       (pthread_setcancelstate): Call __pthread_do_exit instead of
+       pthread_exit.
+       (pthread_setcanceltype): Likewise.
+       (pthread_testcancel): Likewise.
+       (_pthread_cleanup_pop_restore): Likewise.
+       * condvar.c (pthread_cond_wait): Likewise.
+       (pthread_cond_timedwait_relative): Likewise.
+       * manager.c (pthread_start_thread): Likewise.
+       * oldsemaphore.c (__old_sem_wait): Likewise.
+       * pthread.c (pthread_handle_sigcancel): Likewise.
+       * semaphore.c (__new_sem_wait): Likewise.
+       (sem_timedwait): Likewise.
+       * ptlongjmp.c (pthread_cleanup_upto): Also use current stack frame
+       to limit the cleanup handlers which get run.
+       * internals.h: Add prototype for __pthread_do_exit.  Adjust prototype
+       for __pthread_perform_cleanup.
+
+       * Makefile (tests): Add tst-cancel.
+       * tst-cancel.c: New file.
+
 2001-04-08  Hans-Peter Nilsson  <hp@axis.com>
 
        * sysdeps/cris/pt-machine.h: New file.
index 8ecbd22..b719ff8 100644 (file)
@@ -56,7 +56,7 @@ endif
 librt-tests = ex10 ex11
 tests = ex1 ex2 ex3 ex4 ex5 ex6 ex7 ex8 ex9 $(librt-tests) ex12 ex13 joinrace \
        tststack $(tests-nodelete-$(have-z-nodelete)) ecmutex ex14 ex15 ex16 \
-       ex17
+       ex17 tst-cancel
 
 ifeq (yes,$(build-shared))
 tests-nodelete-yes = unload
index 0ae0d12..ed67a68 100644 (file)
@@ -20,6 +20,9 @@
 #include "internals.h"
 #include "spinlock.h"
 #include "restart.h"
+#include <stackinfo.h>
+
+#include <stdio.h>
 
 int pthread_setcancelstate(int state, int * oldstate)
 {
@@ -31,7 +34,7 @@ int pthread_setcancelstate(int state, int * oldstate)
   if (THREAD_GETMEM(self, p_canceled) &&
       THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE &&
       THREAD_GETMEM(self, p_canceltype) == PTHREAD_CANCEL_ASYNCHRONOUS)
-    pthread_exit(PTHREAD_CANCELED);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
   return 0;
 }
 
@@ -45,7 +48,7 @@ int pthread_setcanceltype(int type, int * oldtype)
   if (THREAD_GETMEM(self, p_canceled) &&
       THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE &&
       THREAD_GETMEM(self, p_canceltype) == PTHREAD_CANCEL_ASYNCHRONOUS)
-    pthread_exit(PTHREAD_CANCELED);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
   return 0;
 }
 
@@ -112,7 +115,7 @@ void pthread_testcancel(void)
   pthread_descr self = thread_self();
   if (THREAD_GETMEM(self, p_canceled)
       && THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE)
-    pthread_exit(PTHREAD_CANCELED);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
 }
 
 void _pthread_cleanup_push(struct _pthread_cleanup_buffer * buffer,
@@ -155,15 +158,27 @@ void _pthread_cleanup_pop_restore(struct _pthread_cleanup_buffer * buffer,
   if (THREAD_GETMEM(self, p_canceled) &&
       THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE &&
       THREAD_GETMEM(self, p_canceltype) == PTHREAD_CANCEL_ASYNCHRONOUS)
-    pthread_exit(PTHREAD_CANCELED);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
 }
 
-void __pthread_perform_cleanup(void)
+void __pthread_perform_cleanup(char *currentframe)
 {
   pthread_descr self = thread_self();
   struct _pthread_cleanup_buffer * c;
+
   for (c = THREAD_GETMEM(self, p_cleanup); c != NULL; c = c->__prev)
-    c->__routine(c->__arg);
+    {
+#if _STACK_GROWS_DOWN
+      if ((char *) c <= currentframe)
+       break;
+#elif _STACK_GROWS_UP
+      if ((char *) c >= currentframe)
+       break;
+#else
+# error "Define either _STACK_GROWS_DOWN or _STACK_GROWS_UP"
+#endif
+      c->__routine(c->__arg);
+    }
 
   /* And the TSD which needs special help.  */
   if (THREAD_GETMEM(self, p_libc_specific[_LIBC_TSD_KEY_RPC_VARS]) != NULL)
index f9c46a3..fd0db50 100644 (file)
@@ -93,7 +93,7 @@ int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
 
   if (already_canceled) {
     __pthread_set_own_extricate_if(self, 0);
-    pthread_exit(PTHREAD_CANCELED);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
   }
 
   pthread_mutex_unlock(mutex);
@@ -122,7 +122,7 @@ int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
       && THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE) {
     THREAD_SETMEM(self, p_woken_by_cancel, 0);
     pthread_mutex_lock(mutex);
-    pthread_exit(PTHREAD_CANCELED);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
   }
 
   /* Put back any resumes we caught that don't belong to us. */
@@ -168,7 +168,7 @@ pthread_cond_timedwait_relative(pthread_cond_t *cond,
 
   if (already_canceled) {
     __pthread_set_own_extricate_if(self, 0);
-    pthread_exit(PTHREAD_CANCELED);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
   }
 
   pthread_mutex_unlock(mutex);
@@ -216,7 +216,7 @@ pthread_cond_timedwait_relative(pthread_cond_t *cond,
       && THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE) {
     THREAD_SETMEM(self, p_woken_by_cancel, 0);
     pthread_mutex_lock(mutex);
-    pthread_exit(PTHREAD_CANCELED);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
   }
 
   /* Put back any resumes we caught that don't belong to us. */
index ba0c14d..998f778 100644 (file)
@@ -428,8 +428,10 @@ static inline pthread_descr thread_self (void)
 
 /* Internal global functions */
 
+extern void __pthread_do_exit (void *retval, char *currentframe)
+     __attribute__ ((__noreturn__));
 extern void __pthread_destroy_specifics (void);
-extern void __pthread_perform_cleanup (void);
+extern void __pthread_perform_cleanup (char *currentframe);
 extern void __pthread_init_max_stacksize (void);
 extern int __pthread_initialize_manager (void);
 extern void __pthread_message (char * fmt, ...);
index bbcebe1..a0cdb41 100644 (file)
 
 void pthread_exit(void * retval)
 {
+  __pthread_do_exit (retval, CURRENT_STACK_FRAME);
+}
+
+void __pthread_do_exit(void *retval, char *currentframe)
+{
   pthread_descr self = thread_self();
   pthread_descr joining;
   struct pthread_request request;
@@ -33,7 +38,7 @@ void pthread_exit(void * retval)
      contain cancellation points */
   THREAD_SETMEM(self, p_canceled, 0);
   /* Call cleanup functions and destroy the thread-specific data */
-  __pthread_perform_cleanup();
+  __pthread_perform_cleanup(currentframe);
   __pthread_destroy_specifics();
   /* Store return value */
   __pthread_lock(THREAD_GETMEM(self, p_lock), self);
@@ -144,7 +149,7 @@ int pthread_join(pthread_t thread_id, void ** thread_return)
 
     if (already_canceled) {
       __pthread_set_own_extricate_if(self, 0);
-      pthread_exit(PTHREAD_CANCELED);
+      __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
     }
 
     suspend(self);
@@ -155,7 +160,7 @@ int pthread_join(pthread_t thread_id, void ** thread_return)
     if (THREAD_GETMEM(self, p_woken_by_cancel)
        && THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE) {
       THREAD_SETMEM(self, p_woken_by_cancel, 0);
-      pthread_exit(PTHREAD_CANCELED);
+      __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
     }
     __pthread_lock(&handle->h_lock, self);
   }
index 567ba65..431e149 100644 (file)
@@ -262,7 +262,7 @@ static int pthread_start_thread(void *arg)
   outcome = self->p_start_args.start_routine(THREAD_GETMEM(self,
                                                           p_start_args.arg));
   /* Exit with the given return value */
-  pthread_exit(outcome);
+  __pthread_do_exit(outcome, CURRENT_STACK_FRAME);
   return 0;
 }
 
index da5272c..2099b8b 100644 (file)
@@ -144,7 +144,7 @@ int __old_sem_wait(old_sem_t * sem)
                    }
                }
            }
-            pthread_exit(PTHREAD_CANCELED);
+            __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
        }
     }
 }
index 479531b..ff9c083 100644 (file)
@@ -817,7 +817,7 @@ static void pthread_handle_sigcancel(int sig)
   if (__builtin_expect (THREAD_GETMEM(self, p_canceled), 0)
       && THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE) {
     if (THREAD_GETMEM(self, p_canceltype) == PTHREAD_CANCEL_ASYNCHRONOUS)
-      pthread_exit(PTHREAD_CANCELED);
+      __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
     jmpbuf = THREAD_GETMEM(self, p_cancel_jmp);
     if (jmpbuf != NULL) {
       THREAD_SETMEM(self, p_cancel_jmp, NULL);
index 1c12508..68b9235 100644 (file)
@@ -18,6 +18,7 @@
 #include <setjmp.h>
 #include "pthread.h"
 #include "internals.h"
+#include <stackinfo.h>
 
 /* These functions are not declared anywhere since they shouldn't be
    used at another place but here.  */
@@ -31,11 +32,29 @@ static void pthread_cleanup_upto(__jmp_buf target)
 {
   pthread_descr self = thread_self();
   struct _pthread_cleanup_buffer * c;
+  char *currentframe = CURRENT_STACK_FRAME;
 
   for (c = THREAD_GETMEM(self, p_cleanup);
        c != NULL && _JMPBUF_UNWINDS(target, c);
        c = c->__prev)
-    c->__routine(c->__arg);
+    {
+#if _STACK_GROWS_DOWN
+      if ((char *) c <= currentframe)
+       {
+         c = NULL;
+         break;
+       }
+#elif _STACK_GROWS_UP
+      if ((char *) c >= currentframe)
+       {
+         c = NULL;
+         break;
+       }
+#else
+# error "Define either _STACK_GROWS_DOWN or _STACK_GROWS_UP"
+#endif
+      c->__routine(c->__arg);
+    }
   THREAD_SETMEM(self, p_cleanup, c);
   if (THREAD_GETMEM(self, p_in_sighandler)
       && _JMPBUF_UNWINDS(target, THREAD_GETMEM(self, p_in_sighandler)))
index e5afce4..bb681b3 100644 (file)
@@ -85,7 +85,7 @@ int __new_sem_wait(sem_t * sem)
 
   if (already_canceled) {
     __pthread_set_own_extricate_if(self, 0);
-    pthread_exit(PTHREAD_CANCELED);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
   }
 
   /* Wait for sem_post or cancellation, or fall through if already canceled */
@@ -111,7 +111,7 @@ int __new_sem_wait(sem_t * sem)
   if (THREAD_GETMEM(self, p_woken_by_cancel)
       && THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE) {
     THREAD_SETMEM(self, p_woken_by_cancel, 0);
-    pthread_exit(PTHREAD_CANCELED);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
   }
   /* We got the semaphore */
   return 0;
@@ -245,7 +245,7 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime)
 
   if (already_canceled) {
     __pthread_set_own_extricate_if(self, 0);
-    pthread_exit(PTHREAD_CANCELED);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
   }
 
   spurious_wakeup_count = 0;
@@ -289,7 +289,7 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime)
   if (THREAD_GETMEM(self, p_woken_by_cancel)
       && THREAD_GETMEM(self, p_cancelstate) == PTHREAD_CANCEL_ENABLE) {
     THREAD_SETMEM(self, p_woken_by_cancel, 0);
-    pthread_exit(PTHREAD_CANCELED);
+    __pthread_do_exit(PTHREAD_CANCELED, CURRENT_STACK_FRAME);
   }
   /* We got the semaphore */
   return 0;
diff --git a/linuxthreads/tst-cancel.c b/linuxthreads/tst-cancel.c
new file mode 100644 (file)
index 0000000..da70d12
--- /dev/null
@@ -0,0 +1,153 @@
+/* Tests for cancelation handling.  */
+
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+int fd;
+
+pthread_barrier_t bar;
+
+
+static void
+cleanup (void *arg)
+{
+  int nr = (int) (long int) arg;
+  char s[30];
+  char *cp = stpcpy (s, "cleanup ");
+  *cp++ = '0' + nr;
+  *cp++ = '\n';
+  __libc_write (fd, s, cp - s);
+}
+
+
+static void *
+t1 (void *arg)
+{
+  pthread_cleanup_push (cleanup, (void *) (long int) 1);
+  return NULL;
+  pthread_cleanup_pop (0);
+}
+
+
+static void
+inner (int a)
+{
+  pthread_cleanup_push (cleanup, (void *) (long int) a);
+  if (a)
+    return;
+  pthread_cleanup_pop (0);
+}
+
+
+static void *
+t2 (void *arg)
+{
+  pthread_cleanup_push (cleanup, (void *) (long int) 2);
+  inner ((int) (long int) arg);
+  return NULL;
+  pthread_cleanup_pop (0);
+}
+
+
+static void *
+t3 (void *arg)
+{
+  pthread_cleanup_push (cleanup, (void *) (long int) 4);
+  inner ((int) (long int) arg);
+  pthread_exit (NULL);
+  pthread_cleanup_pop (0);
+}
+
+
+int
+main (void)
+{
+  pthread_t td;
+  int err;
+  char tmp[] = "thtstXXXXXX";
+  struct stat64 st;
+  int result = 0;
+
+  fd = mkstemp (tmp);
+  if (fd == -1)
+    {
+      printf ("cannot create temporary file: %m");
+      exit (1);
+    }
+  unlink (tmp);
+
+  err = pthread_barrier_init (&bar, NULL, 2);
+  if (err != 0 )
+    {
+      printf ("cannot create barrier: %s\n", strerror (err));
+      exit (1);
+    }
+
+  err = pthread_create (&td, NULL, t1, NULL);
+  if (err != 0)
+    {
+      printf ("cannot create thread t1: %s\n", strerror (err));
+      exit (1);
+    }
+
+  err = pthread_join (td, NULL);
+  if (err != 0)
+    {
+      printf ("cannot join thread: %s\n", strerror (err));
+      exit (1);
+    }
+
+  err = pthread_create (&td, NULL, t2, (void *) 3);
+  if (err != 0)
+    {
+      printf ("cannot create thread t2: %s\n", strerror (err));
+      exit (1);
+    }
+
+  err = pthread_join (td, NULL);
+  if (err != 0)
+    {
+      printf ("cannot join thread: %s\n", strerror (err));
+      exit (1);
+    }
+
+  err = pthread_create (&td, NULL, t3, (void *) 5);
+  if (err != 0)
+    {
+      printf ("cannot create thread t3: %s\n", strerror (err));
+      exit (1);
+    }
+
+  err = pthread_join (td, NULL);
+  if (err != 0)
+    {
+      printf ("cannot join thread: %s\n", strerror (err));
+      exit (1);
+    }
+
+  if (fstat64 (fd, &st) < 0)
+    {
+      printf ("cannot stat temporary file: %m\n");
+      result = 1;
+    }
+  else if (st.st_size != 0)
+    {
+      char buf[512];
+      puts ("some cleanup handlers ran:");
+      fflush (stdout);
+      while (1)
+       {
+         ssize_t n = read (fd, buf, sizeof buf);
+         if (n < 0)
+           break;
+         write (STDOUT_FILENO, buf, n);
+       }
+      result = 1;
+    }
+
+  return result;
+}
index e64cc85..b3cae1c 100644 (file)
@@ -25,9 +25,7 @@
 /*  __getcontext (const ucontext_t *ucp)
 
   Saves the machine context in UCP such that when it is activated,
-  it appears as if __getcontext() returned again.  The only difference
-  is that on a first return, %r2 contains 1 and on a subsequent
-  return, it contains 0.
+  it appears as if __getcontext() returned again. 
 
   This implementation is intended to be used for *synchronous* context
   switches only.  Therefore, it does not have to save anything
@@ -61,11 +59,13 @@ ENTRY(__getcontext)
        std     %f14,SC_FPRS+112(%r5)
        std     %f15,SC_FPRS+120(%r5)
 
+       /* Set __getcontext return value to 0.  */
+       slr     %r2,%r2
+
        /* Store general purpose registers.  */
        stm     %r0,%r15,SC_GPRS(%r5)
 
-       /* Return 0.  */
-       slr     %r2,%r2
+       /* Return.  */
        br      %r14
 END(__getcontext)
 
index 40adc85..2bed318 100644 (file)
@@ -62,8 +62,7 @@ ENTRY(__setcontext)
        /* Load general purpose registers.  */
        lm      %r0,%r15,SC_GPRS(%r5)
 
-        /* Return 1.  */
-       la      %r2,1
+       /* Return.  */
        br      %r14
 END(__setcontext)
 
index 2fc21e2..9eba838 100644 (file)
@@ -25,9 +25,7 @@
 /*  __getcontext (const ucontext_t *ucp)
 
   Saves the machine context in UCP such that when it is activated,
-  it appears as if __getcontext() returned again.  The only difference
-  is that on a first return, %r2 contains 1 and on a subsequent
-  return, it contains 0.
+  it appears as if __getcontext() returned again.
 
   This implementation is intended to be used for *synchronous* context
   switches only.  Therefore, it does not have to save anything
@@ -61,11 +59,13 @@ ENTRY(__getcontext)
        std     %f14,SC_FPRS+112(%r5)
        std     %f15,SC_FPRS+120(%r5)
 
+       /* Set __getcontext return value to 0.  */
+       slr     %r2,%r2
+
        /* Store general purpose registers.  */
        stmg    %r0,%r15,SC_GPRS(%r5)
 
-       /* Return 0.  */
-       slgr    %r2,%r2
+       /* Return.  */
        br      %r14
 END(__getcontext)
 
index b3a9fea..a652ad3 100644 (file)
@@ -62,8 +62,7 @@ ENTRY(__setcontext)
        /* Load general purpose registers.  */
        lmg     %r0,%r15,SC_GPRS(%r5)
 
-        /* Return 1.  */
-       la      %r2,1
+       /* Return.  */
        br      %r14
 END(__setcontext)
 
index 9f1be05..2b34b63 100644 (file)
@@ -25,8 +25,14 @@ extern int __setcontext (__const ucontext_t *__ucp) __THROW;
 int
 __swapcontext (ucontext_t *oucp, const ucontext_t *ucp)
 {
-  if (__getcontext (oucp) == 0)
-    __setcontext (ucp);
+  /* Save the current machine context to oucp.  */
+  __getcontext (oucp);
+  /* Modify oucp to skip the __setcontext call on reactivation.  */
+  oucp->uc_mcontext.gregs[14] = &&fake_return;
+  /* Restore the machine context in ucp.  */
+  __setcontext (ucp);
+
+fake_return:
   return 0;
 }