cmogstored.git  about / heads / tags
alternative mogstored implementation for MogileFS
blob 9f980772e9fad3e479937f78a8ad89bce28b4e38 4072 bytes (raw)
$ git show gl-env:iostat_process.c	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
 
/*
 * Copyright (C) 2012-2016 all contributors <cmogstored-public@bogomips.org>
 * License: GPLv3 or later (see COPYING for details)
 */
/*
 * process management for iostat(1)
 * Since iostat(1) watches the entire system, we only spawn it once
 * regardless of the number of mog_svc objects we have.
 */
#include "cmogstored.h"

static pid_t iostat_pid;
static time_t iostat_last_fail;
static struct mog_iostat *iostat;
static time_t iostat_fail_timeout = 10;

enum mog_exerr {
	MOG_EXERR_DUP2 = 3,
	MOG_EXERR_SIGPROCMASK = 4,
	MOG_EXERR_EXECVE = 5
};

static void iostat_atexit(void)
{
	if (iostat_pid > 0)
		kill(iostat_pid, SIGTERM);
}

static int iostat_pipe_init(int *fds)
{
	if (pipe2(fds, O_CLOEXEC) < 0) {
		PRESERVE_ERRNO( syslog(LOG_ERR, "pipe2() failed: %m") );

		/*
		 * don't retry here, MFS can deal with not getting iostat
		 * data for a while
		 */
		if (errno == ENFILE || errno == EMFILE)
			PRESERVE_ERRNO( (void)mog_fdmap_expire(5) );
		return -1;
	}

	CHECK(int, 0, mog_set_nonblocking(fds[0], true));
	/* fds[1] (write end) stays _blocking_ */

	return 0;
}

static const char *exec_cmd(const char *cmd)
{
	time_t last_fail = time(NULL) - iostat_last_fail;
	time_t delay = iostat_fail_timeout - last_fail;

	if (delay <= 0)
		return xasprintf("exec %s", cmd);

	syslog(LOG_DEBUG,
	       "delaying exec of `%s' for %ds due to previous failure",
	       cmd, (int)delay);
	return xasprintf("sleep %d; exec %s", (int)delay, cmd);
}

static int dup2_retry(int oldfd, int newfd) /* vfork-safe */
{
	int rc;

	do
		rc = dup2(oldfd, newfd);
	while (rc < 0 && (errno == EINTR || errno == EBUSY));

	return rc;
}

static void execve_iostat(int out_fd, const char *cmd) /* vfork-safe */
{
	int i;
	union {
		char *argv[4];
		char const *in[4];
	} u;

	u.in[0] = "/bin/sh";
	u.in[1] = "-c";
	u.in[2] = cmd;
	u.in[3] = 0;

	if (dup2_retry(out_fd, STDOUT_FILENO) < 0)
		_exit(MOG_EXERR_DUP2);
	if (!mog_cloexec_atomic)
		mog_cloexec_from(STDERR_FILENO + 1);

	/* ignore errors, not much we can do about missing signals */
	for (i = 1; i < NSIG; i++)
		(void)signal(i, SIG_DFL);

	if (sigprocmask(SIG_SETMASK, &mog_emptyset, NULL) != 0)
		_exit(MOG_EXERR_SIGPROCMASK);

	execve("/bin/sh", u.argv, environ);
	_exit(MOG_EXERR_EXECVE);
}

static pid_t iostat_fork_exec(int out_fd)
{
	/* rely on /bin/sh to parse iostat command-line args */
	const char *cmd = getenv("MOG_IOSTAT_CMD");
	int cs;

	if (!cmd)
		cmd = "iostat -dx 1 30";

	cmd = exec_cmd(cmd);
	CHECK(int, 0, pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs));
	iostat_pid = mog_fork_for_exec();
	if (iostat_pid < 0)
		syslog(LOG_ERR, "fork() for iostat failed: %m");
	else if (iostat_pid == 0) /* child */
		execve_iostat(out_fd, cmd);

	/* parent */
	CHECK(int, 0, pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0));
	if (iostat_pid > 0)
		mog_process_register(iostat_pid, MOG_PROC_IOSTAT);
	mog_close(out_fd);
	mog_free(cmd);

	return iostat_pid;
}

bool mog_iostat_respawn(int oldstatus)
{
	int fds[2];
	struct mog_fd *mfd;

	if (WIFEXITED(oldstatus)) {
		int ex = WEXITSTATUS(oldstatus);
		const char *fn = 0;

		switch (ex) {
		case 0: break;
		case MOG_EXERR_DUP2: fn = "dup2"; break;
		case MOG_EXERR_SIGPROCMASK: fn = "sigprocmask"; break;
		case MOG_EXERR_EXECVE: fn = "execve"; break;
		default: fn = "(unknown)";
		}
		if (fn)
			syslog(LOG_ERR, "iostat exited due to %s failure", fn);
		/* else syslog(LOG_DEBUG, "iostat done, restarting"); */
	} else {
		iostat_last_fail = time(NULL);
		syslog(LOG_WARNING,
		       "iostat done (pid=%d, status=%d), will retry in %ds",
		       (int)iostat_pid, oldstatus, (int)iostat_fail_timeout);
	}
	iostat_pid = 0;

	if (iostat_pipe_init(fds) < 0)
		return false; /* EMFILE || ENFILE */
	if (iostat_fork_exec(fds[1]) < 0)
		return false; /* fork() failure */

	assert(fds[0] >= 0 && "invalid FD");

	mfd = mog_fd_init(fds[0], MOG_FD_TYPE_IOSTAT);

	if (iostat == NULL)
		atexit(iostat_atexit);
	iostat = &mfd->as.iostat;
	iostat->queue = mog_notify_queue;
	mog_iostat_init(iostat);
	mog_idleq_add(iostat->queue, mfd, MOG_QEV_RD);

	return true;
}

git clone https://yhbt.net/cmogstored.git