/* infopath.c -- INFOPATH handling. Copyright 1993-2023 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "info.h" #include "scan.h" #include "util.h" #include "session.h" #include "filesys.h" typedef struct { char *name; /* Path to directory to be searched. */ dev_t device; /* Storage device this directory is on. */ ino_t inode; /* Inode number, used to detect duplicates. */ } INFO_DIR; INFO_DIR **infodirs = 0; size_t infodirs_index = 0; size_t infodirs_slots = 0; /* Exclude default file search directories. */ int infopath_no_defaults_p; static void infopath_add_dir (char *path); char *extract_colon_unit (char *string, int *idx); void infopath_init () { /* Initialize INFOPATH. Highest priority is the environment variable, if set Then comes the user's INFODIR from the Makefile. The hardwired default settings (filesys.h) are the lowest priority. */ char *path_from_env = getenv ("INFOPATH"); if (path_from_env) { infopath_add (path_from_env); } if (!infopath_no_defaults_p) { #ifdef INFODIR /* $infodir, set by configure script in Makefile */ infopath_add (INFODIR); #ifdef INFODIR2 /* $datadir/info, which could be different. */ if (!STREQ (INFODIR, INFODIR2)) infopath_add (INFODIR2); #endif /* INFODIR2 */ #endif /* INFODIR */ } if (!path_from_env) { infopath_add (DEFAULT_INFOPATH); } else { /* Only insert default path if there is a trailing : on INFOPATH. */ unsigned len = strlen (path_from_env); if (len && path_from_env[len - 1] == PATH_SEP[0]) { path_from_env[len - 1] = 0; infopath_add (DEFAULT_INFOPATH); } } } /* Return value to be freed by caller. */ char * infopath_string () { struct text_buffer path; int dir_idx; char *this_dir; this_dir = infopath_first (&dir_idx); if (!this_dir) return ""; text_buffer_init (&path); while (1) { text_buffer_printf (&path, "%s", this_dir); this_dir = infopath_next (&dir_idx); if (!this_dir) break; text_buffer_add_char (&path, ':'); } return text_buffer_base (&path); } /* For each path element PREFIX/DIR in PATH substitute either PREFIX/share/info or PREFIX/info if that directory exists. */ static void build_infopath_from_path (void) { char *path_from_env, *temp_dirname; int dirname_index = 0; struct stat finfo; path_from_env = getenv ("PATH"); while ((temp_dirname = extract_colon_unit (path_from_env, &dirname_index))) { unsigned int i, dir = 0; /* Find end of DIRNAME/ (but ignore "/") */ for (i = 0; temp_dirname[i]; i++) if (i && IS_SLASH (temp_dirname[i])) dir = i + 1; /* Discard path elements ending with "/", "/.", or "/.." */ if (!temp_dirname[dir] || STREQ (temp_dirname + dir, ".") || STREQ (temp_dirname + dir, ".")) dir = 0; if (dir) { temp_dirname = xrealloc (temp_dirname, dir + strlen ("share/info") +1); /* first try DIRNAME/share/info */ strcpy (temp_dirname + dir, "share/info"); if (stat (temp_dirname, &finfo) != 0 || !S_ISDIR (finfo.st_mode)) { /* then try DIRNAME/info */ strcpy (temp_dirname + dir, "info"); if (stat (temp_dirname, &finfo) != 0 || !S_ISDIR (finfo.st_mode)) dir = 0; } } if (dir) infopath_add_dir (temp_dirname); else free (temp_dirname); } } /* Add directory at PATH to Info search path. A reference to PATH is retained, or PATH is freed. */ static void infopath_add_dir (char *path) { struct stat dirinfo; INFO_DIR *entry; int i; if (stat (path, &dirinfo) == -1) { debug (2, ("inaccessible directory %s not added to INFOPATH", path)); free (path); return; /* Doesn't exist, or not accessible. */ } for (i = 0; i < infodirs_index; i++) { if ( dirinfo.st_ino == infodirs[i]->inode && dirinfo.st_dev == infodirs[i]->device /* On MS-Windows, `stat' returns zero as the inode, so we use file-name comparison instead for that OS. */ && (infodirs[i]->inode != 0 || fncmp (path, infodirs[i]->name) == 0)) { debug (2, ("duplicate directory %s not added to INFOPATH", path)); free (path); return; /* We have it already. */ } } debug (2, ("adding %s to INFOPATH", path)); entry = xmalloc (sizeof (INFO_DIR)); entry->name = path; entry->inode = dirinfo.st_ino; entry->device = dirinfo.st_dev; add_pointer_to_array (entry, infodirs_index, infodirs, infodirs_slots, 8); } /* Add PATH to the list of paths found in INFOPATH. PATH should be allocated on the heap and not referenced by the caller after calling this function. If PATH is "PATH", add a sequence of path elements derived from the environment variable PATH. */ void infopath_add (char *path) { int idx = 0; char *dirname; while ((dirname = extract_colon_unit (path, &idx))) { if (!strcmp ("PATH", dirname)) { free (dirname); build_infopath_from_path (); } else infopath_add_dir (dirname); } } /* Used to iterate over INFOPATH. Return value should not be freed by caller. */ char * infopath_next (int *idx) { INFO_DIR *entry; if (!infodirs) return 0; entry = infodirs[(*idx)++]; if (!entry) return 0; return entry->name; } char * infopath_first (int *idx) { *idx = 0; return infopath_next (idx); } /* Given a string containing units of information separated by the PATH_SEP character, return the next one after IDX, or NULL if there are no more. Advance IDX to the character after the colon. */ char * extract_colon_unit (char *string, int *idx) { unsigned int i = (unsigned int) *idx; unsigned int start = i; if (!string || i >= strlen (string)) return NULL; if (!string[i]) /* end of string */ return NULL; /* Advance to next PATH_SEP. */ while (string[i] && string[i] != PATH_SEP[0]) i++; { char *value = xmalloc ((i - start) + 1); strncpy (value, &string[start], (i - start)); value[i - start] = 0; i++; /* move past PATH_SEP */ *idx = i; return value; } }