Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2db009368a | ||
|
|
0233b92eac | ||
|
|
d6a96de4e3 | ||
|
|
06e03193d5 | ||
|
|
dbe681ed2f | ||
|
|
cd69ea9105 | ||
|
|
aeb580ddb9 | ||
|
|
bc4126bfaa | ||
|
|
cb58a5634d | ||
|
|
7326962ec3 |
@ -36,6 +36,11 @@
|
|||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
|
|
||||||
|
#if __sun
|
||||||
|
# include <sys/port.h>
|
||||||
|
# include <port.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Note: May be cast to struct iovec. See writev(2). */
|
/* Note: May be cast to struct iovec. See writev(2). */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char* base;
|
char* base;
|
||||||
@ -52,6 +57,14 @@ typedef uid_t uv_uid_t;
|
|||||||
typedef void* uv_lib_t;
|
typedef void* uv_lib_t;
|
||||||
#define UV_DYNAMIC /* empty */
|
#define UV_DYNAMIC /* empty */
|
||||||
|
|
||||||
|
#if defined(PORT_SOURCE_FILE)
|
||||||
|
# define UV_LOOP_PRIVATE_PLATFORM_FIELDS \
|
||||||
|
ev_io fs_event_watcher; \
|
||||||
|
int fs_fd;
|
||||||
|
#else
|
||||||
|
# define UV_LOOP_PRIVATE_PLATFORM_FIELDS
|
||||||
|
#endif
|
||||||
|
|
||||||
#define UV_LOOP_PRIVATE_FIELDS \
|
#define UV_LOOP_PRIVATE_FIELDS \
|
||||||
ares_channel channel; \
|
ares_channel channel; \
|
||||||
/* \
|
/* \
|
||||||
@ -60,7 +73,8 @@ typedef void* uv_lib_t;
|
|||||||
* definition of ares_timeout(). \
|
* definition of ares_timeout(). \
|
||||||
*/ \
|
*/ \
|
||||||
ev_timer timer; \
|
ev_timer timer; \
|
||||||
struct ev_loop* ev;
|
struct ev_loop* ev; \
|
||||||
|
UV_LOOP_PRIVATE_PLATFORM_FIELDS
|
||||||
|
|
||||||
#define UV_REQ_BUFSML_SIZE (4)
|
#define UV_REQ_BUFSML_SIZE (4)
|
||||||
|
|
||||||
@ -206,12 +220,8 @@ typedef void* uv_lib_t;
|
|||||||
|
|
||||||
#elif defined(__sun)
|
#elif defined(__sun)
|
||||||
|
|
||||||
#include <sys/port.h>
|
|
||||||
#include <port.h>
|
|
||||||
|
|
||||||
#ifdef PORT_SOURCE_FILE
|
#ifdef PORT_SOURCE_FILE
|
||||||
# define UV_FS_EVENT_PRIVATE_FIELDS \
|
# define UV_FS_EVENT_PRIVATE_FIELDS \
|
||||||
ev_io event_watcher; \
|
|
||||||
uv_fs_event_cb cb; \
|
uv_fs_event_cb cb; \
|
||||||
file_obj_t fo;
|
file_obj_t fo;
|
||||||
#else /* !PORT_SOURCE_FILE */
|
#else /* !PORT_SOURCE_FILE */
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "uv.h"
|
#include "uv.h"
|
||||||
#include "unix/internal.h"
|
#include "internal.h"
|
||||||
|
|
||||||
#include <stddef.h> /* NULL */
|
#include <stddef.h> /* NULL */
|
||||||
#include <stdio.h> /* printf */
|
#include <stdio.h> /* printf */
|
||||||
@ -151,6 +151,9 @@ uv_loop_t* uv_loop_new(void) {
|
|||||||
uv_loop_t* loop = calloc(1, sizeof(uv_loop_t));
|
uv_loop_t* loop = calloc(1, sizeof(uv_loop_t));
|
||||||
loop->ev = ev_loop_new(0);
|
loop->ev = ev_loop_new(0);
|
||||||
ev_set_userdata(loop->ev, loop);
|
ev_set_userdata(loop->ev, loop);
|
||||||
|
#if HAVE_PORTS_FS
|
||||||
|
loop->fs_fd = -1;
|
||||||
|
#endif
|
||||||
return loop;
|
return loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,6 +166,12 @@ void uv_loop_delete(uv_loop_t* loop) {
|
|||||||
memset(loop, 0, sizeof *loop);
|
memset(loop, 0, sizeof *loop);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if HAVE_PORTS_FS
|
||||||
|
if (loop->fs_fd != -1) {
|
||||||
|
uv__close(loop->fs_fd);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (loop == default_loop_ptr)
|
if (loop == default_loop_ptr)
|
||||||
default_loop_ptr = NULL;
|
default_loop_ptr = NULL;
|
||||||
else
|
else
|
||||||
@ -182,6 +191,9 @@ uv_loop_t* uv_default_loop(void) {
|
|||||||
default_loop_struct.ev = ev_default_loop(EVBACKEND_KQUEUE);
|
default_loop_struct.ev = ev_default_loop(EVBACKEND_KQUEUE);
|
||||||
#else
|
#else
|
||||||
default_loop_struct.ev = ev_default_loop(EVFLAG_AUTO);
|
default_loop_struct.ev = ev_default_loop(EVFLAG_AUTO);
|
||||||
|
#endif
|
||||||
|
#if HAVE_PORTS_FS
|
||||||
|
default_loop_struct.fs_fd = -1;
|
||||||
#endif
|
#endif
|
||||||
ev_set_userdata(default_loop_struct.ev, default_loop_ptr);
|
ev_set_userdata(default_loop_struct.ev, default_loop_ptr);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,7 @@
|
|||||||
uint64_t uv_hrtime() {
|
uint64_t uv_hrtime() {
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
return (ts.tv_sec * NANOSEC + ts.tv_nsec);
|
return (((uint64_t) ts.tv_sec) * NANOSEC + ts.tv_nsec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void uv_loadavg(double avg[3]) {
|
void uv_loadavg(double avg[3]) {
|
||||||
|
|||||||
@ -2554,7 +2554,6 @@ void
|
|||||||
ev_unref (EV_P)
|
ev_unref (EV_P)
|
||||||
{
|
{
|
||||||
--activecnt;
|
--activecnt;
|
||||||
if (activecnt < 0) abort();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
uint64_t uv_hrtime(void) {
|
uint64_t uv_hrtime(void) {
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
return (ts.tv_sec * NANOSEC + ts.tv_nsec);
|
return (((uint64_t) ts.tv_sec) * NANOSEC + ts.tv_nsec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -134,7 +134,7 @@ static char* basename_r(const char* path) {
|
|||||||
uint64_t uv_hrtime() {
|
uint64_t uv_hrtime() {
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
return (ts.tv_sec * NANOSEC + ts.tv_nsec);
|
return (((uint64_t) ts.tv_sec) * NANOSEC + ts.tv_nsec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void uv_loadavg(double avg[3]) {
|
void uv_loadavg(double avg[3]) {
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
uint64_t uv_hrtime(void) {
|
uint64_t uv_hrtime(void) {
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
return (ts.tv_sec * NANOSEC + ts.tv_nsec);
|
return (((uint64_t) ts.tv_sec) * NANOSEC + ts.tv_nsec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void uv_loadavg(double avg[3]) {
|
void uv_loadavg(double avg[3]) {
|
||||||
|
|||||||
@ -36,7 +36,7 @@
|
|||||||
uint64_t uv_hrtime(void) {
|
uint64_t uv_hrtime(void) {
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
return (ts.tv_sec * NANOSEC + ts.tv_nsec);
|
return (((uint64_t) ts.tv_sec) * NANOSEC + ts.tv_nsec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void uv_loadavg(double avg[3]) {
|
void uv_loadavg(double avg[3]) {
|
||||||
|
|||||||
@ -523,12 +523,19 @@ static void uv__read(uv_stream_t* stream) {
|
|||||||
struct cmsghdr* cmsg;
|
struct cmsghdr* cmsg;
|
||||||
char cmsg_space[64];
|
char cmsg_space[64];
|
||||||
struct ev_loop* ev = stream->loop->ev;
|
struct ev_loop* ev = stream->loop->ev;
|
||||||
|
int count;
|
||||||
|
|
||||||
|
/* Prevent loop starvation when the data comes in as fast as (or faster than)
|
||||||
|
* we can read it. XXX Need to rearm fd if we switch to edge-triggered I/O.
|
||||||
|
*/
|
||||||
|
count = 32;
|
||||||
|
|
||||||
/* XXX: Maybe instead of having UV_READING we just test if
|
/* XXX: Maybe instead of having UV_READING we just test if
|
||||||
* tcp->read_cb is NULL or not?
|
* tcp->read_cb is NULL or not?
|
||||||
*/
|
*/
|
||||||
while ((stream->read_cb || stream->read2_cb) &&
|
while ((stream->read_cb || stream->read2_cb)
|
||||||
stream->flags & UV_READING) {
|
&& (stream->flags & UV_READING)
|
||||||
|
&& (count-- > 0)) {
|
||||||
assert(stream->alloc_cb);
|
assert(stream->alloc_cb);
|
||||||
buf = stream->alloc_cb((uv_handle_t*)stream, 64 * 1024);
|
buf = stream->alloc_cb((uv_handle_t*)stream, 64 * 1024);
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,11 @@
|
|||||||
#if HAVE_PORTS_FS
|
#if HAVE_PORTS_FS
|
||||||
# include <sys/port.h>
|
# include <sys/port.h>
|
||||||
# include <port.h>
|
# include <port.h>
|
||||||
|
|
||||||
|
# define PORT_FIRED 0x69
|
||||||
|
# define PORT_UNUSED 0x0
|
||||||
|
# define PORT_LOADED 0x99
|
||||||
|
# define PORT_DELETED -1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@ -90,36 +95,51 @@ void uv_loadavg(double avg[3]) {
|
|||||||
|
|
||||||
#if HAVE_PORTS_FS
|
#if HAVE_PORTS_FS
|
||||||
static void uv__fs_event_rearm(uv_fs_event_t *handle) {
|
static void uv__fs_event_rearm(uv_fs_event_t *handle) {
|
||||||
if (port_associate(handle->fd,
|
if (handle->fd == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (port_associate(handle->loop->fs_fd,
|
||||||
PORT_SOURCE_FILE,
|
PORT_SOURCE_FILE,
|
||||||
(uintptr_t) &handle->fo,
|
(uintptr_t) &handle->fo,
|
||||||
FILE_ATTRIB | FILE_MODIFIED,
|
FILE_ATTRIB | FILE_MODIFIED,
|
||||||
NULL) == -1) {
|
handle) == -1) {
|
||||||
uv__set_sys_error(handle->loop, errno);
|
uv__set_sys_error(handle->loop, errno);
|
||||||
}
|
}
|
||||||
|
handle->fd = PORT_LOADED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void uv__fs_event_read(EV_P_ ev_io* w, int revents) {
|
static void uv__fs_event_read(EV_P_ ev_io* w, int revents) {
|
||||||
uv_fs_event_t *handle;
|
uv_fs_event_t *handle = NULL;
|
||||||
|
uv_loop_t *loop_;
|
||||||
timespec_t timeout;
|
timespec_t timeout;
|
||||||
port_event_t pe;
|
port_event_t pe;
|
||||||
int events;
|
int events;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
handle = container_of(w, uv_fs_event_t, event_watcher);
|
loop_ = container_of(w, uv_loop_t, fs_event_watcher);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
/* TODO use port_getn() */
|
uint_t n = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note that our use of port_getn() here (and not port_get()) is deliberate:
|
||||||
|
* there is a bug in event ports (Sun bug 6456558) whereby a zeroed timeout
|
||||||
|
* causes port_get() to return success instead of ETIME when there aren't
|
||||||
|
* actually any events (!); by using port_getn() in lieu of port_get(),
|
||||||
|
* we can at least workaround the bug by checking for zero returned events
|
||||||
|
* and treating it as we would ETIME.
|
||||||
|
*/
|
||||||
do {
|
do {
|
||||||
memset(&timeout, 0, sizeof timeout);
|
memset(&timeout, 0, sizeof timeout);
|
||||||
r = port_get(handle->fd, &pe, &timeout);
|
r = port_getn(loop_->fs_fd, &pe, 1, &n, &timeout);
|
||||||
}
|
}
|
||||||
while (r == -1 && errno == EINTR);
|
while (r == -1 && errno == EINTR);
|
||||||
|
|
||||||
if (r == -1 && errno == ETIME)
|
if ((r == -1 && errno == ETIME) || n == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
handle = (uv_fs_event_t *)pe.portev_user;
|
||||||
assert((r == 0) && "unexpected port_get() error");
|
assert((r == 0) && "unexpected port_get() error");
|
||||||
|
|
||||||
events = 0;
|
events = 0;
|
||||||
@ -128,12 +148,12 @@ static void uv__fs_event_read(EV_P_ ev_io* w, int revents) {
|
|||||||
if (pe.portev_events & ~(FILE_ATTRIB | FILE_MODIFIED))
|
if (pe.portev_events & ~(FILE_ATTRIB | FILE_MODIFIED))
|
||||||
events |= UV_RENAME;
|
events |= UV_RENAME;
|
||||||
assert(events != 0);
|
assert(events != 0);
|
||||||
|
handle->fd = PORT_FIRED;
|
||||||
handle->cb(handle, NULL, events, 0);
|
handle->cb(handle, NULL, events, 0);
|
||||||
}
|
}
|
||||||
while (handle->fd != -1);
|
while (handle->fd != PORT_DELETED);
|
||||||
|
|
||||||
if (handle->fd != -1)
|
if (handle != NULL && handle->fd != PORT_DELETED)
|
||||||
uv__fs_event_rearm(handle);
|
uv__fs_event_rearm(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,39 +164,45 @@ int uv_fs_event_init(uv_loop_t* loop,
|
|||||||
uv_fs_event_cb cb,
|
uv_fs_event_cb cb,
|
||||||
int flags) {
|
int flags) {
|
||||||
int portfd;
|
int portfd;
|
||||||
|
int first_run = 0;
|
||||||
|
|
||||||
loop->counters.fs_event_init++;
|
loop->counters.fs_event_init++;
|
||||||
|
|
||||||
/* We don't support any flags yet. */
|
/* We don't support any flags yet. */
|
||||||
assert(!flags);
|
assert(!flags);
|
||||||
|
if (loop->fs_fd == -1) {
|
||||||
if ((portfd = port_create()) == -1) {
|
if ((portfd = port_create()) == -1) {
|
||||||
uv__set_sys_error(loop, errno);
|
uv__set_sys_error(loop, errno);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
loop->fs_fd = portfd;
|
||||||
|
first_run = 1;
|
||||||
|
}
|
||||||
|
|
||||||
uv__handle_init(loop, (uv_handle_t*)handle, UV_FS_EVENT);
|
uv__handle_init(loop, (uv_handle_t*)handle, UV_FS_EVENT);
|
||||||
handle->filename = strdup(filename);
|
handle->filename = strdup(filename);
|
||||||
handle->fd = portfd;
|
handle->fd = PORT_UNUSED;
|
||||||
handle->cb = cb;
|
handle->cb = cb;
|
||||||
|
|
||||||
memset(&handle->fo, 0, sizeof handle->fo);
|
memset(&handle->fo, 0, sizeof handle->fo);
|
||||||
handle->fo.fo_name = handle->filename;
|
handle->fo.fo_name = handle->filename;
|
||||||
uv__fs_event_rearm(handle);
|
uv__fs_event_rearm(handle);
|
||||||
|
|
||||||
ev_io_init(&handle->event_watcher, uv__fs_event_read, portfd, EV_READ);
|
if (first_run) {
|
||||||
ev_io_start(loop->ev, &handle->event_watcher);
|
ev_io_init(&loop->fs_event_watcher, uv__fs_event_read, portfd, EV_READ);
|
||||||
|
ev_io_start(loop->ev, &loop->fs_event_watcher);
|
||||||
ev_unref(loop->ev);
|
ev_unref(loop->ev);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void uv__fs_event_destroy(uv_fs_event_t* handle) {
|
void uv__fs_event_destroy(uv_fs_event_t* handle) {
|
||||||
ev_ref(handle->loop->ev);
|
if (handle->fd == PORT_FIRED) {
|
||||||
ev_io_stop(handle->loop->ev, &handle->event_watcher);
|
port_dissociate(handle->loop->fs_fd, PORT_SOURCE_FILE, (uintptr_t)&handle->fo);
|
||||||
uv__close(handle->fd);
|
}
|
||||||
handle->fd = -1;
|
handle->fd = PORT_DELETED;
|
||||||
free(handle->filename);
|
free(handle->filename);
|
||||||
handle->filename = NULL;
|
handle->filename = NULL;
|
||||||
handle->fo.fo_name = NULL;
|
handle->fo.fo_name = NULL;
|
||||||
|
|||||||
@ -208,12 +208,17 @@ static void uv__udp_recvmsg(uv_udp_t* handle) {
|
|||||||
ssize_t nread;
|
ssize_t nread;
|
||||||
uv_buf_t buf;
|
uv_buf_t buf;
|
||||||
int flags;
|
int flags;
|
||||||
|
int count;
|
||||||
|
|
||||||
assert(handle->recv_cb != NULL);
|
assert(handle->recv_cb != NULL);
|
||||||
assert(handle->alloc_cb != NULL);
|
assert(handle->alloc_cb != NULL);
|
||||||
|
|
||||||
|
/* Prevent loop starvation when the data comes in as fast as (or faster than)
|
||||||
|
* we can read it. XXX Need to rearm fd if we switch to edge-triggered I/O.
|
||||||
|
*/
|
||||||
|
count = 32;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
/* FIXME: hoist alloc_cb out the loop but for now follow uv__read() */
|
|
||||||
buf = handle->alloc_cb((uv_handle_t*)handle, 64 * 1024);
|
buf = handle->alloc_cb((uv_handle_t*)handle, 64 * 1024);
|
||||||
assert(buf.len > 0);
|
assert(buf.len > 0);
|
||||||
assert(buf.base != NULL);
|
assert(buf.base != NULL);
|
||||||
@ -254,6 +259,7 @@ static void uv__udp_recvmsg(uv_udp_t* handle) {
|
|||||||
}
|
}
|
||||||
/* recv_cb callback may decide to pause or close the handle */
|
/* recv_cb callback may decide to pause or close the handle */
|
||||||
while (nread != -1
|
while (nread != -1
|
||||||
|
&& count-- > 0
|
||||||
&& handle->fd != -1
|
&& handle->fd != -1
|
||||||
&& handle->recv_cb != NULL);
|
&& handle->recv_cb != NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,6 +67,9 @@ static void uv_loop_init(uv_loop_t* loop) {
|
|||||||
|
|
||||||
loop->refs = 0;
|
loop->refs = 0;
|
||||||
|
|
||||||
|
/* To prevent uninitialized memory access, loop->time must be intialized */
|
||||||
|
/* to zero before calling uv_update_time for the first time. */
|
||||||
|
loop->time = 0;
|
||||||
uv_update_time(loop);
|
uv_update_time(loop);
|
||||||
|
|
||||||
loop->pending_reqs_tail = NULL;
|
loop->pending_reqs_tail = NULL;
|
||||||
|
|||||||
@ -248,10 +248,8 @@ void fs__open(uv_fs_t* req, const wchar_t* path, int flags, int mode) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Figure out whether path is a file or a directory. */
|
/* Setting this flag makes it possible to open a directory. */
|
||||||
if (GetFileAttributesW(path) & FILE_ATTRIBUTE_DIRECTORY) {
|
|
||||||
attributes |= FILE_FLAG_BACKUP_SEMANTICS;
|
attributes |= FILE_FLAG_BACKUP_SEMANTICS;
|
||||||
}
|
|
||||||
|
|
||||||
file = CreateFileW(path,
|
file = CreateFileW(path,
|
||||||
access,
|
access,
|
||||||
|
|||||||
@ -688,7 +688,7 @@ void uv_process_tty_read_line_req(uv_loop_t* loop, uv_tty_t* handle,
|
|||||||
if (!REQ_SUCCESS(req)) {
|
if (!REQ_SUCCESS(req)) {
|
||||||
/* Read was not successful */
|
/* Read was not successful */
|
||||||
if ((handle->flags & UV_HANDLE_READING) &&
|
if ((handle->flags & UV_HANDLE_READING) &&
|
||||||
!(handle->flags & UV_HANDLE_TTY_RAW)) {
|
handle->read_line_handle != NULL) {
|
||||||
/* Real error */
|
/* Real error */
|
||||||
handle->flags &= ~UV_HANDLE_READING;
|
handle->flags &= ~UV_HANDLE_READING;
|
||||||
uv__set_sys_error(loop, GET_REQ_ERROR(req));
|
uv__set_sys_error(loop, GET_REQ_ERROR(req));
|
||||||
|
|||||||
@ -86,6 +86,7 @@ int run_test(const char* test, int timeout, int benchmark_output) {
|
|||||||
int i;
|
int i;
|
||||||
|
|
||||||
status = 255;
|
status = 255;
|
||||||
|
main_proc = NULL;
|
||||||
process_count = 0;
|
process_count = 0;
|
||||||
|
|
||||||
/* If it's a helper the user asks for, start it directly. */
|
/* If it's a helper the user asks for, start it directly. */
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user