mdadm: Unify forks behaviour

If mdadm is run by udev or systemd, it gets a pipe as each stream.
Forks in the background may run after an event or service has been
processed when udev is detached from pipe. As a result process
fails quietly if any message is written.
To prevent from it, each fork has to close all parent streams. Leave
stderr and stdout opened only for debug purposes.
Unify it across all forks. Introduce other descriptors detection by
scanning /proc/self/fd directory. Add generic method for
managing systemd services.

Signed-off-by: Mariusz Tkaczyk <mariusz.tkaczyk@intel.com>
This commit is contained in:
Mariusz Tkaczyk 2020-11-04 10:02:36 +01:00 committed by Jes Sorensen
parent a8f3cfd54e
commit ff6bb131a4
6 changed files with 100 additions and 101 deletions

52
Grow.c
View File

@ -2982,47 +2982,6 @@ static void catch_term(int sig)
sigterm = 1;
}
static int continue_via_systemd(char *devnm)
{
int skipped, i, pid, status;
char pathbuf[1024];
/* In a systemd/udev world, it is best to get systemd to
* run "mdadm --grow --continue" rather than running in the
* background.
*/
switch(fork()) {
case 0:
/* FIXME yuk. CLOSE_EXEC?? */
skipped = 0;
for (i = 3; skipped < 20; i++)
if (close(i) < 0)
skipped++;
else
skipped = 0;
/* Don't want to see error messages from
* systemctl. If the service doesn't exist,
* we fork ourselves.
*/
close(2);
open("/dev/null", O_WRONLY);
snprintf(pathbuf, sizeof(pathbuf),
"mdadm-grow-continue@%s.service", devnm);
status = execl("/usr/bin/systemctl", "systemctl", "restart",
pathbuf, NULL);
status = execl("/bin/systemctl", "systemctl", "restart",
pathbuf, NULL);
exit(1);
case -1: /* Just do it ourselves. */
break;
default: /* parent - good */
pid = wait(&status);
if (pid >= 0 && status == 0)
return 1;
}
return 0;
}
static int reshape_array(char *container, int fd, char *devname,
struct supertype *st, struct mdinfo *info,
int force, struct mddev_dev *devlist,
@ -3401,6 +3360,7 @@ static int reshape_array(char *container, int fd, char *devname,
default: /* parent */
return 0;
case 0:
manage_fork_fds(0);
map_fork();
break;
}
@ -3509,8 +3469,9 @@ started:
return 1;
}
if (!forked && !check_env("MDADM_NO_SYSTEMCTL"))
if (continue_via_systemd(container ?: sra->sys_name)) {
if (!forked)
if (continue_via_systemd(container ?: sra->sys_name,
GROW_SERVICE)) {
free(fdlist);
free(offsets);
sysfs_free(sra);
@ -3704,8 +3665,8 @@ int reshape_container(char *container, char *devname,
*/
ping_monitor(container);
if (!forked && !freeze_reshape && !check_env("MDADM_NO_SYSTEMCTL"))
if (continue_via_systemd(container))
if (!forked && !freeze_reshape)
if (continue_via_systemd(container, GROW_SERVICE))
return 0;
switch (forked ? 0 : fork()) {
@ -3718,6 +3679,7 @@ int reshape_container(char *container, char *devname,
printf("%s: multi-array reshape continues in background\n", Name);
return 0;
case 0: /* child */
manage_fork_fds(0);
map_fork();
break;
}

View File

@ -1679,6 +1679,7 @@ static void run_udisks(char *arg1, char *arg2)
int pid = fork();
int status;
if (pid == 0) {
manage_fork_fds(1);
execl("/usr/bin/udisks", "udisks", arg1, arg2, NULL);
execl("/bin/udisks", "udisks", arg1, arg2, NULL);
exit(1);

View File

@ -323,10 +323,7 @@ static int make_daemon(char *pidfile)
perror("daemonise");
return 1;
}
close(0);
open("/dev/null", O_RDWR);
dup2(0, 1);
dup2(0, 2);
manage_fork_fds(0);
setsid();
return -1;
}

10
mdadm.h
View File

@ -129,6 +129,14 @@ struct dlm_lksb {
#define FAILED_SLOTS_DIR "/run/mdadm/failed-slots"
#endif /* FAILED_SLOTS */
#ifndef MDMON_SERVICE
#define MDMON_SERVICE "mdmon"
#endif /* MDMON_SERVICE */
#ifndef GROW_SERVICE
#define GROW_SERVICE "mdadm-grow-continue"
#endif /* GROW_SERVICE */
#include "md_u.h"
#include "md_p.h"
#include "bitmap.h"
@ -1497,6 +1505,8 @@ extern int is_standard(char *dev, int *nump);
extern int same_dev(char *one, char *two);
extern int compare_paths (char* path1,char* path2);
extern void enable_fds(int devices);
extern void manage_fork_fds(int close_all);
extern int continue_via_systemd(char *devnm, char *service_name);
extern int parse_auto(char *str, char *msg, int config);
extern struct mddev_ident *conf_get_ident(char *dev);

View File

@ -546,14 +546,7 @@ static int mdmon(char *devnm, int must_fork, int takeover)
}
setsid();
close(0);
open("/dev/null", O_RDWR);
close(1);
ignore = dup(0);
#ifndef DEBUG
close(2);
ignore = dup(0);
#endif
manage_fork_fds(0);
/* This silliness is to stop the compiler complaining
* that we ignore 'ignore'

124
util.c
View File

@ -1915,7 +1915,7 @@ int mdmon_running(char *devnm)
int start_mdmon(char *devnm)
{
int i, skipped;
int i;
int len;
pid_t pid;
int status;
@ -1929,7 +1929,10 @@ int start_mdmon(char *devnm)
if (check_env("MDADM_NO_MDMON"))
return 0;
if (continue_via_systemd(devnm, MDMON_SERVICE))
return 0;
/* That failed, try running mdmon directly */
len = readlink("/proc/self/exe", pathbuf, sizeof(pathbuf)-1);
if (len > 0) {
char *sl;
@ -1943,51 +1946,9 @@ int start_mdmon(char *devnm)
} else
pathbuf[0] = '\0';
/* First try to run systemctl */
if (!check_env("MDADM_NO_SYSTEMCTL"))
switch(fork()) {
case 0:
/* FIXME yuk. CLOSE_EXEC?? */
skipped = 0;
for (i = 3; skipped < 20; i++)
if (close(i) < 0)
skipped++;
else
skipped = 0;
/* Don't want to see error messages from
* systemctl. If the service doesn't exist,
* we start mdmon ourselves.
*/
close(2);
open("/dev/null", O_WRONLY);
snprintf(pathbuf, sizeof(pathbuf), "mdmon@%s.service",
devnm);
status = execl("/usr/bin/systemctl", "systemctl",
"start",
pathbuf, NULL);
status = execl("/bin/systemctl", "systemctl", "start",
pathbuf, NULL);
exit(1);
case -1: pr_err("cannot run mdmon. Array remains readonly\n");
return -1;
default: /* parent - good */
pid = wait(&status);
if (pid >= 0 && status == 0)
return 0;
}
/* That failed, try running mdmon directly */
switch(fork()) {
case 0:
/* FIXME yuk. CLOSE_EXEC?? */
skipped = 0;
for (i = 3; skipped < 20; i++)
if (close(i) < 0)
skipped++;
else
skipped = 0;
manage_fork_fds(1);
for (i = 0; paths[i]; i++)
if (paths[i][0]) {
execl(paths[i], paths[i],
@ -2192,6 +2153,81 @@ void enable_fds(int devices)
setrlimit(RLIMIT_NOFILE, &lim);
}
/* Close all opened descriptors if needed and redirect
* streams to /dev/null.
* For debug purposed, leave STDOUT and STDERR untouched
* Returns:
* 1- if any error occurred
* 0- otherwise
*/
void manage_fork_fds(int close_all)
{
DIR *dir;
struct dirent *dirent;
close(0);
open("/dev/null", O_RDWR);
#ifndef DEBUG
dup2(0, 1);
dup2(0, 2);
#endif
if (close_all == 0)
return;
dir = opendir("/proc/self/fd");
if (!dir) {
pr_err("Cannot open /proc/self/fd directory.\n");
return;
}
for (dirent = readdir(dir); dirent; dirent = readdir(dir)) {
int fd = -1;
if ((strcmp(dirent->d_name, ".") == 0) ||
(strcmp(dirent->d_name, "..")) == 0)
continue;
fd = strtol(dirent->d_name, NULL, 10);
if (fd > 2)
close(fd);
}
}
/* In a systemd/udev world, it is best to get systemd to
* run daemon rather than running in the background.
* Returns:
* 1- if systemd service has been started
* 0- otherwise
*/
int continue_via_systemd(char *devnm, char *service_name)
{
int pid, status;
char pathbuf[1024];
/* Simply return that service cannot be started */
if (check_env("MDADM_NO_SYSTEMCTL"))
return 0;
switch (fork()) {
case 0:
manage_fork_fds(1);
snprintf(pathbuf, sizeof(pathbuf),
"%s@%s.service", service_name, devnm);
status = execl("/usr/bin/systemctl", "systemctl", "restart",
pathbuf, NULL);
status = execl("/bin/systemctl", "systemctl", "restart",
pathbuf, NULL);
exit(1);
case -1: /* Just do it ourselves. */
break;
default: /* parent - good */
pid = wait(&status);
if (pid >= 0 && status == 0)
return 1;
}
return 0;
}
int in_initrd(void)
{
/* This is based on similar function in systemd. */