| #include <net-snmp/net-snmp-config.h> |
| #include <net-snmp/net-snmp-features.h> |
| |
| #if HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| #if HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| #if HAVE_FCNTL_H |
| #include <fcntl.h> |
| #endif |
| #if TIME_WITH_SYS_TIME |
| # include <sys/time.h> |
| # include <time.h> |
| #else |
| # if HAVE_SYS_TIME_H |
| # include <sys/time.h> |
| # else |
| # include <time.h> |
| # endif |
| #endif |
| #include <signal.h> |
| #if HAVE_MACHINE_PARAM_H |
| #include <machine/param.h> |
| #endif |
| #if HAVE_SYS_PARAM_H |
| #include <sys/param.h> |
| #endif |
| #if HAVE_SYS_VMMETER_H |
| #if !(defined(bsdi2) || defined(netbsd1)) |
| #include <sys/vmmeter.h> |
| #endif |
| #endif |
| #if HAVE_SYS_CONF_H |
| #include <sys/conf.h> |
| #endif |
| #if HAVE_ASM_PAGE_H |
| #include <asm/page.h> |
| #endif |
| #if HAVE_SYS_SWAP_H |
| #include <sys/swap.h> |
| #endif |
| #if HAVE_SYS_FS_H |
| #include <sys/fs.h> |
| #else |
| #if HAVE_UFS_FS_H |
| #include <ufs/fs.h> |
| #else |
| #if HAVE_UFS_UFS_DINODE_H |
| #include <ufs/ufs/dinode.h> |
| #endif |
| #if HAVE_UFS_FFS_FS_H |
| #include <ufs/ffs/fs.h> |
| #endif |
| #endif |
| #endif |
| #if HAVE_MTAB_H |
| #include <mtab.h> |
| #endif |
| #include <sys/stat.h> |
| #include <errno.h> |
| #if HAVE_FSTAB_H |
| #include <fstab.h> |
| #endif |
| #if HAVE_SYS_STATFS_H |
| #include <sys/statfs.h> |
| #endif |
| #if HAVE_SYS_STATVFS_H |
| #include <sys/statvfs.h> |
| #endif |
| #if HAVE_SYS_VFS_H |
| #include <sys/vfs.h> |
| #endif |
| #if (!defined(HAVE_STATVFS)) && defined(HAVE_STATFS) |
| #if HAVE_SYS_MOUNT_H |
| #include <sys/mount.h> |
| #endif |
| #if HAVE_SYS_SYSCTL_H |
| #include <sys/sysctl.h> |
| #endif |
| #define statvfs statfs |
| #endif |
| #if HAVE_VM_VM_H |
| #include <vm/vm.h> |
| #endif |
| #if HAVE_VM_SWAP_PAGER_H |
| #include <vm/swap_pager.h> |
| #endif |
| #if HAVE_SYS_FIXPOINT_H |
| #include <sys/fixpoint.h> |
| #endif |
| #if HAVE_MALLOC_H |
| #include <malloc.h> |
| #endif |
| #if HAVE_STRING_H |
| #include <string.h> |
| #endif |
| #include <ctype.h> |
| |
| #include <net-snmp/net-snmp-includes.h> |
| #include <net-snmp/agent/net-snmp-agent-includes.h> |
| #include <net-snmp/agent/auto_nlist.h> |
| #include <net-snmp/agent/agent_callbacks.h> |
| #include <net-snmp/library/system.h> |
| |
| #include "struct.h" |
| #include "extensible.h" |
| #include "mibgroup/util_funcs.h" |
| #include "utilities/execute.h" |
| #include "util_funcs/header_simple_table.h" |
| |
| netsnmp_feature_require(get_exten_instance) |
| netsnmp_feature_require(parse_miboid) |
| |
| extern struct myproc *procwatch; /* moved to proc.c */ |
| extern int numprocs; /* ditto */ |
| extern struct extensible *extens; /* In exec.c */ |
| extern struct extensible *relocs; /* In exec.c */ |
| extern int numextens; /* ditto */ |
| extern int numrelocs; /* ditto */ |
| extern struct extensible *passthrus; /* In pass.c */ |
| extern int numpassthrus; /* ditto */ |
| extern netsnmp_subtree *subtrees; |
| extern struct variable2 extensible_relocatable_variables[]; |
| extern struct variable2 extensible_passthru_variables[]; |
| |
| /* |
| * the relocatable extensible commands variables |
| */ |
| struct variable2 extensible_relocatable_variables[] = { |
| {MIBINDEX, ASN_INTEGER, NETSNMP_OLDAPI_RONLY, |
| var_extensible_relocatable, 1, {MIBINDEX}}, |
| {ERRORNAME, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY, |
| var_extensible_relocatable, 1, {ERRORNAME}}, |
| {SHELLCOMMAND, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY, |
| var_extensible_relocatable, 1, {SHELLCOMMAND}}, |
| {ERRORFLAG, ASN_INTEGER, NETSNMP_OLDAPI_RONLY, |
| var_extensible_relocatable, 1, {ERRORFLAG}}, |
| {ERRORMSG, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY, |
| var_extensible_relocatable, 1, {ERRORMSG}}, |
| {ERRORFIX, ASN_INTEGER, NETSNMP_OLDAPI_RWRITE, |
| var_extensible_relocatable, 1, {ERRORFIX}}, |
| {ERRORFIXCMD, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY, |
| var_extensible_relocatable, 1, {ERRORFIXCMD}} |
| }; |
| |
| |
| void |
| init_extensible(void) |
| { |
| |
| struct variable2 extensible_extensible_variables[] = { |
| {MIBINDEX, ASN_INTEGER, NETSNMP_OLDAPI_RONLY, |
| var_extensible_shell, 1, {MIBINDEX}}, |
| {ERRORNAME, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY, |
| var_extensible_shell, 1, {ERRORNAME}}, |
| {SHELLCOMMAND, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY, |
| var_extensible_shell, 1, {SHELLCOMMAND}}, |
| {ERRORFLAG, ASN_INTEGER, NETSNMP_OLDAPI_RONLY, |
| var_extensible_shell, 1, {ERRORFLAG}}, |
| {ERRORMSG, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY, |
| var_extensible_shell, 1, {ERRORMSG}}, |
| {ERRORFIX, ASN_INTEGER, NETSNMP_OLDAPI_RWRITE, |
| var_extensible_shell, 1, {ERRORFIX}}, |
| {ERRORFIXCMD, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY, |
| var_extensible_shell, 1, {ERRORFIXCMD}} |
| }; |
| |
| /* |
| * Define the OID pointer to the top of the mib tree that we're |
| * registering underneath |
| */ |
| oid extensible_variables_oid[] = |
| { NETSNMP_UCDAVIS_MIB, NETSNMP_SHELLMIBNUM, 1 }; |
| |
| /* |
| * register ourselves with the agent to handle our mib tree |
| */ |
| REGISTER_MIB("ucd-snmp/extensible", extensible_extensible_variables, |
| variable2, extensible_variables_oid); |
| |
| snmpd_register_config_handler("exec", extensible_parse_config, |
| extensible_free_config, |
| "[miboid] name program arguments"); |
| snmpd_register_config_handler("sh", extensible_parse_config, |
| extensible_free_config, |
| "[miboid] name program-or-script arguments"); |
| snmpd_register_config_handler("execfix", execfix_parse_config, NULL, |
| "exec-or-sh-name program [arguments...]"); |
| snmp_register_callback(SNMP_CALLBACK_APPLICATION, |
| SNMPD_CALLBACK_PRE_UPDATE_CONFIG, |
| extensible_unregister, NULL); |
| } |
| |
| extern int pass_compare(const void *a, const void *b); |
| |
| void |
| extensible_parse_config(const char *token, char *cptr) |
| { |
| struct extensible *ptmp, **pp; |
| char *tcptr; |
| int scount; |
| |
| /* |
| * allocate and clear memory structure |
| */ |
| ptmp = (struct extensible *) calloc(1, sizeof(struct extensible)); |
| if (ptmp == NULL) |
| return; /* XXX memory alloc error */ |
| |
| if (*cptr == '.') |
| cptr++; |
| if (isdigit((unsigned char) *cptr)) { |
| /* |
| * its a relocatable extensible mib |
| */ |
| config_perror("WARNING: This output format is not valid, and is only retained for backward compatibility - Please consider using the 'extend' directive instead" ); |
| for (pp = &relocs, numrelocs++; *pp; pp = &((*pp)->next)); |
| (*pp) = ptmp; |
| pp = &relocs; scount = numrelocs; |
| |
| } else { |
| /* |
| * it goes in with the general extensible table |
| */ |
| for (pp = &extens, numextens++; *pp; pp = &((*pp)->next)); |
| (*pp) = ptmp; |
| pp = &extens; scount = numextens; |
| } |
| |
| /* |
| * the rest is pretty much handled the same |
| */ |
| if (!strncasecmp(token, "sh", 2)) |
| ptmp->type = SHPROC; |
| else |
| ptmp->type = EXECPROC; |
| if (isdigit((unsigned char) *cptr)) { |
| ptmp->miblen = parse_miboid(cptr, ptmp->miboid); |
| while (isdigit((unsigned char) *cptr) || *cptr == '.') |
| cptr++; |
| } |
| |
| /* |
| * name |
| */ |
| cptr = skip_white(cptr); |
| copy_nword(cptr, ptmp->name, sizeof(ptmp->name)); |
| cptr = skip_not_white(cptr); |
| cptr = skip_white(cptr); |
| /* |
| * command |
| */ |
| if (cptr == NULL) { |
| config_perror("No command specified on line"); |
| } else { |
| /* |
| * Support multi-element commands in shell configuration |
| * lines, but truncate after the first command for 'exec' |
| */ |
| for (tcptr = cptr; *tcptr != 0 && *tcptr != '#'; tcptr++) |
| if (*tcptr == ';' && ptmp->type == EXECPROC) |
| break; |
| sprintf(ptmp->command, "%.*s", (int) (tcptr - cptr), cptr); |
| } |
| #ifdef NETSNMP_EXECFIXCMD |
| sprintf(ptmp->fixcmd, NETSNMP_EXECFIXCMD, ptmp->name); |
| #endif |
| if (ptmp->miblen > 0) { |
| /* |
| * For relocatable "exec" entries, |
| * register the new (not-strictly-valid) MIB subtree... |
| */ |
| register_mib(token, |
| (struct variable *) extensible_relocatable_variables, |
| sizeof(struct variable2), |
| sizeof(extensible_relocatable_variables) / |
| sizeof(*extensible_relocatable_variables), |
| ptmp->miboid, ptmp->miblen); |
| |
| /* |
| * ... and ensure the entries are sorted by OID. |
| * This isn't needed for entries in the main extTable (which |
| * don't have MIB OIDs explicitly associated with them anyway) |
| */ |
| if (scount > 1 && pp != &extens) { |
| int i; |
| struct extensible **etmp = (struct extensible **) |
| malloc(((sizeof(struct extensible *)) * scount)); |
| if (etmp == NULL) |
| return; /* XXX memory alloc error */ |
| for (i = 0, ptmp = *pp; |
| i < scount && ptmp != 0; i++, ptmp = ptmp->next) |
| etmp[i] = ptmp; |
| qsort(etmp, scount, sizeof(struct extensible *), |
| pass_compare); |
| *pp = (struct extensible *) etmp[0]; |
| ptmp = (struct extensible *) etmp[0]; |
| |
| for (i = 0; i < scount - 1; i++) { |
| ptmp->next = etmp[i + 1]; |
| ptmp = ptmp->next; |
| } |
| ptmp->next = NULL; |
| free(etmp); |
| } |
| } |
| } |
| |
| int |
| extensible_unregister(int major, int minor, |
| void *serverarg, void *clientarg) |
| { |
| extensible_free_config(); |
| return 0; |
| } |
| |
| void |
| extensible_free_config(void) |
| { |
| struct extensible *etmp, *etmp2; |
| oid tname[MAX_OID_LEN]; |
| int i; |
| |
| for (etmp = extens; etmp != NULL;) { |
| etmp2 = etmp; |
| etmp = etmp->next; |
| free(etmp2); |
| } |
| |
| for (etmp = relocs; etmp != NULL;) { |
| etmp2 = etmp; |
| etmp = etmp->next; |
| |
| /* |
| * The new modular API results in the column |
| * objects being registered individually, so |
| * they need to be unregistered individually too! |
| */ |
| memset(tname, 0, MAX_OID_LEN*sizeof(oid)); |
| memcpy(tname, etmp2->miboid, etmp2->miblen*sizeof(oid)); |
| for (i=1; i<4; i++) { |
| tname[etmp2->miblen] = i; |
| unregister_mib(tname, etmp2->miblen+1); |
| } |
| for (i=100; i<=103; i++) { |
| tname[etmp2->miblen] = i; |
| unregister_mib(tname, etmp2->miblen+1); |
| } |
| free(etmp2); |
| } |
| |
| relocs = NULL; |
| extens = NULL; |
| numextens = 0; |
| numrelocs = 0; |
| } |
| |
| |
| #define MAXMSGLINES 1000 |
| |
| struct extensible *extens = NULL; /* In exec.c */ |
| struct extensible *relocs = NULL; /* In exec.c */ |
| int numextens = 0, numrelocs = 0; /* ditto */ |
| |
| |
| /* |
| * var_extensible_shell(... |
| * Arguments: |
| * vp IN - pointer to variable entry that points here |
| * name IN/OUT - IN/name requested, OUT/name found |
| * length IN/OUT - length of IN/OUT oid's |
| * exact IN - TRUE if an exact match was requested |
| * var_len OUT - length of variable or 0 if function returned |
| * write_method |
| * |
| */ |
| |
| /* |
| * find a give entry in the linked list associated with a proc name |
| */ |
| struct extensible * |
| get_exec_by_name(char *name) |
| { |
| struct extensible *etmp; |
| |
| if (name == NULL) |
| return NULL; |
| |
| for (etmp = extens; etmp != NULL && strcmp(etmp->name, name) != 0; |
| etmp = etmp->next); |
| |
| if(NULL == etmp) |
| for (etmp = relocs; etmp != NULL && strcmp(etmp->name, name) != 0; |
| etmp = etmp->next); |
| |
| return etmp; |
| } |
| |
| void |
| execfix_parse_config(const char *token, char *cptr) |
| { |
| char tmpname[STRMAX]; |
| struct extensible *execp; |
| |
| /* |
| * don't allow two entries with the same name |
| */ |
| cptr = copy_nword(cptr, tmpname, sizeof(tmpname)); |
| if ((execp = get_exec_by_name(tmpname)) == NULL) { |
| config_perror("No exec entry registered for this exec name yet."); |
| return; |
| } |
| |
| if (strlen(cptr) > sizeof(execp->fixcmd)) { |
| config_perror("fix command too long."); |
| return; |
| } |
| |
| strlcpy(execp->fixcmd, cptr, sizeof(execp->fixcmd)); |
| } |
| |
| u_char * |
| var_extensible_shell(struct variable * vp, |
| oid * name, |
| size_t * length, |
| int exact, |
| size_t * var_len, WriteMethod ** write_method) |
| { |
| |
| static struct extensible *exten = 0; |
| static long long_ret; |
| int len; |
| |
| if (header_simple_table |
| (vp, name, length, exact, var_len, write_method, numextens)) |
| return (NULL); |
| |
| if ((exten = get_exten_instance(extens, name[*length - 1]))) { |
| switch (vp->magic) { |
| case MIBINDEX: |
| long_ret = name[*length - 1]; |
| return ((u_char *) (&long_ret)); |
| case ERRORNAME: /* name defined in config file */ |
| *var_len = strlen(exten->name); |
| return ((u_char *) (exten->name)); |
| case SHELLCOMMAND: |
| *var_len = strlen(exten->command); |
| return ((u_char *) (exten->command)); |
| case ERRORFLAG: /* return code from the process */ |
| len = sizeof(exten->output); |
| if (exten->type == EXECPROC) { |
| exten->result = run_exec_command( exten->command, NULL, |
| exten->output, &len); |
| } else { |
| exten->result = run_shell_command(exten->command, NULL, |
| exten->output, &len); |
| } |
| long_ret = exten->result; |
| return ((u_char *) (&long_ret)); |
| case ERRORMSG: /* first line of text returned from the process */ |
| len = sizeof(exten->output); |
| if (exten->type == EXECPROC) { |
| exten->result = run_exec_command( exten->command, NULL, |
| exten->output, &len); |
| } else { |
| exten->result = run_shell_command(exten->command, NULL, |
| exten->output, &len); |
| } |
| *var_len = strlen(exten->output); |
| if (exten->output[*var_len - 1] == '\n') |
| exten->output[--(*var_len)] = '\0'; |
| return ((u_char *) (exten->output)); |
| case ERRORFIX: |
| *write_method = fixExecError; |
| long_return = 0; |
| return ((u_char *) & long_return); |
| |
| case ERRORFIXCMD: |
| *var_len = strlen(exten->fixcmd); |
| return ((u_char *) exten->fixcmd); |
| } |
| return NULL; |
| } |
| return NULL; |
| } |
| |
| int |
| fixExecError(int action, |
| u_char * var_val, |
| u_char var_val_type, |
| size_t var_val_len, |
| u_char * statP, oid * name, size_t name_len) |
| { |
| |
| struct extensible *exten; |
| long tmp = 0; |
| int fd; |
| static struct extensible ex; |
| FILE *file; |
| |
| if ((exten = get_exten_instance(extens, name[10]))) { |
| if (var_val_type != ASN_INTEGER) { |
| snmp_log(LOG_ERR, "Wrong type != int\n"); |
| return SNMP_ERR_WRONGTYPE; |
| } |
| tmp = *((long *) var_val); |
| if ((tmp == 1) && (action == COMMIT) && (exten->fixcmd[0] != 0)) { |
| strlcpy(ex.command, exten->fixcmd, sizeof(ex.command)); |
| if ((fd = get_exec_output(&ex)) != -1) { |
| file = fdopen(fd, "r"); |
| while (fgets(ex.output, sizeof(ex.output), file) != NULL); |
| fclose(file); |
| wait_on_exec(&ex); |
| } |
| } |
| return SNMP_ERR_NOERROR; |
| } |
| return SNMP_ERR_WRONGTYPE; |
| } |
| |
| u_char * |
| var_extensible_relocatable(struct variable *vp, |
| oid * name, |
| size_t * length, |
| int exact, |
| size_t * var_len, WriteMethod ** write_method) |
| { |
| |
| int i; |
| int len; |
| struct extensible *exten = 0; |
| static long long_ret; |
| static char errmsg[STRMAX]; |
| char *cp, *cp1; |
| struct variable myvp; |
| oid tname[MAX_OID_LEN]; |
| |
| memcpy(&myvp, vp, sizeof(struct variable)); |
| |
| long_ret = *length; |
| for (i = 1; i <= (int) numrelocs; i++) { |
| exten = get_exten_instance(relocs, i); |
| if (!exten) |
| continue; |
| if ((int) exten->miblen == (int) vp->namelen - 1) { |
| memcpy(myvp.name, exten->miboid, exten->miblen * sizeof(oid)); |
| myvp.namelen = exten->miblen; |
| *length = vp->namelen; |
| memcpy(tname, vp->name, vp->namelen * sizeof(oid)); |
| if (!header_simple_table |
| (&myvp, tname, length, -1, var_len, write_method, -1)) |
| break; |
| else |
| exten = NULL; |
| } |
| } |
| if (i > (int) numrelocs || exten == NULL) { |
| *length = long_ret; |
| *var_len = 0; |
| *write_method = NULL; |
| return (NULL); |
| } |
| |
| *length = long_ret; |
| if (header_simple_table(vp, name, length, exact, var_len, write_method, |
| ((vp->magic == ERRORMSG) ? MAXMSGLINES : 1))) |
| return (NULL); |
| |
| switch (vp->magic) { |
| case MIBINDEX: |
| long_ret = name[*length - 1]; |
| return ((u_char *) (&long_ret)); |
| case ERRORNAME: /* name defined in config file */ |
| *var_len = strlen(exten->name); |
| return ((u_char *) (exten->name)); |
| case SHELLCOMMAND: |
| *var_len = strlen(exten->command); |
| return ((u_char *) (exten->command)); |
| case ERRORFLAG: /* return code from the process */ |
| len = sizeof(exten->output); |
| if (exten->type == EXECPROC) |
| exten->result = run_exec_command( exten->command, NULL, |
| exten->output, &len); |
| else |
| exten->result = run_shell_command(exten->command, NULL, |
| exten->output, &len); |
| long_ret = exten->result; |
| return ((u_char *) (&long_ret)); |
| case ERRORMSG: /* first line of text returned from the process */ |
| len = sizeof(exten->output); |
| if (exten->type == EXECPROC) |
| exten->result = run_exec_command( exten->command, NULL, |
| exten->output, &len); |
| else |
| exten->result = run_shell_command(exten->command, NULL, |
| exten->output, &len); |
| |
| /* |
| * Pick the output string apart into individual lines, |
| * and extract the one being asked for.... |
| */ |
| cp1 = exten->output; |
| for (i = 1; i != (int) name[*length - 1]; i++) { |
| cp = strchr(cp1, '\n'); |
| if (!cp) { |
| *var_len = 0; |
| /* wait_on_exec(exten); ??? */ |
| return NULL; |
| } |
| cp1 = ++cp; |
| } |
| /* |
| * ... and quit if we've run off the end of the output |
| */ |
| if (!*cp1) { |
| *var_len = 0; |
| return NULL; |
| } |
| cp = strchr(cp1, '\n'); |
| if (cp) |
| *cp = 0; |
| strlcpy(errmsg, cp1, sizeof(errmsg)); |
| *var_len = strlen(errmsg); |
| if (errmsg[*var_len - 1] == '\n') |
| errmsg[--(*var_len)] = '\0'; |
| return ((u_char *) (errmsg)); |
| case ERRORFIX: |
| *write_method = fixExecError; |
| long_return = 0; |
| return ((u_char *) & long_return); |
| |
| case ERRORFIXCMD: |
| *var_len = strlen(exten->fixcmd); |
| return ((u_char *) exten->fixcmd); |
| } |
| return NULL; |
| } |
| |
| netsnmp_subtree * |
| find_extensible(netsnmp_subtree *tp, oid *tname, size_t tnamelen, int exact) |
| { |
| size_t tmp; |
| int i; |
| struct extensible *exten = 0; |
| struct variable myvp; |
| oid name[MAX_OID_LEN]; |
| static netsnmp_subtree mysubtree[2] = |
| { { NULL, 0, NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, 0, 0, 0, |
| NULL, NULL, NULL, 0, 0, NULL, 0, 0 }, |
| { NULL, 0, NULL, 0, NULL, 0, NULL, 0, 0, NULL, NULL, 0, 0, 0, |
| NULL, NULL, NULL, 0, 0, NULL, 0, 0 } }; |
| |
| for (i = 1; i <= (int) numrelocs; i++) { |
| exten = get_exten_instance(relocs, i); |
| if (!exten) |
| continue; |
| if (exten->miblen != 0) { |
| memcpy(myvp.name, exten->miboid, exten->miblen * sizeof(oid)); |
| memcpy(name, tname, tnamelen * sizeof(oid)); |
| myvp.name[exten->miblen] = name[exten->miblen]; |
| myvp.namelen = exten->miblen + 1; |
| tmp = exten->miblen + 1; |
| if (!header_simple_table(&myvp, name, &tmp, -1, |
| NULL, NULL, numrelocs)) { |
| break; |
| } |
| } |
| } |
| if (i > (int)numrelocs || exten == NULL) { |
| return (tp); |
| } |
| |
| if (mysubtree[0].name_a != NULL) { |
| free(mysubtree[0].name_a); |
| mysubtree[0].name_a = NULL; |
| } |
| mysubtree[0].name_a = snmp_duplicate_objid(exten->miboid, exten->miblen); |
| mysubtree[0].namelen = exten->miblen; |
| mysubtree[0].variables = (struct variable *)extensible_relocatable_variables; |
| mysubtree[0].variables_len = sizeof(extensible_relocatable_variables) / |
| sizeof(*extensible_relocatable_variables); |
| mysubtree[0].variables_width = sizeof(*extensible_relocatable_variables); |
| mysubtree[1].namelen = 0; |
| return (mysubtree); |
| } |