C-Menu 0.2.9
A User Interface Toolkit
Loading...
Searching...
No Matches
whence.c
Go to the documentation of this file.
1/** @file whence.c
2 @brief Find the full path of a file in the directories specified by the
3 @author Bill Waller
4 Copyright (c) 2025
5 MIT License
6 billxwaller@gmail.com
7 @date 2026-02-09
8 */
9
10#define _GNU_SOURCE
11#include "cm.h"
12#include <argp.h>
13#include <fcntl.h>
14#include <limits.h>
15#include <stdbool.h>
16#include <stddef.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <sys/stat.h>
21#include <sys/types.h>
22#include <unistd.h>
23
24char *path_p;
26// char *file_name[MAXLEN + 1];
27
28int whence(char *, int);
29int next_path(char *, char **);
30int file_spec_parts(char *, char *, char *);
31void ABEND(char *, int, char *);
32size_t strnz__cat(char *, const char *, size_t);
33size_t strnz__cpy(char *, const char *, size_t);
34typedef enum { WH_ALL = 1,
37 WH_SETUID = 8 } WhenceFlags;
38int wh_flags = 0;
40const char *argp_program_bug_address = "billxwaller@gmail.com";
41static char doc[] = _("whence locate files in path");
42static char args_doc[] = "";
43
44static struct argp_option options[] = {
45 {_("all"), 'a', 0, 0, _("list all matches"), 0},
46 {_("setuid"), 's', 0, 0, _("setuid only"), 0},
47 {_("executable"), 'x', 0, 0, _("executable only"), 0},
48 {_("path"), 'p', "text", 0, _("colon separated path string"), 0},
49 {_("verbose"), 'v', 0, 0, _("verbose messages"), 0},
50 {}};
51
52struct wh_opts {
53 int flags;
54 int argc;
55 char *path_env;
56 char *argv[MAXARGS];
57};
58
59static error_t parse_opt(int key, char *arg, struct argp_state *state) {
60 struct wh_opts *wh_opts = state->input;
61 switch (key) {
62 case 'a':
63 wh_opts->flags |= WH_ALL;
64 break;
65 case 's':
66 wh_opts->flags |= WH_SETUID;
67 break;
68 case 'p':
69 wh_opts->path_env = strdup(arg);
70 break;
71 case 'v':
72 wh_opts->flags |= WH_VERBOSE;
73 break;
74 case 'x':
75 wh_opts->flags |= WH_EXECUTABLE;
76 break;
77 case ARGP_KEY_ARG:
78 if (state->arg_num == 0 || state->arg_num == 1) {
79 wh_opts->argv[state->arg_num] = arg;
80 wh_opts->argc = state->arg_num + 1;
81 } else {
82 argp_usage(state);
83 }
84 break;
85 case ARGP_KEY_END:
86 break;
87 default:
88 return ARGP_ERR_UNKNOWN;
89 }
90 return 0;
91}
92static struct argp argp = {options, parse_opt, args_doc, doc,
93 nullptr, nullptr, nullptr};
94
95int main(int argc, char **argv) {
96 struct wh_opts wh_opts = {};
97 wh_opts.flags = 0;
98 wh_opts.argv[0] = nullptr;
99 wh_opts.path_env = nullptr;
100 int i = 0;
101 int found = 0;
102 argp_parse(&argp, argc, argv, 0, 0, &wh_opts);
103 if (wh_opts.path_env != nullptr)
104 path_p = getenv(wh_opts.path_env);
105 else
106 path_p = getenv("PATH");
107 if (path_p == nullptr)
108 ABEND(argv[0], 0, _("Search PATH not set"));
109 if (wh_opts.flags & WH_VERBOSE)
110 printf("%s\n", path_p);
111 while (i < wh_opts.argc)
112 found = whence(wh_opts.argv[i++], wh_opts.flags);
113 if (found == 0)
114 exit(1);
115 exit(0);
116}
117/** @brief Find the full path of a file in the directories specified by the PATH
118 environment variable
119 @param file_spec_p The file specification to search for
120 @param flags Flags to control the behavior of the search (e.g., verbose
121 mode, list all matches)
122 @details This function takes a file specification, extracts the directory
123 and file name components, and searches through the directories specified in
124 the PATH environment variable to find matches. It prints the full path of
125 each match found, and if verbose mode is enabled, it also indicates whether
126 each attempted path was found or not. */
127int whence(char *file_spec_p, int flags) {
128 char file_spec[PATH_MAX];
129 char file_dir[PATH_MAX];
130 char file_name[PATH_MAX];
131 char try_spec[PATH_MAX];
132 char try_dir[PATH_MAX];
133 int path_l;
134 int found = 0;
135 struct stat stat_struct;
136
137 strnz__cpy(file_spec, file_spec_p, MAXLEN - 1);
139 file_spec_parts(file_spec, file_dir, file_name);
140 path_p = path_s;
141 path_l = next_path(try_dir, &path_p);
142 while (path_l != 0) {
143 strnz__cpy(try_spec, try_dir, MAXLEN - 1);
144 if (try_spec[path_l] != '/')
145 strnz__cat(try_spec, "/", MAXLEN - 1);
146 strnz__cat(try_spec, file_name, MAXLEN - 1);
147 if (stat(try_spec, &stat_struct) != 0) {
148 if (flags & WH_VERBOSE)
149 printf("- %s\n", try_spec);
150 path_l = next_path(try_dir, &path_p);
151 continue;
152 }
153 if (flags & WH_SETUID)
154 if (!(stat_struct.st_mode & S_ISUID)) {
155 path_l = next_path(try_dir, &path_p);
156 continue;
157 }
158 if (flags & WH_EXECUTABLE)
159 if (!(stat_struct.st_mode & S_ISUID)) {
160 path_l = next_path(try_dir, &path_p);
161 continue;
162 }
163 if (!(stat_struct.st_mode & S_IXUSR)) {
164 path_l = next_path(try_dir, &path_p);
165 continue;
166 }
167 found++;
168 if (flags & WH_VERBOSE)
169 printf(_("found %s\n"), try_spec);
170 else {
171 printf("%s\n", try_spec);
172 if (!(flags & WH_ALL))
173 return found;
174 }
175 path_l = next_path(try_dir, &path_p);
176 }
177 return found;
178}
179/** @brief Get the next directory path from a colon-separated list of paths
180 @param dp A buffer to store the extracted directory path
181 @param pp A pointer to the current position in the path string
182 @return The length of the extracted directory path
183 @details This function takes a pointer to a colon-separated list of paths
184 and extracts the next directory path from it. If the current character is a
185 colon, it retrieves the current working directory instead. The extracted
186 directory path is stored in the provided buffer, and the function returns
187 the length of the extracted path. */
188int next_path(char *dp, char **pp) {
189 int dl;
190
191 if (**pp == ':') {
192 (*pp)++;
193 getcwd(dp, PATH_MAX);
194 return (strlen(dp));
195 } else {
196 dl = 0;
197 while (**pp != '\0' && **pp != '\n' && **pp != '\r' && **pp != ':') {
198 *dp++ = *(*pp)++;
199 dl++;
200 }
201 *dp = '\0';
202 if (**pp == ':' && *++(*pp) == '\0')
203 (*pp)--;
204 return (dl);
205 }
206}
207/** @brief Split a file specification into directory and file name components
208 @param file_spec The full file specification to split
209 @param file_path A buffer to store the extracted directory path
210 @param file_name A buffer to store the extracted file name
211 @return 0 on success
212 @details This function takes a file specification, checks if it is a
213 directory, and if so, it sets the file path accordingly. If the file
214 specification is empty, it defaults to the current directory. Otherwise, it
215 splits the file specification into the directory and file name components
216 based on the last occurrence of a slash ('/'). */
217int file_spec_parts(char *file_spec, char *file_path, char *file_name) {
218 int i, last_slash;
219 char tmp_file_spec[PATH_MAX];
220 int file_spec_l;
221 struct stat stat_struct;
222
223 if (stat(file_spec, &stat_struct) != -1)
224 if ((stat_struct.st_mode & S_IFMT) == S_IFDIR) {
225 if (file_spec[strlen(file_path)] != '/')
226 strnz__cat(file_spec, "/", MAXLEN - 1);
227 strnz__cpy(file_path, file_spec, MAXLEN - 1);
228 file_name[0] = '\0';
229 return (0);
230 }
231 if (strlen(file_spec) == 0) {
232 strnz__cpy(file_spec, "./", MAXLEN - 1);
233 file_name[0] = '\0';
234 return (0);
235 }
236 strnz__cpy(tmp_file_spec, file_spec, MAXLEN - 1);
237 last_slash = -1;
238 file_spec_l = strlen(tmp_file_spec);
239 if (file_spec_l > 0) {
240 i = 0;
241 while (i < file_spec_l && tmp_file_spec[i] != '\0') {
242 if (tmp_file_spec[i] == '/') {
243 last_slash = i;
244 break;
245 }
246 i++;
247 }
248 }
249 if (last_slash < 0) {
250 strnz__cpy(file_path, "./", MAXLEN - 1);
251 if (strcmp(file_spec, ".") == 0)
252 file_name[0] = '\0';
253 else
254 strnz__cpy(file_name, tmp_file_spec, MAXLEN - 1);
255 strnz__cpy(file_spec, file_path, MAXLEN - 1);
256 strnz__cat(file_spec, file_name, MAXLEN - 1);
257 } else {
258 tmp_file_spec[last_slash] = '\0';
259 strnz__cpy(file_path, tmp_file_spec, MAXLEN - 1);
260 strnz__cat(file_path, "/", MAXLEN - 1);
261 if (last_slash < file_spec_l)
262 last_slash++;
263 strnz__cpy(file_name, tmp_file_spec + last_slash, MAXLEN - 1);
264 }
265 return (0);
266}
267/** @brief Exit the program with an error message
268 @param pgmid The name of the program
269 @param rc The return code to exit with
270 @param err_msg The error message to display
271 @details This function is called to exit the program with an error status.
272 It prints the program name, return code, and error message to the standard
273 error stream, and then exits with the specified return code. */
274void ABEND(char *pgmid, int rc, char *err_msg) {
275 fprintf(stderr, _("%s; error %d; %s\n"), pgmid, rc, err_msg);
276 exit(EXIT_FAILURE);
277}
278
279/** @brief safer alternative to strncpy
280 @ingroup utility_functions
281 @details copies string s to d, ensuring that the total length of d does not
282 exceed max_len, and that the resulting string is null-terminated. It also
283 treats newline and carriage return characters as string terminators,
284 preventing them from being included in the result. This is particularly
285 useful when copying user input or file data, where embedded newlines could
286 cause issues.
287 @param d - destination string
288 @param s - source string
289 @param max_len - maximum length to copy
290 @returns length of resulting string */
291size_t strnz__cpy(char *d, const char *s, size_t max_len) {
292 char *e;
293 size_t len = 0;
294 if (max_len == 0) {
295 if (max_len > 0)
296 *d = '\0';
297 return 0;
298 }
299 e = d + max_len;
300 while (*s != '\0' && *s != '\n' && *s != '\r' && d < e) {
301 *d++ = *s++;
302 len++;
303 }
304 *d = '\0';
305 return len;
306}
307/** @brief safer alternative to strncat
308 @ingroup utility_functions
309 @param d - destination string
310 @param s - source string
311 @param max_len - maximum length to copy
312 @returns length of resulting string
313 @details Append string s to d, ensuring that the total length of d does not
314 exceed max_len, and that the resulting string is null-terminated. It also
315 treats newline and carriage return characters as string terminators,
316 preventing them from being included in the result. This is particularly
317 useful when concatenating user input or file data, where embedded newlines
318 could cause issues.
319 */
320size_t strnz__cat(char *d, const char *s, size_t max_len) {
321 char *e;
322 size_t len = 0;
323 if (max_len == 0) {
324 if (max_len > 0)
325 *d = '\0';
326 return 0;
327 }
328 e = d + max_len;
329 while (*d != '\0' && *d != '\n' && *d != '\r' && d < e) {
330 d++;
331 len++;
332 }
333 while (*s != '\0' && *s != '\n' && *s != '\r' && d < e) {
334 *d++ = *s++;
335 len++;
336 }
337 *d = '\0';
338 return len;
339}
#define MAXARGS
Definition cm.h:64
#define CM_VERSION
Definition version.h:5
void ABEND(int)
The ABEND function is a signal handler that is called when the program receives certain signals (e....
Definition iloan.c:429
#define MAXLEN
Definition curskeys.c:15
int whence(char *, int)
Find the full path of a file in the directories specified by the PATH environment variable.
Definition whence.c:127
char * path_p
Definition whence.c:24
int next_path(char *, char **)
Get the next directory path from a colon-separated list of paths.
Definition whence.c:188
int wh_flags
Definition whence.c:38
int file_spec_parts(char *, char *, char *)
Split a file specification into directory and file name components.
Definition whence.c:217
@ WH_SETUID
Definition whence.c:37
@ WH_ALL
Definition whence.c:34
@ WH_VERBOSE
Definition whence.c:35
@ WH_EXECUTABLE
Definition whence.c:36
char path_s[MAXLEN]
Definition whence.c:25
const char * argp_program_version
Definition init.c:110
const char * argp_program_bug_address
Definition init.c:111
#define _(String)
Definition amort.c:23
int main(int argc, char **argv)
Capture the current terminal settings for later restoration.
Definition amort.c:41
size_t strnz__cpy(char *, const char *, size_t)
safer alternative to strncpy
Definition whence.c:291
size_t strnz__cat(char *, const char *, size_t)
safer alternative to strncat
Definition whence.c:320