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#include <argp.h>
11#include <cm.h>
12#include <fcntl.h>
13#include <limits.h>
14#include <stdbool.h>
15#include <stddef.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19#include <sys/stat.h>
20#include <sys/types.h>
21#include <unistd.h>
22
23char *path_p;
25char *file_name[MAXLEN + 1];
26
27void whence(char *, int);
28int next_path(char *, char **);
29int file_spec_parts(char *, char *, char *);
30void ABEND(char *, int, char *);
31void normalend();
32enum { WH_ALL = 1, WH_VERBOSE = 2 };
33int wh_flags = 0;
35const char *argp_program_bug_address = "billxwaller@gmail.com";
36static char doc[] = "whence locate files in path";
37static char args_doc[] = "";
38
39static struct argp_option options[] = {
40 {"all", 'a', 0, 0, "list all matches", 0},
41 {"verbose", 'v', 0, 0, "verbose messages", 0},
42 {0}};
43
44struct wh_opts {
45 int flags;
46 char *argv[MAXARGS];
47};
48
49static error_t parse_opt(int key, char *arg, struct argp_state *state) {
50 struct wh_opts *wh_opts = state->input;
51 int i = 0;
52 switch (key) {
53 case 'a':
54 wh_opts->flags |= WH_ALL;
55 break;
56 case 'v':
57 wh_opts->flags |= WH_VERBOSE;
58 break;
59 case ARGP_KEY_ARG:
60 if (i >= 1) {
61 argp_usage(state);
62 }
63 wh_opts->argv[i] = arg;
64 optind = i;
65 i++;
66 break;
67 case ARGP_KEY_END:
68 break;
69 default:
70 return ARGP_ERR_UNKNOWN;
71 }
72 return 0;
73}
74static struct argp argp = {options, parse_opt, args_doc, doc,
76
77int main(int argc, char **argv) {
78 struct wh_opts wh_opts = {0};
79 wh_opts.flags = 0;
80 wh_opts.argv[0] = nullptr;
81
82 argp_parse(&argp, argc, argv, 0, 0, &wh_opts);
83 path_p = getenv("PATH");
84 if (path_p == nullptr)
85 ABEND(argv[0], 0, "PATH environment variable not set");
86 if (wh_opts.flags & WH_VERBOSE)
87 printf("%s\n", path_p);
88 while (optind < argc) {
89 whence(wh_opts.argv[optind++], wh_opts.flags);
90 }
92}
93/** @brief Find the full path of a file in the directories specified by the PATH
94 environment variable
95 @param file_spec_p The file specification to search for
96 @param flags Flags to control the behavior of the search (e.g., verbose
97 mode, list all matches)
98 @note This function takes a file specification, extracts the directory and
99 file name components, and searches through the directories specified in the
100 PATH environment variable to find matches. It prints the full path of each
101 match found, and if verbose mode is enabled, it also indicates whether each
102 attempted path was found or not. */
103void whence(char *file_spec_p, int flags) {
104 char file_spec[PATH_MAX];
105 char file_dir[PATH_MAX];
106 char file_name[PATH_MAX];
107 char try_spec[PATH_MAX];
108 char try_dir[PATH_MAX];
109 int path_l;
110 struct stat stat_struct;
111
112 strnz__cpy(file_spec, file_spec_p, MAXLEN - 1);
114 file_spec_parts(file_spec, file_dir, file_name);
115 path_p = path_s;
116 path_l = next_path(try_dir, &path_p);
117 while (path_l != 0) {
118 strnz__cpy(try_spec, try_dir, MAXLEN - 1);
119 if (try_spec[path_l] != '/')
120 strnz__cat(try_spec, "/", MAXLEN - 1);
121 strnz__cat(try_spec, file_name, MAXLEN - 1);
122 if (flags & WH_VERBOSE) {
123 if (stat(try_spec, &stat_struct) == -1)
124 printf("- %s\n", try_spec);
125 else
126 printf("found %s\n", try_spec);
127 } else if (stat(try_spec, &stat_struct) == 0) {
128 printf("%s\n", try_spec);
129 if (!(flags & WH_ALL))
130 return;
131 }
132 path_l = next_path(try_dir, &path_p);
133 }
134}
135/** @brief Extract the next directory path from the PATH string
136 @param dp A buffer to store the extracted directory path
137 @param sp A pointer to the current position in the PATH string
138 @return The length of the extracted directory path
139 @note This function takes a buffer and a pointer to the current position in
140 the PATH string, and extracts the next directory path. If the next character
141 in the PATH string is a colon, it treats it as an empty path and uses the
142 current working directory. Otherwise, it copies characters until it reaches a
143 colon or the end of the string, and returns the length of the extracted path.
144 */
145int next_path(char *dp, char **sp) {
146 int dl;
147
148 if (**sp == ':') {
149 (*sp)++;
150 getcwd(dp, PATH_MAX);
151 return (strlen(dp));
152 } else {
153 dl = 0;
154 while (**sp != '\0' && **sp != '\n' && **sp != '\r' && **sp != ':') {
155 *dp++ = *(*sp)++;
156 dl++;
157 }
158 *dp = '\0';
159 if (**sp == ':' && *++(*sp) == '\0')
160 (*sp)--;
161 return (dl);
162 }
163}
164/** @brief Split a file specification into directory and file name components
165 @param file_spec The full file specification to split
166 @param file_path A buffer to store the extracted directory path
167 @param file_name A buffer to store the extracted file name
168 @return 0 on success
169 @note This function takes a file specification, checks if it is a directory,
170 and if so, it sets the file path accordingly. If the file specification is
171 empty, it defaults to the current directory. Otherwise, it splits the file
172 specification into the directory and file name components based on the last
173 occurrence of a slash ('/'). */
174int file_spec_parts(char *file_spec, char *file_path, char *file_name) {
175 int i, last_slash;
176 char tmp_file_spec[PATH_MAX];
177 int file_spec_l;
178 struct stat stat_struct;
179
180 if (stat(file_spec, &stat_struct) != -1)
181 if ((stat_struct.st_mode & S_IFMT) == S_IFDIR) {
182 if (file_spec[strlen(file_path)] != '/')
183 strnz__cat(file_spec, "/", MAXLEN - 1);
184 strnz__cpy(file_path, file_spec, MAXLEN - 1);
185 file_name[0] = '\0';
186 return (0);
187 }
188 if (strlen(file_spec) == 0) {
189 strnz__cpy(file_spec, "./", MAXLEN - 1);
190 file_name[0] = '\0';
191 return (0);
192 }
193 strnz__cpy(tmp_file_spec, file_spec, MAXLEN - 1);
194 last_slash = -1;
195 file_spec_l = strlen(tmp_file_spec);
196 if (file_spec_l > 0) {
197 i = 0;
198 while (i < file_spec_l && tmp_file_spec[i] != '\0') {
199 if (tmp_file_spec[i] == '/') {
200 last_slash = i;
201 break;
202 }
203 i++;
204 }
205 }
206 if (last_slash < 0) {
207 strnz__cpy(file_path, "./", MAXLEN - 1);
208 if (strcmp(file_spec, ".") == 0)
209 file_name[0] = '\0';
210 else
211 strnz__cpy(file_name, tmp_file_spec, MAXLEN - 1);
212 strnz__cpy(file_spec, file_path, MAXLEN - 1);
213 strnz__cat(file_spec, file_name, MAXLEN - 1);
214 } else {
215 tmp_file_spec[last_slash] = '\0';
216 strnz__cpy(file_path, tmp_file_spec, MAXLEN - 1);
217 strnz__cat(file_path, "/", MAXLEN - 1);
218 if (last_slash < file_spec_l)
219 last_slash++;
220 strnz__cpy(file_name, tmp_file_spec + last_slash, MAXLEN - 1);
221 }
222 return (0);
223}
224/** @brief Exit the program successfully
225 @note This function is called to exit the program with a success status. It
226 simply calls the exit function with EXIT_SUCCESS. */
227void normalend() { exit(EXIT_SUCCESS); }
228/** @brief Exit the program with an error message
229 @param pgmid The name of the program
230 @param rc The return code to exit with
231 @param err_msg The error message to display
232 @note This function is called to exit the program with an error status. It
233 prints the program name, return code, and error message to the standard error
234 stream, and then exits with the specified return code. */
235void ABEND(char *pgmid, int rc, char *err_msg) {
236 fprintf(stderr, "%s; error %d; %s\n", pgmid, rc, err_msg);
237 exit(EXIT_FAILURE);
238}
#define MAXARGS
Definition cm.h:30
#define nullptr
Definition cm.h:23
#define CM_VERSION
Definition version.h:7
void ABEND(int)
Definition iloan.c:402
#define MAXLEN
Definition curskeys.c:15
char * file_name[MAXLEN+1]
Definition whence.c:25
@ WH_ALL
Definition whence.c:32
@ WH_VERBOSE
Definition whence.c:32
void normalend()
Exit the program successfully.
Definition whence.c:227
void whence(char *, int)
Find the full path of a file in the directories specified by the PATH environment variable.
Definition whence.c:103
char * path_p
Definition whence.c:23
char path_s[MAXLEN]
Definition whence.c:24
int wh_flags
Definition whence.c:33
int file_spec_parts(char *, char *, char *)
Split a file specification into directory and file name components.
Definition whence.c:174
int next_path(char *, char **)
Extract the next directory path from the PATH string.
Definition whence.c:145
const char * argp_program_version
Definition init.c:83
const char * argp_program_bug_address
Definition init.c:84
int main(int argc, char **argv)
Capture the current terminal settings for later restoration.
Definition enterchr.c:38
size_t strnz__cpy(char *, const char *, size_t)
safer alternative to strncpy
Definition futil.c:269
size_t strnz__cat(char *, const char *, size_t)
safer alternative to strncat
Definition futil.c:298