/*{{{ proc-incl.c*/ /*----------------------------------------------------------------------*/ /* Usage: */ /* proc-incl [-D include-dictionary-file] [-I include-dir]... [file] */ /*----------------------------------------------------------------------*/ /*{{{ global decls*/ #include #include /* for 'stat ()' */ #include /* " " " */ #define BUFLEN 1000 /* max len of ID or keyword in i/p */ #define PROTECT_LINE_CHAR '@' /* replaces some '#include's */ #define TEMP_FILE_PREFIX "tmp_h" /* replacement 'include' files */ typedef int bool; #define FALSE 0 #define TRUE 1 typedef struct Incl_file { char *filename; int val; int processed; /* a char: u == user incl file (unprocessed) */ /* p == " " " (processed) */ struct Incl_file *next; struct Incl_file *next_file; } Incl_file; #define UNPROCESSED_USR_FILE 'u' #define PROCESSED_USR_FILE 'p' typedef struct Dir_list { char *dir; struct Dir_list *next; } Dir_list; Dir_list *std_incl_list, *usr_incl_list; char *current_dir = NULL, *ip_filename = NULL; char *std_incl_dirs[] = {"/usr/include", 0}; /* '0' is a sentinel */ #define HASHSIZE 101 char buf [BUFLEN]; Incl_file *filetab [HASHSIZE], *first_file, *last_file; int next_val; char *incl_dict_filename = "tmp_incl_dict"; /* 'file dictionary' filename */ bool file_exists (), find_file (), ordinary_file (), replace_filename_by_tmp (); unsigned hash (); int fileval (); /*}}}*/ /*{{{ main (argc, argv) -- OK*/ main (argc, argv) /*-------------*/ int argc; char *argv []; /*----------------------------------------------------------------------*/ /* Usage: */ /* proc-incl [-D include-dictionary-file] [-I include-dir]... [file] */ /*----------------------------------------------------------------------*/ { char *progname = argv [0]; char **pp; Dir_list *dp, *last_dp; /*{{{ initialise 'std_incl_list'*/ /*------------------------------*/ /* Initialise 'std_incl_list' */ /*------------------------------*/ for (pp = std_incl_dirs, last_dp = NULL; *pp; pp++, last_dp = dp) { dp = (Dir_list *) malloc (sizeof (Dir_list)); dp->dir = *pp; dp->next = NULL; if (last_dp) last_dp->next = dp; else std_incl_list = dp; } /*}}}*/ /*{{{ process command line options (starting '-')*/ /*--------------------------------------------------------------*/ /* Command line options, viz: */ /* [-D include-dictionary-file] [-I include-dir]... */ /*--------------------------------------------------------------*/ while (--argc && *(*++argv) == '-') { Dir_list *dp; int c; switch (c = *++(*argv)) { case 'D' : /*{{{ 'incl_dict_filename' (default = "tmp_dict")*/ /*--------------------------------------------------------------*/ /* -D option: should be followed (with or without an space) */ /* by the filename of the 'dictionary of include files', */ /* ('incl_dict_filename'). */ /*--------------------------------------------------------------*/ /*--------------------------------------------------------------*/ /* set '*argv' to point to the start of the filename (which is */ /* either the next character or the next word). */ /*--------------------------------------------------------------*/ if (*++(*argv)) /*------------------------------------------------------*/ /* filename follows -D immediately (without a space) */ /*------------------------------------------------------*/ ; else if ( ! --argc || **++argv == '-') { fputs ("Expect a filename after the \'-D\' option.\n", stderr); goto abort; } incl_dict_filename = *argv; break; /*}}}*/ case 'I' : /*{{{ include directory: add to end 'usr_incl_list' list*/ /*--------------------------------------------------------------*/ /* -I option: should be followed (with or without an space) */ /* by a directory name, which is added to the end of the list */ /* of 'usr_incl_list'. */ /*--------------------------------------------------------------*/ /*--------------------------------------------------------------*/ /* set '*argv' to point to the start of the directory name */ /* (which is either the next character or the next word) */ /*--------------------------------------------------------------*/ if (*++(*argv)) /*------------------------------------------------------*/ /* directory follows -I immediately (without a space) */ /*------------------------------------------------------*/ ; else if ( ! --argc || **++argv == '-') { fputs ("Expect a directory name after the \'-I\' option.\n", stderr); goto abort; } /*--------------------------------------------------------------*/ /* add directory name to the end of the list 'usr_incl_dir' */ /*--------------------------------------------------------------*/ dp = (Dir_list *) malloc (sizeof (Dir_list)); dp->dir = *argv; dp->next = NULL; if (! usr_incl_list) usr_incl_list = dp; else { Dir_list *ndp; for (ndp = usr_incl_list; ndp->next; ndp = ndp->next) ; ndp->next = dp; } break; /*}}}*/ case '\0' : fputs ("Expected an option letter after \'-\'.\n", stderr); goto abort; default : fprintf (stderr, "Illegal option \'-%c\'.\n", c); goto abort; } } /*}}}*/ /*{{{ if given, read & open 'ip_filename' & extract its directory path*/ if (argc) { /*--------------------------------------*/ /* Read and open the 'ip_filename'. */ /*--------------------------------------*/ char *dirsep_p, *p; ip_filename = *argv; if ( ! freopen (ip_filename, "r", stdin) ) { fprintf (stderr, "Cannot open file \"%s\".\n", ip_filename); exit (1); } /*------------------------------------------------------*/ /* If the 'ip_filename' includes a directory path, set */ /* the global variable 'current_dir' to point to it. */ /* (This will be the first directory that is searched */ /* for "..." -delimited 'include' files. If */ /* 'current_dir' isn't set here, its default is "."). */ /*------------------------------------------------------*/ for (p = ip_filename, dirsep_p = NULL; *p; p++) if (*p == '/') dirsep_p = p; if (dirsep_p) { *dirsep_p = '\0'; /* ...so '*ip_filename' is just the directory */ current_dir = (char *) malloc (strlen (ip_filename) + 1); strcpy (current_dir, ip_filename); *dirsep_p = '/'; /* restore 'ip_filename' */ } argc--; argv++; } /*}}}*/ if (argc) { fprintf (stderr, "%s: unexpected argument: \'%s\'.\n", progname, *argv); goto abort; } read_old_dict (); translate_file (); write_new_dict (); exit (0); /* successful execution! */ abort : fprintf (stderr, "Usage: %s [-I include-dir]... [-D include-dictionary-file] [input-file]\n", progname); exit (1); } /*}}}*/ /*{{{ << 'file dictionary' I/O >> -- OK*/ /*{{{ read_old_dict () -- OK*/ read_old_dict () /*------------*/ { register char *p; char *filename; FILE *dict_file; int processed, val; if ( !(dict_file = fopen (incl_dict_filename, "r")) ) return; next_val = 0; while (fgets (buf, BUFLEN, dict_file) ) { /*{{{ read (filename, val, processed) & install in 'filetab'*/ /*--------------------------------------------------------------*/ /* Read the (filename, processed) record from 'buf []', and */ /* install it in the filename table. */ /* */ /* N.B. The line's syntax *isn't* checked. */ /*--------------------------------------------------------------*/ /*------------------------------*/ /* Read the 'processed' field */ /*------------------------------*/ p = buf; processed = *p; /* a char: 'u' or 'p'; see definition of 'Incl_file' */ filename = (p += 2); /* skip over '\t' to start of 'filename' field */ /*---------------*/ /* Set the 'val' */ /*---------------*/ val = ++next_val; /*--------------------------------------------------------------*/ /* Now '*p' should be a 'filename', terminated by '\n'. (Even */ /* if there are blank chars before the '\n', they are still */ /* taken to be part of the filename! ) */ /* We only have to replace the terminating '\n' by '\0', */ /* so '*p' can be used as a string. */ /* */ /* N.B. We don't check for the possibility that the 'buf' is */ /* too small for the whole filename (indicated by '\0' being */ /* found at the end of 'buf', without a prior '\n'). We assume */ /* that BUFLEN (1000 chars) is big enough!! */ /*--------------------------------------------------------------*/ while (*++p != '\n'); /* skip over 'filename' to '\n' */ *p = '\0'; /* terminate 'filename' */ /*----------------------------------------------*/ /* Install 'filename' in the filename table. */ /*----------------------------------------------*/ install_filename (filename, val, processed); /*}}}*/ } fclose (dict_file); return; } /*}}}*/ /*{{{ write_new_dict () -- OK*/ write_new_dict () /*-------------*/ { FILE *dict_file; Incl_file *sp; if ( ! (dict_file = fopen (incl_dict_filename, "w")) ) fprintf (stderr, "*** Error: can\'t open \"%s\" for writing.\n\n", incl_dict_filename); /*------------------------------------------------------*/ /* Output all filenames with their 'processed' values. */ /*------------------------------------------------------*/ for (sp = first_file; sp; sp = sp->next_file) fprintf (dict_file, "%c\t%s\n", sp->processed, sp->filename); fclose (dict_file); } /*}}}*/ /*}}}*/ /*{{{ translate_file () -- OK*/ translate_file () /*-------------*/ /*---------------------------------------------------------------- This function copies the input C file to output, performing the following transformations: -- delete comments & splice lines terminated by '\\'; -- replace whitespace sequences by a single blank; -- transform '#include' lines as follows: #include } -- protect the '#include' from 'cpp' #include token-sequence } by putting an '@' at start-of-line. #include "filename" -- if filename is in a directory in the 'std_incl_list', protect '#include' as above; otherwise, change 'filename' to, e.g., "tmp_xx". ----------------------------------------------------------------*/ { /*{{{ decls*/ bool start_of_line; register int c; int delim; /*}}}*/ start_of_line = TRUE; c = getchar (); while (c != EOF) { switch (c) { case '\'' : case '\"' : /*{{{ start of a char const or string: copy out*/ /*--------------------------------------------------------------*/ /* A character constant or string. Copy it straight to output. */ /*--------------------------------------------------------------*/ delim = c; putchar (delim); start_of_line = FALSE; while ((c = getchar ()) != delim) { switch (c) { case EOF : return; case '\\' : /*---------------------------------------*/ /* copy this & next char unconditionally */ /*---------------------------------------*/ putchar (c); if ((c = getchar ()) == EOF) return; putchar (c); break; default : putchar (c); break; } } putchar (delim); c = getchar (); break; /*}}}*/ case '/' : /*{{{ if start of a comment, replace comment by ' '*/ /*--------------------------------------------------------------*/ /* '/' may be the start of a comment. If so, replace the whole */ /* comment by a single space. */ /* */ /* N.B. Comments don't change the status of 'start_of_line' */ /* (since they're equivalent to whitespace!). */ /*--------------------------------------------------------------*/ if ((c = getchar ()) == '*') { /*-----------------*/ /* It's a comment! */ /*-----------------*/ bool in_comment; if (!start_of_line) putchar (' '); /* replace comment by a single ' ' */ in_comment = TRUE; do { switch (getchar ()) { case EOF : return; case '*' : switch (c = getchar ()) { case EOF : return; case '/' : in_comment = FALSE; break; default : ungetc (c, stdin); break; } break; } } while (in_comment); c = getchar (); } else { /*----------------------------------------------*/ /* Not the start of a comment -- output the '/' */ /*----------------------------------------------*/ putchar ('/'); start_of_line = FALSE; } break; /*}}}*/ case '#' : if (start_of_line) { /*{{{ process it if it's a '#include' line (see intro)*/ /*--------------------------------------------------------------*/ /* If this is a '#include' line, transform it as follows: */ /* */ /* #include } -- protect '#include' from 'cpp' */ /* #include token-sequence } by putting '@' at start-of-line. */ /* #include "filename" -- if filename is: */ /* in a directory in the 'std_incl_list'; */ /* OR not found; */ /* OR not terminated by " (but by by '\n' or EOF) */ /* protect '#include' as above; */ /* otherwise, change 'filename' to, e.g., "tmp_xx". */ /* */ /* N.B. Whitespace preceding the '#' is removed, as is *all* */ /* whitespace at the start of a line! */ /*--------------------------------------------------------------*/ char *filename, *p, *word; start_of_line = FALSE; p = buf; *p++ = '#'; /*{{{ copy whitespace to 'buf []'*/ /*-----------------------------*/ /* copy whitespace to 'buf []' */ /*-----------------------------*/ while ((c = getchar ()) == ' ' || c == '\t' || c == '\\') { if (c == '\\') { if ((c = getchar ()) != '\n') { *p++ = '\\'; goto copy_out; } } else *p++ = c; } /*}}}*/ /*{{{ read next word. If not "include", copy out and break*/ if (c != 'i') /*-----------------------------*/ /* Not "# include" -- copy out */ /*-----------------------------*/ goto copy_out; /*--------------------------------------------------------------*/ /* Copy the next word to 'buf'. */ /* */ /* N.B. Though we expect only lower case letters (e.g. "ifdef", */ /* "include", etc), we test for all characters allowed in an */ /* identifier. This avoids the (unlikely) case of: */ /* #include23 */ /* being replaced by: */ /* @ 23 */ /* and thence back to "#include 23" -- i.e. inserting a space */ /* in an (admittedly misplaced) identifier! */ /*--------------------------------------------------------------*/ word = p; /* start of word after "#[whitespace]" */ do { if (c == '\\') { if ((c = getchar ()) != '\n') { *p++ = '\\'; goto copy_out; } } else *p++ = c; c = getchar (); } while (isalnum (c) || c == '_' || c == '\\'); *p = '\0'; if (strcmp (word, "include")) /*-----------------------------*/ /* Not "# include" -- copy out */ /*-----------------------------*/ goto copy_out; /*}}}*/ /*{{{ copy whitespace to 'buf []'*/ /*--------------------------------------------------------------*/ /* Copy whitespace to 'buf []'. */ /* */ /* N.B. If we find a '\\' not followed by a '\n', we output a */ /* '@' at the start-of-line to protect the '#include' -- i.e. */ /* we treat it like any "#include" not followed by a `\"`! */ /*--------------------------------------------------------------*/ while (c == ' ' || c == '\t' || c == '\\') { if (c == '\\') { if ((c = getchar ()) != '\n') { *p++ = '\\'; putchar (PROTECT_LINE_CHAR); goto copy_out; } } else *p++ = c; c = getchar (); } /*}}}*/ if (c != '\"') { /*{{{ #include <...> | token-seq | err; o/p "@" & 'buf', and break*/ /*--------------------------------------------------------------*/ /* The line is either: */ /* */ /* #include <...> */ /* or #include token-sequence */ /* or #include syntax-error */ /* */ /* Whichever it is, put '@' at the start-of-line to protect the */ /* '#include', so the 'include' file isn't expanded by 'cpp'. */ /* (The '@' will subsequently be removed by 'ctran', exposing */ /* the '#include' again!) */ /*--------------------------------------------------------------*/ putchar (PROTECT_LINE_CHAR); goto copy_out; /*}}}*/ } *p++ = c; /* copy the '\"' to 'buf[]' */ filename = p; /* start of filename */ /*{{{ read the rest of the filename into 'buf[]'*/ /*-------------------------------------------------------------- Read the rest of the filename, up to but excluding the final '\"', into 'buf []'. Notes: ------ (1) This isn't really a C string, but rather a character-for- character copy of a filename (though the C standard says nothing about this!). Hence: -- we don't treat '\\' as an escape character, except to escape '\n'; -- we terminate the filename if '\n' (unescaped) is seen before '\"' -- otherwise a missing '\"' may result in a ridiculously long filename! (2) If an error occurs (i.e. the string isn't terminated by '\"', but rather by '\n' or EOF), we give an errmsg and replace "#include" by "@" in the o/p (rather than 'copying out' the input), so 'cmunge' tries neither to find nor include the file. "#include" will be restored at the end of 'cmunge', so the o/p file will have exactly the same error as the input one! --------------------------------------------------------------*/ while ((c = getchar ()) != '\"' && c != '\n' && c != EOF) { switch (c) { case '\\' : /*----------------------------------------------*/ /* if followed by '\n', splice lines (i.e. skip */ /* this char and the '\n'), otherwise copy the */ /* '\\' to 'buf' (but don't treat it as an */ /* escape character). */ /*----------------------------------------------*/ if ((c = getchar ()) == '\n') break; *p++ = '\\'; if (c == EOF) { *p = '\0'; goto filename_error; } else { ungetc (c, stdin); break; } default : *p++ = c; break; } } *p = '\0'; /* N.B. haven't put final '\"' in 'buf[]'! */ /*}}}*/ if (c != '\"') goto filename_error; if ( ! replace_filename_by_tmp (filename)) putchar (PROTECT_LINE_CHAR); /* a 'system' or 'std' header file */ printf ("%s\"", buf); c = getchar (); continue; /* continue processing with last-read char 'c' */ /*---------------------------------------------------------------*/ /* the following labels are 'goto' targets from the above code...*/ /*---------------------------------------------------------------*/ copy_out : /*{{{ copy-out 'buf' & continue*/ *p = '\0'; fputs (buf, stdout); continue; /* continue processing with last-read char 'c' */ /*}}}*/ filename_error : /*{{{ write errmsg (missing final "), output <@ "filename>, & continue*/ fputs ("\n*** Error: \'include\' filename not terminated by \":\n", stderr); if (ip_filename) fprintf (stderr, "%s: ", ip_filename); fprintf (stderr, "%s\n", buf); putchar (PROTECT_LINE_CHAR); fputs (buf, stdout); continue; /* outer loop deals with 'c' (== '\n' or EOF) */ /*}}}*/ /*}}}*/ } else { putchar (c); c = getchar (); } break; case ' ' : case '\t' : /*{{{ o/p ' ', and skip subsequent whitespace*/ if (!start_of_line) putchar (' '); while ((c = getchar ()) == ' ' || c == '\t') ; /* skip all consecutive whitespace */ break; /* continue normal processing with 'c'! */ /*}}}*/ case '\n' : /*{{{ o/p '\n', and skip subsequent space & '\n's*/ putchar ('\n'); start_of_line = TRUE; while ((c = getchar ()) == ' ' || c == '\t' || c == '\n') ; /* skip consecutive whitespace and newlines */ break; /* continue normal processing with 'c'! */ /*}}}*/ case '\\' : /*{{{ if at EOL, splice lines (i.e. skip this & '\n')*/ /*--------------------------------------------------------------*/ /* If the next char is '\n', splice this line and the next (i.e.*/ /* skip the '\\' and '\n'). Otherwise, just output the '//'. */ /*--------------------------------------------------------------*/ if ((c = getchar ()) == '\n') c = getchar (); else { putchar ('\\'); start_of_line = FALSE; } break; /*}}}*/ default : /*{{{ copy-out 'c'*/ putchar (c); start_of_line = FALSE; c = getchar (); break; /*}}}*/ } } } /*}}}*/ /*{{{ bool replace_filename_by_tmp (filename) -- OK*/ bool replace_filename_by_tmp (filename) /*------------------------------------*/ char *filename; { Dir_list *dp; /*------------------------------------------------------*/ /* don't replace 'filename' if file not found... */ /*------------------------------------------------------*/ if ( ! find_file (filename)) /* errmsg given */ return FALSE; /*------------------------------------------------------*/ /* or if it's in a directory in the 'std_incl_list' */ /* (nornally "/usr/include")... */ /*------------------------------------------------------*/ for (dp = std_incl_list; dp; dp = dp->next) if ( (strncmp (filename, dp->dir, strlen (dp->dir)) == 0) && (*(filename + strlen (dp->dir)) == '/') ) return FALSE; /*-----------------------------------------*/ /* Otherwise, replace it, and return TRUE! */ /*-----------------------------------------*/ sprintf (filename, "%s%d", TEMP_FILE_PREFIX, fileval (filename)); /* N.B. 'fileval ()' installs 'filename' */ /* in 'filetab' if it isn't already there! */ return TRUE; } /*}}}*/ /*{{{ bool find_file (filename) -- OK*/ bool find_file (filename) /*----------------------*/ char *filename; { char *p; Dir_list *dp; int filename_len, dirname_len; if (*filename == '/' && file_exists (filename)) goto found; filename_len = strlen (filename); /*{{{ look for 'filename' in the 'current directory'*/ /*--------------------------------------------------------------*/ /* look for 'filename' in the current directory */ /* ('current_dir', or "." if that is NULL). */ /*--------------------------------------------------------------*/ if (current_dir) { dirname_len = strlen (current_dir); p = (char *) malloc (dirname_len + filename_len + 2); /* +2 for '/' and '\0' */ strcpy (p, current_dir); *(p + dirname_len) = '/'; strcpy (p + dirname_len + 1, filename); if (file_exists (p)) { strcpy (filename, p); free (p); goto found; } free (p); } else if (file_exists (filename)) goto found; /*}}}*/ /*{{{ look for it in the directories in the 'usr_incl_list'*/ /*--------------------------------------------------------------*/ /* look for 'filename' in the 'include' directory(ies) */ /* specified by '-I' arguments (if any). */ /*--------------------------------------------------------------*/ for (dp = usr_incl_list; dp; dp = dp->next) { dirname_len = strlen (dp->dir); p = (char *) malloc (dirname_len + filename_len + 2); /* +2 for '/' and '\0' */ strcpy (p, dp->dir); *(p + dirname_len) = '/'; strcpy (p + dirname_len + 1, filename); if (file_exists (p)) { strcpy (filename, p); free (p); goto found; } free (p); } /*}}}*/ /*{{{ look for it in the directories in the 'std_incl_list'*/ /*--------------------------------------------------------------*/ /* finally, look for 'filename' in the 'standard include' */ /* directory(ies). */ /*--------------------------------------------------------------*/ for (dp = std_incl_list; dp; dp = dp->next) { dirname_len = strlen (dp->dir); p = (char *) malloc (dirname_len + filename_len + 2); /* +2 for '/' and '\0' */ strcpy (p, dp->dir); *(p + dirname_len) = '/'; strcpy (p + dirname_len + 1, filename); if (file_exists (p)) { strcpy (filename, p); free (p); goto found; } free (p); } /*}}}*/ /*------------------------------------------------------*/ /* 'filename' not found; give errmsg & return FALSE */ /*------------------------------------------------------*/ fputs ("\n*** Error: \'include\' file not found:\n", stderr); if (ip_filename) fprintf (stderr, "%s: ", ip_filename); fprintf (stderr, "#include \"%s\"\n", filename); return FALSE; found : /*------------------------------------------------------*/ /* 'filename' found; 'simplify' it & return TRUE */ /*------------------------------------------------------*/ simplify_path (filename); return ordinary_file (filename); /* gives errmsg if not! */ } /*}}}*/ /*{{{ simplify_path (path) -- OK*/ simplify_path (path) /*----------------*/ char *path; /*--------------------------------------------------------------*/ /* Simplify the pathname in 'path' by the following */ /* replacements: */ /* dir/..[/] --> delete */ /* .[/] --> delete */ /* // --> / */ /* */ /* N.B. In the 'simplified' path, all ".." components (if any) */ /* are at the start, and there are no "." components. */ /*--------------------------------------------------------------*/ { register char *fr, *to; bool path_simplified = FALSE; int c, n_named_cmpnts = 0; fr = to = path; if (*fr == '/') { fr++; to++; } while (*fr) { /*{{{ process the next path component*/ /*--------------------------------------------------------------*/ /* Process the next path component (P). */ /* */ /* N.B. We define a 'path component' as *excluding* the */ /* preceding '/' (if any), but including the following '/' */ /* (if any). At this point, 'fr' and 'to' point to the start */ /* of a path component. */ /*--------------------------------------------------------------*/ if (*fr == '/') { /*{{{ P is "/" (i.e. there's a "//" in the pathname). Skip the '/'.*/ /*--------------------------------------------------------------*/ /* P is "/" (i.e. there's a "//" in the pathname!). Skip it! */ /*--------------------------------------------------------------*/ path_simplified = TRUE; fr++; continue; /* do next field */ /*}}}*/ } else if (*fr != '.') { /*{{{ P isn't "." or "..", so copy it*/ /*------------------------------------------------------*/ /* P is named (i.e. not "." or ".."). Copy it. */ /*------------------------------------------------------*/ n_named_cmpnts++; goto copy_compnt; /*}}}*/ } switch (*(fr+1)) { case '.' : c = *(fr+2); /* the char after ".." */ if (c && c != '/') { /*{{{ P isn't "." or ".." (though starting ".."), so copy it. */ /*--------------------------------------------------------------*/ /* P isn't "." or ".." (though it starts ".."), so copy it. */ /*--------------------------------------------------------------*/ n_named_cmpnts++; goto copy_compnt; /*}}}*/ } if ( ! n_named_cmpnts) { /*{{{ "..[/]", but no prev component to cancel with, so copy it*/ /*--------------------------------------------------------------*/ /* P == "..[/]", but either there isn't a preceding path */ /* component or it's also "..", so we can't cancel P with it. */ /* Hence copy P. */ /*--------------------------------------------------------------*/ goto copy_compnt; /*}}}*/ } /*{{{ P is "..[/]": cancel it with the prev (named) path component*/ /*------------------------------------------------------------------ P == "..[/]". Cancel it with the previous (named) path component (i.e. decrement 'to' to point to its start). N.B. The precise rules for what to cancel are as follows (where is 'end of path', is 'beginning of path', and means an 'empty pathname' -- which is replaced by "." at the end): P preceded by cancel viz. - ----------- ------ ---- { dir/../ -> dir/../ anything P { /dir/../ -> / { path/dir/../ -> path/ { P dir/.. -> dir/.. { / P /dir/.. -> / { path/ (P & preceding /) path/dir/.. -> path Notice the complication of the last case (which is handled by the 'if' statement below). ------------------------------------------------------------------*/ path_simplified = TRUE; n_named_cmpnts--; for (to -= 2; to > path && *(to-1) != '/'; to--); fr += (c ? 3 : 2); /* points to '\0' or the start of next field */ if ( !c && to > (path+1)) to--; /* points to preceding '/' (to be replaced by '\0') */ continue; /* do next field */ /*}}}*/ case '/' : /*{{{ P is "./": skip it*/ /*------------------------------*/ /* P == "./", so skip it. */ /*------------------------------*/ path_simplified = TRUE; fr += 2; /* points to start of next field */ continue; /* do next field */ /*}}}*/ case '\0' : /*{{{ P is "." (at the end of the path): skip it*/ /*--------------------------------------------------------------*/ /* P == "." (at end of path), so skip it. */ /* */ /* N.B. For an explanation of what to do with an immediately */ /* preceding '/', see the discussion before the code for "..[/]"*/ /*--------------------------------------------------------------*/ path_simplified = TRUE; fr++; /* points to the '\0' */ if (to > (path+1)) to--; /* points to preceding '/' (to be replaced by '\0') */ continue; /* i.e. quit 'while' loop */ /*}}}*/ default : /*{{{ P isn't "." or ".." (though starting "."), so copy it*/ /*--------------------------------------------------------------*/ /* P isn't "." or ".." (though it starts "."), so copy it. */ /*--------------------------------------------------------------*/ n_named_cmpnts++; break; /*}}}*/ } copy_compnt : if (path_simplified) { /*{{{ copy 'from' field (up to '/', if any) to 'to' field*/ /*--------------------------------------------------------------*/ /* Copy the 'from' field (up to and including a '/', if any) to */ /* the 'to' field. */ /*--------------------------------------------------------------*/ while (*fr && *fr != '/') *to++ = *fr++; if (*fr) *to++ = *fr++; /* copy the '/' */ /*}}}*/ } else { /*{{{ as above , but just increment pointers 'fr' & 'to' (as f r== to)*/ /*--------------------------------------------------------------*/ /* As above, but just increment the 'fr' and 'to' pointers, */ /* rather than actually copying characters (since fr == to). */ /*--------------------------------------------------------------*/ while (*fr && *fr != '/') fr++; if (*fr) fr++; to = fr; /*}}}*/ } /*}}}*/ } if (to == path) /* (simplified) path is empty! */ *to++ = '.'; *to = '\0'; } /*}}}*/ /*{{{ << 'filetab' handling functions >> -- OK*/ /*{{{ unsigned hash (filename) -- OK*/ unsigned hash (filename) /*---------------------*/ register char *filename; /*------------------------------------------------*/ /* Compute hash value for string 'filename' */ /*------------------------------------------------*/ { register unsigned hashval; for (hashval = 0; *filename; filename++) hashval = *filename + 31 * hashval; return hashval % HASHSIZE; } /*}}}*/ /*{{{ install_filename (filename, val, processed) -- OK*/ install_filename (filename, val, processed) /*---------------------------------------*/ char *filename; int val; /* > 0 */ int processed; /* a char: 'x' == processed; ' ' == not */ /*--------------------------------------------------------------*/ /* Called by 'read_old_dict ()' to install a */ /* (filename, val, processed) record, as specified in the */ /* 'file dictionary', into 'filetab'. */ /* */ /* N.B. We assume that the 'filename' isn't already installed. */ /*--------------------------------------------------------------*/ { unsigned hashval; register Incl_file *sp; hashval = hash (filename); /*------------------------------------------------------*/ /* make a new symbol table entry & initialise its 'val' */ /*------------------------------------------------------*/ sp = (Incl_file *) malloc (sizeof (Incl_file)); sp->val = val; sp->processed = processed; /*-----------------------------------------------------------*/ /* make a permanent copy of 'filename' and attach it to 'sp' */ /*-----------------------------------------------------------*/ sp->filename = (char *) malloc (strlen (filename) + 1); /* +1 for final '\0' */ strcpy (sp->filename, filename); /*----------------------------------------------*/ /* link filetab entry into the hash table */ /*----------------------------------------------*/ sp->next = filetab [hashval]; /* add to front of list */ filetab [hashval] = sp; /*------------------------------------------------------*/ /* link the entry to the end of the 'file linked list' */ /*------------------------------------------------------*/ sp->next_file = NULL; if (last_file) last_file = last_file->next_file = sp; else first_file = last_file = sp; /* first entry! */ } /*}}}*/ /*{{{ int fileval (filename) -- OK*/ int fileval (filename) /*-------------------*/ char *filename; { register Incl_file *sp; int val; unsigned hashval = hash (filename); /*--------------------------------------------------------*/ /* Search for 'filename' in the filename table, 'filetab' */ /*--------------------------------------------------------*/ for (sp = filetab [hashval]; sp; sp = sp->next) if (! strcmp (sp->filename, filename)) /* found */ return sp->val; /*------------------------------------------------------*/ /* Not found in the 'filetab', so install it. */ /*------------------------------------------------------*/ val = ++next_val; install_filename (filename, val, UNPROCESSED_USR_FILE); /* unprocessed as not */ /* seen before! */ return val; } /*}}}*/ /*}}}*/ /*{{{ bool file_exists (filename) -- OK*/ bool file_exists (filename) /*------------------------*/ char *filename; /*--------------------------------------------------------------*/ /* Returns 1 if 'filename' exists and is accessible (see */ /* below for the interpretation of this) and 0 otherwise. */ /* */ /* N.B. In the Unix implementation, this function returns 1 */ /* only if the file exists *and* the directories leading to it */ /* can be searched (i.e. have 'x' permission). */ /* Therefore, if any directory leading to the file *cannot**/ /* be searched the function returns 0, even if the file exists. */ /* However, in that case the given 'filename' cannot be opened, */ /* and therefore cannot be overwritten. */ /* In any other implementation of this function, the */ /* important property that must be preserved is that, if the */ /* function returns 0, it must be guaranteed that an attempt to */ /* open 'filename' will not *delete* or *over-write* a */ /* pre-existing file with name 'filename'! */ /* */ /* N.B. System-dependent features: */ /* ------------------------------- */ /* -- Uses the Unix system call 'access()'. */ /*--------------------------------------------------------------*/ { return (access (filename, 0) == 0); } /*}}}*/ /*{{{ bool ordinary_file (filename) -- OK*/ bool ordinary_file (filename) /*--------------------------*/ char *filename; { struct stat stat_buf; mode_t file_type; if (stat (filename, &stat_buf)) /*----------------------------------------------*/ /* File not found. Return FALSE. */ /* */ /* (This shouldn't happen, as this function is */ /* only called after 'filename' has been found */ /* by 'file_exists ()'!) */ /*----------------------------------------------*/ return FALSE; file_type = stat_buf.st_mode & S_IFMT; if (file_type == S_IFREG || file_type == S_IFLNK) /*----------------------------------------------*/ /* Ordinary file or symbolic link. Return TRUE.*/ /* */ /* N.B. I believe that 'stat' returns info */ /* about the file pointed-to by a symbolic link,*/ /* so it shouldn't be the latter!) */ /*----------------------------------------------*/ return TRUE; /*------------------------------------------------------*/ /* Not an ordinary file! Give an errmsg & return FALSE.*/ /*------------------------------------------------------*/ fputs ("\n*** Error: \'include\' file isn't an ordinary file (it's a ", stderr); switch (file_type) { case S_IFDIR : fputs ("directory", stderr); break; case S_IFCHR : case S_IFBLK : fputs ("device file", stderr); break; case S_IFSOCK : fputs ("socket", stderr); break; case S_IFIFO : fputs ("pipe", stderr); break; } fputs ("!):\n", stderr); if (ip_filename) fprintf (stderr, "%s: ", ip_filename); fprintf (stderr, "#include \"%s\"\n", filename); return FALSE; } /*}}}*/ /*}}}*/