Commit Graph

102 Commits

Author SHA1 Message Date
Alejandro Colomar
7dd5ad93a4 Log: fixed typo.
Scripted change:

$ grep -ril recevied src/ | xargs sed -i s/recevied/received/

Reported-by: <https://github.com/jeffdafoe>
Closes: <https://github.com/nginx/unit/issues/920>
Cc: <https://github.com/meezaan>
Cc: Timo Stark <t.stark@nginx.com>
Signed-off-by: Alejandro Colomar <alx@nginx.com>
Reviewed-by: Andrew Clayton <a.clayton@nginx.com>
Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
2023-09-07 23:13:34 +01:00
Andrew Clayton
47ff51009f Wasm: Add support for directory access.
Due to the sandboxed nature of WebAssembly, by default WASM modules
don't have any access to the underlying filesystem.

There is however a capabilities based mechanism[0] for allowing such
access.

This adds a config option to the 'wasm' application type;
'access.filesystem' which takes an array of directory paths that are
then made available to the WASM module. This access works recursively,
i.e everything under a specific path is allowed access to.

Example config might look like

  "access" {
      "filesystem": [
          "/tmp",
          "/var/tmp"
      ]
  }

The actual mechanism used allows directories to be mapped differently in
the guest. But at the moment we don't support that and just map say /tmp
to /tmp. This can be revisited if it's something users clamour for.

Network sockets are another resource that may be controlled in this
manner, for example there is a wasi_config_preopen_socket() function,
however this requires the runtime to open the network socket then
effectively pass this through to the guest.

This is something that can be revisited in the future if users desire
it.

[0]:
<https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-capabilities.md>

Reviewed-by: Alejandro Colomar <alx@nginx.com>
Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
2023-08-17 13:09:49 +01:00
Andrew Clayton
e99854afdf Wasm: Wire up Wasm language module support to the config system.
This exposes various WebAssembly language module specific options.

The application type is "wasm".

There is a "module" option that is required, this specifies the full
path to the WebAssembly module to be run. This module should be in
binary format, i.e a .wasm file.

There are also currently eight function handlers that can be specified.
Three of them are _required_

1) request_handler

The main driving function. This may be called multiple times for a
single HTTP request if the request is larger than the shared memory.

2) malloc_handler

Used to allocate a chunk of memory at language module startup. This
memory is allocated from the WASM modules address space and is what is
sued for communicating between the WASM module (the guest) and Unit (the
host).

3) free_handler

Used to free the memory from above at language module shutdown.

Then there are the following five _optional_ handlers

1) module_init_handler

If set, called at language module startup.

2) module_end_handler

If set, called at language module shutdown.

3) request_init_handler

If set, called at the start of request. Called only once per HTTP
request.

4) request_end_handler

If set, called once all of a request has been sent to the WASM module.

5) response_end_handler

If set, called at the end of a request, once the WASM module has sent
all its headers and data.

Example config

  "applications": {
      "luw-echo-request": {
          "type": "wasm",
          "module": "/path/to/unit-wasm/examples/c/luw-echo-request.wasm",
          "request_handler": "luw_request_handler",
          "malloc_handler": "luw_malloc_handler",
          "free_handler": "luw_free_handler",
          "module_init_handler": "luw_module_init_handler",
          "module_end_handler": "luw_module_end_handler",
      }
  }

Reviewed-by: Alejandro Colomar <alx@nginx.com>
Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
2023-08-17 13:09:49 +01:00
Zhidao HONG
a3c3a29493 NJS: supported loadable modules. 2023-05-08 16:00:25 +08:00
Andrew Clayton
45c45eaeb4 Add per-application logging.
Currently when running in the foreground, unit application processes
will send stdout to the current TTY and stderr to the unit log file.

That behaviour won't change.

When running as a daemon, unit application processes will send stdout to
/dev/null and stderr to the unit log file.

This commit allows to alter the latter case of unit running as a daemon,
by allowing applications to redirect stdout and/or stderr to specific
log files. This is done via two new application options, 'stdout' &
'stderr', e.g

  "applications": {
      "myapp": {
          ...
          "stdout": "/path/to/log/unit/app/stdout.log",
          "stderr": "/path/to/log/unit/app/stderr.log"
      }
  }

These log files are created by the application processes themselves and
thus the log directories need to be writable by the user (and or group)
of the application processes.

E.g

  $ sudo mkdir -p /path/to/log/unit/app
  $ sudo chown APP_USER /path/to/log/unit/app

These need to be setup before starting unit with the above config.

Currently these log files do not participate in log-file rotation
(SIGUSR1), that may change in a future commit. In the meantime these
logs can be rotated using the traditional copy/truncate method.

NOTE:

You may or may not see stuff printed to stdout as stdout was
traditionally used by CGI applications to communicate with the
webserver.

Closes: <https://github.com/nginx/unit/issues/197>
Closes: <https://github.com/nginx/unit/issues/846>
Reviewed-by: Alejandro Colomar <alx@nginx.com>
Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
2023-04-11 19:08:32 +01:00
Andrew Clayton
2e3e1c7e7b Socket: Remove Unix domain listen sockets upon reconfigure.
Currently when using Unix domain sockets for requests, if unit is
reconfigured then it will fail if it tries to bind(2) again to a Unix
domain socket with something like

  2023/02/25 19:15:50 [alert] 35274#35274 bind(\"unix:/tmp/unit.sock\") failed (98: Address already in use)

When closing such a socket we really need to unlink(2) it. However that
presents a problem in that when running as root, while the main process
runs as root and creates the socket, it's the router process, that runs
as an unprivileged user, e.g nobody, that closes the socket and would
thus remove it, but couldn't due to not having permission, even if the
socket is mode 0666, you need write permissions on the containing
directory to remove a file.

There are several options to solve this, all with varying degrees of
complexity and utility.

  1) Give the user who the router process runs as write permission to
     the directory containing the listen sockets. These can then be
     unlink(2)'d from the router process.

     Simple and would work, but perhaps not the most elegant.

  2) Using capabilities(7). The router process could temporarily attain
     the CAP_DAC_OVERRIDE capability, unlink(7) the socket, then
     relinquish the capability until required again.

     These are Linux specific (other systems may have similar mechanisms
     which would be extra work to support). There is also a, albeit
     small, window where the router process is running with elevated
     privileges.

  3) Have the main process do the unlink(2), it is after all the process
     that created the socket.

     This is what this commit implements.

We create a new port IPC message type of NXT_PORT_MSG_SOCKET_UNLINK,
that is used by the router process to notify the main process about a
Unix domain socket to unlink(2).

Upon doing a reconfigure the router process will call
nxt_router_listen_socket_release() which will close the socket, we
extend this function in the case of non-abstract Unix domain sockets, so
that it will send a message to the main process containing a copy of the
nxt_sockaddr_t structure that will contain the filename of the socket.

In the main process the handler that we have defined,
nxt_main_port_socket_unlink_handler(), for this message type will run
and allow us to look for the socket in question in the listen_sockets
array and remove it and unlink(2) the socket.

This then allows the reconfigure to work if it tries to bind(2) again to
a socket that previously existed.

Link: <https://github.com/nginx/unit/issues/669>
Link: <https://github.com/nginx/unit/pull/735>
Reviewed-by: Alejandro Colomar <alx@nginx.com>
Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
2023-03-17 04:28:23 +00:00
Andrew Clayton
ccaad38bc5 Socket: Remove Unix domain listen sockets at shutdown.
If we don't remove the Unix domain listen socket file then when Unit
restarts it get an error like

  2023/02/25 23:10:11 [alert] 36388#36388 bind(\"unix:/tmp/unit.sock\") failed (98: Address already in use)

This patch makes use of the listen_sockets array, that is already
allocated in the main process but never populated, to place the Unix
domain listen sockets into.

At shutdown we can then loop through this array and unlink(2) any Unix
domain sockets found therein.

Closes: <https://github.com/nginx/unit/issues/792>
Reviewed-by: Alejandro Colomar <alx@nginx.com>
Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
2023-03-17 04:03:56 +00:00
Andrew Clayton
3ecdd2c69c Isolation: Rename NXT_HAVE_CLONE -> NXT_HAVE_LINUX_NS.
Due to the need to replace our use of clone/__NR_clone on Linux with
fork(2)/unshare(2) for enabling Linux namespaces(7) to keep the
pthreads(7) API working.  Let's rename NXT_HAVE_CLONE to
NXT_HAVE_LINUX_NS, i.e name it after the feature, not how it's
implemented, then in future if we change how we do namespaces again we
don't have to rename this.

Reviewed-by: Alejandro Colomar <alx@nginx.com>
Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
2023-02-17 21:24:18 +00:00
Andrew Clayton
867a839f10 Isolation: wired up per-application cgroup support internally.
This commit hooks into the cgroup infrastructure added in the previous
commit to create per-application cgroups.

It does this by adding each "prototype process" into its own cgroup,
then each child process inherits its parents cgroup.

If we fail to create a cgroup we simply fail the process. This behaviour
may get enhanced in the future.

This won't actually do anything yet. Subsequent commits will hook this
up to the build and config systems.

Reviewed-by: Alejandro Colomar <alx@nginx.com>
Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
2022-12-10 14:00:20 +00:00
OutOfFocus4
3b970ed934 Removed dead code.
Signed-off-by: Alejandro Colomar <alx@nginx.com>
2022-11-17 21:48:56 +01:00
Alejandro Colomar
d8e0768a5b Fixed support for abstract Unix sockets.
Unix domain sockets are normally backed by files in the
filesystem.  This has historically been problematic when closing
and opening again such sockets, since SO_REUSEADDR is ignored for
Unix sockets (POSIX left the behavior of SO_REUSEADDR as
implementation-defined, and most --if not all-- implementations
decided to just ignore this flag).

Many solutions are available for this problem, but all of them
have important caveats:

- unlink(2) the file when it's not needed anymore.

  This is not easy, because the process that controls the fd may
  not be the same process that created the file, and may not have
  file permissions to remove it.

  Further solutions can be applied to that caveat:

  - unlink(2) the file right after creation.

    This will remove the pathname from the filesystem without
    closing the socket (it will continue to live until the last fd
    is closed).  This is not useful for us, since we need the
    pathname of the socket as its interface.

  - chown(2) or chmod(2) the directory that contains the socket.

    For removing a file from the filesystem, a process needs
    write permissions in the containing directory.  We could
    put sockets in dummy directories that can be chown(2)ed to
    nobody.  This could be dangerous, though, as we don't control
    the socket names.  It is our users who configure the socket
    name in their configuration, and so it's easy that they don't
    understand the many implications of not chosing an appropriate
    socket pathname.  A user could unknowingly put the socket in a
    directory that is not supposed to be owned by user nobody, and
    if we blindly chown(2) or chmod(2) the directory, we could be
    creating a big security hole.

  - Ask the main process to remove the socket.

    This would require a very complex communication mechanism with
    the main process, which is not impossible, but let's avoid it
    if there are simpler solutions.

  - Give the child process the CAP_DAC_OVERRIDE capability.

    That is one of the most powerful capabilities.  A process with
    that capability can be considered root for most practical
    aspects.  Even if the capability is disabled for most of the
    lifetime of the process, there's a slight chance that a
    malicious actor could activate it and then easily do serious
    damage to the system.

- unlink(2) the file right before calling bind(2).

  This is dangerous because another process (for example, another
  running instance of unitd(8)), could be using the socket, and
  removing the pathname from the filesystem would be problematic.
  To do this correctly, a lot of checks should be added before the
  actual unlink(2), which is error-prone, and difficult to do
  correctly, and atomically.

- Use abstract-namespace Unix domain sockets.

  This is the simplest solution, as it only requires accepting a
  slightly different syntax (basically a @ prefix) for the socket
  name, to transform it into a string starting with a null byte
  ('\0') that the kernel can understand.  The patch is minimal.

  Since abstract sockets live in an abstract namespace, they don't
  create files in the filesystem, so there's no need to remove
  them later.  The kernel removes the name when the last fd to it
  has been closed.

  One caveat is that only Linux currently supports this kind of
  Unix sockets.  Of course, a solution to that could be to ask
  other kernels to implement such a feature.

  Another caveat is that filesystem permissions can't be used to
  control access to the socket file (since, of course, there's no
  file).  Anyone knowing the socket name can access to it.  The
  only method to control access to it is by using
  network_namespaces(7).  Since in unitd(8) we're using 0666 file
  sockets, abstract sockets should be no more insecure than that
  (anyone can already read/write to the listener sockets).

- Ask the kernel to implement a simpler way to unlink(2) socket
  files when they are not needed anymore.  I've suggested that to
  the <linux-fsdevel@vger.kernel.org> mailing list, in:
<lore.kernel.org/linux-fsdevel/0bc5f919-bcfd-8fd0-a16b-9f060088158a@gmail.com/T>

In this commit, I decided to go for the easiest/simplest solution,
which is abstract sockets.  In fact, we already had partial
support.  This commit only fixes some small bug in the existing
code so that abstract Unix sockets work:

- Don't chmod(2) the socket if it's an abstract one.

This fixes the creation of abstract sockets, but doesn't make them
usable, since we produce them with a trailing '\0' in their name.
That will be fixed in the following commit.

This closes #669 issue on GitHub.
2022-08-18 18:55:14 +02:00
Alejandro Colomar
a3d19f71a2 Fixed indentation.
Some lines (incorrectly) had an indentation of 3 or 5, or 7 or 9,
or 11 or 13, or 15 or 17 spaces instead of 4, 8, 12, or 16.  Fix them.

Found with:

$ find src -type f | xargs grep -n '^   [^ ]';
$ find src -type f | xargs grep -n '^     [^ *]';
$ find src -type f | xargs grep -n '^       [^ ]';
$ find src -type f | xargs grep -n '^         [^ *]';
$ find src -type f | xargs grep -n '^           [^ +]';
$ find src -type f | xargs grep -n '^             [^ *+]';
$ find src -type f | xargs grep -n '^               [^ +]';
$ find src -type f | xargs grep -n '^                 [^ *+]';
2022-04-26 12:38:48 +02:00
Max Romanov
2bc9599057 Fixing zombie process appearance and hang up on shutdown.
After the c8790d2a89bb commit, the SIGCHLD handler may return before processing
all awaiting PIDs.  To avoid zombie processes and ensure successful main
process termination, waitpid() must be called until an error is returned.

This closes #600 issue on GitHub.
2021-11-24 13:11:50 +03:00
Max Romanov
2c636a03f3 Sending shared port to application prototype.
Application process started with shared port (and queue) already configured.
But still waits for PORT_ACK message from router to start request processing
(so-called "ready state").

Waiting for router confirmation is necessary.  Otherwise, the application may
produce response and send it to router before the router have the information
about the application process.  This is a subject of further optimizations.
2021-11-24 13:11:47 +03:00
Tiago Natel de Moura
e207415a78 Introducing application prototype processes. 2021-11-09 15:48:44 +03:00
Tiago Natel de Moura
1de660b6df Changed nxt_process_* for reuse.
This enables the reuse of process creation functions.
2021-11-09 15:48:44 +03:00
Tiago Natel de Moura
ff6a7053f5 Introduced SCM_CREDENTIALS / SCM_CREDS in the socket control msgs. 2021-11-09 15:48:44 +03:00
Max Romanov
bba97134e9 Moving request limit control to libunit.
Introducting application graceful stop.  For now only used when application
process reach request limit value.

This closes #585 issue on GitHub.
2021-10-28 17:46:54 +03:00
Zhidao HONG
78a4063063 Removed unused declarations.
Declarations became unused after 6976d36be926.

No functional changes.
2021-10-12 10:32:17 +08:00
Zhidao HONG
5fa5b1464f Configuration: automatic migration to the new "share" behavior. 2021-10-09 10:44:31 +08:00
Max Romanov
db03dfad67 Fixed dead assignments.
Found by Clang Static Analyzer.
2021-08-03 13:59:27 +03:00
Oisin Canty
655e321075 Ruby: process and thread lifecycle hooks.
This feature allows one to specify blocks of code that are called when certain
lifecycle events occur.  A user configures a "hooks" property on the app
configuration that points to a script.  This script will be evaluated on boot
and should contain blocks of code that will be called on specific events.

An example of configuration:

{
    "type": "ruby",
    "processes": 2,
    "threads": 2,
    "user": "vagrant",
    "group": "vagrant",
    "script": "config.ru",
    "hooks": "hooks.rb",
    "working_directory": "/home/vagrant/unit/rbhooks",
    "environment": {
        "GEM_HOME": "/home/vagrant/.ruby"
    }
}

An example of a valid "hooks.rb" file follows:

File.write("./hooks.#{Process.pid}", "hooks evaluated")

on_worker_boot do
    File.write("./worker_boot.#{Process.pid}", "worker booted")
end

on_thread_boot do
    File.write("./thread_boot.#{Process.pid}.#{Thread.current.object_id}",
               "thread booted")
end

on_thread_shutdown do
    File.write("./thread_shutdown.#{Process.pid}.#{Thread.current.object_id}",
               "thread shutdown")
end

on_worker_shutdown do
    File.write("./worker_shutdown.#{Process.pid}", "worker shutdown")
end

This closes issue #535 on GitHub.
2021-07-02 12:57:55 +00:00
Oisin Canty
f60389a782 Python: support for multiple targets. 2021-05-20 13:02:45 +00:00
Max Romanov
b1685dbc76 Fixing possible NULL dereference.
For listen socket request reply port can be NULL if Router crashes immediately
after issuing the request.

Found by Coverity (CID 366310).
2021-02-03 23:23:17 +03:00
Max Romanov
8c88537e6e Using shared memory to pass configuration to main process.
This patch is required to remove fragmented messages functionality.
2021-02-03 23:23:06 +03:00
Valentin Bartenev
cac762ab7e Python: multiple values in the "path" option. 2020-12-22 17:53:41 +03:00
Max Romanov
5fd2933d2e Python: supporting ASGI legacy protocol.
Introducing manual protocol selection for 'universal' apps and frameworks.
2020-11-10 22:27:08 +03:00
Max Romanov
d321d454f9 Perl: request processing in multiple threads.
This closes #486 issue on GitHub.
2020-11-05 16:10:59 +03:00
Max Romanov
b6475df79c Ruby: request processing in multiple threads.
This closes #482 issue on GitHub.
2020-11-05 12:45:10 +03:00
Max Romanov
29db46c52b Java: request processing in multiple threads.
This closes #458 issue on GitHub.
2020-11-05 00:06:10 +03:00
Max Romanov
8dcb0b9987 Python: request processing in multiple threads.
This closes #459 issue on GitHub.
2020-11-05 00:04:59 +03:00
Tiago Natel de Moura
0390cb3a61 Isolation: mounting of procfs by default when using "rootfs". 2020-10-29 20:30:53 +00:00
Max Romanov
f16ae01b12 Python: app module callable name configuration.
Now it is possible to specify the name of the application callable using
optional parameter 'callable'.  Default value is 'application'.

This closes #290 issue on GitHub.
2020-09-18 13:41:58 +03:00
Tiago Natel de Moura
b65a8636bb Isolation: added "automount" option.
Now it's possible to disable default bind mounts of
languages by setting:

  {
      "isolation": {
           "automount": {
               "language_deps": false
           }
     }
  }

In this case, the user is responsible to provide a "rootfs"
containing the language libraries and required files for
the application.
2020-08-25 15:25:51 +01:00
Tiago Natel de Moura
a8a7eeb1fc Moved isolation related code to "nxt_isolation.c". 2020-08-20 15:22:58 +01:00
Tiago Natel de Moura
b9ed3384cb Fixed error handling of prefork callback.
Previously, an error during the prefork phase triggered assert:

 src/nxt_port.c:27 assertion failed: port->pair[0] == -1

and resulted in exiting of the main process.

This could be easily reproduced by pushing a configuration with "rootfs",
when daemon is running without required permissions.
2020-08-13 12:25:52 +01:00
Tiago Natel de Moura
e2b53e16c6 Added "rootfs" feature. 2020-05-28 14:57:41 +01:00
Tiago Natel de Moura
e9e5ddd5a5 Refactor of process management.
The process abstraction has changed to:

  setup(task, process)
  start(task, process_data)
  prefork(task, process, mp)

The prefork() occurs in the main process right before fork.

The file src/nxt_main_process.c is completely free of process
specific logic.

The creation of a process now supports a PROCESS_CREATED state.  The
The setup() function of each process can set its state to either
created or ready.  If created, a MSG_PROCESS_CREATED is sent to main
process, where external setup can be done (required for rootfs under
container).

The core processes (discovery, controller and router) doesn't need
external setup, then they all proceeds to their start() function
straight away.

In the case of applications, the load of the module happens at the
process setup() time and The module's init() function has changed
to be the start() of the process.

The module API has changed to:

  setup(task, process, conf)
  start(task, data)

As a direct benefit of the PROCESS_CREATED message, the clone(2) of
processes using pid namespaces now doesn't need to create a pipe
to make the child block until parent setup uid/gid mappings nor it
needs to receive the child pid.
2020-03-09 16:28:25 +00:00
Valentin Bartenev
376d758dd7 PHP: implemented "targets" option.
This allows to specify multiple subsequent targets inside PHP applications.
For example:

  {
      "listeners": {
          "*:80": {
              "pass": "routes"
          }
      },

      "routes": [
          {
              "match": {
                  "uri": "/info"
              },

              "action": {
                  "pass": "applications/my_app/phpinfo"
              }
          },
          {
              "match": {
                  "uri": "/hello"
              },

              "action": {
                  "pass": "applications/my_app/hello"
              }
          },
          {
              "action": {
                  "pass": "applications/my_app/rest"
              }
          }
      ],

      "applications": {
          "my_app": {
              "type": "php",
              "targets": {
                  "phpinfo": {
                      "script": "phpinfo.php",
                      "root": "/www/data/admin",
                  },

                  "hello": {
                      "script": "hello.php",
                      "root": "/www/data/test",
                  },

                  "rest": {
                      "root": "/www/data/example.com",
                      "index": "index.php"
                  },
              }
          }
      }
  }
2020-05-14 13:15:01 +03:00
Max Romanov
64f649f990 Adding "limits/shm" configuration validation and parsing. 2019-12-24 18:04:09 +03:00
Tiago Natel
411daeaa53 Isolation: allowed the use of credentials with unpriv userns.
The setuid/setgid syscalls requires root capabilities but if the kernel
supports unprivileged user namespace then the child process has the full
set of capabilities in the new namespace, then we can allow setting "user"
and "group" in such cases (this is a common security use case).

Tests were added to ensure user gets meaningful error messages for
uid/gid mapping misconfigurations.
2019-12-06 16:52:50 +00:00
Tiago Natel
ed2492a66a Moved credential-related code to nxt_credential.c.
This is required to avoid include cycles, as some nxt_clone_* functions
depend on the credential structures, but nxt_process depends on clone
structures.
2019-12-06 13:28:05 +00:00
Tiago Natel
417cc7be7c Refactor of process init.
Introduces the functions nxt_process_init_create() and
nxt_process_init_creds_set().
2019-11-26 16:26:24 +00:00
Hong Zhi Dao
5d42599e33 Process port refactoring.
- Introduced nxt_runtime_process_port_create().
- Moved nxt_process_use() into nxt_process.c from nxt_runtime.c.
- Renamed nxt_runtime_process_remove_pid() as nxt_runtime_process_remove().
- Some public functions transformed to static.

This closes #327 issue on GitHub.
2019-10-29 16:07:21 +03:00
Max Romanov
65b03a14a7 Allocating process init struct from runtime memory pool.
This avoids memory leak reports from the address sanitizer.
2019-10-29 15:33:45 +03:00
Tiago Natel
0beb8ea5e1 Fixed passing false in namespace flags.
This patch closes #328 in github.
2019-10-11 10:00:06 +00:00
Igor Sysoev
09e7357b08 Style fixes. 2019-10-10 19:42:41 +03:00
Max Romanov
f2b280aa0c Fixed exiting if a signal arrives during discovery.
When Unit starts, the main process waits for module discovery message for a
while.  If a QUIT signal arrives at this time, the router and controller
processes created by main and Unit stay running.  Also, the main process
doesn't stop them after the second QUIT signal is received in this case.
2019-09-30 16:39:08 +03:00
Tiago Natel
93ead87810 Removed linux/sched.h include.
The <sched.h> is already included by nxt_unix.h.

This closes #314 PR on GitHub.
2019-09-20 13:08:04 +00:00
Tiago Natel
20628c74fa Releasing init struct in case of errors.
Found by Coverity (CID 349485).
2019-09-20 13:12:04 +00:00