mirror of
https://github.com/MidnightCommander/mc
synced 2024-12-23 04:46:55 +03:00
Removed src/pipethrough.* files.
Glib have fully-functional replacement: http://library.gnome.org/devel/glib/stable/glib-Spawning-Processes.html Signed-off-by: Slava Zanko <slavazanko@gmail.com>
This commit is contained in:
parent
862652bd1b
commit
8172b511bb
@ -1,65 +0,0 @@
|
||||
.\" $NetBSD: mdoc.template,v 1.5 2002/01/12 02:24:29 wiz Exp $
|
||||
.\"
|
||||
.\" Copyright notice
|
||||
.\"
|
||||
.\" The uncommented requests are required for all man pages.
|
||||
.\" The commented requests should be uncommented and used where appropriate.
|
||||
.Dd September 24, 2004
|
||||
.Dt pipethrough 3
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm pipethrough
|
||||
.Nd pipe a buffer through a child command and receive two
|
||||
buffers containing the stdout and stderr output of the child process.
|
||||
.Sh LIBRARY
|
||||
none -- must be included directly.
|
||||
.Sh SYNOPSIS
|
||||
.Bd -literal
|
||||
#include <pipethrough.h>
|
||||
|
||||
extern int pipethrough(const char *command,
|
||||
const struct pipe_inbuffer *stdin_buf,
|
||||
/*@out@*/ struct pipe_outbuffer *stdout_buf,
|
||||
/*@out@*/ struct pipe_outbuffer *stderr_buf,
|
||||
/*@out@*/ int *status);
|
||||
.Ed
|
||||
.Sh DESCRIPTION
|
||||
The pipethrough function executes the \fIcommand\fR via the shell. The
|
||||
command's input is the data provided in \fIstdin_buf\fR. The output buffers
|
||||
\fIstdout_buf\fR and \fIstderr_buf\fR will be filled with the output of the
|
||||
command. After usage they must be freed using pipe_outbuffer_finalize(3).
|
||||
.Sh RETURN VALUES
|
||||
On success, pipethrough returns 0. On failure it returns -1 and sets
|
||||
\fIerrno\fR to the first error that occurred. Later errors are discarded
|
||||
silently.
|
||||
.Sh ENVIRONMENT
|
||||
.Bl -tag -width Fl
|
||||
.It SHELL
|
||||
The shell invoked by pipethrough. Defaults to /bin/sh.
|
||||
.El
|
||||
.Pp
|
||||
.Sh FILES
|
||||
.Bl -tag -width Fl
|
||||
.It /bin/sh
|
||||
The default shell.
|
||||
.El
|
||||
.Pp
|
||||
.Sh EXAMPLES
|
||||
TODO
|
||||
.Sh ERRORS
|
||||
The various errors that can occur during execution of close(2), dup2(2),
|
||||
read(2), write(2), execl(3).
|
||||
.\" .Sh SEE ALSO
|
||||
.\" Cross-references should be ordered by section (low to high), then in
|
||||
.\" alphabetical order.
|
||||
.Sh STANDARDS
|
||||
pipethrough does not appear in any standard I know of.
|
||||
.\" .Sh HISTORY
|
||||
.Sh AUTHORS
|
||||
Roland Illig
|
||||
.Aq roland.illig@gmx.de .
|
||||
.Sh BUGS
|
||||
None known yet. If you find bugs, please report them to the authors.
|
||||
.Sh SECURITY CONSIDERATIONS
|
||||
The \fIcommand\fR must be a properly quoted shell command. That is,
|
||||
don't include file names without quoting them.
|
@ -1,458 +0,0 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Roland Illig <roland.illig@gmx.de>, 2004.
|
||||
*/
|
||||
|
||||
|
||||
/** \file pipethrough.c
|
||||
* \brief Source: a module for piping
|
||||
*
|
||||
* Pipe a buffer through a child command and receive two containing
|
||||
* the stdout and stderr output of the child process.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h> /* On Solaris, FD_SET invokes memset(3) */
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
#ifdef HAVE_SYS_SELECT_H
|
||||
# include <sys/select.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
|
||||
#include "pipethrough.h"
|
||||
|
||||
#define PIPE_RD 0
|
||||
#define PIPE_WR 1
|
||||
|
||||
/* Internal data types *************************************************/
|
||||
|
||||
struct writer_buffer {
|
||||
/*@temp@*/ const void *data;
|
||||
size_t size;
|
||||
size_t pos;
|
||||
int fd;
|
||||
int lasterror;
|
||||
};
|
||||
|
||||
struct reader_buffer {
|
||||
/*@null@*/ /*@only@*/ void *data;
|
||||
size_t size;
|
||||
size_t maxsize;
|
||||
int fd;
|
||||
int lasterror;
|
||||
};
|
||||
|
||||
struct select_request {
|
||||
int n;
|
||||
fd_set readfds;
|
||||
fd_set writefds;
|
||||
};
|
||||
|
||||
/* Helper functions ****************************************************/
|
||||
|
||||
static int closeref(int *fd)
|
||||
/*@globals internalState, fileSystem, errno; @*/
|
||||
/*@modifies internalState, fileSystem, errno, *fd; @*/
|
||||
{
|
||||
int result = close(*fd);
|
||||
*fd = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void propagate(int *dest, int src)
|
||||
/*@modifies *dest; @*/
|
||||
{
|
||||
if (*dest == 0) {
|
||||
*dest = src;
|
||||
}
|
||||
}
|
||||
|
||||
/* reader_buffer -- implementation *************************************/
|
||||
|
||||
static void reader_buffer_init(/*@out@*/ struct reader_buffer *buf, int fd)
|
||||
/*@modifies *buf; @*/
|
||||
{
|
||||
buf->data = NULL;
|
||||
buf->size = 0;
|
||||
buf->maxsize = 0;
|
||||
buf->fd = fd;
|
||||
buf->lasterror = 0;
|
||||
}
|
||||
|
||||
/* SPlint cannot describe realloc(3) correctly. */
|
||||
/*@-usereleased@*/ /*@-compdef@*/ /*@-branchstate@*/ /*@-compmempass@*/
|
||||
static ssize_t reader_buffer_read(struct reader_buffer *buf)
|
||||
/*@globals internalState, errno; @*/
|
||||
/*@modifies internalState, errno, *buf; @*/
|
||||
{
|
||||
ssize_t res;
|
||||
|
||||
if (buf->size == buf->maxsize) {
|
||||
size_t newsize = buf->maxsize + 4096;
|
||||
/*@only@*/ void *newdata;
|
||||
if (buf->data == NULL) {
|
||||
newdata = malloc(newsize);
|
||||
} else {
|
||||
newdata = realloc(buf->data, newsize);
|
||||
}
|
||||
if (newdata == NULL) {
|
||||
return (ssize_t) -1;
|
||||
}
|
||||
buf->data = newdata;
|
||||
newdata = NULL;
|
||||
buf->maxsize = newsize;
|
||||
}
|
||||
res = read(buf->fd, &(((char *) buf->data)[buf->size]),
|
||||
buf->maxsize - buf->size);
|
||||
if (res == (ssize_t) -1) {
|
||||
return res;
|
||||
}
|
||||
buf->size += (ssize_t) res;
|
||||
return res;
|
||||
}
|
||||
/*@=usereleased@*/ /*@=compdef@*/ /*@=branchstate@*/ /*@=compmempass@*/
|
||||
|
||||
static void reader_buffer_handle(struct reader_buffer *buf)
|
||||
/*@globals internalState, fileSystem, errno; @*/
|
||||
/*@modifies internalState, fileSystem, errno, *buf; @*/
|
||||
{
|
||||
ssize_t rd = reader_buffer_read(buf);
|
||||
if (rd == (ssize_t) -1) {
|
||||
propagate(&buf->lasterror, errno);
|
||||
}
|
||||
if (rd <= 0) {
|
||||
if (closeref(&(buf->fd)) == -1) {
|
||||
propagate(&buf->lasterror, errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*@-mustfreeonly@*/
|
||||
static void reader_buffer_finalize(/*@special@*/ struct reader_buffer *buf)
|
||||
/*@modifies *buf; @*/
|
||||
/*@releases buf->data; @*/
|
||||
{
|
||||
if (buf->data != NULL) {
|
||||
free(buf->data);
|
||||
buf->data = NULL;
|
||||
}
|
||||
buf->size = 0;
|
||||
buf->maxsize = 0;
|
||||
buf->fd = -1;
|
||||
}
|
||||
/*@=mustfreeonly@*/
|
||||
|
||||
/* writer_buffer -- implementation *************************************/
|
||||
|
||||
static void writer_buffer_init(/*@out@*/ struct writer_buffer *buf, int fd,
|
||||
const void *data, size_t size)
|
||||
/*@globals internalState, fileSystem, errno; @*/
|
||||
/*@modifies internalState, fileSystem, errno, *buf; @*/
|
||||
{
|
||||
buf->fd = fd;
|
||||
buf->data = data;
|
||||
buf->size = size;
|
||||
buf->pos = 0;
|
||||
buf->lasterror = 0;
|
||||
if (size == 0) {
|
||||
if (closeref(&(buf->fd)) == -1) {
|
||||
propagate(&buf->lasterror, errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void writer_buffer_handle(struct writer_buffer *buf)
|
||||
/*@globals internalState, fileSystem, errno; @*/
|
||||
/*@modifies internalState, fileSystem, errno, *buf; @*/
|
||||
{
|
||||
typedef void (*my_sighandler_fn) (int);
|
||||
my_sighandler_fn old_sigpipe = signal(SIGPIPE, SIG_IGN);
|
||||
ssize_t wr = write(buf->fd, &(((const char *) buf->data)[buf->pos]),
|
||||
buf->size - buf->pos);
|
||||
if (wr == (ssize_t) -1) {
|
||||
propagate(&buf->lasterror, errno);
|
||||
} else {
|
||||
buf->pos += wr;
|
||||
}
|
||||
if (wr == (ssize_t) -1 || buf->pos == buf->size) {
|
||||
if (closeref(&(buf->fd)) == -1) {
|
||||
propagate(&buf->lasterror, errno);
|
||||
}
|
||||
}
|
||||
(void) signal(SIGPIPE, old_sigpipe);
|
||||
}
|
||||
|
||||
static void writer_buffer_finalize(/*@special@*/ struct writer_buffer *buf)
|
||||
/*@modifies *buf; @*/
|
||||
{
|
||||
buf->data = NULL;
|
||||
buf->size = 0;
|
||||
buf->pos = 0;
|
||||
buf->fd = -1;
|
||||
}
|
||||
|
||||
/* select_request -- implementation ************************************/
|
||||
|
||||
static void select_request_init(/*@out@*/ struct select_request *sr)
|
||||
/*@modifies *sr; @*/
|
||||
{
|
||||
FD_ZERO(&(sr->readfds));
|
||||
FD_ZERO(&(sr->writefds));
|
||||
sr->n = 0;
|
||||
}
|
||||
|
||||
static void select_request_add_reader(struct select_request *sr,
|
||||
const struct reader_buffer *buf)
|
||||
/*@modifies *sr; @*/
|
||||
{
|
||||
if (buf->fd != -1) {
|
||||
FD_SET(buf->fd, &(sr->readfds));
|
||||
if (buf->fd + 1 > sr->n) {
|
||||
sr->n = buf->fd + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void select_request_add_writer(struct select_request *sr,
|
||||
const struct writer_buffer *buf)
|
||||
/*@modifies *sr; @*/
|
||||
{
|
||||
if (buf->fd != -1) {
|
||||
FD_SET(buf->fd, &(sr->writefds));
|
||||
if (buf->fd + 1 > sr->n) {
|
||||
sr->n = buf->fd + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void select_request_handle_writer(const struct select_request *sr,
|
||||
struct writer_buffer *buf)
|
||||
/*@globals internalState, fileSystem, errno; @*/
|
||||
/*@modifies internalState, fileSystem, errno, *buf; @*/
|
||||
{
|
||||
if (buf->fd != -1 && FD_ISSET(buf->fd, &(sr->writefds))) {
|
||||
writer_buffer_handle(buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void select_request_handle_reader(const struct select_request *sr,
|
||||
struct reader_buffer *buf)
|
||||
/*@globals internalState, fileSystem, errno; @*/
|
||||
/*@modifies internalState, fileSystem, errno, *buf; @*/
|
||||
{
|
||||
if (buf->fd != -1 && FD_ISSET(buf->fd, &(sr->readfds))) {
|
||||
reader_buffer_handle(buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* pipethrough -- helper functions *************************************/
|
||||
|
||||
static void pipethrough_child(const char *command,
|
||||
const int *stdin_pipe,
|
||||
const int *stdout_pipe,
|
||||
const int *stderr_pipe)
|
||||
/*@globals internalState, fileSystem, errno, stderr; @*/
|
||||
/*@modifies internalState, fileSystem, errno, *stderr; @*/
|
||||
{
|
||||
const char *shell = getenv("SHELL");
|
||||
if (shell == NULL) {
|
||||
shell = "/bin/sh";
|
||||
}
|
||||
|
||||
if (close(STDIN_FILENO) == -1
|
||||
|| dup2(stdin_pipe[PIPE_RD], STDIN_FILENO) == -1
|
||||
|| close(stdin_pipe[PIPE_RD]) == -1
|
||||
|| close(stdin_pipe[PIPE_WR]) == -1
|
||||
|| close(STDOUT_FILENO) == -1
|
||||
|| dup2(stdout_pipe[PIPE_WR], STDOUT_FILENO) == -1
|
||||
|| close(stdout_pipe[PIPE_RD]) == -1
|
||||
|| close(stdout_pipe[PIPE_WR]) == -1
|
||||
|| close(STDERR_FILENO) == -1
|
||||
|| dup2(stderr_pipe[PIPE_WR], STDERR_FILENO) == -1
|
||||
|| close(stderr_pipe[PIPE_RD]) == -1
|
||||
|| close(stderr_pipe[PIPE_WR]) == -1
|
||||
|| execl(shell, shell, "-c", command, NULL) == -1) {
|
||||
perror("pipethrough_child");
|
||||
(void) fflush(stderr);
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
static int pipethrough_parent(int stdin_fd,
|
||||
int stdout_fd,
|
||||
int stderr_fd,
|
||||
const struct pipe_inbuffer *stdin_buf,
|
||||
/*@out@*/ struct pipe_outbuffer *stdout_buf,
|
||||
/*@out@*/ struct pipe_outbuffer *stderr_buf)
|
||||
/*@globals internalState, fileSystem, errno; @*/
|
||||
/*@modifies internalState, fileSystem, errno,
|
||||
*stdout_buf, *stderr_buf; @*/
|
||||
{
|
||||
struct writer_buffer stdin_wbuf;
|
||||
struct reader_buffer stdout_rbuf;
|
||||
struct reader_buffer stderr_rbuf;
|
||||
struct select_request sr;
|
||||
int select_res;
|
||||
int firsterror;
|
||||
|
||||
firsterror = 0;
|
||||
writer_buffer_init(&stdin_wbuf, stdin_fd, stdin_buf->data,
|
||||
stdin_buf->size);
|
||||
propagate(&firsterror, stdin_wbuf.lasterror);
|
||||
reader_buffer_init(&stdout_rbuf, stdout_fd);
|
||||
propagate(&firsterror, stdout_rbuf.lasterror);
|
||||
reader_buffer_init(&stderr_rbuf, stderr_fd);
|
||||
propagate(&firsterror, stdout_rbuf.lasterror);
|
||||
|
||||
while (stdin_wbuf.fd != -1 || stdout_rbuf.fd != -1
|
||||
|| stderr_rbuf.fd != -1) {
|
||||
select_request_init(&sr);
|
||||
|
||||
retry_select:
|
||||
select_request_add_writer(&sr, &stdin_wbuf);
|
||||
select_request_add_reader(&sr, &stdout_rbuf);
|
||||
select_request_add_reader(&sr, &stderr_rbuf);
|
||||
select_res = select(sr.n, &(sr.readfds), &(sr.writefds),
|
||||
NULL, NULL);
|
||||
if (select_res == -1) {
|
||||
if (errno == EINTR) {
|
||||
goto retry_select;
|
||||
} else {
|
||||
propagate(&firsterror, errno);
|
||||
goto cleanup_select;
|
||||
}
|
||||
}
|
||||
select_request_handle_writer(&sr, &stdin_wbuf);
|
||||
propagate(&firsterror, stdin_wbuf.lasterror);
|
||||
select_request_handle_reader(&sr, &stdout_rbuf);
|
||||
propagate(&firsterror, stdout_rbuf.lasterror);
|
||||
select_request_handle_reader(&sr, &stderr_rbuf);
|
||||
propagate(&firsterror, stdout_rbuf.lasterror);
|
||||
}
|
||||
stdout_buf->data = stdout_rbuf.data;
|
||||
stdout_rbuf.data = NULL;
|
||||
stdout_buf->size = stdout_rbuf.size;
|
||||
stderr_buf->data = stderr_rbuf.data;
|
||||
stderr_rbuf.data = NULL;
|
||||
stderr_buf->size = stderr_rbuf.size;
|
||||
return 0;
|
||||
|
||||
cleanup_select:
|
||||
writer_buffer_finalize(&stdin_wbuf);
|
||||
reader_buffer_finalize(&stdout_rbuf);
|
||||
reader_buffer_finalize(&stderr_rbuf);
|
||||
errno = firsterror;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* pipethrough -- implementation ***************************************/
|
||||
|
||||
extern int pipethrough(const char *command,
|
||||
const struct pipe_inbuffer *stdin_buf,
|
||||
/*@out@*/ struct pipe_outbuffer *stdout_buf,
|
||||
/*@out@*/ struct pipe_outbuffer *stderr_buf,
|
||||
/*@out@*/ int *status)
|
||||
/*@globals internalState, fileSystem, errno, stderr; @*/
|
||||
/*@modifies internalState, fileSystem, errno, *stderr,
|
||||
*stdout_buf, *stderr_buf, *status; @*/
|
||||
{
|
||||
int retval = -1;
|
||||
pid_t pid;
|
||||
int stdin_pipe[2] = { -1, -1 };
|
||||
int stdout_pipe[2] = { -1, -1 };
|
||||
int stderr_pipe[2] = { -1, -1 };
|
||||
int firsterror = 0;
|
||||
|
||||
if (pipe(stdin_pipe) == -1
|
||||
|| pipe(stdout_pipe) == -1
|
||||
|| pipe(stderr_pipe) == -1) {
|
||||
propagate(&firsterror, errno);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if ((pid = fork()) == (pid_t) -1) {
|
||||
propagate(&firsterror, errno);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
pipethrough_child(command, stdin_pipe, stdout_pipe,
|
||||
stderr_pipe);
|
||||
/*@notreached@*/
|
||||
}
|
||||
|
||||
if (closeref(&stdin_pipe[PIPE_RD]) == -1
|
||||
|| closeref(&stdout_pipe[PIPE_WR]) == -1
|
||||
|| closeref(&stderr_pipe[PIPE_WR]) == -1) {
|
||||
propagate(&firsterror, errno);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (pipethrough_parent(stdin_pipe[PIPE_WR], stdout_pipe[PIPE_RD],
|
||||
stderr_pipe[PIPE_RD], stdin_buf,
|
||||
stdout_buf, stderr_buf) == -1) {
|
||||
propagate(&firsterror, errno);
|
||||
} else {
|
||||
retval = 0;
|
||||
}
|
||||
|
||||
/*@-branchstate@*/
|
||||
if (waitpid(pid, status, 0) == (pid_t) -1) {
|
||||
propagate(&firsterror, errno);
|
||||
pipe_outbuffer_finalize(stdout_buf);
|
||||
pipe_outbuffer_finalize(stderr_buf);
|
||||
retval = -1;
|
||||
}
|
||||
/*@=branchstate@*/
|
||||
|
||||
cleanup:
|
||||
(void) close(stderr_pipe[PIPE_RD]);
|
||||
(void) close(stderr_pipe[PIPE_WR]);
|
||||
(void) close(stdout_pipe[PIPE_RD]);
|
||||
(void) close(stdout_pipe[PIPE_WR]);
|
||||
(void) close(stdin_pipe[PIPE_RD]);
|
||||
(void) close(stdin_pipe[PIPE_WR]);
|
||||
if (retval == -1) {
|
||||
errno = firsterror;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* pipe_outbuffer -- implementation ************************************/
|
||||
|
||||
/*@-mustfreeonly@*/ /*@-nullstate@*/
|
||||
extern void pipe_outbuffer_finalize(struct pipe_outbuffer *buf)
|
||||
/*@modifies *buf; @*/
|
||||
/*@releases buf->data; @*/
|
||||
/*@ensures isnull buf->data; @*/
|
||||
{
|
||||
if (buf->data != NULL) {
|
||||
free(buf->data);
|
||||
buf->data = NULL;
|
||||
}
|
||||
buf->size = 0;
|
||||
}
|
||||
/*@=mustfreeonly@*/ /*@=nullstate@*/
|
@ -1,39 +0,0 @@
|
||||
|
||||
/** \file pipethrough.h
|
||||
* \brief Header: a module for piping
|
||||
*
|
||||
* Pipe a buffer through a child command and receive two containing
|
||||
* the stdout and stderr output of the child process.
|
||||
*/
|
||||
|
||||
#ifndef PIPETHROUGH_H
|
||||
#define PIPETHROUGH_H
|
||||
|
||||
/*@-protoparamname@*/
|
||||
|
||||
struct pipe_inbuffer {
|
||||
/*@observer@*/ const void *data;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct pipe_outbuffer {
|
||||
/*@only@*/ /*@null@*/ void *data;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
extern int pipethrough(const char *command,
|
||||
const struct pipe_inbuffer *stdin_buf,
|
||||
/*@out@*/ struct pipe_outbuffer *stdout_buf,
|
||||
/*@out@*/ struct pipe_outbuffer *stderr_buf,
|
||||
/*@out@*/ int *status)
|
||||
/*@globals internalState, fileSystem, errno, stderr; @*/
|
||||
/*@modifies internalState, fileSystem, errno, *stderr, *stdout_buf, *stderr_buf, *status; @*/;
|
||||
|
||||
extern void pipe_outbuffer_finalize(/*@special@*/ struct pipe_outbuffer *buf)
|
||||
/*@modifies *buf; @*/
|
||||
/*@releases buf->data; @*/
|
||||
/*@ensures isnull buf->data; @*/;
|
||||
|
||||
/*@=protoparamname@*/
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user