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