pthread_mutex_trylock is required to fail with EBUSY if the mutex is a non-recursive one and it is already locked by the current thread. Similarly for mtx_trylock.
On native Windows, I observe that the Gnulib implementation does not follow this spec, because - It uses the windows-mutex and windows-timedmutex modules. - This code uses the TryEnterCriticalSection function. - This function succeeds if the current thread already holds the lock, because a Windows CRITICAL_SECTION is semantically a recursive lock. These patches fixes it, and add unit tests to verify the behaviour. 2024-08-06 Bruno Haible <br...@clisp.org> windows-timedrecmutex: Add tests. * tests/test-windows-timedrecmutex-type.c: New file. * modules/windows-timedrecmutex-tests: New file. windows-recmutex: Add tests. * tests/test-windows-recmutex-type.c: New file. * modules/windows-recmutex-tests: New file. windows-timedmutex: Add tests. * tests/test-windows-timedmutex-type.c: New file. * modules/windows-timedmutex-tests: New file. windows-mutex: Add tests. * tests/test-windows-mutex-type.c: New file. * modules/windows-mutex-tests: New file. 2024-08-06 Bruno Haible <br...@clisp.org> windows-mutex, windows-timedmutex: Follow pthread_mutex_trylock spec. * lib/windows-mutex.h (glwthread_mutex_t): Add 'owner' field. * lib/windows-mutex.c: Include <stdlib.h>. (glwthread_mutex_lock): Set the 'owner' field after entering the critical section. (glwthread_mutex_trylock): Detect whether the lock was previously locked by this thread. Set the 'owner' field after entering the critical section. (glwthread_mutex_unlock): Clear the 'owner' field before leaving the critical section. * lib/windows-timedmutex.h (glwthread_timedmutex_t): Add 'owner' field. * lib/windows-timedmutex.c: (glwthread_timedmutex_lock): Set the 'owner' field after entering the critical section. (glwthread_timedmutex_trylock): Detect whether the lock was previously locked by this thread. Set the 'owner' field after entering the critical section. (glwthread_timedmutex_unlock): Clear the 'owner' field before leaving the critical section.
>From 0cd5825421831dc132e335a5b5cd7648bb2e334e Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 6 Aug 2024 15:14:25 +0200 Subject: [PATCH 1/5] windows-mutex, windows-timedmutex: Follow pthread_mutex_trylock spec. * lib/windows-mutex.h (glwthread_mutex_t): Add 'owner' field. * lib/windows-mutex.c: Include <stdlib.h>. (glwthread_mutex_lock): Set the 'owner' field after entering the critical section. (glwthread_mutex_trylock): Detect whether the lock was previously locked by this thread. Set the 'owner' field after entering the critical section. (glwthread_mutex_unlock): Clear the 'owner' field before leaving the critical section. * lib/windows-timedmutex.h (glwthread_timedmutex_t): Add 'owner' field. * lib/windows-timedmutex.c: (glwthread_timedmutex_lock): Set the 'owner' field after entering the critical section. (glwthread_timedmutex_trylock): Detect whether the lock was previously locked by this thread. Set the 'owner' field after entering the critical section. (glwthread_timedmutex_unlock): Clear the 'owner' field before leaving the critical section. --- ChangeLog | 21 +++++++++++++++++++++ lib/windows-mutex.c | 23 +++++++++++++++++++++++ lib/windows-mutex.h | 1 + lib/windows-timedmutex.c | 37 +++++++++++++++++++++++++++++++++++++ lib/windows-timedmutex.h | 1 + 5 files changed, 83 insertions(+) diff --git a/ChangeLog b/ChangeLog index dc27670bda..d7e5c44ddc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2024-08-06 Bruno Haible <br...@clisp.org> + + windows-mutex, windows-timedmutex: Follow pthread_mutex_trylock spec. + * lib/windows-mutex.h (glwthread_mutex_t): Add 'owner' field. + * lib/windows-mutex.c: Include <stdlib.h>. + (glwthread_mutex_lock): Set the 'owner' field after entering the + critical section. + (glwthread_mutex_trylock): Detect whether the lock was previously locked + by this thread. Set the 'owner' field after entering the critical + section. + (glwthread_mutex_unlock): Clear the 'owner' field before leaving the + critical section. + * lib/windows-timedmutex.h (glwthread_timedmutex_t): Add 'owner' field. + * lib/windows-timedmutex.c: (glwthread_timedmutex_lock): Set the 'owner' + field after entering the critical section. + (glwthread_timedmutex_trylock): Detect whether the lock was previously + locked by this thread. Set the 'owner' field after entering the critical + section. + (glwthread_timedmutex_unlock): Clear the 'owner' field before leaving + the critical section. + 2024-08-05 Bruno Haible <br...@clisp.org> Improve a comment. diff --git a/lib/windows-mutex.c b/lib/windows-mutex.c index b112e13b6b..d258dbc1a9 100644 --- a/lib/windows-mutex.c +++ b/lib/windows-mutex.c @@ -23,6 +23,7 @@ #include "windows-mutex.h" #include <errno.h> +#include <stdlib.h> void glwthread_mutex_init (glwthread_mutex_t *mutex) @@ -49,7 +50,13 @@ glwthread_mutex_lock (glwthread_mutex_t *mutex) Sleep (0); } } + /* If this thread already owns the mutex, POSIX pthread_mutex_lock() is + required to deadlock here. But let's not do that on purpose. */ EnterCriticalSection (&mutex->lock); + { + DWORD self = GetCurrentThreadId (); + mutex->owner = self; + } return 0; } @@ -72,6 +79,21 @@ glwthread_mutex_trylock (glwthread_mutex_t *mutex) } if (!TryEnterCriticalSection (&mutex->lock)) return EBUSY; + { + DWORD self = GetCurrentThreadId (); + /* TryEnterCriticalSection succeeded. This means that the mutex was either + previously unlocked (and thus mutex->owner == 0) or previously locked by + this thread (and thus mutex->owner == self). Since the mutex is meant to + be plain, we need to fail in the latter case. */ + if (mutex->owner == self) + { + LeaveCriticalSection (&mutex->lock); + return EBUSY; + } + if (mutex->owner != 0) + abort (); + mutex->owner = self; + } return 0; } @@ -80,6 +102,7 @@ glwthread_mutex_unlock (glwthread_mutex_t *mutex) { if (!mutex->guard.done) return EINVAL; + mutex->owner = 0; LeaveCriticalSection (&mutex->lock); return 0; } diff --git a/lib/windows-mutex.h b/lib/windows-mutex.h index 88de4bdcad..cb676c1b90 100644 --- a/lib/windows-mutex.h +++ b/lib/windows-mutex.h @@ -28,6 +28,7 @@ typedef struct { glwthread_initguard_t guard; /* protects the initialization */ + DWORD owner; CRITICAL_SECTION lock; } glwthread_mutex_t; diff --git a/lib/windows-timedmutex.c b/lib/windows-timedmutex.c index 3dfff56dbe..1beb56df62 100644 --- a/lib/windows-timedmutex.c +++ b/lib/windows-timedmutex.c @@ -72,7 +72,13 @@ glwthread_timedmutex_lock (glwthread_timedmutex_t *mutex) Sleep (0); } } + /* If this thread already owns the mutex, POSIX pthread_mutex_lock() is + required to deadlock here. But let's not do that on purpose. */ EnterCriticalSection (&mutex->lock); + { + DWORD self = GetCurrentThreadId (); + mutex->owner = self; + } return 0; } @@ -104,6 +110,21 @@ glwthread_timedmutex_trylock (glwthread_timedmutex_t *mutex) } if (!TryEnterCriticalSection (&mutex->lock)) return EBUSY; + { + DWORD self = GetCurrentThreadId (); + /* TryEnterCriticalSection succeeded. This means that the mutex was either + previously unlocked (and thus mutex->owner == 0) or previously locked by + this thread (and thus mutex->owner == self). Since the mutex is meant to + be plain, we need to fail in the latter case. */ + if (mutex->owner == self) + { + LeaveCriticalSection (&mutex->lock); + return EBUSY; + } + if (mutex->owner != 0) + abort (); + mutex->owner = self; + } return 0; } @@ -197,6 +218,21 @@ glwthread_timedmutex_timedlock (glwthread_timedmutex_t *mutex, locking it now. */ } } + { + DWORD self = GetCurrentThreadId (); + /* TryEnterCriticalSection succeeded. This means that the mutex was either + previously unlocked (and thus mutex->owner == 0) or previously locked by + this thread (and thus mutex->owner == self). Since the mutex is meant to + be plain, it is useful to fail in the latter case. */ + if (mutex->owner == self) + { + LeaveCriticalSection (&mutex->lock); + return EDEADLK; + } + if (mutex->owner != 0) + abort (); + mutex->owner = self; + } return 0; } @@ -205,6 +241,7 @@ glwthread_timedmutex_unlock (glwthread_timedmutex_t *mutex) { if (!mutex->guard.done) return EINVAL; + mutex->owner = 0; LeaveCriticalSection (&mutex->lock); /* Notify one of the threads that were waiting with a timeout. */ /* SetEvent diff --git a/lib/windows-timedmutex.h b/lib/windows-timedmutex.h index e21879e1a1..6ede4b2408 100644 --- a/lib/windows-timedmutex.h +++ b/lib/windows-timedmutex.h @@ -30,6 +30,7 @@ typedef struct { glwthread_initguard_t guard; /* protects the initialization */ + DWORD owner; HANDLE event; CRITICAL_SECTION lock; } -- 2.34.1
>From 735ca8884afd4755c4733a6c590006b257474b66 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 6 Aug 2024 15:17:03 +0200 Subject: [PATCH 2/5] windows-mutex: Add tests. * tests/test-windows-mutex-type.c: New file. * modules/windows-mutex-tests: New file. --- ChangeLog | 6 +++ modules/windows-mutex-tests | 11 +++++ tests/test-windows-mutex-type.c | 78 +++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 modules/windows-mutex-tests create mode 100644 tests/test-windows-mutex-type.c diff --git a/ChangeLog b/ChangeLog index d7e5c44ddc..31c40e03ab 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2024-08-06 Bruno Haible <br...@clisp.org> + + windows-mutex: Add tests. + * tests/test-windows-mutex-type.c: New file. + * modules/windows-mutex-tests: New file. + 2024-08-06 Bruno Haible <br...@clisp.org> windows-mutex, windows-timedmutex: Follow pthread_mutex_trylock spec. diff --git a/modules/windows-mutex-tests b/modules/windows-mutex-tests new file mode 100644 index 0000000000..9563664aef --- /dev/null +++ b/modules/windows-mutex-tests @@ -0,0 +1,11 @@ +Files: +tests/test-windows-mutex-type.c +tests/macros.h + +Depends-on: + +configure.ac: + +Makefile.am: +TESTS += test-windows-mutex-type +check_PROGRAMS += test-windows-mutex-type diff --git a/tests/test-windows-mutex-type.c b/tests/test-windows-mutex-type.c new file mode 100644 index 0000000000..54d1589588 --- /dev/null +++ b/tests/test-windows-mutex-type.c @@ -0,0 +1,78 @@ +/* Test of locking in multithreaded situations. + Copyright (C) 2024 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible <br...@clisp.org>, 2024. */ + +#include <config.h> + +#if defined _WIN32 && !defined __CYGWIN__ + +/* Specification. */ +# include "windows-mutex.h" + +# include <errno.h> +# include <stdio.h> +# include <string.h> + +# include "macros.h" + +/* Returns the effective type of a lock. */ +static const char * +get_effective_type (glwthread_mutex_t *lock) +{ + /* Lock once. */ + ASSERT (glwthread_mutex_lock (lock) == 0); + + /* Try to lock a second time. */ + int err = glwthread_mutex_trylock (lock); + if (err == 0) + return "RECURSIVE"; + if (err == EBUSY) + return "NORMAL"; + + return "impossible!"; +} + +int +main () +{ + /* Find the effective type of a lock. */ + const char *type; + { + glwthread_mutex_t lock; + glwthread_mutex_init (&lock); + type = get_effective_type (&lock); + } + + printf ("type = %s\n", type); + + ASSERT (strcmp (type, "NORMAL") == 0); + + return test_exit_status; +} + +#else + +# include <stdio.h> + +int +main () +{ + fputs ("Skipping test: not a native Windows system\n", stderr); + return 77; +} + +#endif -- 2.34.1
>From d46563a004044dd898c4277eb3d99275017bbe82 Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 6 Aug 2024 15:18:18 +0200 Subject: [PATCH 3/5] windows-timedmutex: Add tests. * tests/test-windows-timedmutex-type.c: New file. * modules/windows-timedmutex-tests: New file. --- ChangeLog | 4 ++ modules/windows-timedmutex-tests | 11 ++++ tests/test-windows-timedmutex-type.c | 78 ++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 modules/windows-timedmutex-tests create mode 100644 tests/test-windows-timedmutex-type.c diff --git a/ChangeLog b/ChangeLog index 31c40e03ab..244c8e3b06 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2024-08-06 Bruno Haible <br...@clisp.org> + windows-timedmutex: Add tests. + * tests/test-windows-timedmutex-type.c: New file. + * modules/windows-timedmutex-tests: New file. + windows-mutex: Add tests. * tests/test-windows-mutex-type.c: New file. * modules/windows-mutex-tests: New file. diff --git a/modules/windows-timedmutex-tests b/modules/windows-timedmutex-tests new file mode 100644 index 0000000000..9a22bb8d97 --- /dev/null +++ b/modules/windows-timedmutex-tests @@ -0,0 +1,11 @@ +Files: +tests/test-windows-timedmutex-type.c +tests/macros.h + +Depends-on: + +configure.ac: + +Makefile.am: +TESTS += test-windows-timedmutex-type +check_PROGRAMS += test-windows-timedmutex-type diff --git a/tests/test-windows-timedmutex-type.c b/tests/test-windows-timedmutex-type.c new file mode 100644 index 0000000000..9c63ccd46c --- /dev/null +++ b/tests/test-windows-timedmutex-type.c @@ -0,0 +1,78 @@ +/* Test of locking in multithreaded situations. + Copyright (C) 2024 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible <br...@clisp.org>, 2024. */ + +#include <config.h> + +#if defined _WIN32 && !defined __CYGWIN__ + +/* Specification. */ +# include "windows-timedmutex.h" + +# include <errno.h> +# include <stdio.h> +# include <string.h> + +# include "macros.h" + +/* Returns the effective type of a lock. */ +static const char * +get_effective_type (glwthread_timedmutex_t *lock) +{ + /* Lock once. */ + ASSERT (glwthread_timedmutex_lock (lock) == 0); + + /* Try to lock a second time. */ + int err = glwthread_timedmutex_trylock (lock); + if (err == 0) + return "RECURSIVE"; + if (err == EBUSY) + return "NORMAL"; + + return "impossible!"; +} + +int +main () +{ + /* Find the effective type of a lock. */ + const char *type; + { + glwthread_timedmutex_t lock; + ASSERT (glwthread_timedmutex_init (&lock) == 0); + type = get_effective_type (&lock); + } + + printf ("type = %s\n", type); + + ASSERT (strcmp (type, "NORMAL") == 0); + + return test_exit_status; +} + +#else + +# include <stdio.h> + +int +main () +{ + fputs ("Skipping test: not a native Windows system\n", stderr); + return 77; +} + +#endif -- 2.34.1
>From 2f65f0931f39d8276118a1000278ee02691fc32d Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 6 Aug 2024 15:19:31 +0200 Subject: [PATCH 4/5] windows-recmutex: Add tests. * tests/test-windows-recmutex-type.c: New file. * modules/windows-recmutex-tests: New file. --- ChangeLog | 4 ++ modules/windows-recmutex-tests | 11 +++++ tests/test-windows-recmutex-type.c | 78 ++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 modules/windows-recmutex-tests create mode 100644 tests/test-windows-recmutex-type.c diff --git a/ChangeLog b/ChangeLog index 244c8e3b06..5e6ab7af6a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2024-08-06 Bruno Haible <br...@clisp.org> + windows-recmutex: Add tests. + * tests/test-windows-recmutex-type.c: New file. + * modules/windows-recmutex-tests: New file. + windows-timedmutex: Add tests. * tests/test-windows-timedmutex-type.c: New file. * modules/windows-timedmutex-tests: New file. diff --git a/modules/windows-recmutex-tests b/modules/windows-recmutex-tests new file mode 100644 index 0000000000..85da2ab9a5 --- /dev/null +++ b/modules/windows-recmutex-tests @@ -0,0 +1,11 @@ +Files: +tests/test-windows-recmutex-type.c +tests/macros.h + +Depends-on: + +configure.ac: + +Makefile.am: +TESTS += test-windows-recmutex-type +check_PROGRAMS += test-windows-recmutex-type diff --git a/tests/test-windows-recmutex-type.c b/tests/test-windows-recmutex-type.c new file mode 100644 index 0000000000..72cc5d17ef --- /dev/null +++ b/tests/test-windows-recmutex-type.c @@ -0,0 +1,78 @@ +/* Test of locking in multithreaded situations. + Copyright (C) 2024 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible <br...@clisp.org>, 2024. */ + +#include <config.h> + +#if defined _WIN32 && !defined __CYGWIN__ + +/* Specification. */ +# include "windows-recmutex.h" + +# include <errno.h> +# include <stdio.h> +# include <string.h> + +# include "macros.h" + +/* Returns the effective type of a lock. */ +static const char * +get_effective_type (glwthread_recmutex_t *lock) +{ + /* Lock once. */ + ASSERT (glwthread_recmutex_lock (lock) == 0); + + /* Try to lock a second time. */ + int err = glwthread_recmutex_trylock (lock); + if (err == 0) + return "RECURSIVE"; + if (err == EBUSY) + return "NORMAL"; + + return "impossible!"; +} + +int +main () +{ + /* Find the effective type of a lock. */ + const char *type; + { + glwthread_recmutex_t lock; + glwthread_recmutex_init (&lock); + type = get_effective_type (&lock); + } + + printf ("type = %s\n", type); + + ASSERT (strcmp (type, "RECURSIVE") == 0); + + return test_exit_status; +} + +#else + +# include <stdio.h> + +int +main () +{ + fputs ("Skipping test: not a native Windows system\n", stderr); + return 77; +} + +#endif -- 2.34.1
>From 65162c3519d7d718e957d35e6a1cf42d1736db5e Mon Sep 17 00:00:00 2001 From: Bruno Haible <br...@clisp.org> Date: Tue, 6 Aug 2024 15:20:23 +0200 Subject: [PATCH 5/5] windows-timedrecmutex: Add tests. * tests/test-windows-timedrecmutex-type.c: New file. * modules/windows-timedrecmutex-tests: New file. --- ChangeLog | 4 ++ modules/windows-timedrecmutex-tests | 11 ++++ tests/test-windows-timedrecmutex-type.c | 78 +++++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 modules/windows-timedrecmutex-tests create mode 100644 tests/test-windows-timedrecmutex-type.c diff --git a/ChangeLog b/ChangeLog index 5e6ab7af6a..db84b220e2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2024-08-06 Bruno Haible <br...@clisp.org> + windows-timedrecmutex: Add tests. + * tests/test-windows-timedrecmutex-type.c: New file. + * modules/windows-timedrecmutex-tests: New file. + windows-recmutex: Add tests. * tests/test-windows-recmutex-type.c: New file. * modules/windows-recmutex-tests: New file. diff --git a/modules/windows-timedrecmutex-tests b/modules/windows-timedrecmutex-tests new file mode 100644 index 0000000000..2e6c0cc55e --- /dev/null +++ b/modules/windows-timedrecmutex-tests @@ -0,0 +1,11 @@ +Files: +tests/test-windows-timedrecmutex-type.c +tests/macros.h + +Depends-on: + +configure.ac: + +Makefile.am: +TESTS += test-windows-timedrecmutex-type +check_PROGRAMS += test-windows-timedrecmutex-type diff --git a/tests/test-windows-timedrecmutex-type.c b/tests/test-windows-timedrecmutex-type.c new file mode 100644 index 0000000000..a61677cafa --- /dev/null +++ b/tests/test-windows-timedrecmutex-type.c @@ -0,0 +1,78 @@ +/* Test of locking in multithreaded situations. + Copyright (C) 2024 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible <br...@clisp.org>, 2024. */ + +#include <config.h> + +#if defined _WIN32 && !defined __CYGWIN__ + +/* Specification. */ +# include "windows-timedrecmutex.h" + +# include <errno.h> +# include <stdio.h> +# include <string.h> + +# include "macros.h" + +/* Returns the effective type of a lock. */ +static const char * +get_effective_type (glwthread_timedrecmutex_t *lock) +{ + /* Lock once. */ + ASSERT (glwthread_timedrecmutex_lock (lock) == 0); + + /* Try to lock a second time. */ + int err = glwthread_timedrecmutex_trylock (lock); + if (err == 0) + return "RECURSIVE"; + if (err == EBUSY) + return "NORMAL"; + + return "impossible!"; +} + +int +main () +{ + /* Find the effective type of a lock. */ + const char *type; + { + glwthread_timedrecmutex_t lock; + ASSERT (glwthread_timedrecmutex_init (&lock) == 0); + type = get_effective_type (&lock); + } + + printf ("type = %s\n", type); + + ASSERT (strcmp (type, "RECURSIVE") == 0); + + return test_exit_status; +} + +#else + +# include <stdio.h> + +int +main () +{ + fputs ("Skipping test: not a native Windows system\n", stderr); + return 77; +} + +#endif -- 2.34.1