1

In the code below, could you please tell me why when fl_name is about 207 characters in length (212 including the extension, and 227 including the relative path), the stat() function fails and returns errno of 2 and strerror( errno ) that the file doesn't exist?

The printf right after the memcpy in the while loop, prints the correct path, file, and extension as retrieved from struct dirent ep but then says the very same file doesn't exist.

The loop works for all file names of length 207 or less.

I'm using minGW-W64. I tried using the _stat() function also and the results were the same.

Thank you.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>

int main ( void )
 {
    DIR *dp;
    struct dirent *ep;
    struct stat info;
    int rc, q, l, i = 0, v = 0;
    const char *path = NULL;
    char *fl_name,
         *p = NULL;
    const char *sec_path = "../../databases/";
    unsigned int len_sec_path = strlen( sec_path );

    fl_name = malloc( len_sec_path + 261 );
    // Want p to be at the point in fl_name to write the actual file and directory names in order to get the stats.
    // memcpy returns the pointer to the destination; so, just add the length of path to that to get to the end.
    p = memcpy( fl_name, sec_path, len_sec_path + 1 ) + len_sec_path;

    if ( ( dp = opendir( fl_name ) ) == NULL )
      {
        printf( "Failed to open the directory of: %s", fl_name );
        return 1;
      }
    
    while ( ep = readdir( dp ) )
      { 
        memcpy( p, ep->d_name, ( l = strlen( ep->d_name ) ) > 259 ? 260 : l + 1 );
        printf( "%d, %s\n", strlen( fl_name ), fl_name );
        if ( ( rc = stat( fl_name, &info ) ) != 0 )
          { 
            printf( "%s\n", fl_name ); 
            printf( "rc = %d, errno : %d, strerror : %s\n", rc, errno, strerror( errno ) ); 
            continue;
          }
      }  
   return 0;
 }

Modifying the code to change the directory to sec_path before attempting to get the file information solved this issue, at least such that the largest file name Windows will accept will not error.

int main ( void )
 {
    DIR *dp;
    struct dirent *ep;
    struct stat info;
    int rc, q, l, i = 0, v = 0;
    const char *path = NULL;
    char *fl_name,
         *p = NULL,
         name;
    const char *sec_path = "../../databases/";
    unsigned int len_sec_path = strlen( sec_path );

    fl_name = malloc( len_sec_path + 261 );
    // Want p to be at the point in fl_name to write the actual file and directory names in order to get the stats.
    // memcpy returns the pointer to the destination; so, just add the length of path to that to get to the end.
    p = memcpy( fl_name, sec_path, len_sec_path + 1 ) + len_sec_path;
    
    char *buffer;
    if ( (buffer = _getcwd( NULL, 0 )) == NULL )
      {
       printf( "Failed to get current working directory." );
       return 1;
      }

    printf( "buffer is %s\n", buffer );

    if ( ( dp = opendir( fl_name ) ) == NULL )
      {
        printf( "Failed to open the directory of: %s", fl_name );
        return 1;
      }
        
    if ( _chdir( sec_path ) ) 
      {
        printf( "Couldn't change directory." );
        return 1;
      }

    while ( ep = readdir( dp ) )
      { 
        memcpy( p, ep->d_name, ( l = strlen( ep->d_name ) ) > 259 ? 260 : l + 1 );
        printf( "%d, %s\n", strlen( fl_name ), fl_name );
        if ( ( rc = stat( p, &info ) ) != 0 )
          { 
            printf( "%s\n", fl_name ); 
            printf( "rc = %d, errno : %d, strerror : %s\n", rc, errno, strerror( errno ) ); 
            continue;
          }    
      }  

    if ( _chdir( buffer ) ) 
      {
        printf( "Couldn't change directory." );
        return 1;
      }

   printf( "buffer is %s\n", buffer );

   free( buffer );
   return 0;
 }
Gary
  • 2,393
  • 12
  • 31
  • 1
    Please create a [mcve]. In your code currently `fl_name` isn't pointing to anywhere, and you didn't allocate memory for `p`. Besides don't call `strlen( ep->d_name )` multiple times. Store that in a variable instead – phuclv Feb 06 '21 at 04:54
  • @phuclv Thanks. I changed the code to a full example. It runs for me and produces the error if the length of a file name in the `sec_path` directory is too long. Of course, you can make `sec_path` any directory you want relative to the executable file. – Gary Feb 06 '21 at 05:42
  • Although it didn't do anything to alter the results, I left the length expression for the third parameter of the `memcpy` inside the `while` loop in the line above it. I just deleted it from the code example. I apologize. – Gary Feb 06 '21 at 06:04
  • @Gary: if you want to use the POSIX API, why don't you install a Linux distribution (like [Debian](http://debian.org/)....) on your laptop? – Basile Starynkevitch Feb 06 '21 at 06:19
  • @BasileStarynkevitch It is my plan to switch to a Linux OS but I need to get this code to work in Windows also. I was thinking about Arch Linux or Manjaro. I experimented a bit and like the GNOME material shell. I'm very much an amateur and find Windows quite frustrating in documentation. Perhaps I am misunderstanding minGW-W64 and Windows but the documentation for C in Linux seems much better. – Gary Feb 06 '21 at 06:26
  • @Gary the mingw guys just port the things and doesn't update the documentation. If you want good documentation on Windows then you should go to MSDN instead. And `strlen` returns `size_t` which [must be printed using `%zu`](https://stackoverflow.com/q/940087/995714) so you're invoking UB by using `%d` – phuclv Feb 06 '21 at 07:31
  • @phuclv Thank you for letting me know that and for the link. – Gary Feb 07 '21 at 02:19

1 Answers1

1

Is there a different maximum file name length for stat() function than the OS accepts?

Yes, there is one, and that threshold is operating system specific and file system specific.

On Linux with ext3 file systems, stat(2) could accept file paths of more than a thousand bytes (IIRC, maximal is 4095 bytes). See e.g. path_resolution(7). On my Debian, header file /usr/include/linux/limits.h contains

#define PATH_MAX        4096    /* # chars in a path name including nul */

On Windows with VFAT file systems, the limit is rumored to be around 240 bytes. Such file systems are used on USB keys.

the stat() function fails and returns errno of 2 and strerror( errno ) that the file doesn't exist?

This could happen if your program is running in some unexpected current working directory. On Linux, the chdir(2) system call can change it, and getcwd(2) is querying it.

Windows have similar functions, e.g. GetCurrentDirectory and SetCurrentDirectory

You can find open source libraries abstracting the difference between OSes, e.g. for C++ the POCO library or Qt, and for C the Glib library.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • Thanks. I'm a little confused still. Does your mean that, although the OS permits a file name to be say 240 bytes, for example, the `stat()` function may still fail at a number of bytes less than 240? – Gary Feb 06 '21 at 06:17
  • I don't know. You should dive into the documentation of your particular OS. I never used Windows myself. (But Unix and POSIX systems since 1985) – Basile Starynkevitch Feb 06 '21 at 06:20
  • My application runs in a different directory than the `sec_path` in the example. Changing directories to `sec_path` before querying file information solved the issue. Apparently, the relative path prefixed to the filename counts against the limit in `stat()`; I don't know nor could I locate that in documentation. – Gary Feb 06 '21 at 07:11
  • the old *path length* limit on Windows, inherited from DOS, is 260 characters but it has been removed in modern Windows where the real limit is 32767 although users have to opt-in to use it – phuclv Feb 06 '21 at 07:47
  • @Gary obviously the MAX_PATH on Windows applies to the whole path so if the parent directory path is too long then there's not enough space for the file name. See [Windows 7 file name length limited to 129 characters](https://superuser.com/q/811146/241386). Some of the solutions being prepending `\\?\ ` to remove the limit in all Windows, or creating a short mount point or a symlink. In Windows 10 just set `HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled` https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation – phuclv Feb 06 '21 at 07:55
  • @phuclv I'm just an amateur but I don't see anything obvious about the `stat()` function failing because the path and filename are too long, when a pointer to a memory allocation holding that information is passed to it. No maximum is mentioned in the `stat()` function stating that the pointer must reach a termination character before so many bytes. I likely don't know where to locate the best information but I don't see any reference to MAX_PATH in the `stat()` documentation at https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/stat-functions?view=msvc-160. – Gary Feb 07 '21 at 02:30
  • @Gary the win32 API will [normalize and truncate any paths being passed to it](https://learn.microsoft.com/en-us/archive/blogs/bclteam/long-paths-in-net-part-1-of-3-kim-hamilton), unless the path begins with \\?\ prefix, so regardless of the function you call, you'll never be able to open a path longer than the limit – phuclv Feb 07 '21 at 02:45
  • @phuclv Thanks. I tried using the \\?\ prefix and the only files or directories returned are `.` and `..`. All others error as `no such file or directory.` It appears to work on only fully qualified paths also; so, no relative paths permitted. – Gary Feb 07 '21 at 03:07
  • That's only because every directory has a `.` and `..`. I must be doing something wrong or doesn't work in minGW-W64. – Gary Feb 07 '21 at 03:19