Annotation of embedaddon/libpdel/config/app_config.3, revision 1.1.1.1
1.1 misho 1: .\" Copyright (c) 2001-2002 Packet Design, LLC.
2: .\" All rights reserved.
3: .\"
4: .\" Subject to the following obligations and disclaimer of warranty,
5: .\" use and redistribution of this software, in source or object code
6: .\" forms, with or without modifications are expressly permitted by
7: .\" Packet Design; provided, however, that:
8: .\"
9: .\" (i) Any and all reproductions of the source or object code
10: .\" must include the copyright notice above and the following
11: .\" disclaimer of warranties; and
12: .\" (ii) No rights are granted, in any manner or form, to use
13: .\" Packet Design trademarks, including the mark "PACKET DESIGN"
14: .\" on advertising, endorsements, or otherwise except as such
15: .\" appears in the above copyright notice or in the software.
16: .\"
17: .\" THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
18: .\" TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
19: .\" REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
20: .\" THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
21: .\" WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
22: .\" OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
23: .\" OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
24: .\" OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
25: .\" RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE
26: .\" LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
27: .\" OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
28: .\" INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
29: .\" DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
30: .\" USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF
31: .\" LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32: .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
33: .\" THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF
34: .\" THE POSSIBILITY OF SUCH DAMAGE.
35: .\"
36: .\" Author: Archie Cobbs <archie@freebsd.org>
37: .\"
38: .\" $Id: app_config.3,v 1.12 2004/06/02 17:24:36 archie Exp $
39: .\"
40: .Dd April 22, 2002
41: .Dt APP_CONFIG 3
42: .Os
43: .Sh NAME
44: .Nm app_config
45: .Nd application configuration system
46: .Sh LIBRARY
47: PDEL Library (libpdel, \-lpdel)
48: .Sh SYNOPSIS
49: .In sys/types.h
50: .In pdel/config/app_config.h
51: .Ft "struct app_config_ctx *"
52: .Fn app_config_init "struct pevent_ctx *ctx" "const struct app_config *info" "void *cookie"
53: .Ft int
54: .Fn app_config_uninit "struct app_config_ctx **ctxp"
55: .Ft "void *"
56: .Fn app_config_get_cookie "struct app_config_ctx *ctx"
57: .Ft int
58: .Fn app_config_load "struct app_config_ctx *ctx" "const char *path" "int allow_writeback"
59: .Ft int
60: .Fn app_config_reload "struct app_config_ctx *ctx"
61: .Ft "void *"
62: .Fn app_config_new "struct app_config_ctx *ctx"
63: .Ft int
64: .Fn app_config_set "struct app_config_ctx *ctx" "const void *config" "u_long delay" "char *ebuf" "int emax"
65: .Ft "void *"
66: .Fn app_config_get "struct app_config_ctx *ctx" "int pending"
67: .Ft "const struct structs_type *"
68: .Fn app_config_get_type "struct app_config_ctx *ctx"
69: .Ft "void *"
70: .Fn app_config_copy "struct app_config_ctx *ctx" "const void *config"
71: .Ft void
72: .Fn app_config_free "struct app_config_ctx *ctx" "void **configp"
73: .Vt extern const struct app_subsystem app_config_alog_subsystem ;
74: .Vt extern const struct app_subsystem app_config_curconf_subsystem ;
75: .Vt extern const struct app_subsystem app_config_directory_subsystem ;
76: .Vt extern const struct app_subsystem app_config_pidfile_subsystem ;
77: .Sh DESCRIPTION
78: These functions implement an application configuration framework.
79: .\"
80: .Ss Application model
81: .\"
82: The
83: .Nm app_config
84: model assumes that the application's configuration is stored in a single
85: .Em "configuration object" ,
86: which can be any data structure that is describable by a
87: .Xr structs 3
88: type.
89: The configuration can be stored in an XML file which is automatically updated.
90: .Pp
91: The application itself consists of one or more
92: .Em subsystems .
93: A subsystem is an abstract activity that can be started or stopped and
94: whose behavior depends on (some part of) the configuration object.
95: When the configuration object is changed, those subsystems that
96: require it are automatically stopped and then restarted with the new
97: configuration.
98: .Pp
99: The application may provide methods for:
100: .Pp
101: .Bl -bullet -compact -offset 3n
102: .It
103: Creating a new, default configuration object
104: .It
105: Initializing a new configuration object for the current system
106: .It
107: Checking a configuration object for overall validity
108: .It
109: Normalizing a configuration object
110: .It
111: Upgrading a configuration object from an old version
112: .El
113: .Pp
114: Each subsystem may provide methods for:
115: .Pp
116: .Bl -bullet -compact -offset 3n
117: .It
118: Starting the subsystem
119: .It
120: Stopping the subsystem
121: .It
122: Determining if the subsystem will run
123: .It
124: Determining if the subsystem needs to be restarted
125: .El
126: .Pp
127: In the steady state, there is a single currently active configuration object.
128: Only those subsystems that are supposed to be running are.
129: All running subsystems are running with their configurations based on
130: (i.e., consistent with) the active configuration object.
131: .Pp
132: When there needs to be a configuration change, a new configuration object
133: is constructed and the
134: .Nm app_config
135: library is told to apply the new configuration object.
136: After a configurable delay (for hysteresis), those subsystems affected
137: by the change are stopped, the new configuration object is installed
138: in place of the old one, and the stopped subsystems are restarted.
139: .Pp
140: The
141: .Nm app_config
142: library guarantees that only "valid" configurations may be applied,
143: where the meaning of "valid" is determined by the application.
144: .\"
145: .Ss Subsystems
146: .\"
147: A subsystem is defined as anything that can be started and/or stopped.
148: A subsystem typically depends on some portion of the configuration
149: object such that it must be stopped and restarted when that portion changes.
150: .Pp
151: A subsystem is defined by a
152: .Li "struct app_subsystem" :
153: .Pp
154: .Bd -literal -compact -offset 3n
155: struct app_subsystem {
156: const char *name; /* name, null to end list */
157: void *arg; /* opaque subsystem argument */
158: app_ss_startup_t *start; /* start subsystem */
159: app_ss_shutdown_t *stop; /* stop subsystem */
160: app_ss_willrun_t *willrun; /* will subsystem run? */
161: app_ss_changed_t *changed; /* subsystem config changed? */
162: const char **deplist; /* config items dependent on */
163: };
164: .Ed
165: .Pp
166: The
167: .Fa name
168: is currently used only for debugging, but must be
169: .Dv NULL
170: to terminate the list of subsystems (see below).
171: The
172: .Fa arg
173: is ignored and is for the application's private use.
174: The
175: .Fa start ,
176: .Fa stop ,
177: .Fa willrun ,
178: and
179: .Fa changed
180: fields must be pointers to functions having these types:
181: .Pp
182: .Bd -literal -compact -offset 3n
183: typedef int app_ss_startup_t(struct app_config_ctx *ctx,
184: const struct app_subsystem *ss, const void *config);
185: typedef void app_ss_shutdown_t(struct app_config_ctx *ctx,
186: const struct app_subsystem *ss, const void *config);
187: typedef int app_ss_willrun_t(struct app_config_ctx *ctx,
188: const struct app_subsystem *ss, const void *config);
189: typedef int app_ss_changed_t(struct app_config_ctx *ctx,
190: const struct app_subsystem *ss,
191: const void *config1, const void *config2);
192: .Ed
193: .Pp
194: .Fn start
195: starts the subsystem;
196: .Fa config
197: points to a copy of the current (i.e., new) configuration object.
198: .Fn start
199: should return zero if successful, or else -1 with
200: .Va errno
201: set on failure.
202: In the latter case, the
203: .Fn stop
204: method will not be called.
205: .Pp
206: .Fn stop
207: stops the subsystem;
208: .Fa config
209: points to a copy of the current configuration object.
210: .Pp
211: .Fn willrun
212: should return non-zero if the subsystem needs to run at all.
213: .Fa config
214: points to a copy of the current (i.e., new) configuration object.
215: .Pp
216: .Fn changed
217: determines if the subsystem needs to be restarted during a configuration
218: change from
219: .Fa config1
220: to
221: .Fa config2 .
222: It should return 1 if so, zero otherwise.
223: .Pp
224: In the above methods, the configuration object argument(s)
225: become invalid after the method returns.
226: .Pp
227: Alternately, or in conjunction with the
228: .Fn changed
229: method, the
230: .Fa deplist
231: may point to a
232: .Dv NULL
233: terminated list of
234: .Xr structs 3
235: names of fields in the configuration object on which this subsystem depends.
236: The subsystem will automatically be restarted if any of the named fields
237: differ between
238: .Fa config1
239: and
240: .Fa config2 ,
241: as determined by
242: .Xr structs_equal 3 .
243: .Pp
244: All four of the above subsystem methods are optional and may be specified as
245: .Dv NULL .
246: In the case of
247: .Fn startup
248: and
249: .Fn shutdown ,
250: .Dv NULL
251: means "do nothing".
252: .Fn willrun
253: being
254: .Dv NULL
255: is equivalent to it always returning 1.
256: .Fn changed
257: being
258: .Dv NULL
259: is equivalent to it always returning 0.
260: .Fa deplist
261: being
262: .Dv NULL
263: is equivalent to an empty list.
264: .\"
265: .Ss Application
266: .\"
267: An application itself is described by a
268: .Li "struct app_config" :
269: .Pp
270: .Bd -literal -compact -offset 3n
271: struct app_config {
272: u_int version; /* current version # */
273: const struct structs_type **types; /* all version types */
274: const struct app_subsystem **slist; /* list of subsystems */
275: app_config_init_t *init; /* initialize defaults */
276: app_config_getnew_t *getnew; /* generate new config */
277: app_config_checker_t *checker; /* validate a config */
278: app_config_normalize_t *normalize; /* normalize a config */
279: app_config_upgrade_t *upgrade; /* upgrade a config */
280: };
281: .Ed
282: .Pp
283: The list of subsystems supported by the application is pointed to by
284: .Fa slist .
285: This list must be terminated with an entry whose
286: .Fa name
287: is
288: .Dv NULL .
289: .Pp
290: Subsystems are always started in the order they are listed in
291: .Fa slist ,
292: and they are always shutdown in the reverse order.
293: .Pp
294: The
295: .Fa version
296: is the configuration object version number (the first version is zero), and
297: .Fa types
298: points to an array of
299: .Fa "version"
300: + 1 pointers to
301: .Xr structs 3
302: types for the configuration object, where
303: .Li "types[i]"
304: is the
305: .Xr structs 3
306: type for version
307: .Fa i
308: of the configuration object.
309: .Pp
310: The remaining fields are pointers to functions having these types:
311: .Pp
312: .Bd -literal -compact -offset 3n
313: typedef int app_config_init_t(struct app_config_ctx *ctx,
314: void *config);
315: typedef int app_config_getnew_t(struct app_config_ctx *ctx,
316: void *config);
317: typedef int app_config_checker_t(struct app_config_ctx *ctx,
318: const void *config, char *errbuf, size_t ebufsize);
319: typedef void app_config_normalize_t(struct app_config_ctx *ctx,
320: void *config);
321: typedef int app_config_upgrade_t(struct app_config_ctx *ctx,
322: const void *old_conf, u_int old_version,
323: void *new_conf);
324: .Ed
325: .Pp
326: If the default configuration object is not equal to what is provided by
327: .Xr structs_init 3 ,
328: then
329: .Fn init
330: may be implemented.
331: It should further modify the
332: .Fa config
333: as appropriate to get the generic default configuration.
334: .Fn init
335: returns zero on success, or -1 on error with
336: .Va errno
337: set appropriately.
338: .Pp
339: .Fn getnew
340: is invoked when no existing configuration is found by
341: .Fn app_config_load
342: (see below).
343: The
344: .Fa config
345: is as returned by
346: .Fn init .
347: .Fn getnew
348: should apply any further initialization required for this particular system.
349: .Fn getnew
350: returns zero on success, or -1 on error with
351: .Va errno
352: set appropriately.
353: .Pp
354: The distinction between
355: .Fn init
356: and
357: .Fn getnew
358: is somewhat subtle:
359: .Fn init
360: simply initializes a new configuration object.
361: It may be invoked many times during the normal operation of the application
362: as configuration objects are needed.
363: .Fn getnew
364: is only invoked once, at the beginning of application startup, when there
365: is no previously saved configuration found.
366: Therefore, the behavior of
367: .Fn init
368: should not be affected by the "environment", while the behavior of
369: .Fn getnew
370: often is.
371: .Pp
372: .Fn checker
373: determines whether the
374: .Fa config
375: is valid, returning 1 if so or 0 if not.
376: In the latter case, it may print an error message (including '\\0')
377: into the buffer
378: .Fa errbuf ,
379: which has size
380: .Fa ebufsize
381: (see
382: .Xr snprintf 3) .
383: .Pp
384: .Fn normalize
385: gives the application a chance to normalize an otherwise valid
386: configuration object.
387: This is useful when the configuration object contains redundant information,
388: or information that can be represented in more than one way.
389: .Pp
390: All configurations that are applied by
391: .Nm app_config
392: are guaranteed to have been checked and normalized.
393: All configurations passed to
394: .Fn checker
395: are guaranteed to have been passed through
396: .Fn normalize
397: first.
398: .Pp
399: Note: the configurations returned by
400: .Fn init
401: and
402: .Fn getnew
403: must be valid according to
404: .Fa checker .
405: .Pp
406: .Fn upgrade
407: is invoked when an older version of the configuration object is read
408: in from an XML file.
409: The configuration version number is stored as the "version" attribute
410: of the XML document element.
411: .Fa old_conf
412: is the old object, which has version
413: .Fa old_version ,
414: and
415: .Fa new_conf
416: is a newly initialized configuration object of the current version.
417: .Fn upgrade
418: should copy the configuration information from
419: .Fa old_conf
420: to
421: .Fa new_conf .
422: .Pp
423: A quick and dirty way to do this when most of the fields are the same
424: is to use
425: .Xr structs_traverse 3
426: to list the fields in the old configuration object,
427: .Xr structs_get_string 3
428: to get their ASCII values, and
429: .Xr structs_set_string 3
430: to set the same values in the new configuration object.
431: .\"
432: .Ss API
433: .\"
434: .Fn app_config_init
435: should be called at application startup time to initialize
436: .Nm app_config
437: for the application described by
438: .Fa info .
439: A
440: .Xr pevent 3
441: context
442: .Fa ctx
443: must be supplied.
444: .Fn app_config_init
445: returns an application context, with which all configuration and
446: subsystems are associated.
447: Multiple independent application contexts may exist at the same time.
448: The
449: .Fa cookie
450: is saved along with the context but is otherwise ignored.
451: .Pp
452: .Fn app_config_uninit
453: should be called at application shutdown time to release resources
454: allocated by
455: .Nm app_config .
456: It may only be called when all subsystems are shutdown (i.e., the
457: current configuration object pointer is
458: .Dv NULL) .
459: This enables
460: .Fn app_config_init
461: to be called again, if so desired.
462: .Pp
463: Upon return from
464: .Fn app_config_uninit ,
465: .Fa "*ctxp"
466: will be set to
467: .Dv NULL .
468: If
469: .Fa "*ctxp"
470: is already equal to
471: .Dv NULL
472: when
473: .Fn app_config_uninit
474: is invoked, nothing happens.
475: .Pp
476: .Fn app_config_get_cookie
477: retrieves the application cookie provided to
478: .Fn app_config_init .
479: .Pp
480: .Fn app_config_load
481: reads in an application configuration object from the XML file at
482: .Fa path
483: and applies it, making it the current configuration.
484: If
485: .Fa path
486: is empty or non-existent, a new configuration object is created
487: using the application's
488: .Fn getnew
489: method.
490: .Pp
491: If the file contains an old version of the configuration object,
492: it is automatically upgraded to the current version.
493: If
494: .Fa allow_writeback
495: is non-zero, then
496: .Fa path
497: is remembered and the file is updated (i.e., rewritten) every time
498: the application configuration object changes.
499: Updates are done atomically by creating a temporary file with the
500: suffix ".new" and renaming it (see
501: .Xr rename 2) .
502: .Pp
503: In theory, one call to
504: .Fn app_config_load
505: in an application's
506: .Fn main
507: routine is all that is required to get things going.
508: .Pp
509: .Fn app_config_reload
510: reloads the configuration file previously specified to
511: .Fn app_config_load
512: and applies it.
513: This would be the typical response to receiving a
514: .Dv SIGHUP
515: signal.
516: .Pp
517: .Fn app_config_new
518: creates a new configuration object with the application's default values
519: as specified by the application's
520: .Fn init
521: method.
522: The returned pointer should be cast to the appropriate type.
523: The caller is responsible for eventually freeing the returned
524: configuration object by calling
525: .Fn app_config_free .
526: .Pp
527: .Fn app_config_set
528: changes the application's current configuration to be a copy of the
529: configuration pointed to by
530: .Fa config .
531: If this configuration is invalid, -1 is returned with
532: .Va errno
533: set to
534: .Er EINVAL ,
535: and if
536: .Fa ebuf
537: is not
538: .Dv NULL ,
539: the buffer pointed to by
540: .Fa ebuf
541: and having size
542: .Fa emax
543: is filled in with a '\\0'-terminated error message.
544: .Fn app_config_set
545: may also return -1 with
546: .Va errno
547: set to other values in the case of system errors.
548: .Pp
549: If
550: .Fa config
551: is
552: .Dv NULL ,
553: all running subsystems will be shut down.
554: Any configurations passed to
555: .Fn app_config_set
556: subsequent to passing a
557: .Dv NULL
558: configuration, but before the shutdown operation has completed, are ignored.
559: This guarantees that a
560: .Dv NULL
561: configuration does actually shutdown the application.
562: .Pp
563: The new configuration (or shutdown) takes effect after a delay of
564: .Fa delay
565: milliseconds after
566: .Fn app_config_set
567: has successfully returned zero.
568: The appropriate subsystem
569: .Fn stop ,
570: and then
571: .Fn start
572: methods are invoked serially from a new thread.
573: .Pp
574: .Fn app_config_get
575: returns a copy of the current or pending configuration object.
576: The returned pointer should be cast to the appropriate type.
577: If
578: .Fa pending
579: is zero, then the configuration object currently in use is copied.
580: Otherwise, the configuration object most recently applied via
581: .Fn app_config_set
582: is copied.
583: These will be different when there is a pending, but not yet applied,
584: configuration.
585: The caller is responsible for eventually freeing the returned
586: configuration object by calling
587: .Fn app_config_free .
588: .Pp
589: .Fn app_config_get_type
590: returns the
591: .Xr structs 3
592: for the application configuration object.
593: .Pp
594: .Fn app_config_copy
595: copies a configuration object.
596: The returned pointer should be cast to the appropriate type.
597: The caller is responsible for eventually freeing the returned
598: configuration object by calling
599: .Fn app_config_free .
600: .Pp
601: .Fn app_config_free
602: destroys the configuration object pointed to by
603: .Fa "*configp" .
604: Upon return,
605: .Fa "*configp"
606: will be set to
607: .Dv NULL .
608: If
609: .Fa "*configp"
610: is already
611: .Dv NULL
612: when
613: .Fn app_config_free
614: is invoked, nothing happens.
615: .\"
616: .Ss Pre-defined subsystems
617: .\"
618: The
619: .Nm app_config
620: library comes with some predefined subsystem templates.
621: .Pp
622: .Va app_config_alog_subsystem
623: handles configuring error logging for an application.
624: To use
625: .Va app_config_alog_subsystem ,
626: copy the structure and set the
627: .Fa arg
628: field to point to a
629: .Li "struct app_config_alog_info" :
630: .Pp
631: .Bd -literal -compact -offset 3n
632: struct app_config_alog_info {
633: const char *name; /* field name */
634: int channel; /* alog channel */
635: };
636: .Ed
637: .Pp
638: The
639: .Fa name
640: should be the
641: .Xr structs 3
642: field name of the field in the configuration object that configures
643: logging for the
644: .Xr alog 3
645: logging channel
646: .Fa channel .
647: This field should be a
648: .Li "struct alog_config" .
649: .Pp
650: .Va app_config_curconf_subsystem
651: is useful when the application needs efficient access to the currently
652: active configuration.
653: This subsystem assumes that there is a global pointer variable (call it
654: .Va curconf )
655: which by definition always points to a read-only copy of the currently
656: active configuration.
657: For example, if the application's configuration object is a
658: .Li "struct my_config" ,
659: then
660: .Va curconf
661: would be defined as:
662: .Pp
663: .Bd -literal -compact -offset 3n
664: const struct my_config *const curconf;
665: .Ed
666: .Pp
667: Then the function of
668: .Va app_config_curconf_subsystem
669: is to automatically keep this variable up to date.
670: (The
671: .Li "const"
672: keywords reflect the application's point of view:
673: the first is because the structure is read-only, while the second
674: is because the pointer itself is read-only.)
675: .Pp
676: To use
677: .Va app_config_curconf_subsystem ,
678: copy the structure and set the
679: .Fa arg
680: field to point to the application's
681: .Va curconf
682: pointer variable.
683: Typically the
684: .Va app_config_curconf_subsystem
685: will be first in the list of subsystems, so that
686: .Va curconf
687: is always updated before any other subsystem starts.
688: Then at any time
689: .Va "*curconf"
690: can be examined for the currently active configuration.
691: .Pp
692: .Va app_config_directory_subsystem
693: handles configuring the current working directory for the process.
694: To use
695: .Va app_config_directory_subsystem ,
696: copy the structure and set the
697: .Fa arg
698: field to point to a string containing the
699: .Xr structs 3
700: name of the field in the configuration object that contains the
701: directory name.
702: If this name is not the empty string, then the current working directory
703: will be set according to the value of this field.
704: .Pp
705: .Va app_config_pidfile_subsystem
706: handles "PID files", i.e., exclusive application lock files into which
707: the process ID is written.
708: These guard against two instances of the same application running at the
709: same time.
710: To use
711: .Va app_config_pidfile_subsystem ,
712: copy the structure and set the
713: .Fa arg
714: field to point to a string containing the
715: .Xr structs 3
716: name of the field in the configuration object that contains the PID file
717: pathname.
718: .Sh RETURN VALUES
719: All of the
720: .Nm app_config
721: functions return
722: .Dv NULL
723: or -1 to indicate an error and set
724: .Va errno
725: appropriately.
726: .Sh SEE ALSO
727: .Xr alog 3 ,
728: .Xr libpdel 3 ,
729: .Xr pevent 3 ,
730: .Xr structs 3 ,
731: .Xr typed_mem 3
732: .Sh HISTORY
733: The PDEL library was developed at Packet Design, LLC.
734: .Dv "http://www.packetdesign.com/"
735: .Sh AUTHORS
736: .An Archie Cobbs Aq archie@freebsd.org
737: .Sh BUGS
738: There should be explicit support for subsystems that require other
739: subsystems to be running before they may run.
740: As it stands now, such dependencies must be implicitly encoded into the
741: .Fn willrun
742: and
743: .Fn changed
744: methods.
745: Even so, the dependent subsystem cannot detect if the other subsystem
746: fails to start.
747: .Pp
748: Subsystems should be defined more like objects using dynamically
749: allocated structures that can be added and removed from the subsystem
750: list at any time, without having to shutdown and restart the whole
751: application.
752: .Pp
753: It should be possible to start and shutdown subsystems individually.
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>