#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <alloca.h>
#include <errno.h>
#include <fcntl.h>
#include <libscf.h>
#include <priv_utils.h>
#include <netdb.h>
#include <signal.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <zone.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fm/fmd_msg.h>
#include <fm/libfmevent.h>
#include "libfmnotify.h"
#define SENDMAIL "/usr/sbin/sendmail"
#define SVCNAME "system/fm/smtp-notify"
#define XHDR_HOSTNAME "X-FMEV-HOSTNAME"
#define XHDR_CLASS "X-FMEV-CLASS"
#define XHDR_UUID "X-FMEV-UUID"
#define XHDR_MSGID "X-FMEV-CODE"
#define XHDR_SEVERITY "X-FMEV-SEVERITY"
#define XHDR_FMRI "X-FMEV-FMRI"
#define XHDR_FROM_STATE "X-FMEV-FROM-STATE"
#define XHDR_TO_STATE "X-FMEV-TO-STATE"
#define PP_SCRIPT "usr/lib/fm/notify/process_msg_template.sh"
typedef struct email_pref
{
int ep_num_recips;
char **ep_recips;
char *ep_reply_to;
char *ep_template_path;
char *ep_template;
} email_pref_t;
static nd_hdl_t *nhdl;
static char hostname[MAXHOSTNAMELEN + 1];
static const char optstr[] = "dfR:";
static const char DEF_SUBJ_TEMPLATE[] = "smtp-notify-subject-template";
static const char SMF_SUBJ_TEMPLATE[] = "smtp-notify-smf-subject-template";
static const char FM_SUBJ_TEMPLATE[] = "smtp-notify-fm-subject-template";
static const char IREPORT_MSG_TEMPLATE[] = "ireport-msg-template";
static const char SMF_MSG_TEMPLATE[] = "ireport.os.smf-msg-template";
static int
usage(const char *pname)
{
(void) fprintf(stderr, "Usage: %s [-df] [-R <altroot>]\n", pname);
(void) fprintf(stderr,
"\t-d enable debug mode\n"
"\t-f stay in foreground\n"
"\t-R specify alternate root\n");
return (1);
}
static char *
read_template(const char *template)
{
int fd;
struct stat statb;
char *buf;
if (stat(template, &statb) != 0) {
nd_error(nhdl, "Failed to stat %s (%s)", template,
strerror(errno));
return (NULL);
}
if ((fd = open(template, O_RDONLY)) < 0) {
nd_error(nhdl, "Failed to open %s (%s)", template,
strerror(errno));
return (NULL);
}
if ((buf = malloc(statb.st_size + 1)) == NULL) {
nd_error(nhdl, "Failed to allocate %d bytes", statb.st_size);
(void) close(fd);
return (NULL);
}
if (read(fd, buf, statb.st_size) < 0) {
nd_error(nhdl, "Failed to read in template (%s)",
strerror(errno));
free(buf);
(void) close(fd);
return (NULL);
}
buf[statb.st_size] = '\0';
(void) close(fd);
return (buf);
}
static int
process_template(nd_ev_info_t *ev_info, email_pref_t *eprefs)
{
char pp_script[PATH_MAX], tmpfile[PATH_MAX], pp_cli[PATH_MAX];
int ret = -1;
(void) snprintf(pp_script, sizeof (pp_script), "%s%s",
nhdl->nh_rootdir, PP_SCRIPT);
(void) snprintf(tmpfile, sizeof (tmpfile), "%s%s",
nhdl->nh_rootdir, tmpnam(NULL));
(void) sprintf(pp_cli, "%s %s %s %s %s", pp_script,
eprefs->ep_template_path, tmpfile, ev_info->ei_diagcode,
ev_info->ei_severity);
nd_debug(nhdl, "Executing %s", pp_cli);
if (system(pp_cli) != -1)
if ((eprefs->ep_template = read_template(tmpfile)) != NULL)
ret = 0;
(void) unlink(tmpfile);
return (ret);
}
static void
get_svc_config()
{
int s = 0;
uint8_t val;
s = nd_get_boolean_prop(nhdl, SVCNAME, "config", "debug", &val);
nhdl->nh_debug = val;
s += nd_get_astring_prop(nhdl, SVCNAME, "config", "rootdir",
&(nhdl->nh_rootdir));
if (s != 0)
nd_error(nhdl, "Failed to read retrieve service "
"properties\n");
}
static void
nd_sighandler(int sig)
{
if (sig == SIGHUP)
get_svc_config();
else
nd_cleanup(nhdl);
}
static int
build_headers(nd_hdl_t *nhdl, nd_ev_info_t *ev_info, email_pref_t *eprefs,
char **headers)
{
const char *subj_key;
char *subj_fmt, *subj = NULL;
size_t len;
boolean_t is_smf_event = B_FALSE, is_fm_event = B_FALSE;
if (strncmp(ev_info->ei_class, "list.", 5) == 0) {
is_fm_event = B_TRUE;
subj_key = FM_SUBJ_TEMPLATE;
} else if (strncmp(ev_info->ei_class, "ireport.os.smf", 14) == 0) {
is_smf_event = B_TRUE;
subj_key = SMF_SUBJ_TEMPLATE;
} else {
subj_key = DEF_SUBJ_TEMPLATE;
}
if ((subj_fmt = fmd_msg_gettext_key(nhdl->nh_msghdl, NULL,
FMNOTIFY_MSG_DOMAIN, subj_key)) == NULL) {
nd_error(nhdl, "Failed to contruct subject format");
return (-1);
}
if (is_fm_event) {
len = snprintf(NULL, 0, subj_fmt, hostname,
ev_info->ei_diagcode);
subj = alloca(len + 1);
(void) snprintf(subj, len + 1, subj_fmt, hostname,
ev_info->ei_diagcode);
} else if (is_smf_event) {
len = snprintf(NULL, 0, subj_fmt, hostname, ev_info->ei_fmri,
ev_info->ei_from_state, ev_info->ei_to_state);
subj = alloca(len + 1);
(void) snprintf(subj, len + 1, subj_fmt, hostname,
ev_info->ei_fmri, ev_info->ei_from_state,
ev_info->ei_to_state);
} else {
len = snprintf(NULL, 0, subj_fmt, hostname);
subj = alloca(len + 1);
(void) snprintf(subj, len + 1, subj_fmt, hostname);
}
if (is_fm_event) {
len = snprintf(NULL, 0, "%s: %s\n%s: %s\n%s: %s\n%s: %s\n"
"%s: %s\nReply-To: %s\nSubject: %s\n\n", XHDR_HOSTNAME,
hostname, XHDR_CLASS, ev_info->ei_class, XHDR_UUID,
ev_info->ei_uuid, XHDR_MSGID, ev_info->ei_diagcode,
XHDR_SEVERITY, ev_info->ei_severity, eprefs->ep_reply_to,
subj);
*headers = calloc(len + 1, sizeof (char));
(void) snprintf(*headers, len + 1, "%s: %s\n%s: %s\n%s: %s\n"
"%s: %s\n%s: %s\nReply-To: %s\nSubject: %s\n\n",
XHDR_HOSTNAME, hostname, XHDR_CLASS, ev_info->ei_class,
XHDR_UUID, ev_info->ei_uuid, XHDR_MSGID,
ev_info->ei_diagcode, XHDR_SEVERITY, ev_info->ei_severity,
eprefs->ep_reply_to, subj);
} else if (is_smf_event) {
len = snprintf(NULL, 0, "%s: %s\n%s: %s\n%s: %s\n%s: %s\n"
"%s: %s\n%s: %s\n%s: %s\nReply-To: %s\n"
"Subject: %s\n\n", XHDR_HOSTNAME, hostname, XHDR_CLASS,
ev_info->ei_class, XHDR_MSGID, ev_info->ei_diagcode,
XHDR_SEVERITY, ev_info->ei_severity, XHDR_FMRI,
ev_info->ei_fmri, XHDR_FROM_STATE, ev_info->ei_from_state,
XHDR_TO_STATE, ev_info->ei_to_state, eprefs->ep_reply_to,
subj);
*headers = calloc(len + 1, sizeof (char));
(void) snprintf(*headers, len + 1, "%s: %s\n%s: %s\n%s: %s\n"
"%s: %s\n%s: %s\n%s: %s\n%s: %s\nReply-To: %s\n"
"Subject: %s\n\n", XHDR_HOSTNAME, hostname, XHDR_CLASS,
ev_info->ei_class, XHDR_MSGID, ev_info->ei_diagcode,
XHDR_SEVERITY, ev_info->ei_severity, XHDR_FMRI,
ev_info->ei_fmri, XHDR_FROM_STATE, ev_info->ei_from_state,
XHDR_TO_STATE, ev_info->ei_to_state, eprefs->ep_reply_to,
subj);
} else {
len = snprintf(NULL, 0, "%s: %s\n%s: %s\n%s: %s\n%s: %s\n"
"Reply-To: %s\nSubject: %s\n\n", XHDR_HOSTNAME,
hostname, XHDR_CLASS, ev_info->ei_class, XHDR_MSGID,
ev_info->ei_diagcode, XHDR_SEVERITY, ev_info->ei_severity,
eprefs->ep_reply_to, subj);
*headers = calloc(len + 1, sizeof (char));
(void) snprintf(*headers, len + 1, "%s: %s\n%s: %s\n%s: %s\n"
"%s: %s\nReply-To: %s\nSubject: %s\n\n",
XHDR_HOSTNAME, hostname, XHDR_CLASS, ev_info->ei_class,
XHDR_MSGID, ev_info->ei_diagcode, XHDR_SEVERITY,
ev_info->ei_severity, eprefs->ep_reply_to, subj);
}
return (0);
}
static void
send_email(nd_hdl_t *nhdl, const char *headers, const char *body,
const char *recip)
{
FILE *mp;
char sm_cli[PATH_MAX];
(void) snprintf(sm_cli, PATH_MAX, "%s -t %s", SENDMAIL, recip);
nd_debug(nhdl, "Sending email notification to %s", recip);
if ((mp = popen(sm_cli, "w")) == NULL) {
nd_error(nhdl, "Failed to open pipe to %s (%s)", SENDMAIL,
strerror(errno));
return;
}
if (fprintf(mp, "%s", headers) < 0)
nd_error(nhdl, "Failed to write to pipe (%s)", strerror(errno));
if (fprintf(mp, "%s\n.\n", body) < 0)
nd_error(nhdl, "Failed to write to pipe (%s)",
strerror(errno));
(void) pclose(mp);
}
static void
send_email_template(nd_hdl_t *nhdl, nd_ev_info_t *ev_info, email_pref_t *eprefs)
{
char *msg, *headers;
if (build_headers(nhdl, ev_info, eprefs, &headers) != 0)
return;
if ((msg = fmd_msg_decode_tokens(ev_info->ei_payload,
eprefs->ep_template, ev_info->ei_url)) == NULL) {
nd_error(nhdl, "Failed to parse msg template");
free(headers);
return;
}
for (int i = 0; i < eprefs->ep_num_recips; i++)
send_email(nhdl, headers, msg, eprefs->ep_recips[i]);
free(msg);
free(headers);
}
static int
get_email_prefs(nd_hdl_t *nhdl, fmev_t ev, email_pref_t **eprefs)
{
nvlist_t **p_nvl = NULL;
email_pref_t *ep;
uint_t npref, tn1 = 0, tn2 = 0;
char **tmparr1, **tmparr2;
int r, ret = -1;
r = nd_get_notify_prefs(nhdl, "smtp", ev, &p_nvl, &npref);
if (r == SCF_ERROR_NOT_FOUND) {
return (-1);
} else if (r != 0) {
nd_error(nhdl, "Failed to retrieve notification preferences "
"for this event");
return (-1);
}
if ((ep = malloc(sizeof (email_pref_t))) == NULL) {
nd_error(nhdl, "Failed to allocate space for email preferences "
"(%s)", strerror(errno));
goto eprefs_done;
}
(void) memset(ep, 0, sizeof (email_pref_t));
if (npref == 2) {
boolean_t *act1, *act2;
char **arr1, **arr2, **strarr, **reparr1, **reparr2;
uint_t n1, n2, arrsz, repsz;
r = nvlist_lookup_boolean_array(p_nvl[0], "active", &act1, &n1);
r += nvlist_lookup_boolean_array(p_nvl[1], "active", &act2,
&n2);
r += nvlist_lookup_string_array(p_nvl[0], "to", &arr1, &n1);
r += nvlist_lookup_string_array(p_nvl[1], "to", &arr2, &n2);
if (r != 0) {
nd_error(nhdl, "Malformed email notification "
"preferences");
nd_dump_nvlist(nhdl, p_nvl[0]);
nd_dump_nvlist(nhdl, p_nvl[1]);
goto eprefs_done;
} else if (!act1[0] && !act2[0]) {
nd_debug(nhdl, "Email notification is disabled");
goto eprefs_done;
}
if (nd_split_list(nhdl, arr1[0], ",", &tmparr1, &tn1) != 0 ||
nd_split_list(nhdl, arr2[0], ",", &tmparr2, &tn2) != 0) {
nd_error(nhdl, "Error parsing \"to\" lists");
nd_dump_nvlist(nhdl, p_nvl[0]);
nd_dump_nvlist(nhdl, p_nvl[1]);
goto eprefs_done;
}
if ((ep->ep_num_recips = nd_merge_strarray(nhdl, tmparr1, tn1,
tmparr2, tn2, &ep->ep_recips)) < 0) {
nd_error(nhdl, "Error merging email recipient lists");
goto eprefs_done;
}
r = nvlist_lookup_string_array(p_nvl[0], "reply-to", &arr1,
&n1);
r += nvlist_lookup_string_array(p_nvl[1], "reply-to", &arr2,
&n2);
repsz = n1 = n2 = 0;
if (!r &&
nd_split_list(nhdl, arr1[0], ",", &reparr1, &n1) != 0 ||
nd_split_list(nhdl, arr2[0], ",", &reparr2, &n2) != 0 ||
(repsz = nd_merge_strarray(nhdl, tmparr1, n1, tmparr2, n2,
&strarr)) != 0 ||
nd_join_strarray(nhdl, strarr, repsz, &ep->ep_reply_to)
!= 0) {
ep->ep_reply_to = strdup("root@localhost");
}
if (n1)
nd_free_strarray(reparr1, n1);
if (n2)
nd_free_strarray(reparr2, n2);
if (repsz > 0)
nd_free_strarray(strarr, repsz);
if (nvlist_lookup_string_array(p_nvl[0], "msg_template",
&strarr, &arrsz) == 0)
ep->ep_template_path = strdup(strarr[0]);
} else {
char **strarr, **tmparr;
uint_t arrsz;
boolean_t *active;
r = nvlist_lookup_boolean_array(p_nvl[0], "active", &active,
&arrsz);
r += nvlist_lookup_string_array(p_nvl[0], "to", &strarr,
&arrsz);
if (r != 0) {
nd_error(nhdl, "Malformed email notification "
"preferences");
nd_dump_nvlist(nhdl, p_nvl[0]);
goto eprefs_done;
} else if (!active[0]) {
nd_debug(nhdl, "Email notification is disabled");
goto eprefs_done;
}
if (nd_split_list(nhdl, strarr[0], ",", &tmparr, &arrsz)
!= 0) {
nd_error(nhdl, "Error parsing \"to\" list");
goto eprefs_done;
}
ep->ep_num_recips = arrsz;
ep->ep_recips = tmparr;
if (nvlist_lookup_string_array(p_nvl[0], "msg_template",
&strarr, &arrsz) == 0)
ep->ep_template_path = strdup(strarr[0]);
if (nvlist_lookup_string_array(p_nvl[0], "reply-to", &strarr,
&arrsz) == 0)
ep->ep_reply_to = strdup(strarr[0]);
else
ep->ep_reply_to = strdup("root@localhost");
}
ret = 0;
*eprefs = ep;
eprefs_done:
if (ret != 0) {
if (ep->ep_recips)
nd_free_strarray(ep->ep_recips, ep->ep_num_recips);
if (ep->ep_reply_to)
free(ep->ep_reply_to);
free(ep);
}
if (tn1)
nd_free_strarray(tmparr1, tn1);
if (tn2)
nd_free_strarray(tmparr2, tn2);
nd_free_nvlarray(p_nvl, npref);
return (ret);
}
static void
irpt_cbfunc(fmev_t ev, const char *class, nvlist_t *nvl, void *arg)
{
char *body_fmt, *headers = NULL, *body = NULL, tstamp[32];
struct tm ts;
size_t len;
nd_ev_info_t *ev_info = NULL;
email_pref_t *eprefs;
nd_debug(nhdl, "Received event of class %s", class);
if (get_email_prefs(nhdl, ev, &eprefs) < 0)
return;
if (nd_get_event_info(nhdl, class, ev, &ev_info) != 0)
goto irpt_done;
if (eprefs->ep_template != NULL)
free(eprefs->ep_template);
if (eprefs->ep_template_path != NULL &&
process_template(ev_info, eprefs) == 0) {
send_email_template(nhdl, ev_info, eprefs);
goto irpt_done;
}
if (fmev_localtime(ev, &ts) == NULL) {
nd_error(nhdl, "Malformed event: failed to retrieve "
"timestamp");
goto irpt_done;
}
(void) strftime(tstamp, sizeof (tstamp), NULL, &ts);
if (strncmp(class, "ireport.os.smf", 14) == 0) {
if ((body_fmt = fmd_msg_gettext_key(nhdl->nh_msghdl, NULL,
FMNOTIFY_MSG_DOMAIN, SMF_MSG_TEMPLATE)) == NULL) {
nd_error(nhdl, "Failed to format message body");
goto irpt_done;
}
len = snprintf(NULL, 0, body_fmt, hostname, tstamp,
ev_info->ei_fmri, ev_info->ei_from_state,
ev_info->ei_to_state, ev_info->ei_descr,
ev_info->ei_reason);
body = calloc(len, sizeof (char));
(void) snprintf(body, len, body_fmt, hostname, tstamp,
ev_info->ei_fmri, ev_info->ei_from_state,
ev_info->ei_to_state, ev_info->ei_descr,
ev_info->ei_reason);
} else {
if ((body_fmt = fmd_msg_gettext_key(nhdl->nh_msghdl, NULL,
FMNOTIFY_MSG_DOMAIN, IREPORT_MSG_TEMPLATE)) == NULL) {
nd_error(nhdl, "Failed to format message body");
goto irpt_done;
}
len = snprintf(NULL, 0, body_fmt, hostname, tstamp, class);
body = calloc(len, sizeof (char));
(void) snprintf(body, len, body_fmt, hostname, tstamp, class);
}
if (build_headers(nhdl, ev_info, eprefs, &headers) != 0)
goto irpt_done;
for (int i = 0; i < eprefs->ep_num_recips; i++)
send_email(nhdl, headers, body, eprefs->ep_recips[i]);
irpt_done:
free(headers);
free(body);
if (ev_info)
nd_free_event_info(ev_info);
if (eprefs->ep_recips)
nd_free_strarray(eprefs->ep_recips, eprefs->ep_num_recips);
if (eprefs->ep_reply_to)
free(eprefs->ep_reply_to);
free(eprefs);
}
static void
postprocess_msg(char *msg)
{
int i = 0, j = 0;
char *buf;
if ((buf = malloc(strlen(msg) + 1)) == NULL)
return;
buf[j++] = msg[i++];
for (i = 1; i < strlen(msg); i++) {
if (!(msg[i] == '\n' && msg[i - 1] == '\n'))
buf[j++] = msg[i];
}
buf[j] = '\0';
(void) strncpy(msg, buf, j+1);
free(buf);
}
static void
listev_cb(fmev_t ev, const char *class, nvlist_t *nvl, void *arg)
{
char *body = NULL, *headers = NULL;
nd_ev_info_t *ev_info = NULL;
boolean_t domsg;
email_pref_t *eprefs;
nd_debug(nhdl, "Received event of class %s", class);
if (get_email_prefs(nhdl, ev, &eprefs) < 0)
return;
if (nd_get_event_info(nhdl, class, ev, &ev_info) != 0)
goto listcb_done;
if (nvlist_lookup_boolean_value(ev_info->ei_payload, FM_SUSPECT_MESSAGE,
&domsg) == 0 && !domsg) {
nd_debug(nhdl, "Messaging suppressed for this event");
goto listcb_done;
}
if (eprefs->ep_template != NULL)
free(eprefs->ep_template);
if (eprefs->ep_template_path != NULL &&
process_template(ev_info, eprefs) == 0) {
send_email_template(nhdl, ev_info, eprefs);
goto listcb_done;
}
if ((body = fmd_msg_gettext_nv(nhdl->nh_msghdl, NULL,
ev_info->ei_payload)) == NULL) {
nd_error(nhdl, "Failed to format message body");
nd_dump_nvlist(nhdl, ev_info->ei_payload);
goto listcb_done;
}
postprocess_msg(body);
if (build_headers(nhdl, ev_info, eprefs, &headers) != 0)
goto listcb_done;
for (int i = 0; i < eprefs->ep_num_recips; i++)
send_email(nhdl, headers, body, eprefs->ep_recips[i]);
listcb_done:
free(headers);
free(body);
if (ev_info)
nd_free_event_info(ev_info);
if (eprefs->ep_recips)
nd_free_strarray(eprefs->ep_recips, eprefs->ep_num_recips);
if (eprefs->ep_reply_to)
free(eprefs->ep_reply_to);
free(eprefs);
}
int
main(int argc, char *argv[])
{
struct rlimit rlim;
struct sigaction act;
sigset_t set;
int c;
boolean_t run_fg = B_FALSE;
if ((nhdl = malloc(sizeof (nd_hdl_t))) == NULL) {
(void) fprintf(stderr, "Failed to allocate space for notifyd "
"handle (%s)", strerror(errno));
return (1);
}
(void) memset(nhdl, 0, sizeof (nd_hdl_t));
nhdl->nh_keep_running = B_TRUE;
nhdl->nh_log_fd = stderr;
nhdl->nh_pname = argv[0];
get_svc_config();
while (optind < argc) {
while ((c = getopt(argc, argv, optstr)) != -1) {
switch (c) {
case 'd':
nhdl->nh_debug = B_TRUE;
break;
case 'f':
run_fg = B_TRUE;
break;
case 'R':
nhdl->nh_rootdir = strdup(optarg);
break;
default:
free(nhdl);
return (usage(argv[0]));
}
}
}
(void) sigfillset(&set);
(void) sigfillset(&act.sa_mask);
act.sa_handler = nd_sighandler;
act.sa_flags = 0;
(void) sigaction(SIGTERM, &act, NULL);
(void) sigdelset(&set, SIGTERM);
(void) sigaction(SIGHUP, &act, NULL);
(void) sigdelset(&set, SIGHUP);
if (run_fg) {
(void) sigaction(SIGINT, &act, NULL);
(void) sigdelset(&set, SIGINT);
} else
nd_daemonize(nhdl);
rlim.rlim_cur = RLIM_INFINITY;
rlim.rlim_max = RLIM_INFINITY;
(void) setrlimit(RLIMIT_CORE, &rlim);
nhdl->nh_evhdl = fmev_shdl_init(LIBFMEVENT_VERSION_2, NULL, NULL, NULL);
if (nhdl->nh_evhdl == NULL) {
(void) sleep(5);
nd_abort(nhdl, "failed to initialize libfmevent: %s",
fmev_strerror(fmev_errno));
}
if (getzoneid() == GLOBAL_ZONEID)
if (__init_daemon_priv(
PU_RESETGROUPS | PU_LIMITPRIVS | PU_INHERITPRIVS,
60002, 60002, PRIV_PROC_SETID, NULL) != 0)
nd_abort(nhdl, "additional privileges required to run");
nhdl->nh_msghdl = fmd_msg_init(nhdl->nh_rootdir, FMD_MSG_VERSION);
if (nhdl->nh_msghdl == NULL)
nd_abort(nhdl, "failed to initialize libfmd_msg");
(void) gethostname(hostname, MAXHOSTNAMELEN + 1);
nd_debug(nhdl, "Subscribing to ireport.* events");
if (fmev_shdl_subscribe(nhdl->nh_evhdl, "ireport.*", irpt_cbfunc,
NULL) != FMEV_SUCCESS) {
nd_abort(nhdl, "fmev_shdl_subscribe failed: %s",
fmev_strerror(fmev_errno));
}
nd_debug(nhdl, "Subscribing to list.* events");
if (fmev_shdl_subscribe(nhdl->nh_evhdl, "list.*", listev_cb,
NULL) != FMEV_SUCCESS) {
nd_abort(nhdl, "fmev_shdl_subscribe failed: %s",
fmev_strerror(fmev_errno));
}
while (nhdl->nh_keep_running)
(void) sigsuspend(&set);
free(nhdl->nh_rootdir);
free(nhdl);
return (0);
}