/src/pdsh/xpopen.c

https://code.google.com/ · C · 264 lines · 137 code · 47 blank · 80 comment · 46 complexity · 0683f3210d2cba87a77c234d226e8699 MD5 · raw file

  1. /*****************************************************************************\
  2. * $Id$
  3. *****************************************************************************
  4. * Copyright (C) 2001-2006 The Regents of the University of California.
  5. * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
  6. * Written by Jim Garlick <garlick@llnl.gov>.
  7. * UCRL-CODE-2003-005.
  8. *
  9. * This file is part of Pdsh, a parallel remote shell program.
  10. * For details, see <http://www.llnl.gov/linux/pdsh/>.
  11. *
  12. * Pdsh is free software; you can redistribute it and/or modify it under
  13. * the terms of the GNU General Public License as published by the Free
  14. * Software Foundation; either version 2 of the License, or (at your option)
  15. * any later version.
  16. *
  17. * Pdsh is distributed in the hope that it will be useful, but WITHOUT ANY
  18. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  19. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  20. * details.
  21. *
  22. * You should have received a copy of the GNU General Public License along
  23. * with Pdsh; if not, write to the Free Software Foundation, Inc.,
  24. * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
  25. \*****************************************************************************/
  26. #if HAVE_CONFIG_H
  27. #include "config.h"
  28. #endif
  29. #include <stdio.h>
  30. #include <limits.h> /* ARG_MAX */
  31. #if HAVE_UNISTD_H
  32. #include <unistd.h> /* for R_OK, access() */
  33. # if defined(_SC_ARG_MAX)
  34. # if defined(ARG_MAX)
  35. # undef ARG_MAX
  36. # endif
  37. # define ARG_MAX sysconf (_SC_ARG_MAX)
  38. # endif
  39. #endif
  40. #include <sys/wait.h> /* waitpid() */
  41. #include <string.h> /* strcmp() */
  42. #include <stdlib.h>
  43. #include <errno.h>
  44. #include "src/common/xmalloc.h"
  45. #include "src/common/xstring.h"
  46. #include "src/common/err.h"
  47. #include "src/common/list.h"
  48. #define QUOTE '\"'
  49. #define SPACE ' '
  50. #define TAB '\t'
  51. #define NWLN '\n'
  52. extern int errno;
  53. static struct pid {
  54. struct pid *next;
  55. FILE *fp;
  56. pid_t pid;
  57. } *pidlist;
  58. /* forward declaration */
  59. static void _parse_command_with_quotes(char *av[], int maxargs, char *str);
  60. /*
  61. * xpopen(): a safer popen for pdsh.
  62. * We bypass shell by doing a fork and exec'ing the cmd directly.
  63. * also, set euid back to original user. This avoids passing possibly
  64. * user supplied arguments to a suid shell.
  65. *
  66. * xpopen returns NULL if the fork or pipe calls fail, or if it cannot
  67. * allocate memory.
  68. *
  69. * Since a shell is not invoked on the command string, you cannot pass
  70. * things that normally the shell would handle. The only thing xpopen()
  71. * attempts to deal with are double quoted strings.
  72. *
  73. * the returned stream must be closed by xpclose (see below)
  74. *
  75. * cmd (IN) cmd to run, as in popen
  76. * mode(IN) "r" for reading, "w" (or anything else) for writing
  77. * OUT FILE * to output/input stream of child process
  78. */
  79. FILE *xpopen(char *cmd, char *mode)
  80. {
  81. struct pid *cur;
  82. int fds[2], read, fd;
  83. pid_t pid;
  84. char *av[ARG_MAX + 1];
  85. int maxfd = sysconf(_SC_OPEN_MAX);
  86. _parse_command_with_quotes(av, ARG_MAX, cmd);
  87. if ((*mode != 'r' && *mode != 'w') || mode[1] != '\0') {
  88. errno = EINVAL;
  89. return (NULL);
  90. }
  91. cur = Malloc(sizeof(struct pid));
  92. read = (*mode == 'r');
  93. if (pipe(fds) < 0) {
  94. close(fds[0]);
  95. close(fds[1]);
  96. Free((void **) &cur);
  97. errx("%p: unable to dup stdout\n");
  98. }
  99. switch (pid = fork()) {
  100. case -1: /* Error. */
  101. close(fds[0]);
  102. close(fds[1]);
  103. Free((void **) &cur);
  104. return (NULL);
  105. case 0: /* child */
  106. close(fds[read ? 0 : 1]);
  107. dup2(fds[read ? 1 : 0], read ? STDOUT_FILENO : STDIN_FILENO);
  108. for (fd = STDERR_FILENO + 1; fd < maxfd; fd++)
  109. close(fd);
  110. setgid(getgid());
  111. setuid(getuid());
  112. do {
  113. if (access(av[0], F_OK) != 0 && errno != EINTR) {
  114. fprintf(stderr, "%s: not found\n", av[0]);
  115. fflush(stderr);
  116. }
  117. } while (errno == EINTR);
  118. execv(av[0], av);
  119. exit(errno);
  120. } /* switch() */
  121. close(fds[read ? 1 : 0]);
  122. /* insert child pid into pidlist */
  123. cur->fp = fdopen(fds[read ? 0 : 1], mode);
  124. cur->pid = pid;
  125. cur->next = pidlist;
  126. pidlist = cur;
  127. return (cur->fp);
  128. }
  129. /*
  130. * xpclose(): close stream opened with xpopen. reap proper child and
  131. * return exit status.
  132. *
  133. * f (IN) file stream as returned by xpopen()
  134. *
  135. * (OUT) -1 on error (was f opened by xpopen?)
  136. * otherwise, exit status of child as modified by
  137. * WEXITSTATUS macro (see waitpid(3))
  138. * This is different from pclose, which returns the
  139. * unmodified status from waitpid.
  140. */
  141. int xpclose(FILE * f)
  142. {
  143. int status;
  144. pid_t pid;
  145. struct pid *cur, *last;
  146. fclose(f);
  147. for (last = NULL, cur = pidlist; cur; last = cur, cur = cur->next)
  148. if (f == cur->fp)
  149. break;
  150. if (cur == NULL)
  151. return (-1);
  152. do {
  153. pid = waitpid(cur->pid, &status, 0);
  154. } while (pid == -1 && errno == EINTR);
  155. if (last == NULL)
  156. pidlist = cur->next;
  157. else
  158. last->next = cur->next;
  159. Free((void **) &cur);
  160. return (WIFEXITED(status) != 0) ? WEXITSTATUS(status) : -1;
  161. }
  162. /* parse_commaned_with_quotes():
  163. * helper function for xpopen
  164. *
  165. * provides simple string parsing with support for double quoted arguments,
  166. * and that is all. This is probably too simple to do what you want.
  167. * Only makes a minimal effort to complain when there's no matching quote.
  168. *
  169. * argv(IN) container for resulting arguments, strings enclosed in "" are
  170. * treated as a single arg.
  171. * maxn(IN) max number of arguments
  172. * str(IN) string to parse
  173. *
  174. */
  175. static void _parse_command_with_quotes(char *argv[], int maxn, char *str)
  176. {
  177. int i = 0;
  178. char *c, *lc;
  179. c = lc = str;
  180. while (*c != '\0' && i < maxn) {
  181. switch (*c) {
  182. case QUOTE:
  183. lc = ++c;
  184. /* find matching quote */
  185. while (*c != '\0' && *c != QUOTE)
  186. c++;
  187. if (*c == '\0')
  188. errx("%P: Unmatched `%c' in xpopen\n", *lc);
  189. /* nullify quote */
  190. *c = '\0';
  191. /* push token onto argument vector */
  192. if (strlen(lc) > 0)
  193. argv[i++] = lc;
  194. /* move c past null */
  195. lc = ++c;
  196. break;
  197. case SPACE:
  198. case TAB:
  199. case NWLN:
  200. /* nullify and push token onto list */
  201. *c = '\0';
  202. if (lc != NULL && strlen(lc) > 0)
  203. argv[i++] = lc;
  204. lc = ++c;
  205. break;
  206. default:
  207. c++;
  208. }
  209. }
  210. /* hit a null. push last token onto list, if such a one exists */
  211. if (strlen(lc) > 0 && i < maxn)
  212. argv[i++] = lc;
  213. argv[i] = NULL;
  214. }
  215. /*
  216. * vi:tabstop=4 shiftwidth=4 expandtab
  217. */