Back to index

tetex-bin  3.0
files.c
Go to the documentation of this file.
00001 /* files.c -- file-related functions for makeinfo.
00002    $Id: files.c,v 1.5 2004/07/27 00:06:31 karl Exp $
00003 
00004    Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004 Free Software
00005    Foundation, Inc.
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License as published by
00009    the Free Software Foundation; either version 2, or (at your option)
00010    any later version.
00011 
00012    This program is distributed in the hope that it will be useful,
00013    but WITHOUT ANY WARRANTY; without even the implied warranty of
00014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015    GNU General Public License for more details.
00016 
00017    You should have received a copy of the GNU General Public License
00018    along with this program; if not, write to the Free Software Foundation,
00019    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
00020 
00021 #include "system.h"
00022 #include "files.h"
00023 #include "html.h"
00024 #include "index.h"
00025 #include "macro.h"
00026 #include "makeinfo.h"
00027 #include "node.h"
00028 
00029 FSTACK *filestack = NULL;
00030 
00031 static int node_filename_stack_index = 0;
00032 static int node_filename_stack_size = 0;
00033 static char **node_filename_stack = NULL;
00034 
00035 /* Looking for include files.  */
00036 
00037 /* Given a string containing units of information separated by colons,
00038    return the next one pointed to by INDEX, or NULL if there are no more.
00039    Advance INDEX to the character after the colon. */
00040 static char *
00041 extract_colon_unit (char *string, int *index)
00042 {
00043   int start;
00044   int path_sep_char = PATH_SEP[0];
00045   int i = *index;
00046 
00047   if (!string || (i >= strlen (string)))
00048     return NULL;
00049 
00050   /* Each call to this routine leaves the index pointing at a colon if
00051      there is more to the path.  If i > 0, then increment past the
00052      `:'.  If i == 0, then the path has a leading colon.  Trailing colons
00053      are handled OK by the `else' part of the if statement; an empty
00054      string is returned in that case. */
00055   if (i && string[i] == path_sep_char)
00056     i++;
00057 
00058   start = i;
00059   while (string[i] && string[i] != path_sep_char) i++;
00060   *index = i;
00061 
00062   if (i == start)
00063     {
00064       if (string[i])
00065         (*index)++;
00066 
00067       /* Return "" in the case of a trailing `:'. */
00068       return xstrdup ("");
00069     }
00070   else
00071     {
00072       char *value;
00073 
00074       value = xmalloc (1 + (i - start));
00075       memcpy (value, &string[start], (i - start));
00076       value [i - start] = 0;
00077 
00078       return value;
00079     }
00080 }
00081 
00082 /* Return the full pathname for FILENAME by searching along PATH.
00083    When found, return the stat () info for FILENAME in FINFO.
00084    If PATH is NULL, only the current directory is searched.
00085    If the file could not be found, return a NULL pointer. */
00086 char *
00087 get_file_info_in_path (char *filename, char *path, struct stat *finfo)
00088 {
00089   char *dir;
00090   int result, index = 0;
00091 
00092   if (path == NULL)
00093     path = ".";
00094 
00095   /* Handle absolute pathnames.  */
00096   if (IS_ABSOLUTE (filename)
00097       || (*filename == '.'
00098           && (IS_SLASH (filename[1])
00099               || (filename[1] == '.' && IS_SLASH (filename[2])))))
00100     {
00101       if (stat (filename, finfo) == 0)
00102         return xstrdup (filename);
00103       else
00104         return NULL;
00105     }
00106 
00107   while ((dir = extract_colon_unit (path, &index)))
00108     {
00109       char *fullpath;
00110 
00111       if (!*dir)
00112         {
00113           free (dir);
00114           dir = xstrdup (".");
00115         }
00116 
00117       fullpath = xmalloc (2 + strlen (dir) + strlen (filename));
00118       sprintf (fullpath, "%s/%s", dir, filename);
00119       free (dir);
00120 
00121       result = stat (fullpath, finfo);
00122 
00123       if (result == 0)
00124         return fullpath;
00125       else
00126         free (fullpath);
00127     }
00128   return NULL;
00129 }
00130 
00131 /* Prepend and append new paths to include_files_path.  */
00132 void
00133 prepend_to_include_path (char *path)
00134 {
00135   if (!include_files_path)
00136     {
00137       include_files_path = xstrdup (path);
00138       include_files_path = xrealloc (include_files_path,
00139           strlen (include_files_path) + 3); /* 3 for ":.\0" */
00140       strcat (strcat (include_files_path, PATH_SEP), ".");
00141     }
00142   else
00143     {
00144       char *tmp = xstrdup (include_files_path);
00145       include_files_path = xrealloc (include_files_path,
00146           strlen (include_files_path) + strlen (path) + 2); /* 2 for ":\0" */
00147       strcpy (include_files_path, path);
00148       strcat (include_files_path, PATH_SEP);
00149       strcat (include_files_path, tmp);
00150       free (tmp);
00151     }
00152 }
00153 
00154 void
00155 append_to_include_path (char *path)
00156 {
00157   if (!include_files_path)
00158     include_files_path = xstrdup (".");
00159 
00160   include_files_path = (char *) xrealloc (include_files_path,
00161         2 + strlen (include_files_path) + strlen (path));
00162   strcat (include_files_path, PATH_SEP);
00163   strcat (include_files_path, path);
00164 }
00165 
00166 /* Remove the first path from the include_files_path.  */
00167 void
00168 pop_path_from_include_path (void)
00169 {
00170   int i = 0;
00171   char *tmp;
00172 
00173   if (include_files_path)
00174     for (i = 0; i < strlen (include_files_path)
00175         && include_files_path[i] != ':'; i++);
00176 
00177   /* Advance include_files_path to the next char from ':'  */
00178   tmp = (char *) xmalloc (strlen (include_files_path) - i);
00179   strcpy (tmp, (char *) include_files_path + i + 1);
00180 
00181   free (include_files_path);
00182   include_files_path = tmp;
00183 }
00184 
00185 /* Find and load the file named FILENAME.  Return a pointer to
00186    the loaded file, or NULL if it can't be loaded.  If USE_PATH is zero,
00187    just look for the given file (this is used in handle_delayed_writes),
00188    else search along include_files_path.   */
00189 
00190 char *
00191 find_and_load (char *filename, int use_path)
00192 {
00193   struct stat fileinfo;
00194   long file_size;
00195   int file = -1, count = 0;
00196   char *fullpath, *result;
00197   int n, bytes_to_read;
00198 
00199   result = fullpath = NULL;
00200 
00201   fullpath
00202     = get_file_info_in_path (filename, use_path ? include_files_path : NULL, 
00203                              &fileinfo);
00204 
00205   if (!fullpath)
00206     goto error_exit;
00207 
00208   filename = fullpath;
00209   file_size = (long) fileinfo.st_size;
00210 
00211   file = open (filename, O_RDONLY);
00212   if (file < 0)
00213     goto error_exit;
00214 
00215   /* Load the file, with enough room for a newline and a null. */
00216   result = xmalloc (file_size + 2);
00217 
00218   /* VMS stat lies about the st_size value.  The actual number of
00219      readable bytes is always less than this value.  The arcane
00220      mysteries of VMS/RMS are too much to probe, so this hack
00221     suffices to make things work.  It's also needed on Cygwin.  And so
00222     we might as well use it everywhere.  */
00223   bytes_to_read = file_size;
00224   while ((n = read (file, result + count, bytes_to_read)) > 0)
00225     {
00226       count += n;
00227       bytes_to_read -= n;
00228     }
00229   if (0 < count && count < file_size)
00230     result = xrealloc (result, count + 2); /* why waste the slack? */
00231   else if (n == -1)
00232 error_exit:
00233     {
00234       if (result)
00235         free (result);
00236 
00237       if (fullpath)
00238         free (fullpath);
00239 
00240       if (file != -1)
00241         close (file);
00242 
00243       return NULL;
00244     }
00245   close (file);
00246 
00247   /* Set the globals to the new file. */
00248   input_text = result;
00249   input_text_length = count;
00250   input_filename = fullpath;
00251   node_filename = xstrdup (fullpath);
00252   input_text_offset = 0;
00253   line_number = 1;
00254   /* Not strictly necessary.  This magic prevents read_token () from doing
00255      extra unnecessary work each time it is called (that is a lot of times).
00256      INPUT_TEXT_LENGTH is one past the actual end of the text. */
00257   input_text[input_text_length] = '\n';
00258   /* This, on the other hand, is always necessary.  */
00259   input_text[input_text_length+1] = 0;
00260   return result;
00261 }
00262 
00263 /* Pushing and popping files.  */
00264 static void
00265 push_node_filename (void)
00266 {
00267   if (node_filename_stack_index + 1 > node_filename_stack_size)
00268     node_filename_stack = xrealloc
00269     (node_filename_stack, (node_filename_stack_size += 10) * sizeof (char *));
00270 
00271   node_filename_stack[node_filename_stack_index] = node_filename;
00272   node_filename_stack_index++;
00273 }
00274 
00275 static void
00276 pop_node_filename (void)
00277 {
00278   node_filename = node_filename_stack[--node_filename_stack_index];
00279 }
00280 
00281 /* Save the state of the current input file. */
00282 void
00283 pushfile (void)
00284 {
00285   FSTACK *newstack = xmalloc (sizeof (FSTACK));
00286   newstack->filename = input_filename;
00287   newstack->text = input_text;
00288   newstack->size = input_text_length;
00289   newstack->offset = input_text_offset;
00290   newstack->line_number = line_number;
00291   newstack->next = filestack;
00292 
00293   filestack = newstack;
00294   push_node_filename ();
00295 }
00296 
00297 /* Make the current file globals be what is on top of the file stack. */
00298 void
00299 popfile (void)
00300 {
00301   FSTACK *tos = filestack;
00302 
00303   if (!tos)
00304     abort ();                   /* My fault.  I wonder what I did? */
00305 
00306   if (macro_expansion_output_stream)
00307     {
00308       maybe_write_itext (input_text, input_text_offset);
00309       forget_itext (input_text);
00310     }
00311 
00312   /* Pop the stack. */
00313   filestack = filestack->next;
00314 
00315   /* Make sure that commands with braces have been satisfied. */
00316   if (!executing_string && !me_executing_string)
00317     discard_braces ();
00318 
00319   /* Get the top of the stack into the globals. */
00320   input_filename = tos->filename;
00321   input_text = tos->text;
00322   input_text_length = tos->size;
00323   input_text_offset = tos->offset;
00324   line_number = tos->line_number;
00325   free (tos);
00326 
00327   /* Go back to the (now) current node. */
00328   pop_node_filename ();
00329 }
00330 
00331 /* Flush all open files on the file stack. */
00332 void
00333 flush_file_stack (void)
00334 {
00335   while (filestack)
00336     {
00337       char *fname = input_filename;
00338       char *text = input_text;
00339       popfile ();
00340       free (fname);
00341       free (text);
00342     }
00343 }
00344 
00345 /* Return the index of the first character in the filename
00346    which is past all the leading directory characters.  */
00347 static int
00348 skip_directory_part (char *filename)
00349 {
00350   int i = strlen (filename) - 1;
00351 
00352   while (i && !IS_SLASH (filename[i]))
00353     i--;
00354   if (IS_SLASH (filename[i]))
00355     i++;
00356   else if (filename[i] && HAVE_DRIVE (filename))
00357     i = 2;
00358 
00359   return i;
00360 }
00361 
00362 static char *
00363 filename_non_directory (char *name)
00364 {
00365   return xstrdup (name + skip_directory_part (name));
00366 }
00367 
00368 /* Return just the simple part of the filename; i.e. the
00369    filename without the path information, or extensions.
00370    This conses up a new string. */
00371 char *
00372 filename_part (char *filename)
00373 {
00374   char *basename = filename_non_directory (filename);
00375 
00376 #ifdef REMOVE_OUTPUT_EXTENSIONS
00377   /* See if there is an extension to remove.  If so, remove it. */
00378   {
00379     char *temp = strrchr (basename, '.');
00380     if (temp)
00381       *temp = 0;
00382   }
00383 #endif /* REMOVE_OUTPUT_EXTENSIONS */
00384   return basename;
00385 }
00386 
00387 /* Return the pathname part of filename.  This can be NULL. */
00388 char *
00389 pathname_part (char *filename)
00390 {
00391   char *result = NULL;
00392   int i;
00393 
00394   filename = expand_filename (filename, "");
00395 
00396   i = skip_directory_part (filename);
00397   if (i)
00398     {
00399       result = xmalloc (1 + i);
00400       strncpy (result, filename, i);
00401       result[i] = 0;
00402     }
00403   free (filename);
00404   return result;
00405 }
00406 
00407 /* Return the full path to FILENAME. */
00408 static char *
00409 full_pathname (char *filename)
00410 {
00411   int initial_character;
00412   char *result;
00413 
00414   /* No filename given? */
00415   if (!filename || !*filename)
00416     return xstrdup ("");
00417   
00418   /* Already absolute? */
00419   if (IS_ABSOLUTE (filename) ||
00420       (*filename == '.' &&
00421        (IS_SLASH (filename[1]) ||
00422         (filename[1] == '.' && IS_SLASH (filename[2])))))
00423     return xstrdup (filename);
00424 
00425   initial_character = *filename;
00426   if (initial_character != '~')
00427     {
00428       char *localdir = xmalloc (1025);
00429 #ifdef HAVE_GETCWD
00430       if (!getcwd (localdir, 1024))
00431 #else
00432       if (!getwd (localdir))
00433 #endif
00434         {
00435           fprintf (stderr, _("%s: getwd: %s, %s\n"),
00436                    progname, filename, localdir);
00437           xexit (1);
00438         }
00439 
00440       strcat (localdir, "/");
00441       strcat (localdir, filename);
00442       result = xstrdup (localdir);
00443       free (localdir);
00444     }
00445   else
00446     { /* Does anybody know why WIN32 doesn't want to support $HOME?
00447          If the reason is they don't have getpwnam, they should
00448          only disable the else clause below.  */
00449 #ifndef WIN32
00450       if (IS_SLASH (filename[1]))
00451         {
00452           /* Return the concatenation of the environment variable HOME
00453              and the rest of the string. */
00454           char *temp_home;
00455 
00456           temp_home = (char *) getenv ("HOME");
00457           result = xmalloc (strlen (&filename[1])
00458                                     + 1
00459                                     + temp_home ? strlen (temp_home)
00460                                     : 0);
00461           *result = 0;
00462 
00463           if (temp_home)
00464             strcpy (result, temp_home);
00465 
00466           strcat (result, &filename[1]);
00467         }
00468       else
00469         {
00470           struct passwd *user_entry;
00471           int i, c;
00472           char *username = xmalloc (257);
00473 
00474           for (i = 1; (c = filename[i]); i++)
00475             {
00476               if (IS_SLASH (c))
00477                 break;
00478               else
00479                 username[i - 1] = c;
00480             }
00481           if (c)
00482             username[i - 1] = 0;
00483 
00484           user_entry = getpwnam (username);
00485 
00486           if (!user_entry)
00487             return xstrdup (filename);
00488 
00489           result = xmalloc (1 + strlen (user_entry->pw_dir)
00490                                     + strlen (&filename[i]));
00491           strcpy (result, user_entry->pw_dir);
00492           strcat (result, &filename[i]);
00493         }
00494 #endif /* not WIN32 */
00495     }
00496   return result;
00497 }
00498 
00499 /* Return the expansion of FILENAME. */
00500 char *
00501 expand_filename (char *filename, char *input_name)
00502 {
00503   int i;
00504 
00505   if (filename)
00506     {
00507       filename = full_pathname (filename);
00508       if (IS_ABSOLUTE (filename)
00509          || (*filename == '.' &&
00510              (IS_SLASH (filename[1]) ||
00511               (filename[1] == '.' && IS_SLASH (filename[2])))))
00512        return filename;
00513     }
00514   else
00515     {
00516       filename = filename_non_directory (input_name);
00517 
00518       if (!*filename)
00519         {
00520           free (filename);
00521           filename = xstrdup ("noname.texi");
00522         }
00523 
00524       for (i = strlen (filename) - 1; i; i--)
00525         if (filename[i] == '.')
00526           break;
00527 
00528       if (!i)
00529         i = strlen (filename);
00530 
00531       if (i + 6 > (strlen (filename)))
00532         filename = xrealloc (filename, i + 6);
00533       strcpy (filename + i, html ? ".html" : ".info");
00534       return filename;
00535     }
00536 
00537   if (IS_ABSOLUTE (input_name))
00538     {
00539       /* Make it so that relative names work. */
00540       char *result;
00541       
00542       i = strlen (input_name) - 1;
00543 
00544       result = xmalloc (1 + strlen (input_name) + strlen (filename));
00545       strcpy (result, input_name);
00546 
00547       while (!IS_SLASH (result[i]) && i)
00548         i--;
00549       if (IS_SLASH (result[i]))
00550         i++;
00551 
00552       strcpy (&result[i], filename);
00553       free (filename);
00554       return result;
00555     }
00556   return filename;
00557 }
00558 
00559 char *
00560 output_name_from_input_name (char *name)
00561 {
00562   return expand_filename (NULL, name);
00563 }
00564 
00565 
00566 /* Modify the file name FNAME so that it fits the limitations of the
00567    underlying filesystem.  In particular, truncate the file name as it
00568    would be truncated by the filesystem.  We assume the result can
00569    never be longer than the original, otherwise we couldn't be sure we
00570    have enough space in the original string to modify it in place.  */
00571 char *
00572 normalize_filename (char *fname)
00573 {
00574   int maxlen;
00575   char orig[PATH_MAX + 1];
00576   int i;
00577   char *lastdot, *p;
00578 
00579 #ifdef _PC_NAME_MAX
00580   maxlen = pathconf (fname, _PC_NAME_MAX);
00581   if (maxlen < 1)
00582 #endif
00583     maxlen = PATH_MAX;
00584 
00585   i = skip_directory_part (fname);
00586   if (fname[i] == '\0')
00587     return fname;    /* only a directory name -- don't modify */
00588   strcpy (orig, fname + i);
00589 
00590   switch (maxlen)
00591     {
00592       case 12:       /* MS-DOS 8+3 filesystem */
00593        if (orig[0] == '.')  /* leading dots are not allowed */
00594          orig[0] = '_';
00595        lastdot = strrchr (orig, '.');
00596        if (!lastdot)
00597          lastdot = orig + strlen (orig);
00598        strncpy (fname + i, orig, lastdot - orig);
00599        for (p = fname + i;
00600             p < fname + i + (lastdot - orig) && p < fname + i + 8;
00601             p++)
00602          if (*p == '.')
00603            *p = '_';
00604        *p = '\0';
00605        if (*lastdot == '.')
00606          strncat (fname + i, lastdot, 4);
00607        break;
00608       case 14:       /* old Unix systems with 14-char limitation */
00609        strcpy (fname + i, orig);
00610        if (strlen (fname + i) > 14)
00611          fname[i + 14] = '\0';
00612        break;
00613       default:
00614        strcpy (fname + i, orig);
00615        if (strlen (fname) > maxlen - 1)
00616          fname[maxlen - 1] = '\0';
00617        break;
00618     }
00619 
00620   return fname;
00621 }
00622 
00623 /* Delayed writing functions.  A few of the commands
00624    needs to be handled at the end, namely @contents,
00625    @shortcontents, @printindex and @listoffloats.
00626    These functions take care of that.  */
00627 static DELAYED_WRITE *delayed_writes = NULL;
00628 int handling_delayed_writes = 0;
00629 
00630 void
00631 register_delayed_write (char *delayed_command)
00632 {
00633   DELAYED_WRITE *new;
00634 
00635   if (!current_output_filename || !*current_output_filename)
00636     {
00637       /* Cannot register if we don't know what the output file is.  */
00638       warning (_("`%s' omitted before output filename"), delayed_command);
00639       return;
00640     }
00641 
00642   if (STREQ (current_output_filename, "-"))
00643     {
00644       /* Do not register a new write if the output file is not seekable.
00645          Let the user know about it first, though.  */
00646       warning (_("`%s' omitted since writing to stdout"), delayed_command);
00647       return;
00648     }
00649 
00650   /* Don't complain if the user is writing /dev/null, since surely they
00651      don't care, but don't register the delayed write, either.  */
00652   if (FILENAME_CMP (current_output_filename, NULL_DEVICE) == 0
00653       || FILENAME_CMP (current_output_filename, ALSO_NULL_DEVICE) == 0)
00654     return;
00655     
00656   /* We need the HTML header in the output,
00657      to get a proper output_position.  */
00658   if (!executing_string && html)
00659     html_output_head ();
00660   /* Get output_position updated.  */
00661   flush_output ();
00662 
00663   new = xmalloc (sizeof (DELAYED_WRITE));
00664   new->command = xstrdup (delayed_command);
00665   new->filename = xstrdup (current_output_filename);
00666   new->input_filename = xstrdup (input_filename);
00667   new->position = output_position;
00668   new->calling_line = line_number;
00669   new->node = current_node ? xstrdup (current_node): "";
00670 
00671   new->node_order = node_order;
00672   new->index_order = index_counter;
00673 
00674   new->next = delayed_writes;
00675   delayed_writes = new;
00676 }
00677 
00678 void
00679 handle_delayed_writes (void)
00680 {
00681   DELAYED_WRITE *temp = (DELAYED_WRITE *) reverse_list
00682     ((GENERIC_LIST *) delayed_writes);
00683   int position_shift_amount, line_number_shift_amount;
00684   char *delayed_buf;
00685 
00686   handling_delayed_writes = 1;
00687 
00688   while (temp)
00689     {
00690       delayed_buf = find_and_load (temp->filename, 0);
00691 
00692       if (output_paragraph_offset > 0)
00693         {
00694           error (_("Output buffer not empty."));
00695           return;
00696         }
00697 
00698       if (!delayed_buf)
00699         {
00700           fs_error (temp->filename);
00701           return;
00702         }
00703 
00704       output_stream = fopen (temp->filename, "w");
00705       if (!output_stream)
00706         {
00707           fs_error (temp->filename);
00708           return;
00709         }
00710 
00711       if (fwrite (delayed_buf, 1, temp->position, output_stream) != temp->position)
00712         {
00713           fs_error (temp->filename);
00714           return;
00715         }
00716 
00717       {
00718         int output_position_at_start = output_position;
00719         int line_number_at_start = output_line_number;
00720 
00721         /* In order to make warnings and errors
00722            refer to the correct line number.  */
00723         input_filename = temp->input_filename;
00724         line_number = temp->calling_line;
00725 
00726         execute_string ("%s", temp->command);
00727         flush_output ();
00728 
00729         /* Since the output file is modified, following delayed writes
00730            need to be updated by this amount.  */
00731         position_shift_amount = output_position - output_position_at_start;
00732         line_number_shift_amount = output_line_number - line_number_at_start;
00733       }
00734 
00735       if (fwrite (delayed_buf + temp->position, 1,
00736             input_text_length - temp->position, output_stream)
00737           != input_text_length - temp->position
00738           || fclose (output_stream) != 0)
00739         fs_error (temp->filename);
00740 
00741       /* Done with the buffer.  */
00742       free (delayed_buf);
00743 
00744       /* Update positions in tag table for nodes that are defined after
00745          the line this delayed write is registered.  */
00746       if (!html && !xml)
00747         {
00748           TAG_ENTRY *node;
00749           for (node = tag_table; node; node = node->next_ent)
00750             if (node->order > temp->node_order)
00751               node->position += position_shift_amount;
00752         }
00753 
00754       /* Something similar for the line numbers in all of the defined
00755          indices.  */
00756       {
00757         int i;
00758         for (i = 0; i < defined_indices; i++)
00759           if (name_index_alist[i])
00760             {
00761               char *name = ((INDEX_ALIST *) name_index_alist[i])->name;
00762               INDEX_ELT *index;
00763               for (index = index_list (name); index; index = index->next)
00764                 if ((no_headers || STREQ (index->node, temp->node))
00765                     && index->entry_number > temp->index_order)
00766                   index->output_line += line_number_shift_amount;
00767             }
00768       }
00769 
00770       /* Shift remaining delayed positions
00771          by the length of this write.  */
00772       {
00773         DELAYED_WRITE *future_write = temp->next;
00774         while (future_write)
00775           {
00776             if (STREQ (temp->filename, future_write->filename))
00777               future_write->position += position_shift_amount;
00778             future_write = future_write->next;
00779           }
00780       }
00781 
00782       temp = temp->next;
00783     }
00784 }