posix - codigo - dup en c
Obtener el descriptor de archivo más alto asignado (5)
¿Por qué no cierra todos los descriptores de 0 a, digamos, 10000?
Sería bastante rápido, y lo peor que sucedería sería EBADF.
¿Existe una forma portátil (POSIX) para obtener el número de descriptor de archivo más alto asignado para el proceso actual?
Sé que hay una buena forma de obtener el número en AIX, por ejemplo, pero estoy buscando un método portátil.
La razón por la que pregunto es que quiero cerrar todos los descriptores de archivos abiertos. Mi programa es un servidor que se ejecuta como root y forks y execs programas secundarios para usuarios no root. Dejar los descriptores de archivos privilegiados abiertos en el proceso hijo es un problema de seguridad. Algunos descriptores de archivos pueden abrirse con un código que no puedo controlar (la biblioteca C, las bibliotecas de terceros, etc.), por lo que tampoco puedo confiar en FD_CLOEXEC
.
He escrito código para lidiar con todas las características específicas de la plataforma. Todas las funciones son asíncronas seguras. Las personas pensadas pueden encontrar esto útil. Solo probado en OS X en este momento, siéntase libre de mejorar / arreglar.
// Async-signal safe way to get the current process''s hard file descriptor limit.
static int
getFileDescriptorLimit() {
long long sysconfResult = sysconf(_SC_OPEN_MAX);
struct rlimit rl;
long long rlimitResult;
if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {
rlimitResult = 0;
} else {
rlimitResult = (long long) rl.rlim_max;
}
long result;
if (sysconfResult > rlimitResult) {
result = sysconfResult;
} else {
result = rlimitResult;
}
if (result < 0) {
// Both calls returned errors.
result = 9999;
} else if (result < 2) {
// The calls reported broken values.
result = 2;
}
return result;
}
// Async-signal safe function to get the highest file
// descriptor that the process is currently using.
// See also http://.com/questions/899038/getting-the-highest-allocated-file-descriptor
static int
getHighestFileDescriptor() {
#if defined(F_MAXFD)
int ret;
do {
ret = fcntl(0, F_MAXFD);
} while (ret == -1 && errno == EINTR);
if (ret == -1) {
ret = getFileDescriptorLimit();
}
return ret;
#else
int p[2], ret, flags;
pid_t pid = -1;
int result = -1;
/* Since opendir() may not be async signal safe and thus may lock up
* or crash, we use it in a child process which we kill if we notice
* that things are going wrong.
*/
// Make a pipe.
p[0] = p[1] = -1;
do {
ret = pipe(p);
} while (ret == -1 && errno == EINTR);
if (ret == -1) {
goto done;
}
// Make the read side non-blocking.
do {
flags = fcntl(p[0], F_GETFL);
} while (flags == -1 && errno == EINTR);
if (flags == -1) {
goto done;
}
do {
fcntl(p[0], F_SETFL, flags | O_NONBLOCK);
} while (ret == -1 && errno == EINTR);
if (ret == -1) {
goto done;
}
do {
pid = fork();
} while (pid == -1 && errno == EINTR);
if (pid == 0) {
// Don''t close p[0] here or it might affect the result.
resetSignalHandlersAndMask();
struct sigaction action;
action.sa_handler = _exit;
action.sa_flags = SA_RESTART;
sigemptyset(&action.sa_mask);
sigaction(SIGSEGV, &action, NULL);
sigaction(SIGPIPE, &action, NULL);
sigaction(SIGBUS, &action, NULL);
sigaction(SIGILL, &action, NULL);
sigaction(SIGFPE, &action, NULL);
sigaction(SIGABRT, &action, NULL);
DIR *dir = NULL;
#ifdef __APPLE__
/* /dev/fd can always be trusted on OS X. */
dir = opendir("/dev/fd");
#else
/* On FreeBSD and possibly other operating systems, /dev/fd only
* works if fdescfs is mounted. If it isn''t mounted then /dev/fd
* still exists but always returns [0, 1, 2] and thus can''t be
* trusted. If /dev and /dev/fd are on different filesystems
* then that probably means fdescfs is mounted.
*/
struct stat dirbuf1, dirbuf2;
if (stat("/dev", &dirbuf1) == -1
|| stat("/dev/fd", &dirbuf2) == -1) {
_exit(1);
}
if (dirbuf1.st_dev != dirbuf2.st_dev) {
dir = opendir("/dev/fd");
}
#endif
if (dir == NULL) {
dir = opendir("/proc/self/fd");
if (dir == NULL) {
_exit(1);
}
}
struct dirent *ent;
union {
int highest;
char data[sizeof(int)];
} u;
u.highest = -1;
while ((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] != ''.'') {
int number = atoi(ent->d_name);
if (number > u.highest) {
u.highest = number;
}
}
}
if (u.highest != -1) {
ssize_t ret, written = 0;
do {
ret = write(p[1], u.data + written, sizeof(int) - written);
if (ret == -1) {
_exit(1);
}
written += ret;
} while (written < (ssize_t) sizeof(int));
}
closedir(dir);
_exit(0);
} else if (pid == -1) {
goto done;
} else {
do {
ret = close(p[1]);
} while (ret == -1 && errno == EINTR);
p[1] = -1;
union {
int highest;
char data[sizeof(int)];
} u;
ssize_t ret, bytesRead = 0;
struct pollfd pfd;
pfd.fd = p[0];
pfd.events = POLLIN;
do {
do {
// The child process must finish within 30 ms, otherwise
// we might as well query sysconf.
ret = poll(&pfd, 1, 30);
} while (ret == -1 && errno == EINTR);
if (ret <= 0) {
goto done;
}
do {
ret = read(p[0], u.data + bytesRead, sizeof(int) - bytesRead);
} while (ret == -1 && ret == EINTR);
if (ret == -1) {
if (errno != EAGAIN) {
goto done;
}
} else if (ret == 0) {
goto done;
} else {
bytesRead += ret;
}
} while (bytesRead < (ssize_t) sizeof(int));
result = u.highest;
goto done;
}
done:
if (p[0] != -1) {
do {
ret = close(p[0]);
} while (ret == -1 && errno == EINTR);
}
if (p[1] != -1) {
do {
close(p[1]);
} while (ret == -1 && errno == EINTR);
}
if (pid != -1) {
do {
ret = kill(pid, SIGKILL);
} while (ret == -1 && errno == EINTR);
do {
ret = waitpid(pid, NULL, 0);
} while (ret == -1 && errno == EINTR);
}
if (result == -1) {
result = getFileDescriptorLimit();
}
return result;
#endif
}
void
closeAllFileDescriptors(int lastToKeepOpen) {
#if defined(F_CLOSEM)
int ret;
do {
ret = fcntl(lastToKeepOpen + 1, F_CLOSEM);
} while (ret == -1 && errno == EINTR);
if (ret != -1) {
return;
}
#elif defined(HAS_CLOSEFROM)
closefrom(lastToKeepOpen + 1);
return;
#endif
for (int i = getHighestFileDescriptor(); i > lastToKeepOpen; i--) {
int ret;
do {
ret = close(i);
} while (ret == -1 && errno == EINTR);
}
}
Justo cuando tu programa comenzó y no ha abierto nada. Por ejemplo, como el inicio de main (). Tubo y horquilla iniciando inmediatamente un servidor ejecutor De esta manera, la memoria y otros detalles están limpios y puedes darle cosas a fork & exec.
#include <unistd.h>
#include <stdio.h>
#include <memory.h>
#include <stdlib.h>
struct PipeStreamHandles {
/** Write to this */
int output;
/** Read from this */
int input;
/** true if this process is the child after a fork */
bool isChild;
pid_t childProcessId;
};
PipeStreamHandles forkFullDuplex(){
int childInput[2];
int childOutput[2];
pipe(childInput);
pipe(childOutput);
pid_t pid = fork();
PipeStreamHandles streams;
if(pid == 0){
// child
close(childInput[1]);
close(childOutput[0]);
streams.output = childOutput[1];
streams.input = childInput[0];
streams.isChild = true;
streams.childProcessId = getpid();
} else {
close(childInput[0]);
close(childOutput[1]);
streams.output = childInput[1];
streams.input = childOutput[0];
streams.isChild = false;
streams.childProcessId = pid;
}
return streams;
}
struct ExecuteData {
char command[2048];
bool shouldExit;
};
ExecuteData getCommand() {
// maybe use json or semething to read what to execute
// environment if any and etc..
// you can read via stdin because of the dup setup we did
// in setupExecutor
ExecuteData data;
memset(&data, 0, sizeof(data));
data.shouldExit = fgets(data.command, 2047, stdin) == NULL;
return data;
}
void executorServer(){
while(true){
printf("executor server waiting for command/n");
// maybe use json or semething to read what to execute
// environment if any and etc..
ExecuteData command = getCommand();
// one way is for getCommand() to check if stdin is gone
// that way you can set shouldExit to true
if(command.shouldExit){
break;
}
printf("executor server doing command %s", command.command);
system(command.command);
// free command resources.
}
}
static PipeStreamHandles executorStreams;
void setupExecutor(){
PipeStreamHandles handles = forkFullDuplex();
if(handles.isChild){
// This simplifies so we can just use standard IO
dup2(handles.input, 0);
// we comment this out so we see output.
// dup2(handles.output, 1);
close(handles.input);
// we uncomment this one so we can see hello world
// if you want to capture the output you will want this.
//close(handles.output);
handles.input = 0;
handles.output = 1;
printf("started child/n");
executorServer();
printf("exiting executor/n");
exit(0);
}
executorStreams = handles;
}
/** Only has 0, 1, 2 file descriptiors open */
pid_t cleanForkAndExecute(const char *command) {
// You can do json and use a json parser might be better
// so you can pass other data like environment perhaps.
// and also be able to return details like new proccess id so you can
// wait if it''s done and ask other relevant questions.
write(executorStreams.output, command, strlen(command));
write(executorStreams.output, "/n", 1);
}
int main () {
// needs to be done early so future fds do not get open
setupExecutor();
// run your program as usual.
cleanForkAndExecute("echo hello world");
sleep(3);
}
Si desea realizar IO en el programa ejecutado, el servidor ejecutor tendrá que hacer redirecciones de socket y podrá usar sockets de unix.
La forma POSIX es:
int maxfd=sysconf(_SC_OPEN_MAX);
for(int fd=3; fd<maxfd; fd++)
close(fd);
(tenga en cuenta que se está cerrando a partir de 3, para mantener abierta la entrada estándar / stdout / stderr)
close () devuelve sin errores EBADF si el descriptor de archivo no está abierto. No hay necesidad de perder otro sistema de verificación de llamadas.
Algunos Unixes soportan un closefrom (). Esto evita la cantidad excesiva de llamadas a cerrar () dependiendo del número máximo posible de descriptor de archivos. Si bien la mejor solución que conozco es completamente no portátil.
Si bien es portátil, cerrar todos los descriptores de archivos hasta sysconf(_SC_OPEN_MAX)
no es confiable, porque en la mayoría de los sistemas, esta llamada devuelve el límite suave del descriptor de archivos actual, que podría haberse reducido por debajo del descriptor de archivos más alto utilizado. Otro problema es que en muchos sistemas, sysconf(_SC_OPEN_MAX)
puede devolver INT_MAX
, lo que puede causar que este enfoque sea inaceptablemente lento. Desafortunadamente, no existe una alternativa confiable y portátil que no implique iterar sobre todos los posibles descriptores de archivos int no negativos.
Aunque no es portátil, la mayoría de los sistemas operativos de uso común en la actualidad ofrecen una o más de las siguientes soluciones a este problema:
Una función de biblioteca para cerrar todos los descriptores de archivo > = fd . Esta es la solución más simple para el caso común de cerrar todos los descriptores de archivos, aunque no se puede usar para mucho más. Para cerrar todos los descriptores de archivo, excepto un determinado conjunto, se puede usar
dup2
para moverlos al extremo inferior de antemano, y para retrocederlos si es necesario.closefrom(fd)
(Solaris 9 o posterior, FreeBSD 7.3 o 8.0 y posterior, NetBSD 3.0 o posterior, OpenBSD 3.5 o posterior).fcntl(fd, F_CLOSEM, 0)
(AIX, IRIX, NetBSD)
Una función de biblioteca para proporcionar el descriptor de archivos máximo actualmente en uso por el proceso. Para cerrar todos los descriptores de archivo por encima de un cierto número, ciérrelos todos a este máximo o consiga y cierre continuamente el descriptor de archivo más alto en un bucle hasta que se alcance el límite bajo. Lo que es más eficiente depende de la densidad del descriptor de archivo.
fcntl(0, F_MAXFD)
(NetBSD)pstat_getproc(&ps, sizeof(struct pst_status), (size_t)0, (int)getpid())
Devuelve información sobre el proceso, incluido el descriptor de archivo más alto actualmente abierto enps.pst_highestfd
. (HP-UX)
Un directorio que contiene una entrada para cada descriptor de archivo abierto . Este es el enfoque más flexible, ya que permite cerrar todos los descriptores de archivos, encontrar el descriptor de archivos más alto o hacer casi cualquier otra cosa en cada descriptor de archivos abiertos, incluso los de otro proceso (en la mayoría de los sistemas). Sin embargo, esto puede ser más complicado que los otros enfoques para los usos comunes. Además, puede fallar por varios motivos, como proc / fdescfs no montado, un entorno chroot o no hay descriptores de archivo disponibles para abrir el directorio (proceso o límite del sistema). Por lo tanto, el uso de este enfoque a menudo se combina con un mecanismo de reserva. Ejemplo (OpenSSH) , otro ejemplo (glib) .
/proc/
pid/fd/
or/proc/self/fd/
(Linux, Solaris, AIX, Cygwin, NetBSD)
(AIX no admite "self
")/dev/fd/
(FreeBSD, Darwin, OS X)
Puede ser difícil manejar todos los casos de esquina de manera confiable con este enfoque. Por ejemplo, considere la situación en la que todos los descriptores de archivo> = fd deben cerrarse, pero se utilizan todos los descriptores de archivo < fd , el límite del recurso del proceso actual es fd , y hay descriptores de archivo> = fd en uso. Debido a que se ha alcanzado el límite de recursos del proceso, no se puede abrir el directorio. Si el cierre de cada descriptor de archivo desde fd a través del límite de recursos o
sysconf(_SC_OPEN_MAX)
se utiliza como reserva, no se cerrará nada.