xref: /illumos-gate/usr/src/lib/libfsmgt/common/cmd.c (revision 86d949f9497332fe19be6b5d711d265eb957439f)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <sys/types.h>
31 #include <poll.h>
32 #include <sys/wait.h>
33 #include <errno.h>
34 #include <strings.h>
35 #include <sys/stropts.h>
36 #include "libfsmgt.h"
37 
38 #define	MASKVAL (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND)
39 #define	STDOUT 1
40 #define	STDERR 2
41 
42 /*
43  * Public methods
44  */
45 
46 /*
47  * Method: cmd_execute_command
48  *
49  * Description: Executes the given command and returns the output written to
50  * stdout and stderr in two separate file descriptors to be read by the caller.
51  * It is recommended that the caller use the cmd_retrieve_string method or
52  * another polling method to read from the file descriptors especially in the
53  * case that the command output is expected to be lengthy.
54  *
55  * Parameters:
56  *	- char *cmd - The command to execute.
57  *	- int *output_filedes - The file descriptor to which the stdout output
58  *	is written.
59  *	- int *err_filedes -  The file descriptor to which the stderr output
60  *	is written.
61  *
62  * Returns:
63  *	- int - This value will always be zero.  This was intended to be the
64  *	the exit status of the executed command, but in the case of the
65  *	execution of a command with a large amount of output (ex: ls of a large
66  *	directory) we can't wait for the exec'd command to exit.  This is
67  *	because of the way that file descriptors work.  When the child process,
68  *	or the process executing the command, writes of 'x' amount of data to
69  *	a file desciptor (fd), the fd reaches a threshold and will lock and wait
70  *	for a reader to read before writing anymore data.  In this case, we
71  *	don't have a reader since the caller reads from the file descriptors,
72  *	not the parent process.
73  *	The result is that the parent process cannot be allowed to wait for the
74  *	child process to exit.  Hence, cannot get the exit status of the
75  *	executed command.
76  */
77 int
78 cmd_execute_command(char *cmd, int *output_filedes, int *err_filedes) {
79 	pid_t child_pid;
80 	int output[2];
81 	int error[2];
82 	int ret_val;
83 
84 	if (pipe(output) == -1) {
85 		return (errno);
86 	}
87 
88 	if (pipe(error) == -1) {
89 		return (errno);
90 	}
91 
92 	if ((child_pid = fork()) == -1) {
93 		return (errno);
94 	}
95 
96 	if (child_pid == 0) {
97 		/*
98 		 * We are in the child.
99 		 */
100 
101 		/*
102 		 * Close the file descriptors we aren't using.
103 		 */
104 		close(output[0]);
105 		close(error[0]);
106 
107 		/*
108 		 * Close stdout and dup to output[1]
109 		 */
110 		if (close(STDOUT) == -1) {
111 			exit(errno);
112 		}
113 
114 		if (dup(output[1]) == -1) {
115 			exit(errno);
116 		}
117 
118 		close(output[1]);
119 
120 		/*
121 		 * Close stderr and dup to error[1]
122 		 */
123 		if (close(STDERR) == -1) {
124 			exit(errno);
125 		}
126 
127 		if (dup(error[1]) == -1) {
128 			exit(errno);
129 		}
130 
131 		close(error[1]);
132 
133 		if (execl("/usr/bin/sh", "sh", "-c", cmd, (char *)0) == -1) {
134 
135 			exit(errno);
136 		} else {
137 			exit(0);
138 		}
139 	}
140 
141 	/*
142 	 * We are in the parent
143 	 */
144 
145 	/*
146 	 * Close the file descriptors we aren't using.
147 	 */
148 	close(output[1]);
149 	close(error[1]);
150 
151 	*output_filedes = output[0];
152 	*err_filedes = error[0];
153 
154 	/*
155 	 * Do not wait for the child process to exit.  Just return.
156 	 */
157 	ret_val = 0;
158 	return (ret_val);
159 
160 } /* cmd_execute_command */
161 
162 /*
163  * Method: cmd_execute_command_and_retrieve_string
164  *
165  * Description: Executes the given string and returns the output as it is
166  * output as it is written to stdout and stderr in the return string.
167  *
168  * Parameters:
169  *	- char *cmd - the command to execute.
170  *	- int *errp - the error indicator.  This will be set to a non-zero
171  *	upon error.
172  *
173  * Returns:
174  *	char * - The output of the command to stderr and stdout.
175  */
176 char *
177 cmd_execute_command_and_retrieve_string(char *cmd, int *errp) {
178 	pid_t child_pid;
179 	int output[2];
180 	int err;
181 	int status;
182 	char *ret_val;
183 
184 	*errp = 0;
185 	if (pipe(output) == -1) {
186 		*errp = errno;
187 		return (NULL);
188 	}
189 
190 	if ((child_pid = fork()) == -1) {
191 		*errp = errno;
192 		return (NULL);
193 	}
194 
195 	if (child_pid == 0) {
196 		/*
197 		 * We are in the child.
198 		 */
199 
200 		/*
201 		 * Close the unused file descriptor.
202 		 */
203 		close(output[0]);
204 
205 		/*
206 		 * Close stdout and dup to output[1]
207 		 */
208 		if (close(STDOUT) == -1) {
209 			*errp = errno;
210 			exit(*errp);
211 		}
212 
213 		if (dup(output[1]) == -1) {
214 			*errp = errno;
215 			exit(*errp);
216 		}
217 
218 		/*
219 		 * Close stderr and dup to output[1]
220 		 */
221 		if (close(STDERR) == -1) {
222 			*errp = errno;
223 			exit(*errp);
224 		}
225 
226 		if (dup(output[1]) == -1) {
227 			*errp = errno;
228 			exit(*errp);
229 		}
230 
231 		close(output[1]);
232 
233 		if (execl("/usr/bin/sh", "sh", "-c", cmd, (char *)0) == -1) {
234 
235 			*errp = errno;
236 			exit(*errp);
237 		} else {
238 			exit(0);
239 		}
240 	}
241 
242 	/*
243 	 * We are in the parent
244 	 */
245 
246 	/*
247 	 * Close the file descriptors we are not using.
248 	 */
249 	close(output[1]);
250 
251 	/*
252 	 * Wait for the child process to exit.
253 	 */
254 	while ((wait(&status) != child_pid)) {
255 		ret_val = cmd_retrieve_string(output[0], &err);
256 	}
257 
258 	/*
259 	 * Evaluate the wait status and set the evaluated value to
260 	 * the value of errp.
261 	 */
262 	*errp = WEXITSTATUS(status);
263 
264 	ret_val = cmd_retrieve_string(output[0], &err);
265 
266 	/*
267 	 * Caller must free space allocated for ret_val with free()
268 	 */
269 	return (ret_val);
270 } /* cmd_execute_command_and_retrieve_string */
271 
272 /*
273  * Method: cmd_retrieve_string
274  *
275  * Description: Returns the data written to the file descriptor passed in.
276  *
277  * Parameters:
278  *	- int filedes - The file descriptor to be read.
279  *	- int *errp - The error indicator.  This will be set to a non-zero
280  *	value upon error.
281  *
282  * Returns:
283  *	- char * - The data read from the file descriptor.
284  */
285 char *
286 cmd_retrieve_string(int filedes, int *errp) {
287 	int returned_value = 0;
288 	int buffer_size = 1024;
289 	int len;
290 	char *ret_val;
291 	char *buffer;
292 	boolean_t stop_loop = B_FALSE;
293 	struct pollfd pollfds[1];
294 
295 	*errp = 0;
296 	/*
297 	 * Read from the file descriptor passed into the function.  This
298 	 * will read data written to the file descriptor on a FIFO basis.
299 	 * Care must be taken to make sure to get all data from the file
300 	 * descriptor.
301 	 */
302 
303 	ret_val = (char *)calloc((size_t)1, (size_t)sizeof (char));
304 	ret_val[0] = '\0';
305 
306 
307 	/*
308 	 * Set up the pollfd structure with appropriate information.
309 	 */
310 	pollfds[0].fd = filedes;
311 	pollfds[0].events = MASKVAL;
312 	pollfds[0].revents = 0;
313 
314 	while (stop_loop == B_FALSE) {
315 		char *tmp_string;
316 
317 		switch (poll(pollfds, 1, INFTIM)) {
318 			case -1:
319 
320 			case 0:
321 				/*
322 				 * Nothing to read yet so continue.
323 				 */
324 				continue;
325 			default:
326 				buffer = (char *)calloc(
327 					(size_t)(buffer_size + 1),
328 					(size_t)sizeof (char));
329 
330 				if (buffer == NULL) {
331 					/*
332 					 * Out of memory
333 					 */
334 					*errp = errno;
335 					return (NULL);
336 				}
337 
338 				/*
339 				 * Call read to read from the filedesc.
340 				 */
341 				returned_value = read(filedes, buffer,
342 					buffer_size);
343 				if (returned_value <= 0) {
344 					/*
345 					 * Either we errored or didn't read any
346 					 * bytes of data.
347 					 * returned_value == -1 represents an
348 					 * error.
349 					 * returned value == 0 represents 0
350 					 * bytes read.
351 					 */
352 					stop_loop = B_TRUE;
353 					continue;
354 				}
355 
356 				len = strlen(buffer);
357 
358 				/*
359 				 * Allocate space for the new string.
360 				 */
361 				tmp_string =
362 				(char *)calloc((size_t)(len+strlen(ret_val)+1),
363 						(size_t)sizeof (char));
364 
365 				if (tmp_string == NULL) {
366 					/*
367 					 * Out of memory
368 					 */
369 
370 					*errp = errno;
371 					return (NULL);
372 				}
373 
374 				/*
375 				 * Concatenate the the new string in 'buffer'
376 				 * with whatever is in the 'ret_val' buffer.
377 				 */
378 				snprintf(tmp_string, (size_t)(len +
379 					strlen(ret_val) + 1), "%s%s",
380 					ret_val, buffer);
381 
382 				(void) free(ret_val);
383 				ret_val = strdup(tmp_string);
384 
385 				if (ret_val == NULL) {
386 					/*
387 					 * Out of memory
388 					 */
389 					*errp = errno;
390 					return (NULL);
391 				}
392 				(void) free(tmp_string);
393 				(void) free(buffer);
394 
395 		} /* switch (poll(pollfds, 1, INFTIM)) */
396 
397 	} /* while (stop_loop == B_FALSE) */
398 
399 	return (ret_val);
400 } /* cmd_retrieve_string */
401