Move self-isolation code into a separate file
passt/pasta contains a number of routines designed to isolate passt from the rest of the system for security. These are spread through util.c and passt.c. Move them together into a new isolation.c file. Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
This commit is contained in:
parent
5d3b50c100
commit
d72a1e7bb9
8 changed files with 189 additions and 169 deletions
8
Makefile
8
Makefile
|
@ -32,16 +32,16 @@ CFLAGS += -DRLIMIT_STACK_VAL=$(RLIMIT_STACK_VAL)
|
||||||
CFLAGS += -DARCH=\"$(TARGET_ARCH)\"
|
CFLAGS += -DARCH=\"$(TARGET_ARCH)\"
|
||||||
|
|
||||||
PASST_SRCS = arch.c arp.c checksum.c conf.c dhcp.c dhcpv6.c icmp.c igmp.c \
|
PASST_SRCS = arch.c arp.c checksum.c conf.c dhcp.c dhcpv6.c icmp.c igmp.c \
|
||||||
lineread.c mld.c ndp.c netlink.c packet.c passt.c pasta.c pcap.c \
|
isolation.c lineread.c mld.c ndp.c netlink.c packet.c passt.c pasta.c \
|
||||||
siphash.c tap.c tcp.c tcp_splice.c udp.c util.c
|
pcap.c siphash.c tap.c tcp.c tcp_splice.c udp.c util.c
|
||||||
QRAP_SRCS = qrap.c
|
QRAP_SRCS = qrap.c
|
||||||
SRCS = $(PASST_SRCS) $(QRAP_SRCS)
|
SRCS = $(PASST_SRCS) $(QRAP_SRCS)
|
||||||
|
|
||||||
MANPAGES = passt.1 pasta.1 qrap.1
|
MANPAGES = passt.1 pasta.1 qrap.1
|
||||||
|
|
||||||
PASST_HEADERS = arch.h arp.h checksum.h conf.h dhcp.h dhcpv6.h icmp.h \
|
PASST_HEADERS = arch.h arp.h checksum.h conf.h dhcp.h dhcpv6.h icmp.h \
|
||||||
lineread.h ndp.h netlink.h packet.h passt.h pasta.h pcap.h \
|
isolation.h lineread.h ndp.h netlink.h packet.h passt.h pasta.h \
|
||||||
siphash.h tap.h tcp.h tcp_splice.h udp.h util.h
|
pcap.h siphash.h tap.h tcp.h tcp_splice.h udp.h util.h
|
||||||
HEADERS = $(PASST_HEADERS)
|
HEADERS = $(PASST_HEADERS)
|
||||||
|
|
||||||
# On gcc 11.2, with -O2 and -flto, tcp_hash() and siphash_20b(), if inlined,
|
# On gcc 11.2, with -O2 and -flto, tcp_hash() and siphash_20b(), if inlined,
|
||||||
|
|
1
conf.c
1
conf.c
|
@ -40,6 +40,7 @@
|
||||||
#include "tcp.h"
|
#include "tcp.h"
|
||||||
#include "pasta.h"
|
#include "pasta.h"
|
||||||
#include "lineread.h"
|
#include "lineread.h"
|
||||||
|
#include "isolation.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get_bound_ports() - Get maps of ports with bound sockets
|
* get_bound_ports() - Get maps of ports with bound sockets
|
||||||
|
|
167
isolation.c
Normal file
167
isolation.c
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
/* PASST - Plug A Simple Socket Transport
|
||||||
|
* for qemu/UNIX domain socket mode
|
||||||
|
*
|
||||||
|
* PASTA - Pack A Subtle Tap Abstraction
|
||||||
|
* for network namespace/tap device mode
|
||||||
|
*
|
||||||
|
* isolation.c - Self isolation helpers
|
||||||
|
*
|
||||||
|
* Copyright Red Hat
|
||||||
|
* Author: Stefano Brivio <sbrivio@redhat.com>
|
||||||
|
* Author: David Gibson <david@gibson.dropbear.id.au>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <grp.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/if_ether.h>
|
||||||
|
|
||||||
|
#include <linux/audit.h>
|
||||||
|
#include <linux/capability.h>
|
||||||
|
#include <linux/filter.h>
|
||||||
|
#include <linux/seccomp.h>
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
#include "seccomp.h"
|
||||||
|
#include "passt.h"
|
||||||
|
#include "isolation.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* drop_caps() - Drop capabilities we might have except for CAP_NET_BIND_SERVICE
|
||||||
|
*/
|
||||||
|
void drop_caps(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < 64; i++) {
|
||||||
|
if (i == CAP_NET_BIND_SERVICE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* drop_root() - Switch to given UID and GID
|
||||||
|
* @uid: User ID to switch to
|
||||||
|
* @gid: Group ID to switch to
|
||||||
|
*/
|
||||||
|
void drop_root(uid_t uid, gid_t gid)
|
||||||
|
{
|
||||||
|
if (setgroups(0, NULL)) {
|
||||||
|
/* If we don't start with CAP_SETGID, this will EPERM */
|
||||||
|
if (errno != EPERM) {
|
||||||
|
err("Can't drop supplementary groups: %s",
|
||||||
|
strerror(errno));
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!setgid(gid) && !setuid(uid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
err("Can't change user/group, exiting");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sandbox() - Unshare IPC, mount, PID, UTS, and user namespaces, "unmount" root
|
||||||
|
*
|
||||||
|
* Return: negative error code on failure, zero on success
|
||||||
|
*/
|
||||||
|
int sandbox(struct ctx *c)
|
||||||
|
{
|
||||||
|
int flags = CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWUTS;
|
||||||
|
|
||||||
|
if (!c->netns_only) {
|
||||||
|
if (c->pasta_userns_fd == -1)
|
||||||
|
flags |= CLONE_NEWUSER;
|
||||||
|
else
|
||||||
|
setns(c->pasta_userns_fd, CLONE_NEWUSER);
|
||||||
|
}
|
||||||
|
|
||||||
|
c->pasta_userns_fd = -1;
|
||||||
|
|
||||||
|
/* If we run in foreground, we have no chance to actually move to a new
|
||||||
|
* PID namespace. For passt, use CLONE_NEWPID anyway, in case somebody
|
||||||
|
* ever gets around seccomp profiles -- there's no harm in passing it.
|
||||||
|
*/
|
||||||
|
if (!c->foreground || c->mode == MODE_PASST)
|
||||||
|
flags |= CLONE_NEWPID;
|
||||||
|
|
||||||
|
if (unshare(flags)) {
|
||||||
|
perror("unshare");
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mount("", "/", "", MS_UNBINDABLE | MS_REC, NULL)) {
|
||||||
|
perror("mount /");
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mount("", TMPDIR, "tmpfs",
|
||||||
|
MS_NODEV | MS_NOEXEC | MS_NOSUID | MS_RDONLY,
|
||||||
|
"nr_inodes=2,nr_blocks=0")) {
|
||||||
|
perror("mount tmpfs");
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chdir(TMPDIR)) {
|
||||||
|
perror("chdir");
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (syscall(SYS_pivot_root, ".", ".")) {
|
||||||
|
perror("pivot_root");
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (umount2(".", MNT_DETACH | UMOUNT_NOFOLLOW)) {
|
||||||
|
perror("umount2");
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
drop_caps(); /* Relative to the new user namespace this time. */
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* seccomp() - Set up seccomp filters depending on mode, won't return on failure
|
||||||
|
* @c: Execution context
|
||||||
|
*/
|
||||||
|
void seccomp(const struct ctx *c)
|
||||||
|
{
|
||||||
|
struct sock_fprog prog;
|
||||||
|
|
||||||
|
if (c->mode == MODE_PASST) {
|
||||||
|
prog.len = (unsigned short)ARRAY_SIZE(filter_passt);
|
||||||
|
prog.filter = filter_passt;
|
||||||
|
} else {
|
||||||
|
prog.len = (unsigned short)ARRAY_SIZE(filter_pasta);
|
||||||
|
prog.filter = filter_pasta;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) ||
|
||||||
|
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
|
||||||
|
perror("prctl");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
15
isolation.h
Normal file
15
isolation.h
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
* Copyright Red Hat
|
||||||
|
* Author: Stefano Brivio <sbrivio@redhat.com>
|
||||||
|
* Author: David Gibson <david@gibson.dropbear.id.au>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ISOLATION_H
|
||||||
|
#define ISOLATION_H
|
||||||
|
|
||||||
|
void drop_caps(void);
|
||||||
|
void drop_root(uid_t uid, gid_t gid);
|
||||||
|
int sandbox(struct ctx *c);
|
||||||
|
void seccomp(const struct ctx *c);
|
||||||
|
|
||||||
|
#endif /* ISOLATION_H */
|
113
passt.c
113
passt.c
|
@ -19,51 +19,25 @@
|
||||||
* created in a separate network namespace).
|
* created in a separate network namespace).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <sched.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <sys/epoll.h>
|
#include <sys/epoll.h>
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#include <sys/uio.h>
|
|
||||||
#include <sys/syscall.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <sys/mount.h>
|
|
||||||
#include <netinet/ip.h>
|
|
||||||
#include <net/ethernet.h>
|
|
||||||
#include <libgen.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <net/if.h>
|
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <syslog.h>
|
#include <syslog.h>
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
#include <stddef.h>
|
|
||||||
#include <netinet/udp.h>
|
|
||||||
#include <netinet/tcp.h>
|
|
||||||
#include <netinet/if_ether.h>
|
#include <netinet/if_ether.h>
|
||||||
|
|
||||||
#include <linux/seccomp.h>
|
|
||||||
#include <linux/audit.h>
|
|
||||||
#include <linux/filter.h>
|
|
||||||
#include <linux/icmpv6.h>
|
|
||||||
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "seccomp.h"
|
|
||||||
#include "passt.h"
|
#include "passt.h"
|
||||||
#include "dhcp.h"
|
#include "dhcp.h"
|
||||||
#include "dhcpv6.h"
|
#include "dhcpv6.h"
|
||||||
#include "icmp.h"
|
#include "isolation.h"
|
||||||
#include "tcp.h"
|
|
||||||
#include "udp.h"
|
|
||||||
#include "pcap.h"
|
#include "pcap.h"
|
||||||
#include "tap.h"
|
#include "tap.h"
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
|
@ -166,91 +140,6 @@ void proto_update_l2_buf(const unsigned char *eth_d, const unsigned char *eth_s,
|
||||||
udp_update_l2_buf(eth_d, eth_s, ip_da);
|
udp_update_l2_buf(eth_d, eth_s, ip_da);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* seccomp() - Set up seccomp filters depending on mode, won't return on failure
|
|
||||||
* @c: Execution context
|
|
||||||
*/
|
|
||||||
static void seccomp(const struct ctx *c)
|
|
||||||
{
|
|
||||||
struct sock_fprog prog;
|
|
||||||
|
|
||||||
if (c->mode == MODE_PASST) {
|
|
||||||
prog.len = (unsigned short)ARRAY_SIZE(filter_passt);
|
|
||||||
prog.filter = filter_passt;
|
|
||||||
} else {
|
|
||||||
prog.len = (unsigned short)ARRAY_SIZE(filter_pasta);
|
|
||||||
prog.filter = filter_pasta;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) ||
|
|
||||||
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
|
|
||||||
perror("prctl");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sandbox() - Unshare IPC, mount, PID, UTS, and user namespaces, "unmount" root
|
|
||||||
*
|
|
||||||
* Return: negative error code on failure, zero on success
|
|
||||||
*/
|
|
||||||
static int sandbox(struct ctx *c)
|
|
||||||
{
|
|
||||||
int flags = CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWUTS;
|
|
||||||
|
|
||||||
if (!c->netns_only) {
|
|
||||||
if (c->pasta_userns_fd == -1)
|
|
||||||
flags |= CLONE_NEWUSER;
|
|
||||||
else
|
|
||||||
setns(c->pasta_userns_fd, CLONE_NEWUSER);
|
|
||||||
}
|
|
||||||
|
|
||||||
c->pasta_userns_fd = -1;
|
|
||||||
|
|
||||||
/* If we run in foreground, we have no chance to actually move to a new
|
|
||||||
* PID namespace. For passt, use CLONE_NEWPID anyway, in case somebody
|
|
||||||
* ever gets around seccomp profiles -- there's no harm in passing it.
|
|
||||||
*/
|
|
||||||
if (!c->foreground || c->mode == MODE_PASST)
|
|
||||||
flags |= CLONE_NEWPID;
|
|
||||||
|
|
||||||
if (unshare(flags)) {
|
|
||||||
perror("unshare");
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mount("", "/", "", MS_UNBINDABLE | MS_REC, NULL)) {
|
|
||||||
perror("mount /");
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mount("", TMPDIR, "tmpfs",
|
|
||||||
MS_NODEV | MS_NOEXEC | MS_NOSUID | MS_RDONLY,
|
|
||||||
"nr_inodes=2,nr_blocks=0")) {
|
|
||||||
perror("mount tmpfs");
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chdir(TMPDIR)) {
|
|
||||||
perror("chdir");
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (syscall(SYS_pivot_root, ".", ".")) {
|
|
||||||
perror("pivot_root");
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (umount2(".", MNT_DETACH | UMOUNT_NOFOLLOW)) {
|
|
||||||
perror("umount2");
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
drop_caps(); /* Relative to the new user namespace this time. */
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* exit_handler() - Signal handler for SIGQUIT and SIGTERM
|
* exit_handler() - Signal handler for SIGQUIT and SIGTERM
|
||||||
* @unused: Unused, handler deals with SIGQUIT and SIGTERM only
|
* @unused: Unused, handler deals with SIGQUIT and SIGTERM only
|
||||||
|
|
1
pasta.c
1
pasta.c
|
@ -40,6 +40,7 @@
|
||||||
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "passt.h"
|
#include "passt.h"
|
||||||
|
#include "isolation.h"
|
||||||
#include "netlink.h"
|
#include "netlink.h"
|
||||||
|
|
||||||
/* PID of child, in case we created a namespace */
|
/* PID of child, in case we created a namespace */
|
||||||
|
|
51
util.c
51
util.c
|
@ -13,30 +13,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <net/ethernet.h>
|
#include <net/ethernet.h>
|
||||||
#include <net/if.h>
|
|
||||||
#include <netinet/tcp.h>
|
|
||||||
#include <netinet/udp.h>
|
|
||||||
#include <sys/epoll.h>
|
#include <sys/epoll.h>
|
||||||
#include <sys/prctl.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <syslog.h>
|
#include <syslog.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <pwd.h>
|
|
||||||
#include <grp.h>
|
|
||||||
|
|
||||||
#include <linux/capability.h>
|
|
||||||
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "passt.h"
|
#include "passt.h"
|
||||||
|
@ -467,44 +454,6 @@ void procfs_scan_listen(struct ctx *c, uint8_t proto, int ip_version, int ns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* drop_caps() - Drop capabilities we might have except for CAP_NET_BIND_SERVICE
|
|
||||||
*/
|
|
||||||
void drop_caps(void)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < 64; i++) {
|
|
||||||
if (i == CAP_NET_BIND_SERVICE)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* drop_root() - Switch to given UID and GID
|
|
||||||
* @uid: User ID to switch to
|
|
||||||
* @gid: Group ID to switch to
|
|
||||||
*/
|
|
||||||
void drop_root(uid_t uid, gid_t gid)
|
|
||||||
{
|
|
||||||
if (setgroups(0, NULL)) {
|
|
||||||
/* If we don't start with CAP_SETGID, this will EPERM */
|
|
||||||
if (errno != EPERM) {
|
|
||||||
err("Can't drop supplementary groups: %s",
|
|
||||||
strerror(errno));
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!setgid(gid) && !setuid(uid))
|
|
||||||
return;
|
|
||||||
|
|
||||||
err("Can't change user/group, exiting");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ns_enter() - Enter configured user (unless already joined) and network ns
|
* ns_enter() - Enter configured user (unless already joined) and network ns
|
||||||
* @c: Execution context
|
* @c: Execution context
|
||||||
|
|
2
util.h
2
util.h
|
@ -233,8 +233,6 @@ int bitmap_isset(const uint8_t *map, int bit);
|
||||||
char *line_read(char *buf, size_t len, int fd);
|
char *line_read(char *buf, size_t len, int fd);
|
||||||
void procfs_scan_listen(struct ctx *c, uint8_t proto, int ip_version, int ns,
|
void procfs_scan_listen(struct ctx *c, uint8_t proto, int ip_version, int ns,
|
||||||
uint8_t *map, uint8_t *exclude);
|
uint8_t *map, uint8_t *exclude);
|
||||||
void drop_caps(void);
|
|
||||||
void drop_root(uid_t uid, gid_t gid);
|
|
||||||
int ns_enter(const struct ctx *c);
|
int ns_enter(const struct ctx *c);
|
||||||
void write_pidfile(int fd, pid_t pid);
|
void write_pidfile(int fd, pid_t pid);
|
||||||
int __daemon(int pidfile_fd, int devnull_fd);
|
int __daemon(int pidfile_fd, int devnull_fd);
|
||||||
|
|
Loading…
Reference in a new issue