1 /* infopath.c -- INFOPATH handling.
2
3 Copyright 1993-2023 Free Software Foundation, Inc.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "info.h"
20 #include "scan.h"
21 #include "util.h"
22 #include "session.h"
23 #include "filesys.h"
24
25 typedef struct {
26 char *name; /* Path to directory to be searched. */
27 dev_t device; /* Storage device this directory is on. */
28 ino_t inode; /* Inode number, used to detect duplicates. */
29 } INFO_DIR;
30
31 INFO_DIR **infodirs = 0;
32 size_t infodirs_index = 0;
33 size_t infodirs_slots = 0;
34
35 /* Exclude default file search directories. */
36 int infopath_no_defaults_p;
37
38 static void infopath_add_dir (char *path);
39 char *extract_colon_unit (char *string, int *idx);
40
41 void
42 infopath_init ()
43 {
44 /* Initialize INFOPATH.
45 Highest priority is the environment variable, if set
46 Then comes the user's INFODIR from the Makefile.
47 The hardwired default settings (filesys.h) are the lowest priority. */
48 char *path_from_env = getenv ("INFOPATH");
49
50 if (path_from_env)
51 {
52 infopath_add (path_from_env);
53 }
54
55 if (!infopath_no_defaults_p)
56 {
57 #ifdef INFODIR /* $infodir, set by configure script in Makefile */
58 infopath_add (INFODIR);
59 #ifdef INFODIR2 /* $datadir/info, which could be different. */
60 if (!STREQ (INFODIR, INFODIR2))
61 infopath_add (INFODIR2);
62 #endif /* INFODIR2 */
63 #endif /* INFODIR */
64 }
65
66 if (!path_from_env)
67 {
68 infopath_add (DEFAULT_INFOPATH);
69 }
70 else
71 {
72 /* Only insert default path if there is a trailing : on INFOPATH. */
73
74 unsigned len = strlen (path_from_env);
75 if (len && path_from_env[len - 1] == PATH_SEP[0])
76 {
77 path_from_env[len - 1] = 0;
78 infopath_add (DEFAULT_INFOPATH);
79 }
80 }
81 }
82
83 /* Return value to be freed by caller. */
84 char *
85 infopath_string ()
86 {
87 struct text_buffer path;
88 int dir_idx;
89 char *this_dir;
90
91 this_dir = infopath_first (&dir_idx);
92 if (!this_dir)
93 return "";
94
95 text_buffer_init (&path);
96
97 while (1)
98 {
99 text_buffer_printf (&path, "%s", this_dir);
100 this_dir = infopath_next (&dir_idx);
101 if (!this_dir)
102 break;
103 text_buffer_add_char (&path, ':');
104 }
105 return text_buffer_base (&path);
106 }
107
108 /* For each path element PREFIX/DIR in PATH substitute either
109 PREFIX/share/info or PREFIX/info if that directory exists. */
110 static void
111 build_infopath_from_path (void)
112 {
113 char *path_from_env, *temp_dirname;
114 int dirname_index = 0;
115 struct stat finfo;
116
117 path_from_env = getenv ("PATH");
118
119 while ((temp_dirname = extract_colon_unit (path_from_env, &dirname_index)))
120 {
121 unsigned int i, dir = 0;
122
123 /* Find end of DIRNAME/ (but ignore "/") */
124 for (i = 0; temp_dirname[i]; i++)
125 if (i && IS_SLASH (temp_dirname[i]))
126 dir = i + 1;
127
128 /* Discard path elements ending with "/", "/.", or "/.." */
129 if (!temp_dirname[dir] || STREQ (temp_dirname + dir, ".") || STREQ (temp_dirname + dir, "."))
130 dir = 0;
131
132 if (dir)
133 {
134 temp_dirname = xrealloc (temp_dirname, dir + strlen ("share/info") +1);
135
136 /* first try DIRNAME/share/info */
137 strcpy (temp_dirname + dir, "share/info");
138 if (stat (temp_dirname, &finfo) != 0 || !S_ISDIR (finfo.st_mode))
139 {
140 /* then try DIRNAME/info */
141 strcpy (temp_dirname + dir, "info");
142 if (stat (temp_dirname, &finfo) != 0 || !S_ISDIR (finfo.st_mode))
143 dir = 0;
144 }
145 }
146
147 if (dir)
148 infopath_add_dir (temp_dirname);
149 else
150 free (temp_dirname);
151 }
152 }
153
154 /* Add directory at PATH to Info search path. A reference to PATH is retained,
155 or PATH is freed. */
156 static void
157 infopath_add_dir (char *path)
158 {
159 struct stat dirinfo;
160 INFO_DIR *entry;
161 int i;
162
163 if (stat (path, &dirinfo) == -1)
164 {
165 debug (2, ("inaccessible directory %s not added to INFOPATH", path));
166 free (path);
167 return; /* Doesn't exist, or not accessible. */
168 }
169
170 for (i = 0; i < infodirs_index; i++)
171 {
172 if ( dirinfo.st_ino == infodirs[i]->inode
173 && dirinfo.st_dev == infodirs[i]->device
174 /* On MS-Windows, `stat' returns zero as the inode, so we
175 use file-name comparison instead for that OS. */
176 && (infodirs[i]->inode != 0 || fncmp (path, infodirs[i]->name) == 0))
177 {
178 debug (2, ("duplicate directory %s not added to INFOPATH", path));
179 free (path);
180 return; /* We have it already. */
181 }
182 }
183
184 debug (2, ("adding %s to INFOPATH", path));
185 entry = xmalloc (sizeof (INFO_DIR));
186 entry->name = path;
187 entry->inode = dirinfo.st_ino;
188 entry->device = dirinfo.st_dev;
189 add_pointer_to_array (entry, infodirs_index, infodirs, infodirs_slots, 8);
190 }
191
192 /* Add PATH to the list of paths found in INFOPATH. PATH should be allocated
193 on the heap and not referenced by the caller after calling this function.
194 If PATH is "PATH", add a sequence of path elements derived from the
195 environment variable PATH. */
196 void
197 infopath_add (char *path)
198 {
199 int idx = 0;
200 char *dirname;
201
202 while ((dirname = extract_colon_unit (path, &idx)))
203 {
204 if (!strcmp ("PATH", dirname))
205 {
206 free (dirname);
207 build_infopath_from_path ();
208 }
209 else
210 infopath_add_dir (dirname);
211 }
212 }
213
214 /* Used to iterate over INFOPATH. Return value should not be freed
215 by caller. */
216 char *
217 infopath_next (int *idx)
218 {
219 INFO_DIR *entry;
220
221 if (!infodirs)
222 return 0;
223 entry = infodirs[(*idx)++];
224 if (!entry)
225 return 0;
226 return entry->name;
227 }
228
229 char *
230 infopath_first (int *idx)
231 {
232 *idx = 0;
233 return infopath_next (idx);
234 }
235
236 /* Given a string containing units of information separated by the
237 PATH_SEP character, return the next one after IDX, or NULL if there
238 are no more. Advance IDX to the character after the colon. */
239 char *
240 extract_colon_unit (char *string, int *idx)
241 {
242 unsigned int i = (unsigned int) *idx;
243 unsigned int start = i;
244
245 if (!string || i >= strlen (string))
246 return NULL;
247
248 if (!string[i]) /* end of string */
249 return NULL;
250
251 /* Advance to next PATH_SEP. */
252 while (string[i] && string[i] != PATH_SEP[0])
253 i++;
254
255 {
256 char *value = xmalloc ((i - start) + 1);
257 strncpy (value, &string[start], (i - start));
258 value[i - start] = 0;
259
260 i++; /* move past PATH_SEP */
261 *idx = i;
262 return value;
263 }
264 }