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