View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0001975 | GNUnet | Win32 port (deprecated) | public | 2011-12-02 02:58 | 2011-12-26 22:28 |
Reporter | LRN | Assigned To | LRN | ||
Priority | high | Severity | major | Reproducibility | N/A |
Status | closed | Resolution | fixed | ||
Product Version | Git master | ||||
Target Version | 0.9.1 | Fixed in Version | 0.9.1 | ||
Summary | 0001975: Implement pass-sockets-from-arm-to-service for W32 | ||||
Description | Right now this feature is not available on W32, arm interceptor pipes the data to the service for the first connection instead. Piping itself is bad (unnecessarily increases CPU load), but the real problem is that in some (rare) circumstances two concurrent connections to the service that is not running will "succeed", but one of them will actually be accepted and piped by arm, while the other will be immediately closed. Normally only one connection will succeed initially, the other will succeed much later, and will connect to the real service, once it's running. When both connection "succeed" at the arm interception stage, the second one is closed. The handling of that event depend on the service that requested the connection, but at least one testcase (test_mesh_local_1) did not handle it well. Passing the listening socket from arm to the service it runs will fix that, since the socket will never be closed, and the second connection won't have a chance to complete in a wrong way. This might also indicate a bug in network.c. However, it is theoretically possible (IMO) for someone to connect immediately after arm accept()s the first connection, but before it had an opportunity to stop listening on the socket. Anyway, the patch for socket passing is attached. | ||||
Tags | No tags attached. | ||||
Attached Files | 0005-Implement-pass-sockets-from-arm-to-service-for-W32.patch (17,491 bytes)
From a745a93ecad1f1a0dbd20dd3678da25ce1f1522f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D0=BB=D0=B0=D0=BD=20=D0=98=D0=B6=D0=B1=D1?= =?UTF-8?q?=83=D0=BB=D0=B0=D1=82=D0=BE=D0=B2?= <lrn1986@gmail.com> Date: Fri, 2 Dec 2011 05:41:50 +0400 Subject: [PATCH 5/5] Implement pass-sockets-from-arm-to-service for W32 This creates an anonymous pipe and feeds the data necessary for socket duplication over it to the child process. Makes a change in several places to use SOCKTYPE instead of int, when sockets are involved. On W32 sockets are uints, not ints. Adds socket boxing utility function implementation for W32. --- src/arm/do_start_process.c | 2 +- src/arm/gnunet-service-arm.c | 4 +- src/arm/gnunet-service-arm.h | 2 +- src/arm/gnunet-service-arm_interceptor.c | 20 +++-- src/include/gnunet_network_lib.h | 5 +- src/include/gnunet_os_lib.h | 2 +- src/include/platform.h | 8 ++ src/util/network.c | 15 +++- src/util/os_priority.c | 133 ++++++++++++++++++++++++++++-- src/util/service.c | 85 +++++++++++++++++++ 10 files changed, 250 insertions(+), 26 deletions(-) diff --git a/src/arm/do_start_process.c b/src/arm/do_start_process.c index 19b3bc2..f4d3424 100644 --- a/src/arm/do_start_process.c +++ b/src/arm/do_start_process.c @@ -13,7 +13,7 @@ * @return PID of the started process, -1 on error */ static struct GNUNET_OS_Process * -do_start_process (const int *lsocks, const char *first_arg, ...) +do_start_process (const SOCKTYPE *lsocks, const char *first_arg, ...) { va_list ap; char **argv; diff --git a/src/arm/gnunet-service-arm.c b/src/arm/gnunet-service-arm.c index 718d902..d99020d 100644 --- a/src/arm/gnunet-service-arm.c +++ b/src/arm/gnunet-service-arm.c @@ -331,7 +331,7 @@ free_service (struct ServiceList *pos) * @param lsocks -1 terminated list of listen sockets to pass (systemd style), or NULL */ static void -start_process (struct ServiceList *sl, const int *lsocks) +start_process (struct ServiceList *sl, const SOCKTYPE *lsocks) { char *loprefix; char *options; @@ -422,7 +422,7 @@ start_process (struct ServiceList *sl, const int *lsocks) */ int start_service (struct GNUNET_SERVER_Client *client, const char *servicename, - const int *lsocks) + const SOCKTYPE *lsocks) { struct ServiceList *sl; char *binary; diff --git a/src/arm/gnunet-service-arm.h b/src/arm/gnunet-service-arm.h index 689f26c..8b17ec8 100644 --- a/src/arm/gnunet-service-arm.h +++ b/src/arm/gnunet-service-arm.h @@ -37,7 +37,7 @@ */ int start_service (struct GNUNET_SERVER_Client *client, const char *servicename, - const int *lsocks); + const SOCKTYPE *lsocks); /** * Stop listening for connections to a service. diff --git a/src/arm/gnunet-service-arm_interceptor.c b/src/arm/gnunet-service-arm_interceptor.c index 24dd4ad..e4ad121 100644 --- a/src/arm/gnunet-service-arm_interceptor.c +++ b/src/arm/gnunet-service-arm_interceptor.c @@ -980,9 +980,9 @@ acceptConnection (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) struct ServiceListeningInfo *sli = cls; struct ServiceListeningInfo *pos; struct ServiceListeningInfo *next; - int *lsocks; + SOCKTYPE *lsocks; unsigned int ls; - int use_lsocks; + int disable_lsocks; sli->acceptTask = GNUNET_SCHEDULER_NO_TASK; if (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason)) @@ -990,17 +990,17 @@ acceptConnection (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) GNUNET_CONTAINER_DLL_remove (serviceListeningInfoList_head, serviceListeningInfoList_tail, sli); #ifndef MINGW - use_lsocks = GNUNET_NO; + disable_lsocks = GNUNET_NO; if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (cfg, sli->serviceName, "DISABLE_SOCKET_FORWARDING")) - use_lsocks = + disable_lsocks = GNUNET_CONFIGURATION_get_value_yesno (cfg, sli->serviceName, "DISABLE_SOCKET_FORWARDING"); #else - use_lsocks = GNUNET_YES; + disable_lsocks = GNUNET_NO; #endif - if (GNUNET_NO != use_lsocks) + if (GNUNET_NO != disable_lsocks) { accept_and_forward (sli); return; @@ -1028,11 +1028,19 @@ acceptConnection (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) GNUNET_NETWORK_get_fd (sli->listeningSocket)); GNUNET_free (sli->listeningSocket); /* deliberately no closing! */ GNUNET_free (sli->service_addr); +#if WINDOWS + GNUNET_array_append (lsocks, ls, INVALID_SOCKET); +#else GNUNET_array_append (lsocks, ls, -1); +#endif start_service (NULL, sli->serviceName, lsocks); ls = 0; while (lsocks[ls] != -1) +#if WINDOWS + GNUNET_break (0 == closesocket (lsocks[ls++])); +#else GNUNET_break (0 == close (lsocks[ls++])); +#endif GNUNET_array_grow (lsocks, ls, 0); GNUNET_free (sli->serviceName); GNUNET_free (sli); diff --git a/src/include/gnunet_network_lib.h b/src/include/gnunet_network_lib.h index 65b2042..a14d5f0 100644 --- a/src/include/gnunet_network_lib.h +++ b/src/include/gnunet_network_lib.h @@ -96,7 +96,7 @@ GNUNET_NETWORK_socket_accept (const struct GNUNET_NETWORK_Handle *desc, * @return NULL on error (including not supported on target platform) */ struct GNUNET_NETWORK_Handle * -GNUNET_NETWORK_socket_box_native (int fd); +GNUNET_NETWORK_socket_box_native (SOCKTYPE fd); /** @@ -320,8 +320,7 @@ GNUNET_NETWORK_fdset_set (struct GNUNET_NETWORK_FDSet *fds, const struct GNUNET_NETWORK_Handle *desc); -#ifdef __MINGW32__ -/* TODO: maybe #ifdef WINDOWS? -ndurner */ +#if WINDOWS /** * Add a W32 file handle to the fd set * @param fds fd set diff --git a/src/include/gnunet_os_lib.h b/src/include/gnunet_os_lib.h index aed5e3b..ff324e1 100644 --- a/src/include/gnunet_os_lib.h +++ b/src/include/gnunet_os_lib.h @@ -275,7 +275,7 @@ GNUNET_OS_start_process_va (struct GNUNET_DISK_PipeHandle *pipe_stdin, * @return pointer to process structure of the new process, NULL on error */ struct GNUNET_OS_Process * -GNUNET_OS_start_process_v (const int *lsocks, const char *filename, +GNUNET_OS_start_process_v (const SOCKTYPE *lsocks, const char *filename, char *const argv[]); diff --git a/src/include/platform.h b/src/include/platform.h index 8c1e6d6..9ab0f25 100644 --- a/src/include/platform.h +++ b/src/include/platform.h @@ -251,4 +251,12 @@ atoll (const char *nptr); #define MAKE_UNALIGNED(val) val #endif +#if WINDOWS +#define FDTYPE HANDLE +#define SOCKTYPE SOCKET +#else +#define FDTYPE int +#define SOCKTYPE int +#endif + #endif diff --git a/src/util/network.c b/src/util/network.c index c4861ec..d8c6427 100644 --- a/src/util/network.c +++ b/src/util/network.c @@ -377,13 +377,20 @@ GNUNET_NETWORK_socket_close (struct GNUNET_NETWORK_Handle *desc) * @return NULL on error (including not supported on target platform) */ struct GNUNET_NETWORK_Handle * -GNUNET_NETWORK_socket_box_native (int fd) +GNUNET_NETWORK_socket_box_native (SOCKTYPE fd) { + struct GNUNET_NETWORK_Handle *ret; #if MINGW - return NULL; + unsigned long i; + DWORD d; + /* FIXME: Find a better call to check that FD is valid */ + if (WSAIoctl (fd, FIONBIO, (void *) &i, sizeof (i), NULL, 0, &d, NULL, NULL) != 0) + return NULL; /* invalid FD */ + ret = GNUNET_malloc (sizeof (struct GNUNET_NETWORK_Handle)); + ret->fd = fd; + ret->af = AF_UNSPEC; + return ret; #else - struct GNUNET_NETWORK_Handle *ret; - if (fcntl (fd, F_GETFD) < 0) return NULL; /* invalid FD */ ret = GNUNET_malloc (sizeof (struct GNUNET_NETWORK_Handle)); diff --git a/src/util/os_priority.c b/src/util/os_priority.c index 5aaa304..5cf8113 100644 --- a/src/util/os_priority.c +++ b/src/util/os_priority.c @@ -853,7 +853,8 @@ GNUNET_OS_start_process (struct GNUNET_DISK_PipeHandle *pipe_stdin, * @return process ID of the new process, -1 on error */ struct GNUNET_OS_Process * -GNUNET_OS_start_process_v (const int *lsocks, const char *filename, +GNUNET_OS_start_process_v (const SOCKTYPE *lsocks, + const char *filename, char *const argv[]) { #if ENABLE_WINDOWS_WORKAROUNDS @@ -980,7 +981,7 @@ GNUNET_OS_start_process_v (const int *lsocks, const char *filename, char path[MAX_PATH + 1]; - char *our_env[3] = { NULL, NULL, NULL }; + char *our_env[5] = { NULL, NULL, NULL, NULL, NULL }; char *env_block = NULL; char *pathbuf; DWORD pathbuf_len, alloc_len; @@ -989,8 +990,12 @@ GNUNET_OS_start_process_v (const int *lsocks, const char *filename, char *libdir; char *ptr; char *non_const_filename; + struct GNUNET_DISK_PipeHandle *lsocks_pipe; + const struct GNUNET_DISK_FileHandle *lsocks_write_fd; + HANDLE lsocks_read; + HANDLE lsocks_write; - GNUNET_assert (lsocks == NULL); + int fail; /* Search in prefix dir (hopefully - the directory from which * the current module was loaded), bindir and libdir, then in PATH @@ -1104,6 +1109,25 @@ GNUNET_OS_start_process_v (const int *lsocks, const char *filename, GNUNET_free (path); return NULL; } + if (lsocks != NULL) + { + lsocks_pipe = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_NO); + + if (lsocks_pipe == NULL) + { + GNUNET_free (cmd); + GNUNET_free (path); + GNUNET_DISK_pipe_close (lsocks_pipe); + return NULL; + } + lsocks_write_fd = GNUNET_DISK_pipe_handle (lsocks_pipe, + GNUNET_DISK_PIPE_END_WRITE); + GNUNET_DISK_internal_file_handle_ (lsocks_write_fd, + &lsocks_write, sizeof (HANDLE)); + GNUNET_DISK_internal_file_handle_ (GNUNET_DISK_pipe_handle + (lsocks_pipe, GNUNET_DISK_PIPE_END_READ), + &lsocks_read, sizeof (HANDLE)); + } #if DEBUG_OS LOG (GNUNET_ERROR_TYPE_DEBUG, "Opened the parent end of the pipe `%s'\n", @@ -1112,17 +1136,31 @@ GNUNET_OS_start_process_v (const int *lsocks, const char *filename, GNUNET_asprintf (&our_env[0], "%s=", GNUNET_OS_CONTROL_PIPE); GNUNET_asprintf (&our_env[1], "%s", childpipename); - our_env[2] = NULL; + GNUNET_free (childpipename); + if (lsocks == NULL) + our_env[2] = NULL; + else + { + /*This will tell the child that we're going to send lsocks over the pipe*/ + GNUNET_asprintf (&our_env[2], "%s=", "GNUNET_OS_READ_LSOCKS"); + GNUNET_asprintf (&our_env[3], "%lu", lsocks_read); + our_env[4] = NULL; + } env_block = CreateCustomEnvTable (our_env); - GNUNET_free (our_env[0]); - GNUNET_free (our_env[1]); + GNUNET_free_non_null (our_env[0]); + GNUNET_free_non_null (our_env[1]); + GNUNET_free_non_null (our_env[2]); + GNUNET_free_non_null (our_env[3]); - if (!CreateProcess - (path, cmd, NULL, NULL, FALSE, DETACHED_PROCESS | CREATE_SUSPENDED, + if (!CreateProcessA + (path, cmd, NULL, NULL, TRUE, DETACHED_PROCESS | CREATE_SUSPENDED, env_block, NULL, &start, &proc)) { SetErrnoFromWinError (GetLastError ()); LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "CreateProcess"); + GNUNET_DISK_npipe_close (control_pipe); + if (lsocks != NULL) + GNUNET_DISK_pipe_close (lsocks_pipe); GNUNET_free (env_block); GNUNET_free (cmd); return NULL; @@ -1141,6 +1179,85 @@ GNUNET_OS_start_process_v (const int *lsocks, const char *filename, CloseHandle (proc.hThread); GNUNET_free (cmd); + if (lsocks == NULL) + return gnunet_proc; + + GNUNET_DISK_pipe_close_end (lsocks_pipe, GNUNET_DISK_PIPE_END_READ); + + /* This is a replacement for "goto error" that doesn't use goto */ + fail = 1; + do + { + int wrote; + uint64_t size, count, i; + + /* Tell the number of sockets */ + for (count = 0; lsocks && lsocks[count] != INVALID_SOCKET; count++); + + wrote = GNUNET_DISK_file_write (lsocks_write_fd, &count, sizeof (count)); + if (wrote != sizeof (count)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to write %u count bytes to the child: %u\n", sizeof (count), GetLastError ()); + break; + } + for (i = 0; lsocks && lsocks[i] != INVALID_SOCKET; i++) + { + WSAPROTOCOL_INFOA pi; + /* Get a socket duplication info */ + if (SOCKET_ERROR == WSADuplicateSocketA (lsocks[i], gnunet_proc->pid, &pi)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to duplicate an socket[%llu]: %u\n", i, GetLastError ()); + LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "CreateProcess"); + break; + } + /* Synchronous I/O is not nice, but we can't schedule this: + * lsocks will be closed/freed by the caller soon, and until + * the child creates a duplicate, closing a socket here will + * close it for good. + */ + /* Send the size of the structure + * (the child might be built with different headers...) + */ + size = sizeof (pi); + wrote = GNUNET_DISK_file_write (lsocks_write_fd, &size, sizeof (size)); + if (wrote != sizeof (size)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to write %u size[%llu] bytes to the child: %u\n", sizeof (size), i, GetLastError ()); + break; + } + /* Finally! Send the data */ + wrote = GNUNET_DISK_file_write (lsocks_write_fd, &pi, sizeof (pi)); + if (wrote != sizeof (pi)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to write %u socket[%llu] bytes to the child: %u\n", sizeof (pi), i, GetLastError ()); + break; + } + } + /* This will block us until the child makes a final read or closes + * the pipe (hence no 'wrote' check), since we have to wait for it + * to duplicate the last socket, before we return and start closing + * our own copies) + */ + wrote = GNUNET_DISK_file_write (lsocks_write_fd, &count, sizeof (count)); + fail = 0; + } + while (fail); + + GNUNET_DISK_file_sync (lsocks_write_fd); + GNUNET_DISK_pipe_close (lsocks_pipe); + + if (fail) + { + /* If we can't pass on the socket(s), the child will block forever, + * better put it out of its misery. + */ + TerminateProcess (gnunet_proc->handle, 0); + CloseHandle (gnunet_proc->handle); + GNUNET_DISK_npipe_close (gnunet_proc->control_pipe); + GNUNET_free (gnunet_proc); + return NULL; + } + return gnunet_proc; #endif } diff --git a/src/util/service.c b/src/util/service.c index 91fc460..949d821 100644 --- a/src/util/service.c +++ b/src/util/service.c @@ -1078,6 +1078,85 @@ GNUNET_SERVICE_get_server_addresses (const char *serviceName, return resi; } +/* + * Returns GNUNET_YES if ok, GNUNET_NO if not ok (must bind yourself), + * and GNUNET_SYSERR on error. + */ +static int +receive_sockets_from_parent (struct GNUNET_SERVICE_Context *sctx) +{ + const char *env_buf; + int fail; + uint64_t count, i; + HANDLE lsocks_pipe; + + env_buf = getenv ("GNUNET_OS_READ_LSOCKS"); + if ((env_buf == NULL) || (strlen (env_buf) <= 0)) + { + return GNUNET_NO; + } + /* Using W32 API directly here, because this pipe will + * never be used outside of this function, and it's just too much of a bother + * to create a GNUnet API that boxes a HANDLE (the way it is done with socks) + */ + lsocks_pipe = (HANDLE) strtoul (env_buf, NULL, 10); + if (lsocks_pipe == 0 || lsocks_pipe == INVALID_HANDLE_VALUE) + return GNUNET_NO; + + fail = 1; + do + { + int ret; + int fail2; + DWORD rd; + + ret = ReadFile (lsocks_pipe, &count, sizeof (count), &rd, NULL); + if (ret == 0 || rd != sizeof (count) || count == 0) + break; + sctx->lsocks = + GNUNET_malloc (sizeof (struct GNUNET_NETWORK_Handle *) * (count + 1)); + + fail2 = 1; + for (i = 0; i < count; i++) + { + WSAPROTOCOL_INFOA pi; + uint64_t size; + SOCKET s; + ret = ReadFile (lsocks_pipe, &size, sizeof (size), &rd, NULL); + if (ret == 0 || rd != sizeof (size) || size != sizeof (pi)) + break; + ret = ReadFile (lsocks_pipe, &pi, sizeof (pi), &rd, NULL); + if (ret == 0 || rd != sizeof (pi)) + break; + s = WSASocketA (pi.iAddressFamily, pi.iSocketType, pi.iProtocol, &pi, 0, WSA_FLAG_OVERLAPPED); + sctx->lsocks[i] = GNUNET_NETWORK_socket_box_native (s); + if (sctx->lsocks[i] == NULL) + break; + else if (i == count - 1) + fail2 = 0; + } + if (fail2) + break; + sctx->lsocks[count] = NULL; + fail = 0; + } + while (fail); + + CloseHandle (lsocks_pipe); + + if (fail) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Could not access a pre-bound socket, will try to bind myself\n")); + for (i = 0; sctx->lsocks[i] != NULL && i < count; i++) + GNUNET_break (0 == GNUNET_NETWORK_socket_close (sctx->lsocks[i])); + GNUNET_free (sctx->lsocks); + sctx->lsocks = NULL; + return GNUNET_NO; + } + + return GNUNET_YES; +} /** * Setup addr, addrlen, idle_timeout @@ -1175,6 +1254,12 @@ setup_service (struct GNUNET_SERVICE_Context *sctx) unsetenv ("LISTEN_PID"); unsetenv ("LISTEN_FDS"); } +#else + if (getenv ("GNUNET_OS_READ_LSOCKS") != NULL) + { + receive_sockets_from_parent (sctx); + putenv ("GNUNET_OS_READ_LSOCKS="); + } #endif if ((sctx->lsocks == NULL) && -- 1.7.4 | ||||
|
ok, maybe using SOCKTYPE was not a good idea, since this requires platform.h to be included more often (which is not a problem for GNUnet itself, but, for example, gnunet-gtk configure script does not do this; which can be fixed, but i'm starting to think maybe it will cause too much trouble). |
|
I get this post-patch on GNU/Linux: service.c: In function ‘receive_sockets_from_parent’: service.c:1091: error: ‘HANDLE’ undeclared (first use in this function) service.c:1091: error: (Each undeclared identifier is reported only once service.c:1091: error: for each function it appears in.) service.c:1091: error: expected ‘;’ before ‘lsocks_pipe’ service.c:1102: error: ‘lsocks_pipe’ undeclared (first use in this function) service.c:1102: error: expected ‘;’ before ‘strtoul’ service.c:1103: error: ‘INVALID_HANDLE_VALUE’ undeclared (first use in this function) service.c:1111: error: ‘DWORD’ undeclared (first use in this function) service.c:1111: error: expected ‘;’ before ‘rd’ cc1: warnings being treated as errors service.c:1113: error: implicit declaration of function ‘ReadFile’ service.c:1113: error: ‘rd’ undeclared (first use in this function) service.c:1122: error: ‘WSAPROTOCOL_INFOA’ undeclared (first use in this function) service.c:1122: error: expected ‘;’ before ‘pi’ service.c:1124: error: ‘SOCKET’ undeclared (first use in this function) service.c:1124: error: expected ‘;’ before ‘s’ service.c:1126: error: ‘pi’ undeclared (first use in this function) service.c:1131: error: ‘s’ undeclared (first use in this function) service.c:1131: error: implicit declaration of function ‘WSASocketA’ service.c:1131: error: ‘WSA_FLAG_OVERLAPPED’ undeclared (first use in this function) service.c:1145: error: implicit declaration of function ‘CloseHandle’ |
|
Trivial to fix. Will include in SVN patch. |
|
Patch applied in SVN 18473 (I'll resolve the issue as 99% of it is done with this part). |
|
Not sure patch is working yet... |
|
Should be fixed now. |
Date Modified | Username | Field | Change |
---|---|---|---|
2011-12-02 02:58 | LRN | New Issue | |
2011-12-02 02:58 | LRN | Status | new => assigned |
2011-12-02 02:58 | LRN | Assigned To | => NDurner |
2011-12-02 02:58 | LRN | File Added: 0005-Implement-pass-sockets-from-arm-to-service-for-W32.patch | |
2011-12-02 05:16 | LRN | Note Added: 0005007 | |
2011-12-06 15:30 | Christian Grothoff | Priority | low => high |
2011-12-06 15:30 | Christian Grothoff | Target Version | => 0.9.1 |
2011-12-06 18:57 | Christian Grothoff | Note Added: 0005013 | |
2011-12-06 18:58 | Christian Grothoff | Note Added: 0005014 | |
2011-12-06 18:59 | Christian Grothoff | Note Added: 0005015 | |
2011-12-06 18:59 | Christian Grothoff | Status | assigned => resolved |
2011-12-06 18:59 | Christian Grothoff | Fixed in Version | => 0.9.1 |
2011-12-06 18:59 | Christian Grothoff | Resolution | open => fixed |
2011-12-06 18:59 | Christian Grothoff | Assigned To | NDurner => LRN |
2011-12-06 19:04 | Christian Grothoff | Note Added: 0005016 | |
2011-12-06 19:04 | Christian Grothoff | Status | resolved => feedback |
2011-12-06 19:04 | Christian Grothoff | Resolution | fixed => reopened |
2011-12-08 17:00 | Christian Grothoff | Note Added: 0005024 | |
2011-12-08 17:00 | Christian Grothoff | Status | feedback => resolved |
2011-12-08 17:00 | Christian Grothoff | Resolution | reopened => fixed |
2011-12-26 22:28 | Christian Grothoff | Status | resolved => closed |
2024-01-12 14:28 | schanzen | Category | Win32 port => Win32 port (deprecated) |