summaryrefslogtreecommitdiff
path: root/indra/llcommon/llprocess.h
blob: 637b7e2f9c8167f1c1ef7acac737e35434067621 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
/** 
 * @file llprocess.h
 * @brief Utility class for launching, terminating, and tracking child processes.
 *
 * $LicenseInfo:firstyear=2008&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */
 
#ifndef LL_LLPROCESS_H
#define LL_LLPROCESS_H

#include "llinitparam.h"
#include "llsdparam.h"
#include "apr_thread_proc.h"
#include <boost/shared_ptr.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/optional.hpp>
#include <boost/noncopyable.hpp>
#include <iosfwd>                   // std::ostream
#include <stdexcept>

#if LL_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <windows.h>                // HANDLE (eye roll)
#elif LL_LINUX
#if defined(Status)
#undef Status
#endif
#endif

class LLEventPump;

class LLProcess;
/// LLProcess instances are created on the heap by static factory methods and
/// managed by ref-counted pointers.
typedef boost::shared_ptr<LLProcess> LLProcessPtr;

/**
 * LLProcess handles launching an external process with specified command line
 * arguments. It also keeps track of whether the process is still running, and
 * can kill it if required.
 *
 * In discussing LLProcess, we use the term "parent" to refer to this process
 * (the process invoking LLProcess), versus "child" to refer to the process
 * spawned by LLProcess.
 *
 * LLProcess relies on periodic post() calls on the "mainloop" LLEventPump: an
 * LLProcess object's Status won't update until the next "mainloop" tick. For
 * instance, the Second Life viewer's main loop already posts to an
 * LLEventPump by that name once per iteration. See
 * indra/llcommon/tests/llprocess_test.cpp for an example of waiting for
 * child-process termination in a standalone test context.
 */
class LL_COMMON_API LLProcess: public boost::noncopyable
{
	LOG_CLASS(LLProcess);
public:
	/**
	 * Specify what to pass for each of child stdin, stdout, stderr.
	 * @see LLProcess::Params::files.
	 */
	struct FileParam: public LLInitParam::Block<FileParam>
	{
		/**
		 * type of file handle to pass to child process
		 *
		 * - "" (default): let the child inherit the same file handle used by
		 *   this process. For instance, if passed as stdout, child stdout
		 *   will be interleaved with stdout from this process. In this case,
		 *   @a name is moot and should be left "".
		 *
		 * - "file": open an OS filesystem file with the specified @a name.
		 *   <i>Not yet implemented.</i>
		 *
		 * - "pipe" or "tpipe" or "npipe": depends on @a name
		 *
		 *   - @a name.empty(): construct an OS pipe used only for this slot
		 *     of the forthcoming child process.
		 *
		 *   - ! @a name.empty(): in a global registry, find or create (using
		 *     the specified @a name) an OS pipe. The point of the (purely
		 *     internal) @a name is that passing the same @a name in more than
		 *     one slot for a given LLProcess -- or for slots in different
		 *     LLProcess instances -- means the same pipe. For example, you
		 *     might pass the same @a name value as both stdout and stderr to
		 *     make the child process produce both on the same actual pipe. Or
		 *     you might pass the same @a name as the stdout for one LLProcess
		 *     and the stdin for another to connect the two child processes.
		 *     Use LLProcess::getPipeName() to generate a unique name
		 *     guaranteed not to already exist in the registry. <i>Not yet
		 *     implemented.</i>
		 *
		 *   The difference between "pipe", "tpipe" and "npipe" is as follows.
		 *
		 *   - "pipe": direct LLProcess to monitor the parent end of the pipe,
		 *     pumping nonblocking I/O every frame. The expectation (at least
		 *     for stdout or stderr) is that the caller will listen for
		 *     incoming data and consume it as it arrives. It's important not
		 *     to neglect such a pipe, because it's buffered in memory. If you
		 *     suspect the child may produce a great volume of output between
		 *     frames, consider directing the child to write to a filesystem
		 *     file instead, then read the file later.
		 *
		 *   - "tpipe": do not engage LLProcess machinery to monitor the
		 *     parent end of the pipe. A "tpipe" is used only to connect
		 *     different child processes. As such, it makes little sense to
		 *     pass an empty @a name. <i>Not yet implemented.</i>
		 *
		 *   - "npipe": like "tpipe", but use an OS named pipe with a
		 *     generated name. Note that @a name is the @em internal name of
		 *     the pipe in our global registry -- it doesn't necessarily have
		 *     anything to do with the pipe's name in the OS filesystem. Use
		 *     LLProcess::getPipeName() to obtain the named pipe's OS
		 *     filesystem name, e.g. to pass it as the @a name to another
		 *     LLProcess instance using @a type "file". This supports usage
		 *     like bash's &lt;(subcommand...) or &gt;(subcommand...)
		 *     constructs. <i>Not yet implemented.</i>
		 *
		 * In all cases the open mode (read, write) is determined by the child
		 * slot you're filling. Child stdin means select the "read" end of a
		 * pipe, or open a filesystem file for reading; child stdout or stderr
		 * means select the "write" end of a pipe, or open a filesystem file
		 * for writing.
		 *
		 * Confusion such as passing the same pipe as the stdin of two
		 * processes (rather than stdout for one and stdin for the other) is
		 * explicitly permitted: it's up to the caller to construct meaningful
		 * LLProcess pipe graphs.
		 */
		Optional<std::string> type;
		Optional<std::string> name;

		FileParam(const std::string& tp="", const std::string& nm=""):
			type("type"),
			name("name")
		{
			// If caller wants to specify values, use explicit assignment to
			// set them rather than initialization.
			if (! tp.empty()) type = tp;
			if (! nm.empty()) name = nm;
        }
	};

	/// Param block definition
	struct Params: public LLInitParam::Block<Params>
	{
		Params():
			executable("executable"),
			args("args"),
			cwd("cwd"),
			autokill("autokill", true),
			files("files"),
			postend("postend"),
			desc("desc")
		{}

		/// pathname of executable
		Mandatory<std::string> executable;
		/**
		 * zero or more additional command-line arguments. Arguments are
		 * passed through as exactly as we can manage, whitespace and all.
		 * @note On Windows we manage this by implicitly double-quoting each
		 * argument while assembling the command line.
		 */
		Multiple<std::string> args;
		/// current working directory, if need it changed
		Optional<std::string> cwd;
		/// implicitly kill process on destruction of LLProcess object
		/// (default true)
		Optional<bool> autokill;
		/**
		 * Up to three FileParam items: for child stdin, stdout, stderr.
		 * Passing two FileParam entries means default treatment for stderr,
		 * and so forth.
		 *
		 * @note LLInitParam::Block permits usage like this:
		 * @code
		 * LLProcess::Params params;
		 * ...
		 * params.files
		 *     .add(LLProcess::FileParam()) // stdin
		 *     .add(LLProcess::FileParam().type("pipe") // stdout
		 *     .add(LLProcess::FileParam().type("file").name("error.log"));
		 * @endcode
		 *
		 * @note While it's theoretically plausible to pass additional open
		 * file handles to a child specifically written to expect them, our
		 * underlying implementation doesn't yet support that.
		 */
		Multiple<FileParam, AtMost<3> > files;
		/**
		 * On child-process termination, if this LLProcess object still
		 * exists, post LLSD event to LLEventPump with specified name (default
		 * no event). Event contains at least:
		 *
		 * - "id" as obtained from getProcessID()
		 * - "desc" short string description of child (executable + pid)
		 * - "state" @c state enum value, from Status.mState
		 * - "data"	 if "state" is EXITED, exit code; if KILLED, on Posix,
		 *   signal number
		 * - "string" English text describing "state" and "data" (e.g. "exited
		 *   with code 0")
		 */
		Optional<std::string> postend;
		/**
		 * Description of child process for logging purposes. It need not be
		 * unique; the logged description string will contain the PID as well.
		 * If this is omitted, a description will be derived from the
		 * executable name.
		 */
		Optional<std::string> desc;
	};
	typedef LLSDParamAdapter<Params> LLSDOrParams;

	/**
	 * Factory accepting either plain LLSD::Map or Params block.
	 * MAY RETURN DEFAULT-CONSTRUCTED LLProcessPtr if params invalid!
	 */
	static LLProcessPtr create(const LLSDOrParams& params);
	virtual ~LLProcess();

	/// Is child process still running?
	bool isRunning() const;

	/**
	 * State of child process
	 */
	enum state
	{
		UNSTARTED,					///< initial value, invisible to consumer
		RUNNING,					///< child process launched
		EXITED,						///< child process terminated voluntarily
		KILLED						///< child process terminated involuntarily
	};

	/**
	 * Status info
	 */
	struct Status
	{
		Status():
			mState(UNSTARTED),
			mData(0)
		{}

		state mState;				///< @see state
		/**
		 * - for mState == EXITED: mData is exit() code
		 * - for mState == KILLED: mData is signal number (Posix)
		 * - otherwise: mData is undefined
		 */
		int mData;
	};

	/// Status query
	Status getStatus() const;
	/// English Status string query, for logging etc.
	std::string getStatusString() const;
	/// English Status string query for previously-captured Status
	std::string getStatusString(const Status& status) const;
	/// static English Status string query
	static std::string getStatusString(const std::string& desc, const Status& status);

	// Attempt to kill the process -- returns true if the process is no longer running when it returns.
	// Note that even if this returns false, the process may exit some time after it's called.
	bool kill(const std::string& who="");

#if LL_WINDOWS
	typedef int id;                 ///< as returned by getProcessID()
	typedef HANDLE handle;          ///< as returned by getProcessHandle()
#else
	typedef pid_t id;
	typedef pid_t handle;
#endif
	/**
	 * Get an int-like id value. This is primarily intended for a human reader
	 * to differentiate processes.
	 */
	id getProcessID() const;
	/**
	 * Get a "handle" of a kind that you might pass to platform-specific API
	 * functions to engage features not directly supported by LLProcess.
	 */
	handle getProcessHandle() const;

	/**
	 * Test if a process (@c handle obtained from getProcessHandle()) is still
	 * running. Return same nonzero @c handle value if still running, else
	 * zero, so you can test it like a bool. But if you want to update a
	 * stored variable as a side effect, you can write code like this:
	 * @code
	 * hchild = LLProcess::isRunning(hchild);
	 * @endcode
	 * @note This method is intended as a unit-test hook, not as the first of
	 * a whole set of operations supported on freestanding @c handle values.
	 * New functionality should be added as nonstatic members operating on
	 * the same data as getProcessHandle().
	 *
	 * In particular, if child termination is detected by static isRunning()
	 * rather than by nonstatic isRunning(), the LLProcess object won't be
	 * aware of the child's changed status and may encounter OS errors trying
	 * to obtain it. static isRunning() is only intended for after the
	 * launching LLProcess object has been destroyed.
	 */
	static handle isRunning(handle, const std::string& desc="");

	/// Provide symbolic access to child's file slots
	enum FILESLOT { STDIN=0, STDOUT=1, STDERR=2, NSLOTS=3 };

	/**
	 * For a pipe constructed with @a type "npipe", obtain the generated OS
	 * filesystem name for the specified pipe. Otherwise returns the empty
	 * string. @see LLProcess::FileParam::type
	 */
	std::string getPipeName(FILESLOT) const;

	/// base of ReadPipe, WritePipe
	class LL_COMMON_API BasePipe
	{
	public:
		virtual ~BasePipe() = 0;

		typedef std::size_t size_type;
		static const size_type npos;
	};

	/// As returned by getWritePipe() or getOptWritePipe()
	class WritePipe: public BasePipe
	{
	public:
		/**
		 * Get ostream& on which to write to child's stdin.
		 *
		 * @usage
		 * @code
		 * myProcess->getWritePipe().get_ostream() << "Hello, child!" << std::endl;
		 * @endcode
		 */
		virtual std::ostream& get_ostream() = 0;
	};

	/// As returned by getReadPipe() or getOptReadPipe()
	class ReadPipe: public BasePipe
	{
	public:
		/**
		 * Get istream& on which to read from child's stdout or stderr.
		 *
		 * @usage
		 * @code
		 * std::string stuff;
		 * myProcess->getReadPipe().get_istream() >> stuff;
		 * @endcode
		 *
		 * You should be sure in advance that the ReadPipe in question can
		 * fill the request. @see getPump()
		 */
		virtual std::istream& get_istream() = 0;

		/**
		 * Get accumulated buffer length.
		 * Often we need to refrain from actually reading the std::istream
		 * returned by get_istream() until we've accumulated enough data to
		 * make it worthwhile. For instance, if we're expecting a number from
		 * the child, but the child happens to flush "12" before emitting
		 * "3\n", get_istream() >> myint could return 12 rather than 123!
		 */
		virtual size_type size() const = 0;

		/**
		 * Peek at accumulated buffer data without consuming it. Optional
		 * parameters give you substr() functionality.
		 *
		 * @note You can discard buffer data using get_istream().ignore(n).
		 */
		virtual std::string peek(size_type offset=0, size_type len=npos) const = 0;

		/**
		 * Detect presence of a substring (or char) in accumulated buffer data
		 * without retrieving it. Optional offset allows you to search from
		 * specified position.
		 */
		template <typename SEEK>
		bool contains(SEEK seek, size_type offset=0) const
		{ return find(seek, offset) != npos; }

		/**
		 * Search for a substring in accumulated buffer data without
		 * retrieving it. Returns size_type position at which found, or npos
		 * meaning not found. Optional offset allows you to search from
		 * specified position.
		 */
		virtual size_type find(const std::string& seek, size_type offset=0) const = 0;

		/**
		 * Search for a char in accumulated buffer data without retrieving it.
		 * Returns size_type position at which found, or npos meaning not
		 * found. Optional offset allows you to search from specified
		 * position.
		 */
		virtual size_type find(char seek, size_type offset=0) const = 0;

		/**
		 * Get LLEventPump& on which to listen for incoming data. The posted
		 * LLSD::Map event will contain a key "data" whose value is an
		 * LLSD::String containing (part of) the data accumulated in the
		 * buffer.
		 *
		 * If the child sends "abc", and this ReadPipe posts "data"="abc", but
		 * you don't consume it by reading the std::istream returned by
		 * get_istream(), and the child next sends "def", ReadPipe will post
		 * "data"="abcdef".
		 */
		virtual LLEventPump& getPump() = 0;

		/**
		 * Set maximum length of buffer data that will be posted in the LLSD
		 * announcing arrival of new data from the child. If you call
		 * setLimit(5), and the child sends "abcdef", the LLSD event will
		 * contain "data"="abcde". However, you may still read the entire
		 * "abcdef" from get_istream(): this limit affects only the size of
		 * the data posted with the LLSD event. If you don't call this method,
		 * @em no data will be posted: the default is 0 bytes.
		 */
		virtual void setLimit(size_type limit) = 0;

		/**
		 * Query the current setLimit() limit.
		 */
		virtual size_type getLimit() const = 0;
	};

	/// Exception thrown by getWritePipe(), getReadPipe() if you didn't ask to
	/// create a pipe at the corresponding FILESLOT.
	struct NoPipe: public std::runtime_error
	{
		NoPipe(const std::string& what): std::runtime_error(what) {}
	};

	/**
	 * Get a reference to the (only) WritePipe for this LLProcess. @a slot, if
	 * specified, must be STDIN. Throws NoPipe if you did not request a "pipe"
	 * for child stdin. Use this method when you know how you created the
	 * LLProcess in hand.
	 */
	WritePipe& getWritePipe(FILESLOT slot=STDIN);

	/**
	 * Get a boost::optional<WritePipe&> to the (only) WritePipe for this
	 * LLProcess. @a slot, if specified, must be STDIN. The return value is
	 * empty if you did not request a "pipe" for child stdin. Use this method
	 * for inspecting an LLProcess you did not create.
	 */
	boost::optional<WritePipe&> getOptWritePipe(FILESLOT slot=STDIN);

	/**
	 * Get a reference to one of the ReadPipes for this LLProcess. @a slot, if
	 * specified, must be STDOUT or STDERR. Throws NoPipe if you did not
	 * request a "pipe" for child stdout or stderr. Use this method when you
	 * know how you created the LLProcess in hand.
	 */
	ReadPipe& getReadPipe(FILESLOT slot);

	/**
	 * Get a boost::optional<ReadPipe&> to one of the ReadPipes for this
	 * LLProcess. @a slot, if specified, must be STDOUT or STDERR. The return
	 * value is empty if you did not request a "pipe" for child stdout or
	 * stderr. Use this method for inspecting an LLProcess you did not create.
	 */
	boost::optional<ReadPipe&> getOptReadPipe(FILESLOT slot);

private:
	/// constructor is private: use create() instead
	LLProcess(const LLSDOrParams& params);
	void autokill();
	// Classic-C-style APR callback
	static void status_callback(int reason, void* data, int status);
	// Object-oriented callback
	void handle_status(int reason, int status);
	// implementation for get[Opt][Read|Write]Pipe()
	template <class PIPETYPE>
	PIPETYPE& getPipe(FILESLOT slot);
	template <class PIPETYPE>
	boost::optional<PIPETYPE&> getOptPipe(FILESLOT slot);
	template <class PIPETYPE>
	PIPETYPE* getPipePtr(std::string& error, FILESLOT slot);

	std::string mDesc;
	std::string mPostend;
	apr_proc_t mProcess;
	bool mAutokill;
	Status mStatus;
	// explicitly want this ptr_vector to be able to store NULLs
	typedef boost::ptr_vector< boost::nullable<BasePipe> > PipeVector;
	PipeVector mPipes;
};

/// for logging
LL_COMMON_API std::ostream& operator<<(std::ostream&, const LLProcess::Params&);

#endif // LL_LLPROCESS_H