1 
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <signal.h>
6 #include <sys/ioctl.h>
7 #include <sys/stat.h>
8 #include <fcntl.h>
9 #include <unistd.h>
10 #include <string.h>
11 
12 #define TRUE  1
13 #define FALSE 0
14 
15 #define PLUGIN_MSG_PARSE_ERR        -1	// also interpreted as 'no more output'
16 #define PLUGIN_PROGRESS_MSG         0
17 #define PLUGIN_WARN_MSG             1
18 #define PLUGIN_ERR_MSG              2
19 
20 #define CHECK_INTERVAL              0.475
21 
22 int add_argv (char **dest, char *content);
23 int open_a_pty (int *pty, int *tty);
24 char **create_argv_for_execution_using_shell (char *command);
25 int execute_ripper_encoder_with_plugin (char **program_argv,
26                                         char **plugin_argv,
27                                         pid_t * program_pid, pid_t * plugin_pid,
28                                         int *read_fd);
29 void err_handler (char *msg);
30 int read_and_process_plugin_output (int read_fd, double *progress, char *msg);
31 int parse_plugin_output (char *out, double *progress, char *msg);
32 
33 int
34 add_argv (char **dest, char *content)
35 {
36 	size_t i;
37 
38 	i = 0;
39 
40 	while (content[i++] != '\0');
41 
42 	if ((*dest = malloc (i)) == NULL) {
43 		err_handler ("Cannot allocate memory");
44 		return FALSE;
45 	}
46 
47 	strcpy (*dest, content);
48 	return TRUE;
49 }
50 
51 void
52 err_handler (char *msg)
53 {
54 	fprintf (stderr, "Error : %s\n", msg);
55 }
56 
57 int
58 open_a_pty (int *pty, int *tty)
59 {
60 	char line[12];
61 	char *p1, *p2;
62 	char *cp;
63 	int i;
64 
65 	sprintf (line, "/dev/ptyXX");
66 	p1 = &line[8];
67 	p2 = &line[9];
68 
69 	for (cp = "pqrstuvwxyzPQRST"; *cp; cp++) {
70 		struct stat stb;
71 
72 		*p1 = *cp;
73 		*p2 = '0';
74 		/*
75 		 * This stat() check is just to keep us from
76 		 * looping through all 256 combinations if there
77 		 * aren't that many ptys available.
78 		 */
79 		if (stat (line, &stb) < 0)
80 			break;
81 		for (i = 0; i < 16; i++) {
82 			*p2 = "0123456789abcdef"[i];
83 			*pty = open (line, O_RDWR);
84 			if (*pty > 0) {
85 				line[5] = 't';
86 				/* Now open appropriate tty */
87 				if ((*tty = open (line, O_RDWR)) < 0) {
88 					line[5] = 'p';
89 					close (*pty);
90 					continue;
91 				}
92 				return 0;
93 			}
94 		}
95 	}
96 	return -1;
97 }
98 
99 char **
100 create_argv_for_execution_using_shell (char *command)
101 {
102 	char *shell;
103 	char **argv;
104 
105 	shell = "/bin/bash";
106 
107 	if ((argv = (char **) malloc (sizeof (char *) * 4)) == NULL) {
108 		err_handler ("malloc failed");
109 		return NULL;
110 	}
111 	argv[0] = NULL;
112 	argv[1] = NULL;
113 	argv[2] = NULL;
114 	argv[3] = NULL;
115 
116 	if (add_argv (&(argv[0]), shell) == FALSE)
117 		return NULL;
118 	if (add_argv (&(argv[1]), "-c") == FALSE)
119 		return NULL;
120 	if (add_argv (&(argv[2]), command) == FALSE)
121 		return NULL;
122 	return argv;
123 }
124 
125 int
126 execute_ripper_encoder_with_plugin (char **program_argv,
127                                     char **plugin_argv,
128                                     pid_t * program_pid, pid_t * plugin_pid,
129                                     int *read_fd)
130 {
131 	int pty_fd0, tty_fd0, pty_fd1, tty_fd1;
132 	pid_t pid;
133 
134 	/* Open two pty/tty pairs */
135 	if (open_a_pty (&pty_fd0, &tty_fd0) < 0) {
136 		err_handler ("Cannot open pty/tty pair");
137 		return -1;
138 	}
139 	if (open_a_pty (&pty_fd1, &tty_fd1) < 0) {
140 		close (pty_fd0);
141 		close (tty_fd0);
142 		err_handler ("Cannot open pty/tty pair");
143 		return -1;
144 	}
145 
146 	// fork & exec & link plugin
147 	if ((pid = fork ()) < 0) {
148 		err_handler ("Cannot fork");
149 		return -1;
150 	}
151 	*plugin_pid = pid;
152 
153 	if (pid == 0) {
154 		// We're in the child process
155 		// save stderr
156 		int stderr_fd;
157 		stderr_fd = dup (2);
158 
159 		dup2 (pty_fd0, 0);
160 		dup2 (tty_fd1, 1);
161 
162 		execvp (plugin_argv[0], plugin_argv);
163 
164 		dup2 (stderr_fd, 2);
165 		perror ("Failed to exec plugin");
166 		_exit (127);
167 	}
168 
169 	// we're in the parent process
170 	close (pty_fd0);
171 	close (tty_fd1);
172 	*read_fd = pty_fd1;
173 
174 	// fork the real program
175 	if ((pid = fork ()) < 0) {
176 		err_handler ("Cannot fork");
177 		return -1;
178 	}
179 	*program_pid = pid;
180 
181 	if (pid == 0) {
182 		// We're in the child process
183 		// save stderr
184 		int stderr_fd;
185 		stderr_fd = dup (2);
186 
187 		dup2 (tty_fd0, 1);
188 		dup2 (tty_fd0, 2);
189 
190 		execvp (program_argv[0], program_argv);
191 
192 		dup2 (stderr_fd, 2);
193 		perror ("Failed to exec the specified program");
194 		_exit (127);
195 	}
196 	close (tty_fd0);
197 
198 	return 0;
199 }
200 
201 int
202 read_and_process_plugin_output (int read_fd, double *progress, char *msg)
203 {
204 	int bytes_avail;
205 	char buf[1024];
206 	FILE *read_stream;
207 
208 	ioctl (read_fd, FIONREAD, &bytes_avail);
209 	if (bytes_avail <= 0) {
210 		fprintf (stderr, "*** No report available from plugin. Ctrl-C to stop\n");
211 		// the plugin hasn't printed anything yet. return PLUGIN_MSG_PARSE_ERR
212 		// which the caller should ignore.
213 		return PLUGIN_MSG_PARSE_ERR;
214 	}
215 
216 	// all the lines are terminated with '\n' and if the plugin started to
217 	// print something then it'll finish it soon. so using fgets is
218 	// reasonable
219 
220 	read_stream = fdopen (read_fd, "r");
221 	if (fgets (buf, sizeof (buf), read_stream) == NULL)
222 		return PLUGIN_MSG_PARSE_ERR;
223 	fprintf (stderr, "*** Output read from plugin : %s", buf);
224 	return parse_plugin_output (buf, progress, msg);
225 }
226 
227 int
228 parse_plugin_output (char *out, double *progress, char *msg)
229 {
230 	int pos, done, s, d;
231 	char ch;
232 
233 	*progress = -1;
234 	msg[0] = '\0';
235 
236 	pos = 0;
237 	while (out[pos] != '\0' && out[pos] != '[')
238 		pos++;
239 
240 	// parse err point 0 : cannot find beginning '['
241 	if (out[pos] != '[') {
242 		fprintf (stderr, "*** parse err at : 0 : cannot find leading '['\n");
243 		return PLUGIN_MSG_PARSE_ERR;
244 	}
245 	pos++;
246 
247 	// read the type character
248 	ch = out[pos++];
249 
250 	if (ch == 'P')
251 		// if it's a msg reporting progess, read the percentage
252 		sscanf (out + pos, "%lf", progress);
253 
254 	while (out[pos] != '\0' && out[pos] != '"' && out[pos] != ']')
255 		pos++;
256 
257 	if (out[pos] == '"') {
258 		// we've got some message
259 		pos++;
260 
261 		// copy the message
262 		done = 0;
263 		s = pos;
264 		d = 0;
265 		while (!done) {
266 			if (out[s] != '\\' && out[s] != '"' &&
267 				        out[s] != ']' && out[s] != '\0')
268 				msg[d++] = out[s++];
269 			else if (out[s] == '\\') {
270 				msg[d] = out[s + 1];
271 				s += 2;
272 				d++;
273 			} else {
274 				msg[d] = '\0';
275 				done = 1;
276 			}
277 		}
278 	}
279 
280 	switch (ch) {
281 	case 'P':
282 		// parse err point 1 : invalid progress
283 		if (*progress < 0 || *progress > 1) {
284 			fprintf (stderr, "*** parse err at : 1 : invalid progress : %f\n",
285 			         *progress);
286 			return PLUGIN_MSG_PARSE_ERR;
287 		}
288 		return PLUGIN_PROGRESS_MSG;
289 	case 'W':
290 		// parse err point 2 : warning report without msg
291 		if (msg[0] == '\0') {
292 			fprintf (stderr, "*** parse err at : 2 : warning report w/o msg\n");
293 			return PLUGIN_MSG_PARSE_ERR;
294 		}
295 		return PLUGIN_WARN_MSG;
296 	case 'E':
297 		// parse err point 3 : error report without msg
298 		if (msg[0] == '\0') {
299 			fprintf (stderr, "*** parse err at : 3 : error report w/o msg\n");
300 			return PLUGIN_MSG_PARSE_ERR;
301 		}
302 		return PLUGIN_ERR_MSG;
303 	default:
304 		return PLUGIN_MSG_PARSE_ERR;
305 	}
306 }
307 
308 int
309 main (int argc, char **argv)
310 {
311 	char *pg_com, *pi_com;
312 	char **pg_argv, **pi_argv;
313 	pid_t pg_pid, pi_pid;
314 	int read_fd;
315 	double progress;
316 	char msg[1024];
317 	int rs;
318 
319 	if (argc != 3) {
320 		fprintf (stderr, "Syntax: ripperX_plugin_tester"
321 		         " \"program command with args\" \"plugin command\"\n");
322 		exit (1);
323 	}
324 	pg_com = argv[1];
325 	pi_com = argv[2];
326 	fprintf (stdout, "***Executing \"%s\"\n"
327 	         "with plugin \"%s\"\n", pg_com, pi_com);
328 
329 	pg_argv = create_argv_for_execution_using_shell (pg_com);
330 	pi_argv = create_argv_for_execution_using_shell (pi_com);
331 
332 	pg_pid = 0;
333 	pi_pid = 0;
334 
335 	if (execute_ripper_encoder_with_plugin (pg_argv, pi_argv, &pg_pid, &pi_pid,
336 		                                        &read_fd) < 0)
337 		exit (1);
338 
339 	while (1) {
340 		usleep (CHECK_INTERVAL * 1000000);
341 		printf ("\n");
342 		if ((rs = read_and_process_plugin_output (read_fd, &progress, msg))
343 			        == PLUGIN_MSG_PARSE_ERR) {
344 			//if (msg != NULL)
345 			//free(msg);
346 			fprintf (stderr, "*** PLUGIN_MSG_PARSE_ERR returned\n");
347 			continue;
348 		}
349 		switch (rs) {
350 		case PLUGIN_PROGRESS_MSG:
351 			printf ("Report type : progress, Progress : %f,\nMsg : %s\n",
352 			        progress, msg);
353 			break;
354 		case PLUGIN_WARN_MSG:
355 			printf ("Report type : warning,\nMsg : %s\n", msg);
356 			break;
357 		case PLUGIN_ERR_MSG:
358 			printf ("Report type : error,\nMsg : %s\n", msg);
359 			break;
360 		}
361 	}
362 
363 	return 0;
364 }
365