This is expand.c in view mode; [Download] [Up]
/* @(#)src/expand.c 1.2 24 Oct 1990 05:22:50 */ /* * Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll * * See the file COPYING, distributed with smail, for restriction * and warranty information. */ /* * expand.c: * expand filenames used by directors. * * external functions: expand_string, build_cmd_line */ #include <stdio.h> #include <ctype.h> #include <pwd.h> #include "defs.h" #include "smail.h" #include "addr.h" #include "transport.h" #include "log.h" #include "alloc.h" #include "dys.h" #include "exitcodes.h" #ifndef DEPEND # include "debug.h" # include "extern.h" #endif /* library functions */ extern long time(); /* functions local to this file */ static char **build_argv(); static char *substitute(); static char *lc_fold(); static char *uc_fold(); static char *strip_fold(); #ifndef NODEBUG static void bad_subst(); #endif /* * expand_string - expand a string containing parts to be expanded * * This function does ~user and ~/ expansion and also performs expansions * of the form $name or ${name}. See substitute() for the possible * substitutions. * * If `addr' is NULL, then a dummy addr structure is formed with all * items NULL except for home and user, which are taken from the * arguments to expand_string(). * * return NULL on error attempting expansion. The area returned may * be reused on subsequent calls. If the caller wishes to retain the * returned data, it should be copied elsewhere. */ char * expand_string(string, addr, home, user) register char *string; /* unexpanded string */ struct addr *addr; /* addr structure with values */ char *home; /* home directory */ char *user; /* user name for $user */ { char *save_string = string; /* save pointer to start of string */ static struct str str; /* build strings here */ static int inited = FALSE; /* TRUE if str inited */ int modified = FALSE; /* TRUE if any expansion ocured */ static struct addr *dummy_addr = NULL; /* dummy addr for home and user */ DEBUG3(DBG_DRIVER_HI, "expand_string(%s, %s, %s) called\n", string, home, user); if (! inited) { STR_INIT(&str); inited = TRUE; } else { STR_CHECK(&str); str.i = 0; } if (addr == NULL) { /* no addr structure given, setup a dummy one */ if (dummy_addr == NULL) { dummy_addr = alloc_addr(); } addr = dummy_addr; addr->home = home; addr->next_addr = user; } if (string[0] == '~') { /* do some kind of twiddle expansion */ if (string[1] == '/') { /* ~/ turns into home/ */ if (addr->home) { modified = TRUE; string++; STR_CAT(&str, addr->home); } else { /* no home directory, so ~/ is not valid */ DEBUG(DBG_DRIVER_MID, "no home directory, ~/ invalid\n"); return NULL; } } else { /* ~user turns into home director for the given user */ char *p = string + 1; struct passwd *pw; extern struct passwd *getpwbyname(); while (*string && *string != '/') { string++; } if (*string) { *string = '\0'; pw = getpwbyname(p); *string = '/'; } else { pw = getpwbyname(p); } if (pw == NULL) { /* ~user but username isn't valid */ DEBUG1(DBG_DRIVER_MID, "user not found, ~%s invalid\n", p); return NULL; } modified = TRUE; STR_CAT(&str, pw->pw_dir); } } /* * we have the silly ~ business out of the way, now * get all of the rest of the silly business out of the way */ while (*string) { if (*string == '$') { /* do a $-substitution */ string++; if (*string == '{') { /* * handle expansions of the form ${name} */ char *p = string + 1; char *new; while (*string && *string != '}') { string++; } if (*string == '\0') { /* no matching } for the opening ${ */ return NULL; } new = substitute(addr, (char *)NULL, p, string - p); if (new) { STR_CAT(&str, new); } else { /* unrecognized substitution */ #ifndef NODEBUG bad_subst(p, string - p); #endif return NULL; } string++; } else { /* * handle $name expansions */ char *p = string; char *new; while (*string && (isalnum(*++string) || *string == '_')) ; new = substitute(addr, (char *)NULL, p, string - p); if (new) { STR_CAT(&str, new); } else { /* unrecognized substitution */ #ifndef NODEBUG bad_subst(p, string - p); #endif return NULL; } } modified = TRUE; } else { /* * regular character, copy it into the result */ STR_NEXT(&str, *string++); } } if (!modified) { /* * no expansion needed to be done, just return the old string */ DEBUG1(DBG_DRIVER_HI, "unmodified, expand_string returns %s\n", save_string); return save_string; } /* * expansion was done, finish up the string and return it */ STR_NEXT(&str, '\0'); DEBUG1(DBG_DRIVER_HI, "expand_string returns %s\n", str.p); return str.p; } #ifndef NODEBUG /* * bad_subst - generate a debugging message for a failed substitution. * * note that we can't use "%*.*s" here since dprintf() is simple-minded. */ static void bad_subst(var, len) char *var; int len; { int c_save = var[len]; var[len] = 0; DEBUG1(DBG_DRIVER_MID, "expand_string: no expansion for $%s\n", var); var[len] = c_save; } #endif /* * build_cmd_line - build up an arg vector suitable for execv * * transports can call this to build up a command line in a standard * way. Of course, if they want to they can build up a command line in * a totally different fashion. * * Caution: return value points to a region which may be reused by * subsequent calls to build_cmd_line() * * Notes on the replacement algorithm: * o Within a $( and $) pair, substitutions are made once for * each address on the input list. * o Otherwise the substitution is made relative to the first * address on the input list. * o Substitutions: * o grade ==> $grade * o addr->next_host ==> $host * o addr->next_addr ==> $addr or $user * o addr->home ==> $home or $HOME * o sender ==> $from or $sender * o file ==> $file * o message_id ==> $message_id * o unix_date() ==> $ctime * o get_arpa_date() ==> $date * o getpid() ==> $$ * o uucp_name ==> $uucp_name * o visible_name ==> $visible_name * o primary_name ==> $primary_name * o VERSION ==> $version * o version() ==> $version_string * o single quotes, double quotes and backslash work as with /bin/sh * * return NULL for parsing errors, and load `error' with a message * explaining the error. */ char ** build_cmd_line(cmd, addr, file, error) register char *cmd; /* input command line */ struct addr *addr; /* list of remote addresses */ char *file; /* substitution for $file */ char **error; /* error message */ { static struct str str; /* generated region */ static int inited = FALSE; /* TRUE if str has been inited */ char *mark; /* temp mark in cmd line */ char *new; /* new string from substitute */ int ct = 1; /* count of args, at least one */ int state = 0; /* notes about parse state */ struct addr *save_addr = addr; /* replace addr from this after $) */ char *save_cmd; /* start of a $( ... $) group */ int last_char = '\0'; /* hold last *cmd value */ #define DQUOTE 0x01 /* double quote in effect */ #define GROUP 0x02 /* $( ... $) grouping in effect */ /* initialize for building up the arg vectors */ if (! inited) { STR_INIT(&str); inited = TRUE; } else { STR_CHECK(&str); str.i = 0; } while (*cmd) { switch (*cmd) { case '\'': /* after "'" copy literally to before next "'" char */ mark = index(cmd+1, '\''); if (mark == NULL) { panic(EX_DATAERR, "no matching ' for cmd in transport %s", addr->transport->name); /*NOTREACHED*/ } *mark = '\0'; /* put null in for copy */ STR_CAT(&str, cmd+1); *mark = '\''; /* put quote back */ last_char = '\''; cmd = mark; break; case '\\': /* * char after \ is literal, unless in quote, in which case * this is not so if the following char is not " or $ or \ */ if (*cmd++ == '\0') { *error = "\\ at end of command"; return NULL; } if (!(state&DQUOTE) || *cmd == '\\' || *cmd == '"' || *cmd == '$') { STR_NEXT(&str, *cmd); } else { STR_NEXT(&str, '\\'); STR_NEXT(&str, *cmd); } last_char = '\\'; break; case '"': /* double quote is a toggle */ state ^= DQUOTE; last_char = '"'; break; case '$': /* perform parameter substitution */ cmd++; if (*cmd == '\0') { *error = "$ at end of command"; return NULL; } if (*cmd == '(') { if (state&GROUP) { *error = "recursive $( ... $)"; return NULL; } if (state&DQUOTE) { *error = "$( illegal inside \"...\""; return NULL; } save_cmd = cmd; state |= GROUP; break; } if (*cmd == ')') { if ((state&GROUP) == 0) { *error = "no match for $)"; return NULL; } if (state&DQUOTE) { *error = "$) illegal inside \"...\""; return NULL; } if (!isspace(last_char)) { /* end previous vector, create a new one */ ct++; STR_NEXT(&str, '\0'); } addr = addr->succ; if (addr) { cmd = save_cmd; } else { /* no more addrs to put in group */ addr = save_addr; state &= ~GROUP; } last_char = ' '; /* don't create an extra vector */ break; } if (*cmd == '{') { mark = cmd+1; cmd = index(mark, '}'); if (cmd == NULL) { *error = "no match for {"; return NULL; } } else { /* use at least one char after $ for substitute name */ mark = cmd; while (isalnum(*++cmd) || *cmd == '_') ; /* cmd now one beyond where it should be */ } new = substitute(addr, file, mark, cmd - mark); if (new == NULL) { int c_save = mark[cmd-mark]; mark[cmd-mark] = '\0'; /* TODO: This is a memory leak */ *error = xprintf("bad substition: $s", mark); mark[cmd-mark] = c_save; return NULL; } STR_CAT(&str, new); if (*cmd != '}') { --cmd; /* correct next char pointer */ } last_char = '$'; break; case ' ': /* when not in a quote */ case '\t': /* white space separates words */ case '\n': if (state&DQUOTE) { STR_NEXT(&str, *cmd); } else if (!isspace(last_char)) { /* end the previous arg vector */ STR_NEXT(&str, '\0'); ct++; /* start a new one */ } last_char = *cmd; break; default: STR_NEXT(&str, *cmd); last_char = *cmd; } cmd++; /* advance to next char */ } if (state&DQUOTE) { *error = "no match for opening \""; return NULL; } if (state&GROUP) { *error = "no match for $("; return NULL; } if (isspace(last_char)) { --ct; /* don't count just blanks */ } STR_NEXT(&str, '\0'); /* null terminate the strings */ return build_argv(str.p, ct); } /* * build_argv - build arg vectors from inline strings * * build_cmd_line produces chars with null characters separating * strings. build_argv takes these chars and turns them into * an arg vector suitable for execv. * * Caution: the value returned by build_argv() points to a region * which may be reused on subsequent calls to build_argv(). */ static char ** build_argv(p, ct) register char *p; /* strings, one after another */ register int ct; /* count of strings */ { static char **argv = NULL; /* reusable vector area */ static int argc; register char **argp; if (argv == NULL) { argc = ct + 1; argv = (char **)xmalloc(argc * sizeof(*argv)); } else { if (ct + 1 > argc) { X_CHECK(argv); argc = ct + 1; argv = (char **)xrealloc((char *)argv, argc * sizeof(*argv)); } } argp = argv; DEBUG(DBG_REMOTE_MID, "cmd ="); while (ct--) { *argp++ = p; DEBUG1(DBG_REMOTE_MID, " '%s'", p); if (ct) { while (*p++) ; /* scan for next string */ } } DEBUG(DBG_REMOTE_MID, "\n"); *argp = NULL; /* terminate vectors */ return argv; } /* * substitute - relace a $paramater with its value * * panic on errors, see build_cmd_line for details. */ static char * substitute(addr, file, var, len) struct addr *addr; /* source for $host, $addr, $user */ char *file; /* source for $file */ register char *var; /* start of variable */ register int len; /* length of variable */ { #define MATCH(x) (len==sizeof(x)-1 && strncmpic(var, x, sizeof(x)-1) == 0) if (strncmpic(var, "lc:", sizeof("lc:") - 1) == 0) { return lc_fold(substitute(addr, file, var + 3, len - 3)); } if (strncmpic(var, "uc:", sizeof("uc:") - 1) == 0) { return uc_fold(substitute(addr, file, var + 3, len - 3)); } if (strncmpic(var, "strip:", sizeof("strip:") - 1) == 0) { return strip_fold(substitute(addr, file, var + 6, len - 6)); } if (strncmpic(var, "parent:", sizeof("parent:") - 1) == 0) { struct addr *parent = addr->parent; if (parent == NULL) { return NULL; } return substitute(parent, file, var + 7, len - 7); } if (strncmpic(var, "top:", sizeof("top:") - 1) == 0) { struct addr *top = addr; while (top->parent) { top = top->parent; } return substitute(top, file, var + 4, len - 4); } if (MATCH("grade")) { static char grade_str[2] = { 0, 0 }; grade_str[0] = msg_grade; return grade_str; } if (MATCH("user") || MATCH("addr")) { return addr->next_addr; } if (MATCH("host")) { return addr->next_host; } if (MATCH("HOME") || MATCH("home")) { return addr->home; } if (MATCH("sender") || MATCH("from")) { return sender; } if (MATCH("file")) { return file; } if (MATCH("message_id") || MATCH("id")) { return message_id; } if (MATCH("ctime")) { return unix_date(); } if (MATCH("date")) { /* get the current date in ARPA format */ return get_arpa_date(time((long *)0)); } if (MATCH("spool_date")) { /* get the spool date in ARPA format */ return get_arpa_date(message_date()); } if (MATCH("$") || MATCH("pid")) { static char pidbuf[10]; (void) sprintf(pidbuf, "%d", getpid()); return pidbuf; } if (MATCH("uucp_name")) { return uucp_name; } if (MATCH("visible_name") || MATCH("name")) { return visible_name; } if (MATCH("primary_name") || MATCH("primary")) { return primary_name; } if (MATCH("version")) { return version_number; } if (MATCH("version_string")) { return version(); } if (MATCH("release_date") || MATCH("release")) { return release_date; } if (MATCH("patch_number") || MATCH("patch")) { return patch_number; } if (MATCH("patch_date")) { return patch_date; } if (MATCH("bat")) { return bat; } if (MATCH("compile_num") || MATCH("ld_num")) { static char s_compile_num[10]; (void) sprintf(s_compile_num, "%d", compile_num); return s_compile_num; } if (MATCH("compile_date") || MATCH("ld_date")) { return compile_date; } if (MATCH("smail_lib_dir") || MATCH("lib_dir")) { return smail_lib_dir; } return NULL; /* no match */ #undef MATCH } /* * lc_fold - meta substitution to convert value to lower case */ static char * lc_fold(value) register char *value; { static int lc_size; /* keep size of allocated region */ int value_size; static char *lc = NULL; /* retained malloc region */ register char *p; /* for scanning through lc */ if (value == NULL) { return NULL; } value_size = strlen(value) + 1; /* get a region at least large enough for the value */ if (lc == NULL) { lc = xmalloc(lc_size = value_size); } else if (value_size > lc_size) { X_CHECK(lc); lc = xrealloc(lc, lc_size = value_size); } p = lc; while (*value) { *p++ = lowercase(*value++); } *p = '\0'; return lc; } /* * uc_fold - meta substitution to convert value to upper case */ static char * uc_fold(value) register char *value; { static int uc_size; /* keep size of allocated region */ int value_size; static char *uc = NULL; /* retained malloc region */ register char *p; /* for scanning through lc */ if (value == NULL) { return NULL; } value_size = strlen(value) + 1; /* get a region at least large enough for the value */ if (uc == NULL) { uc = xmalloc(uc_size = value_size); } else if (value_size > uc_size) { X_CHECK(uc); uc = xrealloc(uc, uc_size = value_size); } p = uc; while (*value) { *p++ = uppercase(*value++); } *p = '\0'; return uc; } /* * strip_fold - strip quotes and collapse spaces and dots * * strip quotes from the input string and collapse any sequence of one * or more white space and `.' characters into a single `.'. */ static char * strip_fold(value) char *value; { static int strip_size; /* keep size of allocated region */ int value_size; static char *strip_buf = NULL; /* retained malloc region */ register char *p; /* for scanning through strip_buf */ register char *q; /* also for scanning strip_buf */ if (value == NULL) { return NULL; } value_size = strlen(value) + 1; if (strip_buf == NULL) { strip_buf = xmalloc(strip_size = value_size); } else if (value_size > strip_size) { X_CHECK(strip_buf); strip_buf = xrealloc(strip_buf, strip_size = value_size); } (void) strcpy(strip_buf, value); (void) strip(strip_buf); /* q reads and p writes */ p = q = strip_buf; /* strip initial -'s */ while (*q == '-') { q++; } while (*q) { /* collapse multiple white-space chars and .'s into single dots */ if (isspace(*q) || *q == '.') { while (isspace(*++q) || *q == '.') ; *p++ = '.'; continue; } *p++ = *q++; } *p = '\0'; /* finish off strip_buf */ return strip_buf; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.