Fix ktmux_helper for tmux 1.8 with run_shell -b.
[profile.git] / opt / bin / ktmux_helper
1 #!/usr/bin/perl
2 #
3 # ktmux_helper: Run krenew in the background for tmux.
4 # Usage: ktmux_helper [options]
5 # Options: -I <path>      Specify path to kinit.
6 #          -L <path>      Specify path to klist.
7 #          -R <path>      Specify path to krenew.
8 #          -T <path>      Specify path to tmux.
9 #          -p <pid>       Specify tmux PID.
10 #          -s <session>   Specify tmux session number.
11 # Notes: The -p and -s flags are only used to identify instances when
12 #        viewed in ps output.
13 #
14
15 use FindBin;
16 use Getopt::Std;
17 use POSIX ":sys_wait_h";
18
19 my $PROG = $FindBin::Script;
20
21 # Remember args for re-exec.
22 my @execargs;
23 foreach my $arg (@ARGV) {
24   push @execargs, $arg;
25 }
26
27 my %opts;
28 getopts('I:L:R:T:p:s:', \%opts);
29
30 # Ensure tmux is our parent and find its PID if not already known.
31 our $tmux_pid = &get_tmux_pid($opts{'p'});
32 unless ($tmux_pid) {
33   print STDERR "$PROG: Not a child of tmux!\n";
34   exit 100;
35 }
36
37 # Find session if possible.
38 our $tmux_session = &get_tmux_session($opts{'s'});
39
40 # Fix the environment.
41 $ENV{TMUX} =~ s/,\d+,-?\d+$/,$tmux_pid,$tmux_session/;
42
43 # Ensure there isn't already a helper running for this tmux.
44 my $tmux_helper = &get_tmux_helper;
45 exit 0 if $tmux_helper;
46 $tmux_helper = $$;
47
48 # Re-exec ourselves purely so the session and PID are set in the environment.
49 my @args;
50 push @args, "-p", $tmux_pid unless defined $opts{'p'};
51 push @args, "-s", $tmux_session unless defined $opts{'s'};
52 exec $0, @args, @execargs if @args;
53
54 my $kinit = $opts{'I'} || "kinit";
55 my $klist = $opts{'L'} || "klist";
56 my $krenew = $opts{'R'} || "krenew";
57 my $tmux = $opts{'T'} || "tmux";
58
59 my $avoid_race = 0;
60
61 my $exitasap = 0;
62 my $pid = 0;
63
64 $SIG{INT} = \&cleanup;
65 $SIG{QUIT} = \&cleanup;
66 $SIG{TERM} = \&cleanup;
67 $SIG{USR1} = \&want_credentials;
68
69 LOOP: while (&ping_tmux) {
70   $pid = fork;
71   die "$PROG: Can't fork: $!\n" unless defined $pid;
72
73   if ($pid) {
74     while (&ping_tmux) {
75       my $exited = waitpid $pid, WNOHANG;
76       goto LOOP if $exited == $pid || $exited < 0;
77       sleep 1;
78     }
79
80     # tmux is dead so kill krenew.
81     kill QUIT, $pid;
82     waitpid $pid, 0;
83     exit 0;
84   }
85   else {
86     exit 1 if &check_credentials;
87     exec $krenew, "-K", "60";
88     print STDERR "$PROG: Can't run krenew: $!\n";
89     exit 111;
90   }
91 }
92
93 sub get_tmux_pid {
94   # Set from command line?
95   my $pid = shift;
96
97   # Set from environment?
98   unless ($pid) {
99     if ($ENV{TMUX} =~ /,(\d+),[^,]+$/) {
100       $pid = $1;
101     }
102   }
103
104   my $ppid = getppid;
105
106   if ($pid) {
107     # Check it really is our parent.
108     if ($pid == $ppid) {
109       # Check that it's still running.
110       return $pid if kill -0, $pid;
111     }
112   }
113
114   return undef;
115
116   # Everything below probably can't happen so should be removed.
117   my $cmd = `/bin/ps -o args= -p $pid`;
118   return $pid if $cmd =~ /\btmux\b/;
119   return undef;
120 }
121
122 sub get_tmux_session {
123   # Set from command line?
124   my $session = shift;
125
126   # Set from environment?
127   unless ($session) {
128     # The session identifier will be -1 if we were launched from run-shell
129     # because a run-shell command doesn't count as being part of a session.
130     if ($ENV{TMUX} =~ /,(-?\d+)$/) {
131       $session = $1;
132     }
133   }
134
135   return $session;
136 }
137
138 # Check that a given process was launched from our session.
139 sub check_tmux_session {
140   my $pid = shift;
141
142   return undef unless defined $tmux_session;
143
144   my $cmd;
145   if ($^O eq "linux") {
146     $cmd = "xargs -0 -n 1 < /proc/$pid/environ";
147   }
148   elsif ($^O eq "solaris") {
149     $cmd = "pargs -Fe $pid";
150   }
151   else {
152     # Don't know how to check on this OS.
153     return undef;
154   }
155
156   return undef unless (open IN, "$cmd | ");
157
158   while (<IN>) {
159     chomp;
160     next unless /TMUX=.+,(-?\d+)/;
161     # Abort if the running helper doesn't know what session it's for.
162     if ($1 eq $tmux_session || $1 eq "-1") {
163       close IN;
164       return 1;
165     }
166   }
167
168   close IN;
169   return 0;
170 }
171
172 sub check_kinit_child {
173   foreach my $pid (`/bin/ps -o ppid= -C kinit`) {
174     next unless $pid =~ /^\s*$tmux_pid\s*$/;
175     next if &check_tmux_session($pid);
176     return 1;
177   }
178   return 0;
179 }
180
181 sub get_tmux_helper {
182   my $pid = undef;
183   if (open IN, "pgrep -x -P $tmux_pid $PROG | ") {
184     while (<IN>) {
185       chomp;
186       s/[^\d]//g;
187       next if $_ == $$;
188       $pid = $_;
189       return $pid if &check_tmux_session($pid);
190     }
191     close IN;
192   }
193   return undef;
194 }
195
196 sub ping_tmux {
197   return kill 0, $tmux_pid;
198 }
199
200 # Try to check existing Kerberos credentials.
201 sub check_credentials {
202   system $klist, "-s";
203   return 1 if $? < 0;
204   return 0 unless $?;
205   kill USR1, $tmux_helper;
206   return 111;
207 }
208
209 # We were signalled by our child which noticed that our credentials expired.
210 sub want_credentials {
211   return sleep 1 if $avoid_race;
212   $avoid_race = 1;
213   # Do we already know?
214   system $tmux, "new-window", "-n", "Renew Kerberos credentials", "exec $kinit" unless &check_kinit_child;
215   sleep 1;
216   $avoid_race = 0;
217 }
218
219 sub cleanup {
220   unless ($exitasap) {
221     $exitasap = 1;
222     kill $pid;
223     waitpid $pid, WNOHANG;
224     exit 0;
225   }
226 }