NeoMutt  2023-03-22
Teaching an old dog new tricks
DOXYGEN
path.c File Reference

Path manipulation functions. More...

#include "config.h"
#include <errno.h>
#include <libgen.h>
#include <limits.h>
#include <pwd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "path.h"
#include "buffer.h"
#include "logging.h"
#include "memory.h"
#include "message.h"
#include "string2.h"
+ Include dependency graph for path.c:

Go to the source code of this file.

Functions

bool mutt_path_tidy_slash (char *buf, bool is_dir)
 Remove unnecessary slashes and dots. More...
 
bool mutt_path_tidy_dotdot (char *buf)
 Remove dot-dot-slash from a path. More...
 
bool mutt_path_tidy (char *buf, bool is_dir)
 Remove unnecessary parts of a path. More...
 
bool mutt_path_pretty (char *buf, size_t buflen, const char *homedir, bool is_dir)
 Tidy a filesystem path. More...
 
bool mutt_path_tilde (char *buf, size_t buflen, const char *homedir)
 Expand '~' in a path. More...
 
bool mutt_path_canon (char *buf, size_t buflen, const char *homedir, bool is_dir)
 Create the canonical version of a path. More...
 
const char * mutt_path_basename (const char *f)
 Find the last component for a pathname. More...
 
char * mutt_path_concat (char *d, const char *dir, const char *fname, size_t l)
 Join a directory name and a filename. More...
 
char * mutt_path_dirname (const char *path)
 Return a path up to, but not including, the final '/'. More...
 
bool mutt_path_to_absolute (char *path, const char *reference)
 Convert relative filepath to an absolute path. More...
 
size_t mutt_path_realpath (char *buf)
 Resolve path, unraveling symlinks. More...
 
bool mutt_path_parent (char *buf)
 Find the parent of a path. More...
 
bool mutt_path_abbr_folder (char *buf, const char *folder)
 Create a folder abbreviation. More...
 
char * mutt_path_escape (const char *src)
 Escapes single quotes in a path for a command string. More...
 
const char * mutt_path_getcwd (struct Buffer *cwd)
 Get the current working directory. More...
 

Detailed Description

Path manipulation functions.

Authors
  • Richard Russon

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 http://www.gnu.org/licenses/.

Definition in file path.c.

Function Documentation

◆ mutt_path_tidy_slash()

bool mutt_path_tidy_slash ( char *  buf,
bool  is_dir 
)

Remove unnecessary slashes and dots.

Parameters
[in,out]bufPath to modify
[in]is_dirShould a trailing / be removed?
Return values
trueSuccess

Collapse repeated '//' and '/./'

Definition at line 54 of file path.c.

55{
56 if (!buf)
57 return false;
58
59 char *r = buf;
60 char *w = buf;
61
62 while (*r != '\0')
63 {
64 *w++ = *r++;
65
66 if (r[-1] == '/') /* After a '/' ... */
67 {
68 for (; (r[0] == '/') || (r[0] == '.'); r++)
69 {
70 if (r[0] == '/') /* skip multiple /s */
71 continue;
72 if (r[0] == '.')
73 {
74 if (r[1] == '/') /* skip /./ */
75 {
76 r++;
77 continue;
78 }
79 else if (r[1] == '\0') /* skip /.$ */
80 {
81 r[0] = '\0';
82 }
83 break; /* dot-anything-else isn't special */
84 }
85 }
86 }
87 }
88
89 /* Strip a trailing / as long as it's not the only character */
90 if (is_dir && (w > (buf + 1)) && (w[-1] == '/'))
91 w--;
92
93 *w = '\0';
94
95 return true;
96}
+ Here is the caller graph for this function:

◆ mutt_path_tidy_dotdot()

bool mutt_path_tidy_dotdot ( char *  buf)

Remove dot-dot-slash from a path.

Parameters
[in,out]bufPath to modify
Return values
trueSuccess

Collapse dot-dot patterns, like '/dir/../'

Definition at line 105 of file path.c.

106{
107 if (!buf || (buf[0] != '/'))
108 return false;
109
110 char *dd = buf;
111
112 mutt_debug(LL_DEBUG3, "Collapse path: %s\n", buf);
113 while ((dd = strstr(dd, "/..")))
114 {
115 if (dd[3] == '/') /* paths follow dots */
116 {
117 char *dest = NULL;
118 if (dd != buf) /* not at start of string */
119 {
120 dd[0] = '\0';
121 dest = strrchr(buf, '/');
122 }
123 if (!dest)
124 dest = buf;
125
126 memmove(dest, dd + 3, strlen(dd + 3) + 1);
127 }
128 else if (dd[3] == '\0') /* dots at end of string */
129 {
130 if (dd == buf) /* at start of string */
131 {
132 dd[1] = '\0';
133 }
134 else
135 {
136 dd[0] = '\0';
137 char *s = strrchr(buf, '/');
138 if (s == buf)
139 s[1] = '\0';
140 else if (s)
141 s[0] = '\0';
142 }
143 }
144 else
145 {
146 dd += 3; /* skip over '/..dir/' */
147 continue;
148 }
149
150 dd = buf; /* restart at the beginning */
151 }
152
153 mutt_debug(LL_DEBUG3, "Collapsed to: %s\n", buf);
154 return true;
155}
#define mutt_debug(LEVEL,...)
Definition: logging.h:84
@ LL_DEBUG3
Log at debug level 3.
Definition: logging.h:42
+ Here is the caller graph for this function:

◆ mutt_path_tidy()

bool mutt_path_tidy ( char *  buf,
bool  is_dir 
)

Remove unnecessary parts of a path.

Parameters
[in,out]bufPath to modify
[in]is_dirIs the path a directory?
Return values
trueSuccess

Remove unnecessary dots and slashes from a path

Definition at line 165 of file path.c.

166{
167 if (!buf || (buf[0] != '/'))
168 return false;
169
170 if (!mutt_path_tidy_slash(buf, is_dir))
171 return false;
172
173 return mutt_path_tidy_dotdot(buf);
174}
bool mutt_path_tidy_dotdot(char *buf)
Remove dot-dot-slash from a path.
Definition: path.c:105
bool mutt_path_tidy_slash(char *buf, bool is_dir)
Remove unnecessary slashes and dots.
Definition: path.c:54
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_path_pretty()

bool mutt_path_pretty ( char *  buf,
size_t  buflen,
const char *  homedir,
bool  is_dir 
)

Tidy a filesystem path.

Parameters
bufPath to modify
buflenLength of the buffer
homedirHome directory for '~' substitution
is_dirIs the path a directory?
Return values
trueSuccess

Tidy a path and replace a home directory with '~'

Definition at line 186 of file path.c.

187{
188 if (!buf)
189 return false;
190
191 mutt_path_tidy(buf, is_dir);
192
193 size_t len = mutt_str_startswith(buf, homedir);
194 if (len == 0)
195 return false;
196
197 if ((buf[len] != '/') && (buf[len] != '\0'))
198 return false;
199
200 buf[0] = '~';
201 if (buf[len] == '\0')
202 {
203 buf[1] = '\0';
204 return true;
205 }
206
207 mutt_str_copy(buf + 1, buf + len, buflen - len);
208 return true;
209}
bool mutt_path_tidy(char *buf, bool is_dir)
Remove unnecessary parts of a path.
Definition: path.c:165
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:227
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:652
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_path_tilde()

bool mutt_path_tilde ( char *  buf,
size_t  buflen,
const char *  homedir 
)

Expand '~' in a path.

Parameters
bufPath to modify
buflenLength of the buffer
homedirHome directory for '~' substitution
Return values
trueSuccess

Behaviour:

  • ~/dir (~ expanded)
  • ~realuser/dir (~realuser expanded)
  • ~nonuser/dir (~nonuser not changed)

Definition at line 223 of file path.c.

224{
225 if (!buf || (buf[0] != '~'))
226 return false;
227
228 char result[PATH_MAX] = { 0 };
229 char *dir = NULL;
230 size_t len = 0;
231
232 if ((buf[1] == '/') || (buf[1] == '\0'))
233 {
234 if (!homedir)
235 {
236 mutt_debug(LL_DEBUG3, "no homedir\n");
237 return false;
238 }
239
240 len = mutt_str_copy(result, homedir, sizeof(result));
241 dir = buf + 1;
242 }
243 else
244 {
245 char user[128] = { 0 };
246 dir = strchr(buf + 1, '/');
247 if (dir)
248 mutt_str_copy(user, buf + 1, MIN(dir - buf, (unsigned) sizeof(user)));
249 else
250 mutt_str_copy(user, buf + 1, sizeof(user));
251
252 struct passwd *pw = getpwnam(user);
253 if (!pw || !pw->pw_dir)
254 {
255 mutt_debug(LL_DEBUG1, "no such user: %s\n", user);
256 return false;
257 }
258
259 len = mutt_str_copy(result, pw->pw_dir, sizeof(result));
260 }
261
262 size_t dirlen = mutt_str_len(dir);
263 if ((len + dirlen) >= buflen)
264 {
265 mutt_debug(LL_DEBUG3, "result too big for the buffer %ld >= %ld\n", len + dirlen, buflen);
266 return false;
267 }
268
269 mutt_str_copy(result + len, dir, sizeof(result) - len);
270 mutt_str_copy(buf, result, buflen);
271
272 return true;
273}
@ LL_DEBUG1
Log at debug level 1.
Definition: logging.h:40
#define MIN(a, b)
Definition: memory.h:31
size_t mutt_str_len(const char *a)
Calculate the length of a string, safely.
Definition: string.c:567
#define PATH_MAX
Definition: mutt.h:41
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_path_canon()

bool mutt_path_canon ( char *  buf,
size_t  buflen,
const char *  homedir,
bool  is_dir 
)

Create the canonical version of a path.

Parameters
bufPath to modify
buflenLength of the buffer
homedirHome directory for '~' substitution
is_dirIs the path a directory?
Return values
trueSuccess

Remove unnecessary dots and slashes from a path and expand '~'.

Definition at line 285 of file path.c.

286{
287 if (!buf)
288 return false;
289
290 char result[PATH_MAX] = { 0 };
291
292 if (buf[0] == '~')
293 {
294 mutt_path_tilde(buf, buflen, homedir);
295 }
296 else if (buf[0] != '/')
297 {
298 if (!getcwd(result, sizeof(result)))
299 {
300 mutt_debug(LL_DEBUG1, "getcwd failed: %s (%d)\n", strerror(errno), errno);
301 return false;
302 }
303
304 size_t cwdlen = mutt_str_len(result);
305 size_t dirlen = mutt_str_len(buf);
306 if ((cwdlen + dirlen + 1) >= buflen)
307 {
308 mutt_debug(LL_DEBUG3, "result too big for the buffer %ld >= %ld\n",
309 cwdlen + dirlen + 1, buflen);
310 return false;
311 }
312
313 result[cwdlen] = '/';
314 mutt_str_copy(result + cwdlen + 1, buf, sizeof(result) - cwdlen - 1);
315 mutt_str_copy(buf, result, buflen);
316 }
317
318 if (!mutt_path_tidy(buf, is_dir))
319 return false;
320
321 return true;
322}
bool mutt_path_tilde(char *buf, size_t buflen, const char *homedir)
Expand '~' in a path.
Definition: path.c:223
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_path_basename()

const char * mutt_path_basename ( const char *  f)

Find the last component for a pathname.

Parameters
fString to be examined
Return values
ptrPart of pathname after last '/' character

Definition at line 329 of file path.c.

330{
331 if (!f)
332 return NULL;
333
334 const char *p = strrchr(f, '/');
335 if (p)
336 return p + 1;
337 return f;
338}
+ Here is the caller graph for this function:

◆ mutt_path_concat()

char * mutt_path_concat ( char *  d,
const char *  dir,
const char *  fname,
size_t  l 
)

Join a directory name and a filename.

Parameters
dBuffer for the result
dirDirectory name
fnameFile name
lLength of buffer
Return values
ptrDestination buffer

If both dir and fname are supplied, they are separated with '/'. If either is missing, then the other will be copied exactly.

Definition at line 351 of file path.c.

352{
353 if (!d || !dir || !fname)
354 return NULL;
355
356 const char *fmt = "%s/%s";
357
358 if ((fname[0] == '\0') || ((dir[0] != '\0') && (dir[strlen(dir) - 1] == '/')))
359 fmt = "%s%s";
360
361 snprintf(d, l, fmt, dir, fname);
362 return d;
363}
+ Here is the caller graph for this function:

◆ mutt_path_dirname()

char * mutt_path_dirname ( const char *  path)

Return a path up to, but not including, the final '/'.

Parameters
pathPath
Return values
ptrThe directory containing p

Unlike the IEEE Std 1003.1-2001 specification of dirname(3), this implementation does not modify its parameter, so callers need not manually copy their paths into a modifiable buffer prior to calling this function.

Note
The caller must free the returned string

Definition at line 376 of file path.c.

377{
378 if (!path)
379 return NULL;
380
381 char buf[PATH_MAX] = { 0 };
382 mutt_str_copy(buf, path, sizeof(buf));
383 return mutt_str_dup(dirname(buf));
384}
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_path_to_absolute()

bool mutt_path_to_absolute ( char *  path,
const char *  reference 
)

Convert relative filepath to an absolute path.

Parameters
pathRelative path
referenceAbsolute path that path is relative to
Return values
trueSuccess
falseFailure

Use POSIX functions to convert a path to absolute, relatively to another path

Note
path should be at least of PATH_MAX length

Definition at line 397 of file path.c.

398{
399 if (!path || !reference)
400 return false;
401
402 char abs_path[PATH_MAX] = { 0 };
403 int path_len;
404
405 /* if path is already absolute, don't do anything */
406 if ((strlen(path) > 1) && (path[0] == '/'))
407 {
408 return true;
409 }
410
411 char *dirpath = mutt_path_dirname(reference);
412 mutt_str_copy(abs_path, dirpath, sizeof(abs_path));
413 FREE(&dirpath);
414 mutt_strn_cat(abs_path, sizeof(abs_path), "/", 1); /* append a / at the end of the path */
415
416 path_len = sizeof(abs_path) - strlen(path);
417
418 mutt_strn_cat(abs_path, sizeof(abs_path), path, (path_len > 0) ? path_len : 0);
419
420 path = realpath(abs_path, path);
421 if (!path && (errno != ENOENT))
422 {
423 mutt_perror(_("Error: converting path to absolute"));
424 return false;
425 }
426
427 return true;
428}
#define mutt_perror(...)
Definition: logging.h:88
#define FREE(x)
Definition: memory.h:43
#define _(a)
Definition: message.h:28
char * mutt_path_dirname(const char *path)
Return a path up to, but not including, the final '/'.
Definition: path.c:376
char * mutt_strn_cat(char *d, size_t l, const char *s, size_t sl)
Concatenate two strings.
Definition: string.c:294
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_path_realpath()

size_t mutt_path_realpath ( char *  buf)

Resolve path, unraveling symlinks.

Parameters
bufBuffer containing path
Return values
numString length of resolved path
0Error, buf is not overwritten

Resolve and overwrite the path in buf.

Note
Size of buf should be at least PATH_MAX bytes.

Definition at line 440 of file path.c.

441{
442 if (!buf)
443 return 0;
444
445 char s[PATH_MAX] = { 0 };
446
447 if (!realpath(buf, s))
448 return 0;
449
450 return mutt_str_copy(buf, s, sizeof(s));
451}
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_path_parent()

bool mutt_path_parent ( char *  buf)

Find the parent of a path.

Parameters
bufBuffer for the result
Return values
trueSuccess

Definition at line 458 of file path.c.

459{
460 if (!buf)
461 return false;
462
463 int n = mutt_str_len(buf);
464 if (n < 2)
465 return false;
466
467 if (buf[n - 1] == '/')
468 n--;
469
470 // Find the previous '/'
471 for (n--; ((n >= 0) && (buf[n] != '/')); n--)
472 ; // do nothing
473
474 if (n == 0) // Always keep at least one '/'
475 n++;
476
477 buf[n] = '\0';
478 return true;
479}
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_path_abbr_folder()

bool mutt_path_abbr_folder ( char *  buf,
const char *  folder 
)

Create a folder abbreviation.

Parameters
bufPath to modify
folderBase path for '=' substitution
Return values
truePath was abbreviated

Abbreviate a path using '=' to represent the 'folder'. If the folder path is passed, it won't be abbreviated to just '='

Definition at line 490 of file path.c.

491{
492 if (!buf || !folder)
493 return false;
494
495 size_t flen = mutt_str_len(folder);
496 if (flen < 2)
497 return false;
498
499 if (folder[flen - 1] == '/')
500 flen--;
501
502 if (!mutt_strn_equal(buf, folder, flen))
503 return false;
504
505 if (buf[flen + 1] == '\0') // Don't abbreviate to '=/'
506 return false;
507
508 size_t rlen = mutt_str_len(buf + flen + 1);
509
510 buf[0] = '=';
511 memmove(buf + 1, buf + flen + 1, rlen + 1);
512 return true;
513}
bool mutt_strn_equal(const char *a, const char *b, size_t num)
Check for equality of two strings (to a maximum), safely.
Definition: string.c:496
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ mutt_path_escape()

char * mutt_path_escape ( const char *  src)

Escapes single quotes in a path for a command string.

Parameters
srcthe path to escape
Return values
ptrThe escaped string

Definition at line 520 of file path.c.

521{
522 if (!src)
523 return NULL;
524
525 static char dest[STR_COMMAND];
526 char *destp = dest;
527 int destsize = 0;
528
529 while (*src && (destsize < sizeof(dest) - 1))
530 {
531 if (*src != '\'')
532 {
533 *destp++ = *src++;
534 destsize++;
535 }
536 else
537 {
538 /* convert ' into '\'' */
539 if (destsize + 4 < sizeof(dest))
540 {
541 *destp++ = *src++;
542 *destp++ = '\\';
543 *destp++ = '\'';
544 *destp++ = '\'';
545 destsize += 4;
546 }
547 else
548 break;
549 }
550 }
551 *destp = '\0';
552
553 return dest;
554}
#define STR_COMMAND
Enough space for a long command line.
Definition: string2.h:35
+ Here is the caller graph for this function:

◆ mutt_path_getcwd()

const char * mutt_path_getcwd ( struct Buffer cwd)

Get the current working directory.

Parameters
cwdBuffer for the result
Return values
ptrString of current working directory

Definition at line 561 of file path.c.

562{
563 if (!cwd)
564 return NULL;
565
567 char *retval = getcwd(cwd->data, cwd->dsize);
568 while (!retval && (errno == ERANGE))
569 {
570 mutt_buffer_alloc(cwd, cwd->dsize + 256);
571 retval = getcwd(cwd->data, cwd->dsize);
572 }
573 if (retval)
575 else
577
578 return retval;
579}
void mutt_buffer_alloc(struct Buffer *buf, size_t new_size)
Make sure a buffer can store at least new_size bytes.
Definition: buffer.c:313
void mutt_buffer_fix_dptr(struct Buffer *buf)
Move the dptr to end of the Buffer.
Definition: buffer.c:189
void mutt_buffer_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:85
size_t dsize
Length of data.
Definition: buffer.h:37
char * data
Pointer to data.
Definition: buffer.h:35
+ Here is the call graph for this function:
+ Here is the caller graph for this function: