Below is the approach taken in the function allocpath()
in the book "Advanced Programming in Unix Environment", which I have modified to simply determine the maximum size of the path.
FILENAME_MAX
from <stdio.h>
has not been used because the documentation for GNU C Library states:
Unlike
PATH_MAX
, this macro is defined even if there is no actual limit imposed. In such a case, its value is typically a very large number. This is always the case on GNU/Hurd systems.Usage Note: Don’t use
FILENAME_MAX
as the size of an array in which to store a file name! You can’t possibly make an array that big!
glibc also seems to be using the same approach here: realpath()
.
For UNIX-like systems, Vim does not meddle with sysconf()
/pathconf()
and simply does:
/* Use the system path length if it makes sense. */
#if defined(PATH_MAX) && PATH_MAX > 1000
#define PATH_MAXL PATH_MAX
#else
#define PATH_MAXL 1024
#endif
and similar for Windows.
CPython has a similar check as Vim.
Code:
#define _POSIX_C_SOURCE 200819
#define _XOPEN_SOURCE 700
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <unistd.h>
#ifdef PATH_MAX
static long path_max = PATH_MAX;
#else
static long path_max = 0;
#endif /* PATH_MAX */
static long posix_version = 0;
static long xsi_version = 0;
/* If PATH_MAX is indeterminate, no guarantee this is adequate. */
#define PATH_MAX_GUESS 1024
ssize_t get_path_max(void)
{
if (path_max == 0) { /* First time through. */
errno = 0;
if ((path_max = pathconf("/", _PC_PATH_MAX)) == -1) {
if (errno != 0) {
return -1;
}
path_max = PATH_MAX_GUESS; /* It's indeterminate */
} else {
path_max++; /* Add one since it is relative to root. */
}
}
if (posix_version == 0) {
if ((posix_version = sysconf(_SC_VERSION)) == -1) {
return path_max;
}
}
if (xsi_version == 0) {
if ((xsi_version = sysconf(_SC_XOPEN_VERSION)) == -1) {
return path_max;
}
}
/* Before POSIX.1-2001, we aren't guaranteed that PATH_MAX includes
* the terminating null byte. Same goes for XPG3. */
ssize_t size = path_max;
if ((posix_version < 200112L) && (xsi_version < 4)) {
return path_max + 1;
}
return path_max;
}
int main(void)
{
printf("%zu\n", get_path_max());
}
Review Request:
Is this reliable? Can it be made more robust?
1 Answer 1
Your code already seems good (it follows C good practices and it's well written, overall). These are the enhancements I'd implement:
1) Add bounds checking for the path_max
value
#define MIN_PATH_LENGTH 256
#define MAX_PATH_LENGTH 32767
I set MAX_PATH_LENGTH
to a value that works on many modern systems, if you have particular requirements you may change it.
2) Generic function overhaul
ssize_t get_path_max(void)
{
if (path_max == 0) {
errno = 0;
path_max = pathconf("/", _PC_PATH_MAX);
if (path_max == -1) {
if (errno != 0) {
return -1;
}
path_max = PATH_MAX_GUESS;
} else {
path_max++; /* Add one for root */
}
/* Sanity check the value */
if (path_max < MIN_PATH_LENGTH) {
path_max = MIN_PATH_LENGTH;
} else if (path_max > MAX_PATH_LENGTH) {
path_max = MAX_PATH_LENGTH;
}
}
/* Cache POSIX/XSI versions */
if (posix_version == 0) {
posix_version = sysconf(_SC_VERSION);
if (posix_version == -1) {
posix_version = 200809L; /* Assume recent POSIX */
}
}
if (xsi_version == 0) {
xsi_version = sysconf(_SC_XOPEN_VERSION);
if (xsi_version == -1) {
xsi_version = 700; /* Assume recent XSI */
}
}
/* Add space for null terminator for older standards */
if ((posix_version < 200112L) && (xsi_version < 4)) {
return path_max + 1;
}
return path_max;
}
This approach is more reliable than using FILENAME_MAX
and provides better portability across different UNIX
-like systems. The bounds checking helps prevent possibly wrong values, while still maintaining flexibility for different systems.