aboutsummaryrefslogtreecommitdiffstats
path: root/feature-tests/exec.pl
diff options
context:
space:
mode:
authorTom Feist <shabble@metavore.org>2011-04-13 08:30:08 +0000
committerTom Feist <shabble@metavore.org>2011-04-13 08:30:08 +0000
commitc6752d86b8a161d5ee3c4b008eb78a008b755e40 (patch)
tree0df24ca7cb704f9db5a5c4d6a8f2bd8045e9a19c /feature-tests/exec.pl
parentrl_history_search: make split info text bold (diff)
downloadirssi-scripts-c6752d86b8a161d5ee3c4b008eb78a008b755e40.tar.gz
irssi-scripts-c6752d86b8a161d5ee3c4b008eb78a008b755e40.zip
added exec.pl, a pseudo-replacement for teh native /exec since it's broken and I
can't figure out why.
Diffstat (limited to 'feature-tests/exec.pl')
-rw-r--r--feature-tests/exec.pl248
1 files changed, 248 insertions, 0 deletions
diff --git a/feature-tests/exec.pl b/feature-tests/exec.pl
new file mode 100644
index 0000000..e268291
--- /dev/null
+++ b/feature-tests/exec.pl
@@ -0,0 +1,248 @@
+# exec.pl
+# a (currently stupid) alternative to the built-in /exec, because it's broken
+# on OSX. This thing stll needs a whole bunch of actual features, but for now,
+# you can actually run commands.
+
+# Obviously, that's pretty dangerous. Use at your own risk.
+
+# EXEC [-] [-nosh] [-out | -msg <target> | -notice <target>] [-name <name>] <cmd line>
+# EXEC -out | -window | -msg <target> | -notice <target> | -close | -<signal> %<id>
+# EXEC -in %<id> <text to send to process>
+#
+# -: Don't print "process terminated ..." message
+#
+# -nosh: Don't start command through /bin/sh
+#
+# -out: Send output to active channel/query
+#
+# -msg: Send output to specified nick/channel
+#
+# -notice: Send output to specified nick/channel as notices
+#
+# -name: Name the process so it could be accessed easier
+#
+# -window: Move the output of specified process to active window
+#
+# -close: Forcibly close (or "forget") a process that doesn't die.
+# This only removes all information from irssi concerning the
+# process, it doesn't send SIGKILL or any other signal
+# to the process.
+#
+# -<signal>: Send a signal to process. <signal> can be either numeric
+# or one of the few most common ones (hup, term, kill, ...)
+#
+# -in: Send text to standard input of the specified process
+#
+# -interactive: Creates a query-like window item. Text written to it is
+# sent to executed process, like /EXEC -in.
+#
+# Execute specified command in background. Output of process is printed to
+# active window by default, but can be also sent as messages or notices to
+# specified nick or channel.
+#
+# Processes can be accessed either by their ID or name if you named it. Process
+# identifier must always begin with '%' character, like %0 or %name.
+#
+# Once the process is started, its output can still be redirected elsewhere with
+# the -window, -msg, etc. options. You can send text to standard input of the
+# process with -in option.
+#
+# -close option shouldn't probably be used if there's a better way to kill the
+# process. It is meant to remove the processes that don't die even with
+# SIGKILL. This option just closes the pipes used to communicate with the
+# process and frees all memory it used.
+#
+# EXEC without any arguments displays the list of started processes.
+#
+
+
+
+use 5.010; # 5.10 or above, necessary to get the return value from a command.
+
+use strict;
+use warnings;
+use English '-no_match_vars';
+
+use Irssi;
+use POSIX;
+use Time::HiRes qw/sleep/;
+use IO::Handle;
+use IO::Pipe;
+use IPC::Open3;
+
+
+use Data::Dumper;
+
+our $VERSION = '0.1';
+our %IRSSI = (
+ authors => 'shabble',
+ contact => 'shabble+irssi@metavore.org',
+ name => 'exec.pl',
+ description => '',
+ license => 'Public Domain',
+ );
+
+
+my $forked = 0;
+
+my $command;
+my $command_options;
+
+
+sub parse_options {
+ my ($args) = @_;
+ my @options = Irssi::command_parse_options($command, $args);
+ if (@options) {
+ my $opt_hash = $options[0];
+ my $rest = $options[1];
+
+ print Dumper($opt_hash);
+ return ($opt_hash, $rest);
+ } else {
+ _error("Error parsing $command options");
+ return ();
+ }
+}
+
+
+
+sub do_fork_and_exec {
+ my ($options, $cmd) = @_;
+
+ my $stdout_pipe = IO::Pipe->new;
+ my $stderr_pipe = IO::Pipe->new;
+
+# return if $forked;
+
+ #my $pid = fork();
+
+ if (not defined $pid) {
+ _error("Fork failed: $! Aborting");
+ $_->close for $stdout_pipe->handles;
+ undef $stdout_pipe;
+ return;
+ }
+
+# $forked = 1;
+
+ if ($pid > 0) { # this is the parent (Irssi)
+ my $tag;
+
+ Irssi::pidwait_add($pid);
+
+ my $stdout_reader = $stdout_pipe->reader;
+ $stdout_reader->autoflush;
+
+ my @args = ($stdout_reader, \$tag, $pid, $cmd, $options);
+ $tag = Irssi::input_add($stdout_reader->fileno,
+ Irssi::INPUT_READ,
+ \&child_output,
+ \@args);
+
+ } else { # child
+ # make up some data - block if we like.
+ drop_privs();
+ my $stdout_fh = $stdout_pipe->writer;
+ $stdout_fh->autoflush;
+
+ my @data = qx/$cmd/;
+ my $retval = ${^CHILD_ERROR_NATIVE};
+
+ $stdout_fh->print($_) for @data;
+
+ my $done_str = "__DONE__$retval\n";
+ if ($data[$#data] =~ m/\n$/) {
+ } else {
+ $done_str = "\n" . $done_str;
+ }
+ $stdout_fh->print($done_str);
+
+ $stdout_fh->close;
+
+ POSIX::_exit(1);
+ }
+}
+sub drop_privs {
+ my @temp = ($EUID, $EGID);
+ my $orig_uid = $UID;
+ my $orig_gid = $GID;
+ $EUID = $UID;
+ $EGID = $GID;
+ # Drop privileges
+ $UID = $orig_uid;
+ $GID = $orig_gid;
+ # Make sure privs are really gone
+ ($EUID, $EGID) = @temp;
+ die "Can't drop privileges"
+ unless $UID == $EUID && $GID eq $EGID;
+}
+
+sub child_output {
+ my $args = shift;
+ my ($stdout_reader, $tag_ref, $pid, $cmd, $options) = @$args;
+
+ my $return_value = 0;
+
+ while (defined(my $data = <$stdout_reader>)) {
+
+ chomp $data;
+
+ # TODO: do we want to remove empty lines?
+ #return unless length $data;
+
+ if ($data =~ m/^__DONE__(\d+)$/) {
+ $return_value = $1;
+ last;
+ } else {
+ _msg("$data");
+ }
+ }
+
+ if (not exists $options->{'-'}) {
+ _msg("process %d (%s) terminated with return code %d",
+ $pid, $cmd, $return_value);
+ }
+
+ $stdout_reader->close;
+ Irssi::input_remove($$tag_ref);
+}
+
+sub _error {
+ my ($msg) = @_;
+ my $win = Irssi::active_win();
+ $win->print($msg, Irssi::MSGLEVEL_CLIENTERROR);
+}
+
+sub _msg {
+ my ($msg, @params) = @_;
+ my $win = Irssi::active_win();
+ my $str = sprintf($msg, @params);
+ $win->print($str, Irssi::MSGLEVEL_CLIENTCRAP);
+}
+
+sub cmd_exec {
+
+ my ($args, $server, $witem) = @_;
+ # TODO: parse some options here.
+ Irssi::signal_stop;
+
+ my @options = parse_options($args);
+ if (@options) {
+ do_fork_and_exec(@options)
+ }
+
+}
+
+sub exec_init {
+ $command = "exec";
+ $command_options = join ' ',
+ (
+ '!-', 'interactive', 'nosh', '+name', '+msg',
+ '+notice', 'window', 'close', '+level', 'quiet'
+ );
+
+ Irssi::command_bind($command, \&cmd_exec);
+ Irssi::command_set_options($command, $command_options);
+}
+
+exec_init();