在 minGW-W64 中一起使用 readdir() 和 stat() 以获取 file/directory 信息并避免最大路径问题的正确方法?

Proper way to use readdir() and stat() together in minGW-W64 to get file/directory information and avoid max path issues?

我没有问这个问题,我第一次发布它时应该用措辞。所以,我在这里做了一些编辑。

下面有三段代码,它们都读取目录的内容并尝试获取内容的统计信息。这三个都有效,除非 path/file 名称达到或超过 260 个字符。

在所有情况下,readdir() returns 目录内容都没有问题,但 stat() 失败并显示文件或目录不存在的错误。

第一个块将指向路径和文件名的指针传递给 stat()。第二个更改工作目录,然后传递一个仅指向文件名的指针。第三次尝试使用带有完全限定路径的前缀 //?/ 来扩展接受的最大路径。 None 有所作为。否则,除长路径外,一切正常。

我的问题是有没有办法让它工作,这样 stat() 不会因 OS 接受的最长文件名而失败。我正在使用 Windows 7 并只键入一个文件名,直到输入框不接受更多字符。或者,是否有总体上更好的方法可能不需要指向路径的指针但需要不同的标识符?

谢谢。


我认为有关 stat() 的特定问题的答案是您不能使用前缀将最大长度扩展到超过 260,因为前缀仅适用于 Win32 函数而不适用于 POSIX 函数。而且没有其他选择。


原问题: 在minGW-W64中,除了在stat()函数中重复路径名之外,还有其他获取文件信息的方法吗?

从检查 dirent.h header 中,似乎 dirent struct 中没有任何东西可以在阅读后简单地遍历目录中的记录而不必重复路径。

stat() 函数中重复路径并不难,但它似乎对最大路径应用了不同的大小限制,我无法获得 \?\ 或 [=25 的前缀=] 使用它。 \?\ 前缀完全失败; \.\ returns 统计数据,但在相同的大小限制下仍然失败。

似乎应该有更好的方法,因为 readdir() 正在返回 file/directories 并且应该有一个标识符,但我没有看到一个可用的。或者能够将 ep->d_name 传递给 stat() 而不是必须包含路径,这是下面 fl_name 中的内容,因为 p 只是指向指向路径后的 fl_name

谢谢。 更完整的代码:

#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;
 }

版本 2。

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;
 }

代码试图使用 \?\ 前缀来增加最大路径。我认为上面的版本 2 可以正常工作,我认为带有前缀的这个版本可以正常工作,但发现两者都不起作用。即使更改目录,将指向文件名的指针传递给 stat(),而不需要路径,看起来路径仍以某种方式包含在 max 中,因为如果路径加上文件名到达260stat()失败,提示找不到文件。我想这没什么大不了的,因为我可以捕获并处理错误并通知用户。令人恼火的是,用户可以在他们的目录中使用 OS 将接受的文件名,但我无法在应用程序中的 UI 中显示它们。

int get_dir_2( void )    
 {    
    DIR *dp;
    struct dirent *ep;
    struct _stat info;
    int rc, q, i = 0, v = 0;

    char *fl_name,
         *p = NULL,
         *path = NULL;
    const char *sec_path = "../../databases/",
                   *prefix = "\\?\"; 

    unsigned int l, len_sec_path = strlen( sec_path );

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

    char *dirCWD;

    if ( (dirCWD = _getcwd( NULL, 0 )) == NULL )
      {
       printf( "Failed to get current working directory." );
       return 1;
      }

    path = "";
    l = strlen( prefix ) + strlen( dirCWD ) + strlen( path );
    fl_name = malloc( l + 301 );
    p = ( fl_name + l );

    sprintf( fl_name, "%s%s%s", prefix, dirCWD, path );
    printf( "fl_name is %s\n", fl_name );

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

    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 ? 261 : l + 1 );
        //printf( "%d, %s\n", strcmp( ep->d_name, p ), p );
        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;
          }
       printf( "ctime : %d, mtime : %d, atime : %d\n", info.st_ctime, info.st_mtime, info.st_atime ); 
      }  

   free( fl_name );
   return 0;
}

我怀疑您在几个地方出现了问题,但没有进行验证检查以在错误发生时发现错误,并且由于您使用了非标准 C 函数,因此很难准确判断问题出在哪里躺下。除了验证之外,您还试图在 void* 指针上使用指针算术:

p = memcpy( fl_name, sec_path, len_sec_path + 1 ) + len_sec_path;

这也会导致问题。

您使用的 _getcwd()_chdir() 不是标准的 C 函数。我不确定这些是从哪里获得的。

为避免这些问题,请仅使用有效的 C 函数并验证程序中的每个步骤,这些步骤对于代码的持续定义操作是必要的。例如,验证每个分配:

#define FN_BUF_LEN 260      /* if you need a constant, #define one (or more) */
...
    const char *sec_path = "../../cb/";
    
    size_t len_sec_path = strlen (sec_path);

    if (!(fl_name = malloc (len_sec_path + FN_BUF_LEN + 1))) {  /* validate malloc */
        perror ("malloc-fl_name");
        exit (EXIT_FAILURE);
    }

不要尝试对 memcpy()void* return 进行指针运算,例如

    /* 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 + FN_BUF_LEN + 1);
    p += len_sec_path;      /* memcpy() is type void*, cannot use with arithmetic */

验证对 chddir() 的每个调用,例如

    if (chdir (sec_path) == -1) {                   /* _chdir() is not std C */
        perror ("chdir()");                         /* validate result */
        return 1;
    }

    if (chdir (buffer)) {
        perror ("chdir()");
        return 1;
    }

将所有的部分放在一起并在下面的评论中添加额外的想法,你可以这样做:

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

#define FN_BUF_LEN 260      /* if you need a constant, #define one (or more) */

int main (void) {
    
    DIR *dp;
    struct dirent *ep;
    struct stat info;
    
    char *fl_name,
        *p = NULL, 
        *buffer = NULL;
        
    // const char *sec_path = "../../databases/";
    const char *sec_path = "../../cb/";
    
    size_t len_sec_path = strlen (sec_path);

    if (!(fl_name = malloc (len_sec_path + FN_BUF_LEN + 1))) {  /* validate malloc */
        perror ("malloc-fl_name");
        exit (EXIT_FAILURE);
    }
    
    /* 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 + FN_BUF_LEN + 1);
    p += len_sec_path;      /* memcpy() is type void*, cannot use with arithmetic */
    
    if ((buffer = getcwd (NULL, 0)) == NULL) {      /* _getcwd() is not std C */
        perror ("getcwd()");                        /* validate result */
        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) == -1) {                   /* _chdir() is not std C */
        perror ("chdir()");                         /* validate result */
        return 1;
    }

    while ((ep = readdir (dp))) {
        size_t l = strlen (ep->d_name);             /* declare vars in scope needed */
        if (l > 259) {                              /* validate length before memcpy */
            fprintf (stderr, "error: %s length %u exceeds allowable.\n", ep->d_name, l);
            continue;
        }
        /* filter the "." and ".." directories out */
        if (strcmp (ep->d_name, ".") == 0 || strcmp (ep->d_name, "..") == 0)
            continue;
        
        memcpy (p, ep->d_name, l + 1);
        printf ("%u, %s\n", strlen (fl_name), fl_name);
        
        int rc = stat (p, &info);
        if (rc == -1) {
            printf ("%s\n", fl_name);
            printf ("rc = %d, errno : %d, strerror : %s\n", rc, errno, strerror (errno));
            continue;
        }
    }

    if (chdir (buffer)) {
        perror ("chdir()");
        return 1;
    }

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

    free (buffer);      /* free all memory you have allocated */
    free (fl_name);
    
    return 0;
}

例子Use/Output

使用 MinGW 5.1(旧 TDM-MinGW)在 Win7 上进行测试并将 sec_path 更改为 "../../cb/" 因此 Win7 客户机上存在文件和目录,您将拥有:

>bin\readdir_prob.exe
buffer is C:\Users\david\Documents\dev\src-c\tmp
18, ../../cb/debugtest
21, ../../cb/default.conf
19, ../../cb/farmtotals
22, ../../cb/farmtotalsfmt
14, ../../cb/first
15, ../../cb/helopp
19, ../../cb/matrixsolv
16, ../../cb/toupper
18, ../../cb/windialog
17, ../../cb/winframe
buffer is C:\Users\david\Documents\dev\src-c\tmp

问题似乎是由于使用了非标准函数、您在 void* 上的指针算法或其中一个步骤缺乏验证导致失败被忽视并最终 未定义的行为.

如果您总是在启用警告的情况下进行编译——编译器会指出其中的许多问题。 gcc 最少使用:

-Wall -Wextra -pedantic -Wshadow

查看更改,如果您还有其他问题,请告诉我。