Add an extra level of race avoidance.
[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 # Notes: Doesn't handle multiple sessions properly.
10 #
11
12 use FindBin;
13 use Getopt::Std;
14 use POSIX ":sys_wait_h";
15
16 my $PROG = $FindBin::Script;
17
18 # Ensure tmux is our parent and find its PID.
19 our $tmux_pid = &get_tmux_pid;
20 unless ($tmux_pid) {
21   print STDERR "$PROG: Not a child of tmux!\n";
22   exit 100;
23 }
24
25 # Ensure there isn't already a helper running for this tmux.
26 my $tmux_helper = &get_tmux_helper;
27 exit 0 if $tmux_helper;
28 $tmux_helper = $$;
29
30 my %opts;
31 getopts('I:L:R:T:', \%opts);
32
33 my $kinit = $opts{'I'} || "kinit";
34 my $klist = $opts{'L'} || "klist";
35 my $krenew = $opts{'R'} || "krenew";
36 my $tmux = $opts{'T'} || "tmux";
37
38 my $avoid_race = 0;
39
40 my $exitasap = 0;
41 my $pid = 0;
42
43 $SIG{INT} = \&cleanup;
44 $SIG{QUIT} = \&cleanup;
45 $SIG{TERM} = \&cleanup;
46 $SIG{USR1} = \&want_credentials;
47
48 LOOP: while (&ping_tmux) {
49   $pid = fork;
50   die "$PROG: Can't fork: $!\n" unless defined $pid;
51
52   if ($pid) {
53     while (&ping_tmux) {
54       my $exited = waitpid $pid, WNOHANG;
55       goto LOOP if $exited == $pid || $exited < 0;
56       sleep 1;
57     }
58
59     # tmux is dead so kill krenew.
60     kill QUIT, $pid;
61     waitpid $pid, 0;
62     exit 0;
63   }
64   else {
65     exit 1 if &check_credentials;
66     exec $krenew, "-K", "60";
67     print "$PROG: Can't run krenew: $!\n";
68     exit 111;
69   }
70 }
71
72 sub get_tmux_pid {
73   my $pid = getppid;
74   my $cmd = `/bin/ps -o args= -p $pid`;
75   return $pid if $cmd =~ /\btmux\b/;
76   return undef;
77 }
78
79 sub check_kinit_child {
80   foreach my $pid (`/bin/ps -o ppid= -C kinit`) {
81     return 1 if $pid =~ /^\s*$tmux_pid\s*$/;
82   }
83   return 0;
84 }
85
86 sub get_tmux_helper {
87   my $pid = undef;
88   if (open IN, "pgrep -x -P $tmux_pid $PROG | ") {
89     while (<IN>) {
90       chomp;
91       s/[^\d]//g;
92       next if $_ == $$;
93       $pid = $_;
94       last;
95     }
96     close IN;
97   }
98   return $pid;
99 }
100
101 sub ping_tmux {
102   return kill 0, $tmux_pid;
103 }
104
105 # Try to check existing Kerberos credentials.
106 sub check_credentials {
107   system $klist, "-s";
108   return 1 if $? < 0;
109   return 0 unless $?;
110   kill USR1, $tmux_helper;
111   return 111;
112 }
113
114 # We were signalled by our child which noticed that our credentials expired.
115 sub want_credentials {
116   return sleep 1 if $avoid_race;
117   $avoid_race = 1;
118   # Do we already know?
119   system $tmux, "new-window", "-n", "Renew Kerberos credentials", "exec $kinit" unless &check_kinit_child;
120   sleep 1;
121   $avoid_race = 0;
122 }
123
124 sub cleanup {
125   unless ($exitasap) {
126     $exitasap = 1;
127     kill $pid;
128     waitpid $pid, WNOHANG;
129     exit 0;
130   }
131 }