mpw-shell/mpw-shell.cpp

613 lines
12 KiB
C++
Raw Permalink Normal View History

2016-01-27 15:43:34 +00:00
#include <vector>
#include <string>
#include <unordered_map>
2016-08-31 00:59:44 +00:00
#include <atomic>
#include <algorithm>
2016-01-27 15:43:34 +00:00
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
2016-01-30 17:45:50 +00:00
#include <cerrno>
2016-02-05 18:19:20 +00:00
#include <signal.h>
2016-08-06 02:07:22 +00:00
#include <sys/wait.h>
2016-08-31 00:57:37 +00:00
#include <getopt.h>
2016-01-27 15:43:34 +00:00
#include "mpw-shell.h"
#include "mpw_parser.h"
2016-02-02 01:38:29 +00:00
#include "fdset.h"
2016-01-27 15:43:34 +00:00
2016-07-23 19:29:22 +00:00
#include "macroman.h"
2016-02-11 20:46:40 +00:00
#include "cxx/mapped_file.h"
2016-02-11 20:49:05 +00:00
#include "cxx/filesystem.h"
#include "cxx/string_splitter.h"
#include "error.h"
2016-02-02 03:32:21 +00:00
2016-08-06 02:06:36 +00:00
#include <readline/readline.h>
2016-08-06 02:14:36 +00:00
#include <readline/history.h>
2016-01-27 15:43:34 +00:00
2016-02-03 02:57:53 +00:00
#include <sys/types.h>
#include <pwd.h>
#include <sysexits.h>
2016-02-22 17:02:27 +00:00
#include <paths.h>
2016-02-03 02:57:53 +00:00
2016-06-17 01:55:39 +00:00
#include "version.h"
2016-02-11 20:49:05 +00:00
namespace fs = filesystem;
2016-07-23 19:29:22 +00:00
bool utf8 = false;
2016-02-03 02:57:53 +00:00
fs::path home() {
const char *cp = getenv("HOME");
if (cp && cp) {
auto pw = getpwuid(getuid());
if (pw) return fs::path(pw->pw_dir);
}
return fs::path();
}
2016-02-11 20:49:05 +00:00
fs::path root() {
2016-02-03 02:57:53 +00:00
2016-02-11 20:49:05 +00:00
static fs::path root;
bool init = false;
static std::array<filesystem::path, 2> locations = { {
"/usr/share/mpw/",
"/usr/local/share/mpw/"
} };
if (!init) {
init = true;
std::error_code ec;
fs::path p;
p = home();
if (!p.empty()) {
p /= "mpw/";
if (fs::is_directory(p, ec)) {
root = std::move(p);
return root;
2016-02-03 02:57:53 +00:00
}
}
for (fs::path p : locations) {
p /= "mpw/";
if (fs::is_directory(p, ec)) {
root = std::move(p);
return root;
}
}
fprintf(stderr, "### Warning: Unable to find mpw directory.\n");
2016-02-03 02:57:53 +00:00
}
return root;
}
2016-01-27 15:43:34 +00:00
// should set {MPW}, {MPWVersion}, then execute {MPW}StartUp
2016-02-03 02:57:53 +00:00
void init(Environment &env) {
env.set("mpw", root());
2016-06-17 01:55:39 +00:00
env.set("status", 0);
env.set("exit", 1); // terminate script on error.
env.set("echo", 1);
2016-01-27 15:43:34 +00:00
}
2016-02-05 18:19:20 +00:00
int read_file(Environment &e, const std::string &file, const fdmask &fds) {
2016-08-09 18:40:27 +00:00
std::error_code ec;
const mapped_file mf(file, mapped_file::readonly, ec);
if (ec) {
fprintf(stderr, "# Error reading %s: %s\n", file.c_str(), ec.message().c_str());
return e.status(-1, false);
2016-08-09 18:40:27 +00:00
}
2016-02-02 03:32:21 +00:00
mpw_parser p(e, fds);
e.status(0, false);
try {
p.parse(mf.begin(), mf.end());
p.finish();
} catch(const execution_of_input_terminated &ex) {
return ex.status();
}
return e.status();
2016-02-02 03:32:21 +00:00
}
int read_string(Environment &e, const std::string &s, const fdmask &fds) {
mpw_parser p(e, fds);
e.status(0, false);
try {
p.parse(s);
p.finish();
} catch(const execution_of_input_terminated &ex) {
return ex.status();
}
return e.status();
}
int read_fd(Environment &e, int fd, const fdmask &fds) {
2016-01-30 17:45:50 +00:00
unsigned char buffer[2048];
ssize_t size;
mpw_parser p(e, fds);
e.status(0, false);
try {
for (;;) {
size = read(fd, buffer, sizeof(buffer));
if (size < 0) {
if (errno == EINTR) continue;
perror("read");
e.status(-1, false);
}
if (size == 0) break;
p.parse(buffer, buffer + size);
2016-01-30 17:45:50 +00:00
}
p.finish();
} catch(const execution_of_input_terminated &ex) {
return ex.status();
2016-01-30 17:45:50 +00:00
}
return e.status();
2016-01-30 17:45:50 +00:00
}
2016-02-06 04:00:42 +00:00
void launch_mpw(const Environment &env, const std::vector<std::string> &argv, const fdmask &fds);
2016-08-11 00:05:35 +00:00
fs::path which(const Environment &env, const std::string &name);
2016-02-06 04:00:42 +00:00
int read_make(Environment &env, const std::vector<std::string> &argv) {
2016-02-06 04:00:42 +00:00
int out[2];
int ok;
2016-06-17 01:55:39 +00:00
env.set("echo", 1);
env.set("exit", 1);
2016-02-06 04:00:42 +00:00
ok = pipe(out);
if (ok < 0) {
perror("pipe");
exit(EX_OSERR);
}
fcntl(out[0], F_SETFD, FD_CLOEXEC);
fcntl(out[1], F_SETFD, FD_CLOEXEC);
int child = fork();
if (child < 0) {
perror("fork");
exit(EX_OSERR);
}
if (child == 0) {
// child.
fdmask fds = {-1, out[1], -1};
launch_mpw(env, argv, fds);
exit(EX_OSERR);
}
close(out[1]);
int rv = read_fd(env, out[0]);
2016-02-06 04:00:42 +00:00
close(out[0]);
2016-02-06 04:00:42 +00:00
// check for make errors.
for(;;) {
int status;
int ok = waitpid(child, &status, 0);
if (ok < 0) {
if (errno == EINTR) continue;
perror("waitpid: ");
exit(EX_OSERR);
}
if (WIFEXITED(status)) {
ok = WEXITSTATUS(status);
env.status(ok, false);
break;
}
if (WIFSIGNALED(status)) {
env.status(-9, false);
break;
}
fprintf(stderr, "waitpid - unexpected result\n");
exit(EX_OSERR);
}
return rv;
2016-02-06 04:00:42 +00:00
}
std::atomic<int> control_c{0};
2016-02-05 18:19:20 +00:00
void control_c_handler(int signal, siginfo_t *sinfo, void *context) {
// libedit gobbles up the first control-C and doesn't return until the second.
// GNU's readline may return on the first.
2016-02-05 18:19:20 +00:00
if (control_c > 3) abort();
++control_c;
2016-02-05 18:19:20 +00:00
//fprintf(stderr, "interrupt!\n");
}
2016-07-21 19:14:27 +00:00
int interactive(Environment &env) {
2016-02-02 02:17:46 +00:00
2016-02-03 03:59:18 +00:00
std::string history_file = root();
history_file += ".history";
read_history(history_file.c_str());
2016-02-05 18:19:20 +00:00
struct sigaction act;
struct sigaction old_act;
memset(&act, 0, sizeof(struct sigaction));
sigemptyset(&act.sa_mask);
act.sa_sigaction = control_c_handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGINT, &act, &old_act);
mpw_parser p(env, true);
2016-07-21 19:14:27 +00:00
2016-02-02 02:17:46 +00:00
for(;;) {
2016-02-05 02:57:17 +00:00
const char *prompt = "# ";
if (p.continuation()) prompt = "> ";
2016-02-05 02:57:17 +00:00
char *cp = readline(prompt);
2016-02-05 18:19:20 +00:00
if (!cp) {
if (control_c) {
control_c = 0;
fprintf(stdout, "\n");
p.abort();
2016-02-05 18:47:34 +00:00
env.status(-9, false);
2016-02-05 18:19:20 +00:00
continue;
}
break;
}
control_c = 0;
2016-02-02 02:17:46 +00:00
std::string s(cp);
free(cp);
2016-07-21 19:14:27 +00:00
//if (s.empty()) continue;
// don't add if same as previous entry.
2016-07-21 19:14:27 +00:00
if (!s.empty()) {
HIST_ENTRY *he = history_get(history_length);
if (he == nullptr || s != he->line)
add_history(s.c_str());
}
2016-07-23 19:29:22 +00:00
if (utf8)
s = utf8_to_macroman(s);
2016-02-02 02:17:46 +00:00
s.push_back('\n');
p.parse(s);
2016-02-02 02:17:46 +00:00
}
p.finish();
2016-02-02 02:17:46 +00:00
2016-02-05 18:19:20 +00:00
sigaction(SIGINT, &old_act, nullptr);
2016-02-03 03:59:18 +00:00
write_history(history_file.c_str());
2016-02-02 02:17:46 +00:00
fprintf(stdout, "\n");
2016-02-03 03:59:18 +00:00
2016-02-02 02:17:46 +00:00
return 0;
}
2016-02-03 03:15:31 +00:00
void help() {
2016-07-19 16:37:12 +00:00
#undef _
#define _(x) puts(x)
_("MPW Shell " VERSION " (" VERSION_DATE ")");
_("mpw-shell [option...]");
2016-07-21 15:48:41 +00:00
_(" -c string # read commands from string");
2016-07-19 16:37:12 +00:00
_(" -d name[=value] # define variable name");
2016-07-21 15:48:41 +00:00
_(" -f # don't load MPW:Startup file");
2016-07-19 16:37:12 +00:00
_(" -h # display help information");
2016-07-21 15:48:41 +00:00
_(" -v # be verbose (echo = 1)");
2016-07-19 16:37:12 +00:00
#undef _
2016-02-03 03:15:31 +00:00
}
2016-07-19 16:37:12 +00:00
2016-02-03 03:15:31 +00:00
void define(Environment &env, const std::string &s) {
auto pos = s.find('=');
2016-06-17 01:55:39 +00:00
if (pos == s.npos) env.set(s, 1);
2016-02-03 03:15:31 +00:00
else {
std::string k = s.substr(0, pos);
std::string v = s.substr(pos+1);
env.set(k, v);
}
}
2016-08-11 00:05:35 +00:00
/*
*
* todo: prevent -r and -s (don't generate shell code)
*/
2016-02-06 17:55:00 +00:00
void make_help(void) {
#undef _
#define _(x) puts(x)
_("Make # build up-to-date version of a program");
_("Make [option...] [target...]");
_(" -d name[=value] # define variable name (overrides makefile definition)");
_(" -e # rebuild everything regardless of dates");
_(" -f filename # read dependencies from specified file (default: MakeFile)");
_(" -i dirname # additional directory to search for include files");
#if 0
_(" -[no]mf # [don't] use temporary memory (default: mf)");
#endif
_(" -p # write progress information to diagnostics");
_(" -r # display the roots of the dependency graph");
_(" -s # display the structure of the dependency graph");
_(" -t # touch dates of targets and prerequisites");
_(" -u # write list of unreachable targets to diagnostics");
_(" -v # write verbose explanations to diagnostics (implies -p)");
_(" -w # suppress warning messages");
_(" -y # like -v, but omit announcing up-to-date targets");
_("");
_(" --help # display help");
_(" --dry-run, --test # show what commands would run");
#undef _
2016-02-06 04:00:42 +00:00
}
int make(int argc, char **argv) {
Environment e;
init(e);
std::vector<std::string> args;
args.reserve(argc+1);
2016-08-31 00:57:37 +00:00
int c;
bool passthrough = false;
2016-02-06 04:00:42 +00:00
2016-08-31 00:57:37 +00:00
static struct option longopts[] = {
{ "help", no_argument, nullptr, 'h' },
{ "verbose", no_argument, nullptr, 'v' },
{ "test", no_argument, nullptr, 1 },
{ "dry-run", no_argument, nullptr, 2 },
{ nullptr, 0, nullptr, 0},
};
2016-08-11 00:05:35 +00:00
args.push_back(""); // place-holder.
2016-02-06 04:00:42 +00:00
2016-08-31 00:57:37 +00:00
while ((c = getopt_long(argc, argv, "d:ef:i:prstuvwy", longopts, nullptr)) != -1) {
std::string flag = "-"; flag.push_back(c);
switch(c) {
default:
make_help();
return EX_USAGE;
2016-02-06 04:00:42 +00:00
2016-08-31 00:57:37 +00:00
case 'h':
make_help();
return 0;
2016-02-06 04:00:42 +00:00
2016-08-31 00:57:37 +00:00
case 1:
e.set("test", 1);
break;
case 2:
passthrough = true;
break;
case 'd':
case 'f':
case 'i':
args.push_back(std::move(flag));
args.push_back(optarg);
break;
case 'e':
case 'p':
case 't':
case 'u':
case 'v':
case 'w':
case 'y':
args.push_back(std::move(flag));
break;
2016-02-06 04:00:42 +00:00
2016-08-31 00:57:37 +00:00
case 'r':
case 's':
args.push_back(std::move(flag));
passthrough = true;
break;
2016-02-06 04:00:42 +00:00
}
2016-08-31 00:57:37 +00:00
2016-02-06 04:00:42 +00:00
}
2016-08-31 00:59:44 +00:00
2016-08-31 00:57:37 +00:00
argc -= optind;
argv += optind;
std::transform(argv, argv+argc, std::back_inserter(args), [](const char *cp){
return std::string(cp);
});
2016-02-06 04:00:42 +00:00
e.startup(true);
read_file(e, root() / "Startup");
e.startup(false);
2016-08-11 00:05:35 +00:00
auto path = which(e, "Make");
if (path.empty()) {
fputs("### MPW Shell - Command \"Make\" was not found.\n", stderr);
return -1;
}
e.set("command", path);
args[0] = path;
2016-08-31 00:57:37 +00:00
if (passthrough) {
launch_mpw(e, args, fdmask());
exit(EX_OSERR);
}
return read_make(e, args);
2016-02-06 04:00:42 +00:00
}
2016-02-11 20:49:05 +00:00
fs::path mpw_path() {
static fs::path path;
if (path.empty()) {
std::error_code ec;
2016-02-22 17:02:27 +00:00
const char *cp = getenv("PATH");
if (!cp) cp = _PATH_DEFPATH;
std::string s(cp);
2016-02-11 20:49:05 +00:00
string_splitter ss(s, ':');
for (; ss; ++ss) {
if (ss->empty()) continue;
fs::path p(*ss);
p /= "mpw";
if (fs::is_regular_file(p, ec)) {
path = std::move(p);
break;
}
}
//also check /usr/local/bin
if (path.empty()) {
fs::path p = "/usr/local/bin/mpw";
if (fs::is_regular_file(p, ec)) {
path = std::move(p);
}
}
if (path.empty()) {
fs::path p = root() / "bin/mpw";
if (fs::is_regular_file(p, ec)) {
path = std::move(p);
}
}
if (path.empty()) {
fprintf(stderr, "Unable to find mpw executable\n");
fprintf(stderr, "PATH = %s\n", s.c_str());
path = "mpw";
}
}
return path;
}
2016-07-23 19:29:22 +00:00
void init_locale() {
/*
* libedit assumes utf-8 if locale is C.
* MacRoman is en_US. utf-8 is en_US.UTF8.
*/
const char *lang = getenv("LANG");
/*
if (lang && !strcmp(lang, "en_US")) {
setlocale(LC_ALL, "POSIX");
}
*/
utf8 = false;
if (lang && strcasestr(lang, ".UTF-8")) {
utf8 = true;
}
}
2016-01-27 15:43:34 +00:00
int main(int argc, char **argv) {
2016-02-06 04:00:42 +00:00
2016-07-23 19:29:22 +00:00
init_locale();
2016-07-21 15:48:41 +00:00
2016-02-11 20:49:05 +00:00
mpw_path();
2016-02-06 04:00:42 +00:00
2016-08-06 02:14:36 +00:00
fs::path self = fs::path(argv[0]).filename();
2016-08-31 00:57:37 +00:00
if (self == "mpw-make") return make(argc, argv);
if (self == "mpw-shell" && argc > 1 && !strcmp(argv[1],"make")) {
argv[1] = (char *)"mpw-make";
return make(argc - 1, argv + 1);
}
2016-01-27 15:43:34 +00:00
2016-02-02 01:38:29 +00:00
Environment e;
init(e);
2016-01-27 15:43:34 +00:00
2016-02-03 03:39:06 +00:00
const char *cflag = nullptr;
2016-07-23 19:29:22 +00:00
bool fflag = false;
2016-02-03 03:39:06 +00:00
2016-02-03 03:15:31 +00:00
int c;
2016-07-23 19:20:24 +00:00
while ((c = getopt(argc, argv, "c:D:vhf")) != -1) {
2016-02-03 03:15:31 +00:00
switch (c) {
2016-02-03 03:39:06 +00:00
case 'c':
// -c command
cflag = optarg;
break;
2016-02-03 03:15:31 +00:00
case 'D':
// -Dname or -Dname=value
define(e, optarg);
break;
case 'v':
// -v verbose
e.set("echo", "1");
break;
2016-07-21 15:48:41 +00:00
case 'f':
fflag = true;
break;
2016-02-03 03:15:31 +00:00
case 'h':
help();
exit(0);
default:
help();
exit(EX_USAGE);
}
}
2016-06-17 01:55:39 +00:00
if (!cflag) fprintf(stdout, "MPW Shell " VERSION "\n");
2016-07-21 15:48:41 +00:00
if (!fflag) {
fs::path startup = root() / "Startup";
2016-07-21 15:48:41 +00:00
e.startup(true);
mpw_parser p(e);
try {
read_file(e, startup);
} catch (const std::system_error &ex) {
fprintf(stderr, "### %s: %s\n", startup.c_str(), ex.what());
} catch (const quit_command_t &) {
}
2016-07-21 15:48:41 +00:00
e.startup(false);
}
2016-02-02 03:32:21 +00:00
try {
2016-02-03 03:39:06 +00:00
int rv = 0;
if (cflag) {
rv = read_string(e, cflag);
exit(rv);
}
2016-01-30 17:45:50 +00:00
if (isatty(STDIN_FILENO))
rv = interactive(e);
else
rv = read_fd(e, STDIN_FILENO);
exit(rv);
}
catch (const quit_command_t &) {
exit(0);
}
2016-01-27 15:43:34 +00:00
}