Vì bạn có chủ đề bạn không thể kiểm soát, tôi khuyên bạn nên viết thư viện được tải sẵn để chuyển cuộc gọi system()
(và có thể cả popen()
v.v.) với việc triển khai của riêng bạn. Tôi cũng sẽ bao gồm trình xử lý SIGCHLD
của bạn trong thư viện.
Nếu bạn không muốn chạy chương trình của bạn qua env LD_PRELOAD=libwhatever.so yourprogram
, bạn có thể thêm một cái gì đó giống như
const char *libs;
libs = getenv("LD_PRELOAD");
if (!libs || !*libs) {
setenv("LD_PRELOAD", "libwhatever.so", 1);
execv(argv[0], argv);
_exit(127);
}
lúc bắt đầu chương trình của bạn, để có nó lại thực hiện bản thân với LD_PRELOAD một cách thích hợp thiết lập. (Lưu ý rằng có những quirks để xem xét nếu chương trình của bạn là setuid hoặc setgid; xem man ld.so
để biết chi tiết. Cụ thể, nếu libwhatever.so
không được cài đặt trong thư mục hệ thống thư viện, bạn phải chỉ định đường dẫn đầy đủ.)
Một cách tiếp cận có thể sẽ sử dụng một mảng không khóa (sử dụng các trình xây dựng nguyên tử được cung cấp bởi trình biên dịch C) của các trẻ đang chờ xử lý.Thay vì waitpid()
, việc triển khai system()
của bạn phân bổ một trong các mục nhập, gắn con PID vào đó và đợi trên một semaphore để trẻ thoát ra thay vì gọi waitpid()
.
Đây là một thực hiện ví dụ:
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <semaphore.h>
#include <dlfcn.h>
#include <errno.h>
/* Maximum number of concurrent children waited for.
*/
#define MAX_CHILDS 256
/* Lockless array of child processes waited for.
*/
static pid_t child_pid[MAX_CHILDS] = { 0 }; /* 0 is not a valid PID */
static sem_t child_sem[MAX_CHILDS];
static int child_status[MAX_CHILDS];
/* Helper function: allocate a child process.
* Returns the index, or -1 if all in use.
*/
static inline int child_get(const pid_t pid)
{
int i = MAX_CHILDS;
while (i-->0)
if (__sync_bool_compare_and_swap(&child_pid[i], (pid_t)0, pid)) {
sem_init(&child_sem[i], 0, 0);
return i;
}
return -1;
}
/* Helper function: release a child descriptor.
*/
static inline void child_put(const int i)
{
sem_destroy(&child_sem[i]);
__sync_fetch_and_and(&child_pid[i], (pid_t)0);
}
/* SIGCHLD signal handler.
* Note: Both waitpid() and sem_post() are async-signal safe.
*/
static void sigchld_handler(int signum __attribute__((unused)),
siginfo_t *info __attribute__((unused)),
void *context __attribute__((unused)))
{
pid_t p;
int status, i;
while (1) {
p = waitpid((pid_t)-1, &status, WNOHANG);
if (p == (pid_t)0 || p == (pid_t)-1)
break;
i = MAX_CHILDS;
while (i-->0)
if (p == __sync_fetch_and_or(&child_pid[i], (pid_t)0)) {
child_status[i] = status;
sem_post(&child_sem[i]);
break;
}
/* Log p and status? */
}
}
/* Helper function: close descriptor, without affecting errno.
*/
static inline int closefd(const int fd)
{
int result, saved_errno;
if (fd == -1)
return EINVAL;
saved_errno = errno;
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
if (result == -1)
result = errno;
else
result = 0;
errno = saved_errno;
return result;
}
/* Helper function: Create a close-on-exec socket pair.
*/
static int commsocket(int fd[2])
{
int result;
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) {
fd[0] = -1;
fd[1] = -1;
return errno;
}
do {
result = fcntl(fd[0], F_SETFD, FD_CLOEXEC);
} while (result == -1 && errno == EINTR);
if (result == -1) {
closefd(fd[0]);
closefd(fd[1]);
return errno;
}
do {
result = fcntl(fd[1], F_SETFD, FD_CLOEXEC);
} while (result == -1 && errno == EINTR);
if (result == -1) {
closefd(fd[0]);
closefd(fd[1]);
return errno;
}
return 0;
}
/* New system() implementation.
*/
int system(const char *command)
{
pid_t child;
int i, status, commfd[2];
ssize_t n;
/* Allocate the child process. */
i = child_get((pid_t)-1);
if (i < 0) {
/* "fork failed" */
errno = EAGAIN;
return -1;
}
/* Create a close-on-exec socket pair. */
if (commsocket(commfd)) {
child_put(i);
/* "fork failed" */
errno = EAGAIN;
return -1;
}
/* Create the child process. */
child = fork();
if (child == (pid_t)-1)
return -1;
/* Child process? */
if (!child) {
char *args[4] = { "sh", "-c", (char *)command, NULL };
/* If command is NULL, return 7 if sh is available. */
if (!command)
args[2] = "exit 7";
/* Close parent end of comms socket. */
closefd(commfd[0]);
/* Receive one char before continuing. */
do {
n = read(commfd[1], &status, 1);
} while (n == (ssize_t)-1 && errno == EINTR);
if (n != 1) {
closefd(commfd[1]);
_exit(127);
}
/* We won't receive anything else. */
shutdown(commfd[1], SHUT_RD);
/* Execute the command. If successful, this closes the comms socket. */
execv("/bin/sh", args);
/* Failed. Return the errno to the parent. */
status = errno;
{
const char *p = (const char *)&status;
const char *const q = (const char *)&status + sizeof status;
while (p < q) {
n = write(commfd[1], p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
break;
else
if (errno != EINTR)
break;
}
}
/* Explicitly close the socket pair. */
shutdown(commfd[1], SHUT_RDWR);
closefd(commfd[1]);
_exit(127);
}
/* Parent process. Close the child end of the comms socket. */
closefd(commfd[1]);
/* Update the child PID in the array. */
__sync_bool_compare_and_swap(&child_pid[i], (pid_t)-1, child);
/* Let the child proceed, by sending a char via the socket. */
status = 0;
do {
n = write(commfd[0], &status, 1);
} while (n == (ssize_t)-1 && errno == EINTR);
if (n != 1) {
/* Release the child entry. */
child_put(i);
closefd(commfd[0]);
/* Kill the child. */
kill(child, SIGKILL);
/* "fork failed". */
errno = EAGAIN;
return -1;
}
/* Won't send anything else over the comms socket. */
shutdown(commfd[0], SHUT_WR);
/* Try reading an int from the comms socket. */
{
char *p = (char *)&status;
char *const q = (char *)&status + sizeof status;
while (p < q) {
n = read(commfd[0], p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
break;
else
if (errno != EINTR)
break;
}
/* Socket closed with nothing read? */
if (n == (ssize_t)0 && p == (char *)&status)
status = 0;
else
if (p != q)
status = EAGAIN; /* Incomplete error code, use EAGAIN. */
/* Close the comms socket. */
shutdown(commfd[0], SHUT_RDWR);
closefd(commfd[0]);
}
/* Wait for the command to complete. */
sem_wait(&child_sem[i]);
/* Did the command execution fail? */
if (status) {
child_put(i);
errno = status;
return -1;
}
/* Command was executed. Return the exit status. */
status = child_status[i];
child_put(i);
/* If command is NULL, then the return value is nonzero
* iff the exit status was 7. */
if (!command) {
if (WIFEXITED(status) && WEXITSTATUS(status) == 7)
status = 1;
else
status = 0;
}
return status;
}
/* Library initialization.
* Sets the sigchld handler,
* makes sure pthread library is loaded, and
* unsets the LD_PRELOAD environment variable.
*/
static void init(void) __attribute__((constructor));
static void init(void)
{
struct sigaction act;
int saved_errno;
saved_errno = errno;
sigemptyset(&act.sa_mask);
act.sa_sigaction = sigchld_handler;
act.sa_flags = SA_NOCLDSTOP | SA_RESTART | SA_SIGINFO;
sigaction(SIGCHLD, &act, NULL);
(void)dlopen("libpthread.so.0", RTLD_NOW | RTLD_GLOBAL);
unsetenv("LD_PRELOAD");
errno = saved_errno;
}
Nếu bạn lưu trên như nói child.c
, bạn có thể biên dịch nó thành libchild.so
sử dụng
gcc -W -Wall -O3 -fpic -fPIC -c child.c -lpthread
gcc -W -Wall -O3 -shared -Wl,-soname,libchild.so child.o -ldl -lpthread -o libchild.so
Nếu bạn có một chương trình thử nghiệm mà không system()
cuộc gọi trong các chủ đề khác nhau, bạn có thể chạy nó với system()
interposed (và trẻ em tự động gặt hái) bằng cách sử dụng
env LD_PRELOAD=/path/to/libchild.so test-program
Lưu ý rằng tùy thuộc vào chính xác những gì những chủ đề mà không phải là dưới sự kiểm soát của bạn làm, bạn có thể cần phải xen chức năng hơn nữa, bao gồm signal()
, sigaction()
, sigprocmask()
, pthread_sigmask()
, và như vậy, để đảm bảo những chủ đề không thay đổi bố trí của bạn xử lý SIGCHLD
(sau khi cài đặt bởi các thư viện libchild.so
).
Nếu những chủ đề không kiểm soát đó sử dụng popen()
, bạn có thể xen vào (và pclose()
) với mã rất giống với system()
ở trên, chỉ chia làm hai phần.
(Nếu bạn tự hỏi tại sao mã số system()
của mình làm phiền để báo cáo lỗi exec()
cho quy trình gốc, đó là vì tôi thường sử dụng một biến thể của mã này nhận lệnh như một chuỗi các chuỗi; lệnh không được tìm thấy, hoặc không thể được thực hiện do không đủ đặc quyền, vv Trong trường hợp cụ thể này lệnh luôn là /bin/sh
. Tuy nhiên, vì ổ cắm truyền thông là cần thiết để tránh việc chạy đua giữa lối ra của trẻ và có cập nhật PID trong mảng * child_pid [] *, tôi quyết định để lại mã "phụ".)
Không nên hệ thống() chặn SIGCHLD? Và bạn có ý nghĩa gì bởi "SIGCHLD được gọi trong một chủ đề khác"? – LostBoy
Có lẽ bạn có thể sử dụng một thư viện (Glib, Pocolib, Boost, QtCore, ...) để cho phép bạn kiểm soát tốt hơn các quy trình hơn là 'hệ thống (3)' có. –
@LostBoy 'system()' không chặn SIGCHLD nhưng chỉ cho chuỗi gọi. nó sử dụng 'sigprocmask()' được viết thành "Mỗi luồng trong một tiến trình có mặt nạ tín hiệu riêng." – shoosh