τau / os/exec /

exec.tau

source
/Users/niconex/Documents/tau/stdlib/os/exec/exec.tau
1# exec - running other programs.2#3# Run takes the program and its arguments as a list, no shell in between, and4# waits for it to finish:5#6#	exec = import("os/exec")7#8#	r = exec.Run(["git", "rev-parse", "HEAD"])9#	if r.Status != 0 { ... }10#	println(r.Stdout)11#12# Output is the short way to the text of a program that is expected to work,13# and returns an error when it doesn't:14#15#	head = exec.Output(["git", "rev-parse", "HEAD"])16#17# The arguments go to the program as they are, so a file named "; rm -rf ."18# is a file name and nothing else. Sh runs a command line through /bin/sh when19# pipes and redirections are really wanted, and then the string is a program:20# don't build it out of anything that came from outside.21#22# ponytail: the program is waited for, its output arrives at the end. Nothing23# runs in the background and nothing is read while it runs, which is what a24# long lived child would need.2526syscall = import("syscall")27os = import("os")28strings = import("strings")2930# MaxOutput is how much of the output of a program is kept by default, one31# megabyte. Options.Max raises it for a program that writes more.32MaxOutput = 1 << 203334mergeStderr = 135inherit = 23637# Options returns the settings of a run, to change and pass to RunWith:38#39#	o = exec.Options()40#	o.Input = "text on the standard input"41#	o.Stderr = true          # the error output is kept along with the output42#	o.Quiet = false          # true lets the program write to the terminal43#	o.Max = 1 << 24          # keep up to sixteen megabytes44#	o.Dir = "/tmp"           # run it there45Options = fn() {46	o = new()47	o.Input = null48	o.Stderr = false49	o.Quiet = false50	o.Max = MaxOutput51	o.Dir = null52	return o53}5455# blobOf packs the arguments the way Spawn wants them: every one of them56# followed by a zero byte.57blobOf = fn(argv) {58	out = []59	for i = 0; i < len(argv); ++i {60		# Bytes are taken as they are: turning them into a string first would61		# stop at the first zero byte, which is what a program may not be62		# given as an argument anyway.63		b = if type(argv[i]) == "bytes" { argv[i] } else { bytes(string(argv[i])) }64		for j = 0; j < len(b); ++j {65			if b[j] == 0 {66				return error("exec: argument {i} has a zero byte in it")67			}68			out = append(out, b[j])69		}70		out = append(out, 0)71	}72	return bytes(out)73}7475# RunWith runs argv with the given options and returns the result: Status is76# the exit status, Stdout what the program wrote, and Truncated whether more77# came out than Max allowed.78RunWith = fn(argv, opts) {79	if type(argv) == "string" {80		argv = [argv]81	}82	if len(argv) == 0 {83		return error("exec: nothing to run")84	}85	if opts == null {86		opts = Options()87	}8889	if failed(blob = blobOf(argv)) {90		return blob91	}9293	input = null94	if opts.Input != null {95		if failed(input = bytes(string(opts.Input))) {96			return input97		}98	}99100	flags = 0101	if opts.Stderr {102		flags = flags | mergeStderr103	}104	if opts.Quiet {105		flags = flags | inherit106	}107108	out = null109	if !opts.Quiet {110		out = bytes(if opts.Max == null { MaxOutput } else { opts.Max })111	}112113	# A directory of its own means going there and back, since the child is114	# forked from where this process stands.115	back = null116	if opts.Dir != null {117		if failed(back = os.Getwd()) {118			return back119		}120		if failed(err = os.Chdir(opts.Dir)) {121			return err122		}123	}124125	n = syscall.Spawn(blob, len(argv), input, out, flags)126	status = syscall.SpawnStatus()127128	if back != null {129		os.Chdir(back)130	}131	if failed(n) {132		return error("exec: cannot run {argv[0]}: {n}")133	}134135	r = new()136	r.Argv = argv137	r.Status = status138	r.Truncated = out != null && n > len(out)139	r.Stdout = if out == null { "" } else { string(slice(out, 0, if r.Truncated { len(out) } else { n })) }140	return r141}142143# Run runs argv, keeps what it writes, and returns the result. Only a program144# that could not be started at all is an error: one that ran and failed is a145# Status other than zero.146Run = fn(argv) { RunWith(argv, null) }147148# Output returns what argv wrote on its standard output, and an error when it149# ended with a status other than zero.150Output = fn(argv) {151	if failed(r = Run(argv)) {152		return r153	}154	if r.Status != 0 {155		return error("exec: {r.Argv[0]} ended with status {r.Status}")156	}157	return r.Stdout158}159160# Status runs argv without keeping its output, which goes to the terminal, and161# returns the exit status.162Status = fn(argv) {163	o = Options()164	o.Quiet = true165166	if failed(r = RunWith(argv, o)) {167		return r168	}169	return r.Status170}171172# Sh runs a command line through /bin/sh, pipes, redirections and all. The173# command is a program, so keep anything that came from outside out of it, or174# pass it in Options.Input.175Sh = fn(cmd) { Run(["/bin/sh", "-c", string(cmd)]) }176177# Look returns the path of the program name, searched for in PATH the way a178# shell would, or an error when there is no such program.179Look = fn(name) {180	name = string(name)181	if strings.Contains(name, "/") {182		if os.Exists(name) {183			return name184		}185		return error("exec: {name} is not there")186	}187188	path = os.Getenv("PATH")189	if path == null || path == "" {190		path = "/usr/local/bin:/usr/bin:/bin"191	}192193	dirs = strings.Split(path, ":")194	for i = 0; i < len(dirs); ++i {195		if dirs[i] == "" {196			continue197		}198199		full = dirs[i] + "/" + name200		if os.Exists(full) {201			return full202		}203	}204	return error("exec: {name} is not in PATH")205}