dir.c revision 56160
1/* dir.c -- how to build a special "dir" node from "localdir" files.
2   $Id: dir.c,v 1.7 1998/06/28 19:51:36 karl Exp $
3
4   Copyright (C) 1993, 97, 98 Free Software Foundation, Inc.
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2, or (at your option)
9   any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20   Written by Brian Fox (bfox@ai.mit.edu). */
21
22#include "info.h"
23#include "info-utils.h"
24#include "filesys.h"
25#include "tilde.h"
26
27/* The "dir" node can be built from the contents of a file called "dir",
28   with the addition of the menus of every file named in the array
29   dirs_to_add which are found in INFOPATH. */
30
31static void add_menu_to_file_buffer (), insert_text_into_fb_at_binding ();
32
33static char *dirs_to_add[] = {
34  "dir", "localdir", (char *)NULL
35};
36
37
38/* Return zero if the file represented in the stat structure TEST has
39   already been seen, nonzero else.  */
40
41typedef struct
42{
43  unsigned long device;
44  unsigned long inode;
45} dir_file_list_entry_type;
46
47static int
48new_dir_file_p (test)
49    struct stat *test;
50{
51  static unsigned dir_file_list_len = 0;
52  static dir_file_list_entry_type *dir_file_list = NULL;
53  unsigned i;
54
55  for (i = 0; i < dir_file_list_len; i++)
56    {
57      dir_file_list_entry_type entry;
58      entry = dir_file_list[i];
59      if (entry.device == test->st_dev && entry.inode == test->st_ino)
60        return 0;
61    }
62
63  dir_file_list_len++;
64  dir_file_list = xrealloc (dir_file_list,
65                        dir_file_list_len * sizeof (dir_file_list_entry_type));
66  dir_file_list[dir_file_list_len - 1].device = test->st_dev;
67  dir_file_list[dir_file_list_len - 1].inode = test->st_ino;
68  return 1;
69}
70
71
72void
73maybe_build_dir_node (dirname)
74     char *dirname;
75{
76  int path_index, update_tags;
77  char *this_dir;
78  FILE_BUFFER *dir_buffer = info_find_file (dirname);
79
80  /* If there is no "dir" in the current info path, we cannot build one
81     from nothing. */
82  if (!dir_buffer)
83    return;
84
85  /* If this directory has already been built, return now. */
86  if (dir_buffer->flags & N_CannotGC)
87    return;
88
89  /* Initialize the list we use to avoid reading the same dir file twice
90     with the dir file just found.  */
91  new_dir_file_p (&dir_buffer->finfo);
92
93  path_index = update_tags = 0;
94
95  /* Using each element of the path, check for one of the files in
96     DIRS_TO_ADD.  Do not check for "localdir.info.Z" or anything else.
97     Only files explictly named are eligible.  This is a design decision.
98     There can be an info file name "localdir.info" which contains
99     information on the setting up of "localdir" files. */
100  while ((this_dir = extract_colon_unit (infopath, &path_index)))
101    {
102      register int da_index;
103      char *from_file;
104
105      /* Expand a leading tilde if one is present. */
106      if (*this_dir == '~')
107        {
108          char *tilde_expanded_dirname;
109
110          tilde_expanded_dirname = tilde_expand_word (this_dir);
111          if (tilde_expanded_dirname != this_dir)
112            {
113              free (this_dir);
114              this_dir = tilde_expanded_dirname;
115            }
116        }
117
118      /* For every different file named in DIRS_TO_ADD found in the
119         search path, add that file's menu to our "dir" node. */
120      for (da_index = 0; (from_file = dirs_to_add[da_index]); da_index++)
121        {
122          struct stat finfo;
123          int statable;
124          int namelen = strlen (from_file);
125          char *fullpath = xmalloc (3 + strlen (this_dir) + namelen);
126
127          strcpy (fullpath, this_dir);
128          if (!IS_SLASH (fullpath[strlen (fullpath) - 1]))
129            strcat (fullpath, "/");
130          strcat (fullpath, from_file);
131
132          statable = (stat (fullpath, &finfo) == 0);
133
134          /* Only add this file if we have not seen it before.  */
135          if (statable && S_ISREG (finfo.st_mode) && new_dir_file_p (&finfo))
136            {
137              long filesize;
138	      int compressed;
139              char *contents = filesys_read_info_file (fullpath, &filesize,
140                                                       &finfo, &compressed);
141              if (contents)
142                {
143                  update_tags++;
144                  add_menu_to_file_buffer (contents, filesize, dir_buffer);
145                  free (contents);
146                }
147            }
148
149          free (fullpath);
150        }
151      free (this_dir);
152    }
153
154  if (update_tags)
155    build_tags_and_nodes (dir_buffer);
156
157  /* Flag that the dir buffer has been built. */
158  dir_buffer->flags |= N_CannotGC;
159}
160
161/* Given CONTENTS and FB (a file buffer), add the menu found in CONTENTS
162   to the menu found in FB->contents.  Second argument SIZE is the total
163   size of CONTENTS. */
164static void
165add_menu_to_file_buffer (contents, size, fb)
166     char *contents;
167     long size;
168     FILE_BUFFER *fb;
169{
170  SEARCH_BINDING contents_binding, fb_binding;
171  long contents_offset, fb_offset;
172
173  contents_binding.buffer = contents;
174  contents_binding.start = 0;
175  contents_binding.end = size;
176  contents_binding.flags = S_FoldCase | S_SkipDest;
177
178  fb_binding.buffer = fb->contents;
179  fb_binding.start = 0;
180  fb_binding.end = fb->filesize;
181  fb_binding.flags = S_FoldCase | S_SkipDest;
182
183  /* Move to the start of the menus in CONTENTS and FB. */
184  contents_offset = search_forward (INFO_MENU_LABEL, &contents_binding);
185  fb_offset = search_forward (INFO_MENU_LABEL, &fb_binding);
186
187  /* If there is no menu in CONTENTS, quit now. */
188  if (contents_offset == -1)
189    return;
190
191  /* There is a menu in CONTENTS, and contents_offset points to the first
192     character following the menu starter string.  Skip all whitespace
193     and newline characters. */
194  contents_offset += skip_whitespace_and_newlines (contents + contents_offset);
195
196  /* If there is no menu in FB, make one. */
197  if (fb_offset == -1)
198    {
199      /* Find the start of the second node in this file buffer.  If there
200         is only one node, we will be adding the contents to the end of
201         this node. */
202      fb_offset = find_node_separator (&fb_binding);
203
204      /* If not even a single node separator, give up. */
205      if (fb_offset == -1)
206        return;
207
208      fb_binding.start = fb_offset;
209      fb_binding.start +=
210        skip_node_separator (fb_binding.buffer + fb_binding.start);
211
212      /* Try to find the next node separator. */
213      fb_offset = find_node_separator (&fb_binding);
214
215      /* If found one, consider that the start of the menu.  Otherwise, the
216         start of this menu is the end of the file buffer (i.e., fb->size). */
217      if (fb_offset != -1)
218        fb_binding.start = fb_offset;
219      else
220        fb_binding.start = fb_binding.end;
221
222      insert_text_into_fb_at_binding
223        (fb, &fb_binding, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL));
224
225      fb_binding.buffer = fb->contents;
226      fb_binding.start = 0;
227      fb_binding.end = fb->filesize;
228      fb_offset = search_forward (INFO_MENU_LABEL, &fb_binding);
229      if (fb_offset == -1)
230        abort ();
231    }
232
233  /* CONTENTS_OFFSET and FB_OFFSET point to the starts of the menus that
234     appear in their respective buffers.  Add the remainder of CONTENTS
235     to the end of FB's menu. */
236  fb_binding.start = fb_offset;
237  fb_offset = find_node_separator (&fb_binding);
238  if (fb_offset != -1)
239    fb_binding.start = fb_offset;
240  else
241    fb_binding.start = fb_binding.end;
242
243  /* Leave exactly one blank line between directory entries. */
244  {
245    int num_found = 0;
246
247    while ((fb_binding.start > 0) &&
248           (whitespace_or_newline (fb_binding.buffer[fb_binding.start - 1])))
249      {
250        num_found++;
251        fb_binding.start--;
252      }
253
254    /* Optimize if possible. */
255    if (num_found >= 2)
256      {
257        fb_binding.buffer[fb_binding.start++] = '\n';
258        fb_binding.buffer[fb_binding.start++] = '\n';
259      }
260    else
261      {
262        /* Do it the hard way. */
263        insert_text_into_fb_at_binding (fb, &fb_binding, "\n\n", 2);
264        fb_binding.start += 2;
265      }
266  }
267
268  /* Insert the new menu. */
269  insert_text_into_fb_at_binding
270    (fb, &fb_binding, contents + contents_offset, size - contents_offset);
271}
272
273static void
274insert_text_into_fb_at_binding (fb, binding, text, textlen)
275     FILE_BUFFER *fb;
276     SEARCH_BINDING *binding;
277     char *text;
278     int textlen;
279{
280  char *contents;
281  long start, end;
282
283  start = binding->start;
284  end = fb->filesize;
285
286  contents = (char *)xmalloc (fb->filesize + textlen + 1);
287  memcpy (contents, fb->contents, start);
288  memcpy (contents + start, text, textlen);
289  memcpy (contents + start + textlen, fb->contents + start, end - start);
290  free (fb->contents);
291  fb->contents = contents;
292  fb->filesize += textlen;
293  fb->finfo.st_size = fb->filesize;
294}
295