c-在minGW-W64中正确使用readdir()和stat()来获取文件/目录信息,避免最大路径问题



我没有问这个问题,因为它在我第一次发布时应该是措辞正确的。所以,我在这里编辑了一些。

下面有三个代码块,它们都读取目录的内容,并试图获取内容的统计信息。除了路径/文件名达到260个字符或更多字符外,这三种方法都有效。

在所有情况下,readdir()都会毫无问题地返回目录内容,但stat()会失败,并出现文件或目录不存在的错误。

第一个块将指向路径和文件名的指针传递给stat()。第二个更改工作目录,然后只传递一个指向文件名的指针。第三种尝试使用具有完全限定路径的前缀//?/来扩展所接受的最大路径。没有什么不同。否则,除了长路径外,所有路径都可以正常工作。

我的问题是,有没有一种方法可以让stat()在操作系统接受的最长文件名下不会失败。我使用的是Windows7,只需键入一个文件名,直到输入框无法接受更多字符为止。或者,总的来说,有没有更好的方法可能不需要指向路径的指针,而是需要不同的标识符?

谢谢。


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


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

从检查dirent.h标头来看,dirent struct中似乎没有任何内容可以在读取后简单地遍历目录中的记录,而不必重复路径。

stat()函数中重复路径并不困难,但似乎对最大路径应用了不同的大小限制,并且我无法获得\?\.的前缀来处理它。\?前缀完全失败;\.返回统计数据,但在与没有它相同的大小限制下仍然失败。

似乎应该有更好的方法,因为readdir()正在返回文件/目录,并且应该有一个标识符,但我看不到可用的标识符。或者能够仅传递ep->d_namestat(),而不必包括路径,这就是下面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, %sn", strlen( fl_name ), fl_name );
if ( ( rc = stat( fl_name, &info ) ) != 0 )
{ 
printf( "%sn", fl_name ); 
printf( "rc = %d, errno : %d, strerror : %sn", 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 %sn", 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, %sn", strlen( fl_name ), fl_name );
if ( ( rc = stat( p, &info ) ) != 0 )
{ 
printf( "%sn", fl_name ); 
printf( "rc = %d, errno : %d, strerror : %sn", rc, errno, strerror( errno ) ); 
continue;
}    
}  
if ( _chdir( buffer ) ) 
{
printf( "Couldn't change directory." );
return 1;
}
printf( "buffer is %sn", buffer );
free( buffer );
return 0;
}

尝试使用\?前缀来增加最大路径的代码。我认为上面的版本2有效,我认为这个带有前缀的版本有效,但发现两者都不起作用。即使当目录被更改,使得指向文件名的指针被传递到stat(),而不需要路径时,该路径似乎仍然以某种方式被包括在最大值中,因为如果路径加上文件名达到260,则stat()失败,说明找不到文件。我想这没什么大不了的,因为我可以捕捉和处理错误并通知用户。令人恼火的是,用户可以在他们的目录中使用操作系统会接受的文件名,但我不能在应用程序的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 %sn", 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, %sn", strcmp( ep->d_name, p ), p );
printf( "%d, %sn", strlen( fl_name ), fl_name );
if ( ( rc = _stat( p, &info ) ) != 0 )
{ 
printf( "%sn", fl_name ); 
printf( "rc = %d, errno : %d, strerror : %sn", rc, errno, strerror( errno ) ); 
continue;
}
printf( "ctime : %d, mtime : %d, atime : %dn", 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*返回上尝试指针运算,例如

/* 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 %sn", 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, %sn", strlen (fl_name), fl_name);

int rc = stat (p, &info);
if (rc == -1) {
printf ("%sn", fl_name);
printf ("rc = %d, errno : %d, strerror : %sn", rc, errno, strerror (errno));
continue;
}
}
if (chdir (buffer)) {
perror ("chdir()");
return 1;
}
printf ("buffer is %sn", buffer);
free (buffer);      /* free all memory you have allocated */
free (fl_name);

return 0;
}

示例使用/输出

使用MinGW 5.1(旧的TDM-MinGW)在Win7上进行测试,并将sec_path更改为"../../cb/",以便在Win7来宾上存在文件和目录,您将拥有:

>binreaddir_prob.exe
buffer is C:UsersdavidDocumentsdevsrc-ctmp
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:UsersdavidDocumentsdevsrc-ctmp

这些问题似乎是由于使用了非标准函数、void*上的指针算法,或者其中一个步骤缺乏验证,导致故障未被注意到,最终导致未定义的行为。

如果您总是在启用警告的情况下编译,编译器就会指出其中的许多问题。对于gcc,最低使用:

-Wall -Wextra -pedantic -Wshadow

仔细查看这些变化,如果您还有其他问题,请告诉我。

最新更新