1 /* CONSPAWN.C   (c) "Fish" (David B. Trout), 2005-2012               */
2 /*              Spawn console command                                */
3 
4 /*-------------------------------------------------------------------*/
5 /*       This program is spawned by Hercules as a result of          */
6 /*       the 'sh' (shell) command. Its purpose is to simply          */
7 /*       call the host's shell (command interpreter) program         */
8 /*       with the arguments supplied (usually to invoke yet          */
9 /*       another program), redirecting the results back to           */
10 /*       Hercules for display. NOTE that this program does           */
11 /*       not perform the actual stdio redirection itself, but        */
12 /*       rather relies on Hercules to have set that up before        */
13 /*       invoking this program.                                      */
14 /*-------------------------------------------------------------------*/
15 
16 #include "hstdinc.h"
17 
18 #define PGMNAME  "conspawn"
19 
main(int argc,char * argv[])20 int main(int argc, char* argv[])
21 {
22     int i, k, rc;
23     char* p;
24 
25 #ifdef _MSVC_
26 #pragma comment(lib,"shell32.lib")   // (need ShellExecute)
27     // --------------------------------------------------------
28     // PROGRAMMING NOTE: We MUST use "ShellExecute" for Windows
29     // GUI programs since: 1) GUI programs don't use stdio,
30     // 2) they never exit until the user manually closes them.
31     //
32     // Erroneously using the 'system()' function to start a GUI
33     // program causes HercGUI to hang at PowerOff as it waits
34     // for its child process to close its stdio handles which
35     // GUI programs never do until they exit (which they never
36     // do until the user manually closes them).
37     //
38     // The reason this phenomenon occurs even though Hercules
39     // does indeed close ITS OWN stdio handles when it ends is
40     // because its child processes that it creates ALSO inherit
41     // the same stdio handles! (I.e. the GUI programs that Herc
42     // starts end up never closing "Herc's" [inherited] stdio
43     // handles, which are the same handles that HercGUI waits
44     // on! Thus GUI programs started by Herc using the 'system'
45     // API end up hanging HercGUI! (during Herc PowerOff))
46     //
47     // Thus, for GUI apps, we MUST use "ShellExecute" here
48     // to prevent HercGUI from hanging at PowerOff. Also note
49     // that this hang obviously does not occur when Hercules
50     // is run in command-line (non-HercGUI) mode even when
51     // the 'system()' API is erroneously used, since Herc is
52     // obviously (duh!) not being run under the control of an
53     // external GUI in such a situation.   -- Fish, Aug. 2006
54     // --------------------------------------------------------
55 
56     if (argc >= 2 && strcasecmp(argv[1],"startgui") == 0)
57     {
58         ////////////////////////////////////////////////////////
59         // Windows GUI program; no stdio; use 'ShellExecute'...
60         ////////////////////////////////////////////////////////
61 
62         // REFERENCE: upon entry:
63 
64         //   argv[0]       "conspawn"
65         //   argv[1]       "startgui"
66         //   argv[2]       (program to start)
67         //   argv[3..n]    (arguments for program)
68 
69         HWND     hwnd          = NULL;
70         LPCTSTR  lpOperation   = NULL;
71         LPCTSTR  lpFile        = argv[2];
72         LPCTSTR  lpParameters  = NULL;
73         LPCTSTR  lpDirectory   = NULL;
74         INT      nShowCmd      = SW_SHOWNORMAL;
75         char*    pszErrMsg     = NULL;
76 
77         // Build arguments string from passed args...
78 
79         for (i=3, k=0; i < argc; i++)
80             k += strlen(argv[i]) + 1;
81 
82         if (k)
83         {
84             // (allocate room for arguments string)
85 
86             if (!(p = malloc(sizeof(char)*k)))
87             {
88                 errno = ENOMEM;
89                 perror( PGMNAME );
90                 return -1;
91             }
92             *p = 0;
93 
94             // (build arguments string from args)
95 
96             for (i=3; i < argc; ++i)
97             {
98                 strcat(p,argv[i]);
99                 if (i != (argc-1)) strcat(p," ");
100             }
101 
102             lpParameters = p;
103         }
104         else
105             p = NULL;
106 
107         rc = (intptr_t) ShellExecute( hwnd, lpOperation, lpFile, lpParameters, lpDirectory, nShowCmd );
108 
109         if (p)
110             free(p);
111 
112         if ( rc > 32)
113             return 0;       // rc > greater than 32 == success...
114 
115         // rc <= less than or equal 32 == error...
116 
117         switch (rc)
118         {
119             case 0:                      pszErrMsg = "The operating system is out of memory or resources"; break;
120             case SE_ERR_FNF:             pszErrMsg = "The specified file was not found"; break;
121             case SE_ERR_PNF:             pszErrMsg = "The specified path was not found"; break;
122             case SE_ERR_ACCESSDENIED:    pszErrMsg = "The operating system denied access to the specified file"; break;
123             case ERROR_BAD_FORMAT:       pszErrMsg = "The .exe file is invalid (non-Microsoft Win32 .exe or error in .exe image)"; break;
124             case SE_ERR_OOM:             pszErrMsg = "There was not enough memory to complete the operation"; break;
125             case SE_ERR_DLLNOTFOUND:     pszErrMsg = "The specified dynamic-link library (DLL) was not found"; break;
126             case SE_ERR_SHARE:           pszErrMsg = "A sharing violation occurred"; break;
127             case SE_ERR_ASSOCINCOMPLETE: pszErrMsg = "The file name association is incomplete or invalid"; break;
128             case SE_ERR_DDETIMEOUT:      pszErrMsg = "The DDE transaction could not be completed because the request timed out"; break;
129             case SE_ERR_DDEFAIL:         pszErrMsg = "The DDE transaction failed"; break;
130             case SE_ERR_DDEBUSY:         pszErrMsg = "The Dynamic Data Exchange (DDE) transaction could not be completed because other DDE transactions were being processed"; break;
131             case SE_ERR_NOASSOC:         pszErrMsg = "There is no application associated with the given file name extension. This error will also be returned if you attempt to print a file that is not printable"; break;
132 
133             default:
134                 printf(PGMNAME": ShellExecute(\"%s\", \"%s\",...) failed: Unknown error; rc=%d (0x%08.8X).\n",
135                     lpFile, lpParameters, rc, rc );
136                 return -1;
137         }
138 
139         printf( PGMNAME": ShellExecute(\"%s\", \"%s\",...) failed: %s.\n",
140             lpFile, lpParameters, pszErrMsg );
141 
142         return -1;
143     }
144 #endif // _MSVC_
145 
146     ////////////////////////////////////////////////////////
147     // Command line program using stdio; use 'system()'...
148     ////////////////////////////////////////////////////////
149 
150     // Re-build a complete command line from passed args...
151 
152     for (i=1, k=0; i < argc; i++)
153         k += strlen(argv[i]) + 1;
154 
155     if (!k)
156     {
157         errno = EINVAL;
158         perror( PGMNAME );
159         printf( PGMNAME": Usage: command [args]\n");
160         return -1;
161     }
162 
163     // (allocate room for command line)
164 
165     if (!(p = malloc(sizeof(char)*k)))
166     {
167         errno = ENOMEM;
168         perror( PGMNAME );
169         return -1;
170     }
171     *p = 0;
172 
173     // (rebuild command-line from args)
174 
175     for (i=1; i < argc; ++i)
176     {
177         strcat(p,argv[i]);
178         if (i != (argc-1)) strcat(p," ");
179     }
180 
181     // Ask system() to process command line...
182 
183     // NOTE: the below call WILL NOT RETURN
184     // until the program being called exits!
185 
186     rc = system(p);
187 
188     free(p);
189 
190     // --------------------------------------------------------
191     // PROGRAMMING NOTE: only rc == -1 need be reported since,
192     // if the command interpreter called a batch/cmd file for
193     // example, it could have set its own custom return code.
194     //
195     // Only -1 means the system() call itself failed, which
196     // is the only thing that actually needs to be reported.
197     // --------------------------------------------------------
198 
199     if ( -1 == rc )
200         perror( PGMNAME );
201 
202     return rc;
203 }
204