Beispiel #1
0
// public API
APIE process_kill(Process *process, ProcessSignal signal) {
	int rc;
	APIE error_code;

	// FIXME: here is a race condition, because the child process might already
	//        be dead at this point, but the process state didn't get updated
	//        yet. this can result in trying to kill a process that's not
	//        existing anymore. or even worse, the process ID has already been
	//        reused and an unrelated process gets killed here
	if (!process_is_alive(process)) {
		log_warn("Cannot send signal (number: %d) to an already dead child process (executable: %s)",
		         signal, process->executable->buffer);

		return API_E_INVALID_OPERATION;
	}

	rc = kill(process->pid, signal);

	if (rc < 0) {
		error_code = api_get_error_code_from_errno();

		log_error("Could not send signal (number: %d) to child process (executable: %s, pid: %u): %s (%d)",
		          signal, process->executable->buffer, process->pid, get_errno_name(errno), errno);

		return error_code;
	}

	return API_E_SUCCESS;
}
Beispiel #2
0
static void process_handle_state_change(void *opaque) {
	Process *process = opaque;
	ProcessStateChange change;

	if (pipe_read(&process->state_change_pipe, &change, sizeof(change)) < 0) {
		log_error("Could not read from state change pipe for child process (executable: %s, pid: %u): %s (%d)",
		          process->executable->buffer, process->pid, get_errno_name(errno), errno);

		return;
	}

	process->state = change.state;
	process->timestamp = change.timestamp;
	process->exit_code = change.exit_code;

	if (!process_is_alive(process)) {
		process->pid = 0;
	}

	if (process->state_changed != NULL) {
		process->state_changed(process->opaque);
	}

	// only send a process-state-changed callback if there is at least one
	// external reference to the process object. otherwise there is no one that
	// could be interested in this callback anyway. also this logic avoids
	// sending process-state-changed callbacks for scheduled program executions
	if (process->base.external_reference_count > 0) {
		api_send_process_state_changed_callback(process->base.id, change.state,
		                                        change.timestamp, change.exit_code);
	}

	if (process->release_on_death && !process_is_alive(process)) {
		process->release_on_death = false; // only release-on-death once

		object_remove_internal_reference(&process->base);
	}
}
void program_scheduler_shutdown(ProgramScheduler *program_scheduler) {
	if (program_scheduler->shutdown) {
		return;
	}

	program_scheduler->shutdown = true;

	program_scheduler_stop(program_scheduler, NULL);

	if (program_scheduler->last_spawned_process != NULL &&
	    process_is_alive(program_scheduler->last_spawned_process)) {
		process_kill(program_scheduler->last_spawned_process, PROCESS_SIGNAL_KILL);
	}
}
Beispiel #4
0
static void process_destroy(Object *object) {
	Process *process = (Process *)object;
	int rc;
	bool stuck = false;

	// remove the state change pipe from the event loop to avoid sending
	// callbacks in case the child process is still alive and has to be killed
	event_remove_source(process->state_change_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC);

	// FIXME: this code here has the same race condition as process_kill
	if (process_is_alive(process)) {
		log_warn("Destroying process object (id: %u, executable: %s) while child process (pid: %u) is still alive",
		         process->base.id, process->executable->buffer, process->pid);

		rc = kill(process->pid, SIGKILL);

		if (rc < 0) {
			if (errno != ESRCH) {
				stuck = true;
			}

			log_error("Could not send SIGKILL signal to child process (executable: %s, pid: %u): %s (%d)",
			          process->executable->buffer, process->pid, get_errno_name(errno), errno);
		}
	}

	if (!stuck) {
		thread_join(&process->wait_thread);
	}

	thread_destroy(&process->wait_thread);

	pipe_destroy(&process->state_change_pipe);

	file_release(process->stderr);
	file_release(process->stdout);
	file_release(process->stdin);
	string_unlock_and_release(process->working_directory);
	list_unlock_and_release(process->environment);
	list_unlock_and_release(process->arguments);
	string_unlock_and_release(process->executable);

	free(process);
}
void program_scheduler_spawn_process(ProgramScheduler *program_scheduler) {
	int phase = 0;
	APIE error_code;
	File *stdin;
	File *stdout;
	File *stderr;
	Program *program = containerof(program_scheduler, Program, scheduler);
	struct timeval timestamp;
	Process *process;

	program_scheduler_abort_observer(program_scheduler);

	if (program_scheduler->last_spawned_process != NULL) {
		if (process_is_alive(program_scheduler->last_spawned_process)) {
			return; // don't spawn a new process while another one is already running
		}
	}

	// prepare stdin
	stdin = program_scheduler_prepare_stdin(program_scheduler);

	if (stdin == NULL) {
		goto cleanup;
	}

	phase = 1;

	// record timestamp
	if (gettimeofday(&timestamp, NULL) < 0) {
		timestamp.tv_sec = time(NULL);
		timestamp.tv_usec = 0;
	}

	// prepare stdout
	stdout = program_scheduler_prepare_stdout(program_scheduler, timestamp);

	if (stdout == NULL) {
		goto cleanup;
	}

	phase = 2;

	// prepare stderr
	stderr = program_scheduler_prepare_stderr(program_scheduler, timestamp, stdout);

	if (stderr == NULL) {
		goto cleanup;
	}

	phase = 3;

	// spawn process
	error_code = process_spawn(program->config.executable->base.id,
	                           program->config.arguments->base.id,
	                           program->config.environment->base.id,
	                           program_scheduler->absolute_working_directory->base.id,
	                           1000, 1000,
	                           stdin->base.id, stdout->base.id, stderr->base.id,
	                           NULL, OBJECT_CREATE_FLAG_INTERNAL, false,
	                           program_scheduler_handle_process_state_change,
	                           program_scheduler,
	                           NULL, &process);

	if (error_code != API_E_SUCCESS) {
		program_scheduler_handle_error(program_scheduler, false,
		                               "Could not spawn process: %s (%d)",
		                               api_get_error_code_name(error_code), error_code);

		goto cleanup;
	}

	phase = 4;

	if (program_scheduler->last_spawned_process != NULL) {
		object_remove_internal_reference(&program_scheduler->last_spawned_process->base);
	}

	program_scheduler->last_spawned_process = process;
	program_scheduler->last_spawned_timestamp = timestamp.tv_sec;

	program_scheduler->process_spawned(program_scheduler->opaque);

	object_remove_internal_reference(&stdin->base);
	object_remove_internal_reference(&stdout->base);
	object_remove_internal_reference(&stderr->base);

cleanup:
	switch (phase) { // no breaks, all cases fall through intentionally
	case 3:
		object_remove_internal_reference(&stderr->base);

	case 2:
		object_remove_internal_reference(&stdout->base);

	case 1:
		object_remove_internal_reference(&stdin->base);

	default:
		break;
	}

	if (phase != 4) {
		// an error occurred, continue-after-error if conditions are met
		if (program_scheduler->state == PROGRAM_SCHEDULER_STATE_RUNNING &&
		    program->config.continue_after_error &&
		    program->config.start_mode == PROGRAM_START_MODE_ALWAYS) {
			// FIXME: call this decoupled over the event loop to avoid recursion
			//        and possible stack overflow
			//program_scheduler_spawn_process(program_scheduler);
		}
	}
}
Beispiel #6
0
static int nilfs_cleaner_open_queue(struct nilfs_cleaner *cleaner)
{
	char nambuf[NAME_MAX - 4];
	char uuidbuf[36 + 1];
	struct mq_attr attr = {
		.mq_maxmsg = 3,
		.mq_msgsize = sizeof(struct nilfs_cleaner_response)
	};
	int ret;

	uuid_generate(cleaner->client_uuid);
	uuid_unparse_lower(cleaner->client_uuid, uuidbuf);

	/* receive queue */
	ret = snprintf(nambuf, sizeof(nambuf), "/nilfs-cleanerq-%s", uuidbuf);
	if (ret < 0)
		goto error;

	assert(ret < sizeof(nambuf));
	cleaner->recvq_name = strdup(nambuf);
	if (!cleaner->recvq_name)
		goto error;

	cleaner->recvq = mq_open(nambuf, O_RDONLY | O_CREAT | O_EXCL, 0600,
				 &attr);
	if (cleaner->recvq < 0) {
		nilfs_cleaner_logger(LOG_ERR,
				     _("Error: cannot create receive queue: "
				       "%s."),
				     strerror(errno));
		free(cleaner->recvq_name);
		goto abort;
	}
	/* send queue */
	if (cleaner->dev_ino == 0) {
		ret = snprintf(nambuf, sizeof(nambuf),
			       "/nilfs-cleanerq-%llu",
			       (unsigned long long)cleaner->dev_id);
	} else {
		ret = snprintf(nambuf, sizeof(nambuf),
			       "/nilfs-cleanerq-%llu-%llu",
			       (unsigned long long)cleaner->dev_id,
			       (unsigned long long)cleaner->dev_ino);
	}
	if (ret < 0)
		goto error;

	assert(ret < sizeof(nambuf));

	cleaner->sendq = mq_open(nambuf, O_WRONLY);
	if (cleaner->sendq < 0) {
		if (errno == ENOENT) {
			nilfs_cleaner_logger(LOG_NOTICE,
					     _("No cleaner found on %s."),
					     cleaner->device);
		} else {
			nilfs_cleaner_logger(LOG_ERR,
					     _("Error: cannot open cleaner on "
					       "%s: %s."),
					     cleaner->device, strerror(errno));
		}
		goto abort;
	}

	return 0;

error:
	nilfs_cleaner_logger(LOG_ERR,
			     _("Error: fatal error during queue setting: %s."),
			     strerror(errno));
abort:
	if (cleaner->recvq >= 0) {
		mq_close(cleaner->recvq);
		cleaner->recvq = -1;
		mq_unlink(cleaner->recvq_name);
		free(cleaner->recvq_name);
	}
	return -1;
}

static void nilfs_cleaner_close_queue(struct nilfs_cleaner *cleaner)
{
	if (cleaner->recvq >= 0) {
		mq_close(cleaner->recvq);
		mq_unlink(cleaner->recvq_name);
		free(cleaner->recvq_name);
		cleaner->recvq = -1;
		cleaner->recvq_name = NULL;
	}
	if (cleaner->sendq >= 0) {
		mq_close(cleaner->sendq);
		cleaner->sendq = -1;
	}
}

struct nilfs_cleaner *nilfs_cleaner_launch(const char *device,
					   const char *mntdir,
					   unsigned long protperiod)
{
	struct nilfs_cleaner *cleaner;

	cleaner = malloc(sizeof(*cleaner));
	if (!cleaner)
		goto error;
	memset(cleaner, 0, sizeof(*cleaner));
	cleaner->sendq = -1;
	cleaner->recvq = -1;

	cleaner->device = strdup(device);
	cleaner->mountdir = strdup(mntdir);
	if (!cleaner->device || !cleaner->mountdir)
		goto error;

	if (nilfs_launch_cleanerd(device, mntdir, protperiod,
				  &cleaner->cleanerd_pid) < 0)
		goto abort;

	if (nilfs_cleaner_get_device_id(cleaner) < 0)
		goto abort;

	return cleaner; /* cleanerd started */

error:
	nilfs_cleaner_logger(LOG_ERR,  _("Error: %s"), strerror(errno));
abort:
	if (cleaner) {
		free(cleaner->device); /* free(NULL) is just ignored */
		free(cleaner->mountdir);
		free(cleaner);
	}
	return NULL;
}

struct nilfs_cleaner *nilfs_cleaner_open(const char *device,
					 const char *mntdir, int oflag)
{
	struct nilfs_cleaner *cleaner;

	cleaner = malloc(sizeof(*cleaner));
	if (!cleaner)
		goto error;
	memset(cleaner, 0, sizeof(*cleaner));
	cleaner->sendq = -1;
	cleaner->recvq = -1;

	if (nilfs_cleaner_find_fs(cleaner, device, mntdir) < 0)
		goto abort;

	if (nilfs_cleaner_get_device_id(cleaner) < 0)
		goto abort;

	if ((oflag & NILFS_CLEANER_OPEN_GCPID) && cleaner->cleanerd_pid == 0) {
		nilfs_cleaner_logger(LOG_ERR,
				     _("Error: cannot get cleanerd pid"));
		goto abort;
	}

	if ((oflag & NILFS_CLEANER_OPEN_QUEUE) &&
	    nilfs_cleaner_open_queue(cleaner) < 0)
		goto abort;

	return cleaner;

error:
	nilfs_cleaner_logger(LOG_ERR,  _("Error: %s"), strerror(errno));
abort:
	if (cleaner) {
		free(cleaner->device); /* free(NULL) is just ignored */
		free(cleaner->mountdir);
		free(cleaner);
	}
	return NULL;
}

int nilfs_cleaner_ping(struct nilfs_cleaner *cleaner)
{
	return process_is_alive(cleaner->cleanerd_pid);
}

pid_t nilfs_cleaner_pid(const struct nilfs_cleaner *cleaner)
{
	return cleaner->cleanerd_pid;
}

const char *nilfs_cleaner_device(const struct nilfs_cleaner *cleaner)
{
	return cleaner->device;
}

void nilfs_cleaner_close(struct nilfs_cleaner *cleaner)
{
	nilfs_cleaner_close_queue(cleaner);
	free(cleaner->device);
	free(cleaner->mountdir);
	free(cleaner);
}