waitport.c (3839B) download
1#include <unistd.h>
2#include <errmsg.h>
3#include <time.h>
4#include <string.h>
5#include <fcntl.h>
6#include <ctype.h>
7
8#include <libowfat/fmt.h>
9#include <libowfat/scan.h>
10#include <libowfat/ip4.h>
11#include <libowfat/ip6.h>
12#include <libowfat/stralloc.h>
13#include <libowfat/buffer.h>
14
15static int netstat(const char* addr,unsigned int wantedport) {
16 /* see linux/Documentation/networking/proc_net_tcp.txt */
17 int fd=-1;
18 char rbuf[4096]; /* since these files can become arbitrarily large, we'll use a real buffer to read from them */
19 const char* filenames[] = { "/proc/net/tcp6", "/proc/net/tcp" };
20 buffer b;
21 unsigned int fn;
22 stralloc line;
23
24 for (fn=0; fn<sizeof(filenames)/sizeof(filenames[0]); ++fn) {
25 const char* filename=filenames[fn];
26
27 fd=open(filename,O_RDONLY|O_CLOEXEC);
28 if (fd==-1)
29 continue;
30 buffer_init(&b,read,fd,rbuf,sizeof(rbuf)); /* can't fail */
31 for (;;) {
32 int r;
33 char* c;
34 char* local;
35 int v6;
36 stralloc_zero(&line);
37 if ((r=buffer_getline_sa(&b,&line))==-1) {
38 close(fd);
39 die(1,"read error from ",filename);
40 }
41 if (r==0) break;
42 if (line.len < 1 || line.s[line.len-1]!='\n') {
43parseerror:
44 close(fd);
45 die(1,"parse error in ",filename);
46 }
47 line.s[line.len-1]=0; /* string is now null terminated */
48
49 /* First token is something like "917:", skip */
50 for (c=line.s; *c && *c!=':'; ++c) ;
51 if (*c != ':') continue; /* first line is boilerplate text and has no :-token, skip it */
52 ++c;
53 for (; *c==' '; ++c) ;
54 /* Next token is something like "00000000:1770" or "00000000000000000000000000000000:0016" */
55 local=c;
56 for (; isxdigit(*c); ++c) ;
57 if (c-local != 8 && c-local != 32) /* we only support ipv4 and ipv6; this is neither */
58 continue;
59 v6=(c-local==32);
60 if (*c!=':') goto parseerror;
61 for (r=1; r<5; ++r) {
62 if (!isxdigit(c[r])) goto parseerror;
63 }
64 if (c[5]!=' ') goto parseerror;
65 c[5]=0;
66 c+=6;
67 /* Next token is the same thing, but we don't really need it, so
68 * just skip till next whitespace */
69 for (; *c && *c!=' '; ++c) ;
70 if (*c!=' ') goto parseerror;
71 ++c;
72 /* Next is the state; if we are looking at tcp, 0A means LISTEN */
73 if (filename[10]=='t' && c[0]=='0' && c[1]=='A') {
74 /* TCP LISTEN */
75 size_t n;
76 union {
77 char ip[16];
78 uint32_t ints[4];
79 } omgwtfbbq;
80 char ipstring[FMT_IP6];
81 unsigned short port;
82 unsigned long temp;
83
84 /* we can only be here if the hex string is 8 or 32 bytes long, see above */
85 for (n=0; n<(unsigned int)v6*3+1; ++n) {
86 scan_xlongn(local+n*8,8,&temp);
87 omgwtfbbq.ints[n]=temp;
88 }
89
90 if (scan_xshort(local+(((unsigned int)v6*3+1)*8)+1,&port)!=4) /* can't happen, we validated with isxdigit */
91 goto parseerror;
92
93 ipstring[v6 ? fmt_ip6c(ipstring,omgwtfbbq.ip) : fmt_ip4(ipstring,omgwtfbbq.ip)]=0;
94
95 if (!strcmp(ipstring,addr?addr : (v6?"::":"0.0.0.0")) && port==wantedport) {
96 close(fd);
97 return 1;
98 }
99
100 }
101 }
102 close(fd);
103 fd=-1;
104 }
105
106 return 0;
107}
108
109int main(int argc,char* argv[],char* envp[]) {
110 unsigned short port;
111
112 unsigned int i;
113 struct timespec req,rem;
114
115 char* s=argv[1];
116 char* t=strchr(s,'/');
117
118 errmsg_iam("waitport");
119 if (argc<2)
120usage:
121 die(0,"usage: waitport ::/111 some.rpcd\n\twaits for a service to bind to TCP port 111 on ::, then executes the rest of the command line");
122
123 {
124 if (t) {
125 *t=0;
126 if (scan_ushort(t+1,&port)==0) goto usage;
127 } else {
128 if (scan_ushort(s,&port)==0) goto usage;
129 s=0;
130 }
131 }
132
133 req.tv_sec=0; req.tv_nsec=100000000;
134 for (i=0; i<1000; ++i) {
135 if (netstat(s,port)) {
136 if (argv[2]) {
137 execve(argv[2],argv+2,envp);
138 diesys(1,"execve");
139 }
140 return 0;
141 }
142 nanosleep(&req,&rem);
143 }
144 die(1,"service on port not showing up");
145}