source: branches/release_1_8/base/src/pextlib1.0/system.c @ 60770

Last change on this file since 60770 was 60770, checked in by jmr@…, 8 years ago

1.8 branch: similar change to trunk's r60769:

  • include command output in the return value of the system proc only if not in debug or verbose mode (#21084)
  • instruct user to run again in debug mode on failure, or give the ticket guidelines URL if in debug mode already
  • Property svn:keywords set to Id
File size: 6.6 KB
Line 
1/*
2 * system.c
3 * $Id: system.c 60770 2009-11-23 04:32:54Z jmr@macports.org $
4 *
5 * Copyright (c) 2009 The MacPorts Project
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of The MacPorts Project nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33#if HAVE_CONFIG_H
34#include <config.h>
35#endif
36
37#include <tcl.h>
38
39#if HAVE_PATHS_H
40#include <paths.h>
41#endif
42
43#include <fcntl.h>
44#include <stdlib.h>
45#include <string.h>
46#include <unistd.h>
47
48#include "system.h"
49#include "Pextlib.h"
50
51#if HAVE_CRT_EXTERNS_H
52#include <crt_externs.h>
53#define environ (*_NSGetEnviron())
54#else
55extern char **environ;
56#endif
57
58#ifndef _PATH_DEVNULL
59#define _PATH_DEVNULL "/dev/null"
60#endif
61
62#define CBUFSIZ 30
63
64struct linebuf {
65        size_t len;
66        char *line;
67};
68
69int SystemCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
70{
71        char *buf;
72        struct linebuf circbuf[CBUFSIZ];
73        size_t linelen;
74        char *args[4];
75        char *cmdstring;
76        FILE *pdes;
77        int fdset[2], nullfd;
78        int fline, pos, ret;
79        int osetsid = 0;
80        pid_t pid;
81        Tcl_Obj *errbuf;
82        Tcl_Obj *tcl_result;
83        int read_failed, status;
84
85        /* usage: system [-notty] command */
86        if (objc == 2) {
87                cmdstring = Tcl_GetString(objv[1]);
88        } else if (objc == 3) {
89                char *arg = Tcl_GetString(objv[1]);
90                cmdstring = Tcl_GetString(objv[2]);
91
92                if (strcmp(arg, "-notty") == 0) {
93                        osetsid = 1;
94                } else {
95                        tcl_result = Tcl_NewStringObj("bad option ", -1);
96                        Tcl_AppendObjToObj(tcl_result, Tcl_NewStringObj(arg, -1));
97                        Tcl_SetObjResult(interp, tcl_result);
98                        return TCL_ERROR;
99                }
100        } else {
101                Tcl_WrongNumArgs(interp, 1, objv, "command");
102                return TCL_ERROR;
103        }
104
105        /*
106         * Fork a child to run the command, in a popen() like fashion -
107         * popen() itself is not used because stderr is also desired.
108         */
109        if (pipe(fdset) != 0) {
110                return TCL_ERROR;
111        }
112
113        pid = fork();
114        switch (pid) {
115        case -1: /* error */
116                return TCL_ERROR;
117                break;
118        case 0: /* child */
119                close(fdset[0]);
120
121                if ((nullfd = open(_PATH_DEVNULL, O_RDONLY)) == -1)
122                        _exit(1);
123                dup2(nullfd, STDIN_FILENO);
124                dup2(fdset[1], STDOUT_FILENO);
125                dup2(fdset[1], STDERR_FILENO);
126                /* drop the controlling terminal if requested */
127                if (osetsid) {
128                        if (setsid() == -1)
129                                _exit(1);
130                }
131                /* XXX ugly string constants */
132                args[0] = "sh";
133                args[1] = "-c";
134                args[2] = cmdstring;
135                args[3] = NULL;
136                execve("/bin/sh", args, environ);
137                _exit(1);
138                break;
139        default: /* parent */
140                break;
141        }
142
143        close(fdset[1]);
144
145        /* read from simulated popen() pipe */
146        read_failed = 0;
147        pos = 0;
148        bzero(circbuf, sizeof(circbuf));
149        pdes = fdopen(fdset[0], "r");
150        while ((buf = fgetln(pdes, &linelen)) != NULL) {
151                char *sbuf;
152                int slen;
153
154                /*
155                 * Allocate enough space to insert a terminating
156                 * '\0' if the line is not terminated with a '\n'
157                 */
158                if (buf[linelen - 1] == '\n')
159                        slen = linelen;
160                else
161                        slen = linelen + 1;
162
163                if (circbuf[pos].len == 0)
164                        sbuf = malloc(slen);
165                else {
166                        sbuf = realloc(circbuf[pos].line, slen);
167                }
168
169                if (sbuf == NULL) {
170                        read_failed = 1;
171                        break;
172                }
173
174                memcpy(sbuf, buf, linelen);
175                /* terminate line with '\0',replacing '\n' if it exists */
176                sbuf[slen - 1] = '\0';
177
178                circbuf[pos].line = sbuf;
179                circbuf[pos].len = slen;
180
181                if (pos++ == CBUFSIZ - 1) {
182                        pos = 0;
183                }
184
185                if (ui_info(interp, sbuf) != TCL_OK) {
186                        read_failed = 1;
187                        break;
188                }
189        }
190        fclose(pdes);
191
192        status = TCL_ERROR;
193
194        if (wait(&ret) == pid && WIFEXITED(ret) && !read_failed) {
195                /* Normal exit, and reading from the pipe didn't fail. */
196                if (WEXITSTATUS(ret) == 0) {
197                        status = TCL_OK;
198                } else {
199                    Tcl_Obj* errorCode;
200                    const char *portverbose;
201
202                        /* set errorCode [list CHILDSTATUS <pid> <code>] */
203                        errorCode = Tcl_NewListObj(0, NULL);
204                        Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj("CHILDSTATUS", -1));
205                        Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewIntObj(pid));
206                        Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewIntObj(WEXITSTATUS(ret)));
207                        Tcl_SetObjErrorCode(interp, errorCode);
208
209                        /* set result */
210                        tcl_result = Tcl_NewStringObj("shell command \"", -1);
211                        Tcl_AppendToObj(tcl_result, cmdstring, -1);
212                        Tcl_AppendToObj(tcl_result, "\" returned error ", -1);
213                        Tcl_AppendObjToObj(tcl_result, Tcl_NewIntObj(WEXITSTATUS(ret)));
214                       
215                        portverbose = Tcl_GetVar(interp, "portverbose", TCL_GLOBAL_ONLY);
216                        /* include last 30 lines of output only if they haven't already
217                           been shown due to debug or verbose mode */
218                        if (portverbose && strcmp("yes", portverbose) != 0) {
219                            /* Copy the contents of the circular buffer to errbuf */
220                errbuf = Tcl_NewStringObj(NULL, 0);
221                for (fline = pos; pos < fline + CBUFSIZ; pos++) {
222                    if (circbuf[pos % CBUFSIZ].len == 0)
223                    continue; /* skip empty lines */
224
225                    /* Append line, minus trailing NULL */
226                    Tcl_AppendToObj(errbuf, circbuf[pos % CBUFSIZ].line,
227                            circbuf[pos % CBUFSIZ].len - 1);
228
229                    /* Re-add previously stripped newline */
230                    Tcl_AppendToObj(errbuf, "\n", 1);
231                }
232                            Tcl_AppendToObj(tcl_result, "\nCommand output: ", -1);
233                            Tcl_AppendObjToObj(tcl_result, errbuf);
234                        }
235                        Tcl_SetObjResult(interp, tcl_result);
236                }
237        }
238
239        /* Cleanup. */
240        close(fdset[0]);
241        for (fline = 0; fline < CBUFSIZ; fline++) {
242                if (circbuf[fline].len != 0) {
243                        free(circbuf[fline].line);
244                }
245        }
246
247        return status;
248}
Note: See TracBrowser for help on using the repository browser.