1 /*
2 #             (C) 2009 Hans de Goede <hdegoede@redhat.com>
3 
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU Lesser General Public License as published by
6 # the Free Software Foundation; either version 2.1 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335  USA
17  */
18 
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <errno.h>
23 #include <string.h>
24 #include <signal.h>
25 #include <sys/wait.h>
26 #include "libv4lconvert-priv.h"
27 
28 #define READ_END  0
29 #define WRITE_END 1
30 
31 /* <sigh> Unfortunately I've failed in contact some Authors of decompression
32    code of out of tree drivers. So I've no permission to relicense their code
33    their code from GPL to LGPL. To work around this, these decompression
34    algorithms are put in separate executables and we pipe data through these
35    to decompress.
36 
37    The "protocol" is very simple:
38 
39    From libv4l to the helper the following is send:
40    int			width
41    int			height
42    int			flags
43    int			data length
44    unsigned char[]	data (data length long)
45 
46    From the helper to libv4l the following is send:
47    int			data length (-1 in case of a decompression error)
48    unsigned char[]	data (not present when a decompression error happened)
49  */
50 
v4lconvert_helper_start(struct v4lconvert_data * data,const char * helper)51 static int v4lconvert_helper_start(struct v4lconvert_data *data,
52 		const char *helper)
53 {
54 	if (pipe(data->decompress_in_pipe)) {
55 		V4LCONVERT_ERR("with helper pipe: %s\n", strerror(errno));
56 		goto error;
57 	}
58 
59 	if (pipe(data->decompress_out_pipe)) {
60 		V4LCONVERT_ERR("with helper pipe: %s\n", strerror(errno));
61 		goto error_close_in_pipe;
62 	}
63 
64 	data->decompress_pid = fork();
65 	if (data->decompress_pid == -1) {
66 		V4LCONVERT_ERR("with helper fork: %s\n", strerror(errno));
67 		goto error_close_out_pipe;
68 	}
69 
70 	if (data->decompress_pid == 0) {
71 		/* We are the child */
72 
73 		/* Closed unused read / write end of the pipes */
74 		close(data->decompress_out_pipe[WRITE_END]);
75 		close(data->decompress_in_pipe[READ_END]);
76 
77 		/* Connect stdin / out to the pipes */
78 		if (dup2(data->decompress_out_pipe[READ_END], STDIN_FILENO) == -1) {
79 			perror("libv4lconvert: error with helper dup2");
80 			exit(1);
81 		}
82 		if (dup2(data->decompress_in_pipe[WRITE_END], STDOUT_FILENO) == -1) {
83 			perror("libv4lconvert: error with helper dup2");
84 			exit(1);
85 		}
86 
87 		/* And execute the helper */
88 		execl(helper, helper, NULL);
89 
90 		/* We should never get here */
91 		perror("libv4lconvert: error starting helper");
92 		exit(1);
93 	} else {
94 		/* Closed unused read / write end of the pipes */
95 		close(data->decompress_out_pipe[READ_END]);
96 		close(data->decompress_in_pipe[WRITE_END]);
97 	}
98 
99 	return 0;
100 
101 error_close_out_pipe:
102 	close(data->decompress_out_pipe[READ_END]);
103 	close(data->decompress_out_pipe[WRITE_END]);
104 error_close_in_pipe:
105 	close(data->decompress_in_pipe[READ_END]);
106 	close(data->decompress_in_pipe[WRITE_END]);
107 error:
108 	return -1;
109 }
110 
111 /* IMPROVE ME: we could block SIGPIPE here using pthread_sigmask()
112    and then in case of EPIPE consume the signal using
113    sigtimedwait (we need to check if a blocked signal wasn't present
114    before the write, otherwise we will consume that) and
115    after consuming the signal try to restart the helper.
116 
117    Note we currently do not do this, as SIGPIPE only happens if the
118    decompressor crashes, which in case of an embedded decompressor
119    would mean end of program, so by not handling SIGPIPE we treat
120    external decompressors identical. */
v4lconvert_helper_write(struct v4lconvert_data * data,const void * b,size_t count)121 static int v4lconvert_helper_write(struct v4lconvert_data *data,
122 		const void *b, size_t count)
123 {
124 	const unsigned char *buf = b;
125 	size_t ret, written = 0;
126 
127 	while (written < count) {
128 		ret = write(data->decompress_out_pipe[WRITE_END], buf + written,
129 				count - written);
130 		if (ret == -1) {
131 			if (errno == EINTR)
132 				continue;
133 
134 			V4LCONVERT_ERR("writing to helper: %s\n", strerror(errno));
135 			return -1;
136 		}
137 		written += ret;
138 	}
139 
140 	return 0;
141 }
142 
v4lconvert_helper_read(struct v4lconvert_data * data,void * b,size_t count)143 static int v4lconvert_helper_read(struct v4lconvert_data *data, void *b,
144 		size_t count)
145 {
146 	unsigned char *buf = b;
147 	size_t ret, r = 0;
148 
149 	while (r < count) {
150 		ret = read(data->decompress_in_pipe[READ_END], buf + r, count - r);
151 		if (ret == -1) {
152 			if (errno == EINTR)
153 				continue;
154 
155 			V4LCONVERT_ERR("reading from helper: %s\n", strerror(errno));
156 			return -1;
157 		}
158 		if (ret == 0) {
159 			V4LCONVERT_ERR("reading from helper: unexpected EOF\n");
160 			return -1;
161 		}
162 		r += ret;
163 	}
164 
165 	return 0;
166 }
167 
v4lconvert_helper_decompress(struct v4lconvert_data * data,const char * helper,const unsigned char * src,int src_size,unsigned char * dest,int dest_size,int width,int height,int flags)168 int v4lconvert_helper_decompress(struct v4lconvert_data *data,
169 		const char *helper, const unsigned char *src, int src_size,
170 		unsigned char *dest, int dest_size, int width, int height, int flags)
171 {
172 	int r;
173 
174 	if (data->decompress_pid == -1) {
175 		if (v4lconvert_helper_start(data, helper))
176 			return -1;
177 	}
178 
179 	if (v4lconvert_helper_write(data, &width, sizeof(int)))
180 		return -1;
181 
182 	if (v4lconvert_helper_write(data, &height, sizeof(int)))
183 		return -1;
184 
185 	if (v4lconvert_helper_write(data, &flags, sizeof(int)))
186 		return -1;
187 
188 	if (v4lconvert_helper_write(data, &src_size, sizeof(int)))
189 		return -1;
190 
191 	if (v4lconvert_helper_write(data, src, src_size))
192 		return -1;
193 
194 	if (v4lconvert_helper_read(data, &r, sizeof(int)))
195 		return -1;
196 
197 	if (r < 0) {
198 		V4LCONVERT_ERR("decompressing frame data\n");
199 		return -1;
200 	}
201 
202 	if (dest_size < r) {
203 		V4LCONVERT_ERR("destination buffer to small\n");
204 		return -1;
205 	}
206 
207 	return v4lconvert_helper_read(data, dest, r);
208 }
209 
v4lconvert_helper_cleanup(struct v4lconvert_data * data)210 void v4lconvert_helper_cleanup(struct v4lconvert_data *data)
211 {
212 	int status;
213 
214 	if (data->decompress_pid != -1) {
215 		close(data->decompress_out_pipe[WRITE_END]);
216 		close(data->decompress_in_pipe[READ_END]);
217 		waitpid(data->decompress_pid, &status, 0);
218 		data->decompress_pid = -1;
219 	}
220 }
221