File: exec.ha

package info (click to toggle)
hare 0.25.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,948 kB
  • sloc: asm: 1,264; makefile: 123; sh: 114; lisp: 101
file content (176 lines) | stat: -rw-r--r-- 4,343 bytes parent folder | download
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
175
176
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use errors;
use fs;
use io;
use os;
use rt;
use types::c;
use unix;
use path;

// Forks the current process, returning the [[process]] of the child (to the
// parent) and void (to the child), or an error.
export fn fork() (process | void | error) = {
	match (rt::fork()) {
	case let err: rt::errno  =>
		return errors::errno(err);
	case let i: int =>
		return i: process;
	case void =>
		return void;
	};
};

// Creates an anonymous pipe for use with [[addfile]]. Any data written to the
// second file may be read from the first file. The caller should close one or
// both of the file descriptors after they have transferred them to another
// process, and after they have finished using them themselves, if applicable.
//
// This function will abort the process if the system is unable to allocate the
// resources for a pipe. If you need to handle this error gracefully, you may
// call [[unix::pipe]] yourself, but this may reduce the portability of your
// software.
//
// To capture the standard output of a process:
//
// 	const (read, write) = exec::pipe();
// 	exec::addfile(&cmd, os::stdout_file, write);
// 	let proc = exec::start(&cmd)!;
// 	io::close(write)!;
//
// 	let data = io::drain(read)!;
// 	io::close(read)!;
// 	exec::wait(&proc)!;
//
// To write to the standard input of a process:
//
// 	const (read, write) = exec::pipe();
// 	exec::addfile(&cmd, os::stdin_file, read);
// 	let proc = exec::start(&cmd)!;
// 	io::close(read)!;
//
// 	io::writeall(write, data)!;
// 	io::close(write)!;
// 	exec::wait(&proc)!;
export fn pipe() (io::file, io::file) = {
	return unix::pipe()!;
};

fn open(path: str) (platform_cmd | error) = {
	if (os::access(path, os::amode::X_OK)?) {
		// Length was already checked by access()
		return path::init(path)!;
	};
	return errors::noaccess;
};

fn platform_exec(cmd: *command) error = {
	let need_devnull = false;
	for (let file &.. cmd.files) {
		const from = match (file.0) {
		case let file: io::file =>
			yield file;
		case nullfd =>
			need_devnull = true;
			continue;
		case closefd =>
			continue;
		};

		file.0 = match (rt::fcntl(from, rt::F_DUPFD_CLOEXEC, 0)) {
		case let fd: int =>
			yield fd;
		case let err: rt::errno =>
			return errors::errno(err);
		};
	};

	const devnull: io::file = if (need_devnull) {
		yield os::open("/dev/null", fs::flag::RDWR)!;
	} else -1;

	for (let file .. cmd.files) {
		const from = match (file.0) {
		case let file: io::file =>
			yield file;
		case nullfd =>
			yield devnull;
		case closefd =>
			io::close(file.1)?;
			continue;
		};

		if (file.1 == from) {
			let flags = match (rt::fcntl(from, rt::F_GETFD, 0)) {
			case let flags: int =>
				yield flags;
			case let e: rt::errno =>
				return errors::errno(e);
			};
			rt::fcntl(from, rt::F_SETFD, flags & ~rt::FD_CLOEXEC)!;
		} else {
			match (rt::dup2(from, file.1)) {
			case int => void;
			case let e: rt::errno =>
				return errors::errno(e);
			};
		};
	};

	if (cmd.dir != "") {
		os::chdir(cmd.dir)?;
	};

	let envp: nullable *[*]nullable *const c::char = null;
	if (len(cmd.env) > 1) {
		envp = cmd.env: *[*]nullable *const c::char;
	};

	return errors::errno(rt::execve(path::string(&cmd.platform),
		cmd.argv: *[*]nullable *const u8,
		envp: *[*]nullable *const u8));
};

fn platform_start(cmd: *command) (process | errors::error) = {
	// TODO: Let the user configure clone more to their taste (e.g. SIGCHLD)
	let pipe: [2]int = [0...];
	match (rt::pipe2(&pipe, rt::O_CLOEXEC)) {
	case let err: rt::errno =>
		return errors::errno(err);
	case void => void;
	};

	match (rt::fork()) {
	case let err: rt::errno =>
		return errors::errno(err);
	case let pid: int =>
		rt::close(pipe[1])!;
		defer rt::close(pipe[0])!;
		let errno: int = 0;
		match (rt::read(pipe[0], &errno, size(int))) {
		case let err: rt::errno =>
			return errors::errno(err);
		case let n: size =>
			switch (n) {
			case size(int) =>
				return errors::errno(errno);
			case 0 =>
				return pid;
			case =>
				abort("Unexpected rt::read result");
			};
		};
	case void =>
		rt::close(pipe[0])!;
		let err = platform_exec(cmd);
		if (!(err is errors::opaque_)) {
			rt::exit(1);
		};
		let err = err as errors::opaque_;
		let err = &err.data: *rt::errno;
		rt::write(pipe[1], err, size(int))!;
		rt::exit(1);
	};
};