/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at CDDL.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at CDDL.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "@(#)scfdot.c 1.23 05/10/18 SMI"
/*
* Generate a dot file for the SMF dependency graph on this machine.
*
* We operate in two modes: with and without -L. Without -L, we print nodes
* for each instance and edges for each dependency. Fortunately dot allows
* forward references, so we can do this in one pass. Options are
*
* -s width,height Size, in inches, that the graph should be limited to.
*
* -l legend.ps PostScript file which should be used as the legend.
*
* -x opts Simplify the graph. opts should be a comma-separated
* list of
*
* omit_net_deps Omit most of the dependencies on
* allowable_net_dep().)
*
* consolidate_inetd_svcs Consolidate services which only depend on
*
* consolidate_rpcbind_svcs Consolidate services which only depend on
*
* Other hard-coded graph settings (rankdir, nodesep, margin) were intended
* for a 42" plotter.
*
* -L causes the program to print a dot file for use as a legend. It
* currently consists of eight nodes which demonstrate the color scheme and
* the dependency types. The nodes are enclosed in a box which is labeled
* "legend".
*/
#include <assert.h>
#include <libscf.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
/* Private libscf function */
const char **property);
/*
* We color nodes by FMRI and enabledness. For each class we specify
* a foreground color, which will be the color of the text and the outline,
* and a background color, which will fill the node.
*
* In this scheme we'll color services who's FMRIs begin with "system" orange,
* "network" blue, "milestone" green, and other services light gray. For each
* category we'll color the disabled services a faded shade of their enabled
* counterparts. The foreground of the enabled services will all be black,
* and the backgrounds will be
*/
/*
* To make disabled services appear faded, I took the above colors and halved
* their saturation.
*/
static const struct coloring {
const char *cat;
} category_colors[] = {
};
/* Graph simplification options, for use with getsubopt(). */
static const char * const x_opts[] = {
"omit_net_deps",
"consolidate_inetd_svcs",
"consolidate_rpcbind_svcs",
};
static int omit_net_deps = 0;
static int consolidate_inetd_svcs = 0;
static int consolidate_rpcbind_svcs = 0;
/* Consolidation strings */
static scf_handle_t *h;
/* Scratch libscf objects, to save time. */
static void
{
perror("realloc");
exit(1);
}
}
}
static void
{
exit(1);
}
/*
* Return 1 if inst is enabled, 0 otherwise. Uses g_pg, g_prop, and g_val.
*/
static int
{
uint8_t b;
if (scf_error() != SCF_ERROR_NOT_FOUND)
scfdie();
return (0);
}
if (scf_error() != SCF_ERROR_NOT_FOUND)
scfdie();
return (0);
}
switch (scf_error()) {
case SCF_ERROR_NOT_FOUND:
return (0);
default:
scfdie();
}
}
if (scf_value_get_boolean(g_val, &b) != 0) {
if (scf_error() != SCF_ERROR_TYPE_MISMATCH)
scfdie();
return (0);
}
return (b != 0);
}
/*
* Fill in buf with the restarter of instance i. Uses g_pg, g_prop, and
* g_val.
*/
static void
{
scfdie();
buf[0] = '\0';
if (scf_error() != SCF_ERROR_NOT_FOUND)
scfdie();
return;
}
switch (scf_error()) {
case SCF_ERROR_NOT_FOUND:
return;
default:
scfdie();
}
}
if (scf_error() != SCF_ERROR_TYPE_MISMATCH)
scfdie();
}
}
static void
{
"Usage: %1$s [-s width,height] [-l legend.ps] [-x opts]\n"
" %1$s -L\n", argv0);
if (help) {
const char * const *opt;
"Where opts is a comma-separated list of\n");
}
}
/*
* Make name suitable for dot.
*/
static void
{
if (*name == '-')
*name = '_';
}
}
/*
* Print an edge for a dependency. port should be the name of the dependency
* group.
*/
static void
{
(void) putchar(',');
}
if (weight != 1)
(void) putchar(']');
}
(void) puts(";");
}
/*
* Choose a coloring for the given service. Returns a pointer to an array of
* two string pointers, the first being the text color and the second being
* the fill color.
*/
static const char * const *
{
break;
}
}
/*
* Print a node for a service. dependencies should either be an empty string
* or a string of "<dependency port name> dependency name" strings joined by
* pipes ("|").
*/
static void
const char *dependencies, const char * const *colors)
{
if (dependencies[0] != '\0')
(void) printf("\"%s\" [shape=record,color=\"%s\",style=filled,"
"fillcolor=\"%s\",fontcolor=\"%s\","
else
(void) printf("\"%s\" [shape=record,color=\"%s\",style=filled,"
"fillcolor=\"%s\",fontcolor=\"%s\",label=\"%s\"];\n",
}
/*
* Print some fake service nodes in a box to demonstrate the coloring and the
* dependency types.
*/
static void
{
const char *fmri;
(void) printf("digraph legend {\n"
"node [fontname=\"Helvetica\",fontsize=11];\n"
"ranksep=\"2\";\n"
"rankdir=LR;\n");
(void) printf("\nsubgraph clusterlegend {\n"
"label=\"legend\";\n"
"color=\"black\";\n");
(void) putchar('\n');
choose_color(fmri, 0));
10);
choose_color(fmri, 0));
choose_color(fmri, 0));
"label=\"optional_all\",style=dashed", 1);
choose_color(fmri, 0));
"label=\"exclude_all\",arrowtail=odot", 1);
(void) printf("}\n}\n");
}
/*
* dependents, which would produce a bad graph.)
*/
static int
{
}
/* dependency name accumulator */
static char *allpgs;
static void
{
/* Append sprintf("<%s> %s|", str, str) */
}
/*
* For the given instance, generate a node and the appropriate edges.
*/
static int
{
int enabled;
int ndeps;
int inetd_svc;
int non_rpcbind;
assert(i);
/*
* Node generation: Collect the name, restarter, dependency names, and
* enabled status and call print_service_node(). The dependency names
* will be accumulated in allpgs.
*/
scf_strerror(scf_error()));
return (-1);
}
instname);
ndeps = 0;
allpgs[0] = '\0';
inetd_svc = 0;
if (depname[0] != '\0') {
++ndeps;
add_dep("restarter");
}
} else {
if (scf_error() != SCF_ERROR_NOT_FOUND)
scfdie();
}
SCF_GROUP_DEPENDENCY) != 0)
scfdie();
non_rpcbind = 0;
0) {
if (scf_error() == SCF_ERROR_NOT_FOUND)
continue;
scfdie();
}
++ndeps;
scfdie();
non_rpcbind = 1;
}
return (0);
}
ndeps == 2) {
/*
* dependents.
*/
return (0);
}
}
if (allpgs[0] != '\0')
enabled = is_enabled(i);
/*
* Edges: One for the restarter, if it is not the default (svc.startd)
* (denoted by an empty string), and one for each dependency service.
* Remember that dependency groups can name multiple services, and
* each service can have multiple instances.
*/
if (depname[0] != '\0')
SCF_GROUP_DEPENDENCY) != 0)
scfdie();
for (;;) {
int r;
if (r == 0)
break;
if (r < 0)
scfdie();
scfdie();
/* The grouping will dictate how we draw the edge */
0)
scfdie();
scfdie();
0)
scfdie();
/* ENTITIES holds the FMRIs of the dependencies */
0)
scfdie();
scfdie();
for (;;) {
size_t l;
if (r == 0)
break;
if (r < 0)
scfdie();
max_value_len + 1) < 0)
scfdie();
/*
* This will fail if the dependency is on a file:,
* which is legitimate, but we'll skip.
*
* This function leaves sname & iname pointing into
* depname_copy, which may be modified.
*/
continue;
if (scf_error() != SCF_ERROR_NOT_FOUND)
scfdie();
continue;
}
if (omit_net_deps &&
continue;
opts[0] = '\0';
l = 0;
sizeof (opts));
sizeof (opts));
weight += 2;
weight += 1;
}
if (l >= sizeof (opts)) {
exit(1);
}
if (opts[0] != '\0')
/*
* This is a service dependency. Look up the
* service and output edges connecting that
* service node to each of its instances.
*/
g_svc) != 0)
scfdie();
for (;;) {
int i = 0;
g_inst);
if (r == 0)
break;
if (r < 0)
scfdie();
scfdie();
i = 2;
}
} else {
weight += 2;
weight);
}
}
}
return (0);
}
/*
* If requested, print the legend. Otherwise print some graph settings and
* call process_instance() for each service instance in the repository.
*/
int
{
int r;
char *svcname;
for (;;) {
if (o == -1)
break;
switch (o) {
case 's':
break;
case 'l':
legendfile = optarg;
break;
case 'x':
while (*optarg != '\0') {
char *valp;
int so;
&valp);
switch (so) {
case 0:
omit_net_deps = 1;
break;
case 1:
break;
case 2:
break;
default:
abort();
}
}
break;
case 'L':
print_legend();
return (0);
case '?':
default:
}
}
assert(r >= 0);
(void) printf("digraph scf {\n");
(void) printf("node [shape=box,fontname=\"Helvetica\",fontsize=11];\n");
(void) printf("ranksep=\"2\";\n"
"rankdir=LR;\n"
"margin=1;\n");
if (legendfile != NULL)
/*
* The legend is just a node with the given PostScript as its
* shape. dot will put it on the highest rank. It usually
* particular); avoid that with a sufficiently large margin.
* (See expand.awk .)
*/
(void) printf("\n/* legend */\n"
"legend [shape=epsf,shapefile=\"%s\",label=\"\"];\n",
(void) putchar('\n');
h = scf_handle_create(SCF_VERSION);
if (scf_handle_bind(h) != 0)
scfdie();
scfdie();
scfdie();
allpgs_sz = 100;
inetd_svcs_sz = 100;
rpcbind_svcs_sz = 100;
perror("malloc");
exit(1);
}
inetd_svcs[0] = '\0';
rpcbind_svcs[0] = '\0';
scfdie();
scfdie();
for (;;) {
if (r == 0)
break;
if (r != 1)
scfdie();
scfdie();
scfdie();
/* Otherwise this shows up as an unconnected node. */
continue;
for (;;) {
if (r == 0)
break;
if (r != 1)
scfdie();
(void) fputs("process_instance() failed",
stderr);
exit(1);
}
}
}
if (inetd_svcs[0] != '\0') {
}
if (rpcbind_svcs[0] != '\0') {
"<restarter> restarter | <rpcbind> rpcbind",
}
(void) printf("}\n");
return (0);
}