首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >创建随机文件的系统实用程序

创建随机文件的系统实用程序
EN

Code Review用户
提问于 2023-05-06 20:35:18
回答 1查看 123关注 0票数 4

以下程序生成随机文件。默认情况下,它们是表单/tmp/rfile-XXX。某些选项允许用户指定将这些文件放置在何处(使用-d dir标志)或使用什么名称来生成(使用生成name-XXX-n name标志),然后使用mkstemp(3),然后在本文的上下文中使用fopen(3),我对以下内容感兴趣:

  1. 如何使这段代码更有效率?我必须重复很多检查(比如if(print_flag == USER)来测试用户是否需要更详细的输出,或者必须多次测试dir_flag以确定其目录名称和长度。在第二个实现中,是否最好使用pthreads来创建每个文件,并为每个线程生成一个/dir/name-XXX列表以进行操作?
  2. 错误处理和安全性,需要更改或添加什么来处理其他故障或问题?还有其他函数我需要检查错误吗?在第2版中,我希望根据最大路径名实现fullname_len检查(我不记得变量/值)。还有什么可能出现错误,或者以其他方式造成问题?
  3. 我试着始终保持格式的一致性,但是我想要一些关于它的输入。我知道我的代码(有意)看起来更像BSD style(9)指南,我不想知道其他样式的标准/指南是否更好。我只想知道它是否一致,是否有利于代码的检查/阅读。
代码语言:javascript
复制
/*  FILE: mkrfiles.c
    AUTHOR: Jordan Effinger
    DATE: 1 May 2023
    VERSION: 0
    
    LICENSE: BSD-3 Clause

    COPYRIGHT<2023><Jordan Effinger>
    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the     distribution.

    3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL        DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,        WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

    COMMENT: This program was written as an exercise in argument parsing, error handling, user i/o and in printing verbose error messages. It was written on a MacOS 10.15.7, with      Apple clang 12.0.0 on an x86_64 system.
*/

#include <string.h>
#include <unistd.h>

#include <stdio.h>
#include <stdlib.h>

#include <sys/errno.h>

#define DFLT 0
#define USER 1

void
print_error(char *, pid_t, char *, unsigned, unsigned);

unsigned uerrno = 0;

int
main(int argc, char **argv)
{
    //  Debugging information:
    pid_t main_pid = getpid();  
    char *exec_name = calloc( strlen(argv[0]), sizeof(char) );
    if(exec_name == NULL)
    {
        print_error(argv[0], main_pid, __FILE__, __LINE__, errno);
        return(errno);
    }

    //  Default settings:
    const char *dir_dflt = "/tmp";
    const char *name_dflt = "rfile";
    const char *fullname_dflt = "/tmp/rfile-XXX";

    const size_t dir_len_dflt = strlen(dir_dflt);
    const size_t name_len_dflt = strlen(name_dflt);
    const size_t fullname_len_dflt = strlen(fullname_dflt)+1;

    //  User set settings:
    char *dir_user, *name_user;
    size_t dir_len, name_len, fullname_len = 0;
    unsigned long count_user;
    
    int count_flag =0;
    int dir_flag = 0;
    int name_flag = 0;
    int print_flag = 0;

    int ch;
    while( (ch = getopt(argc, argv, "c:d:hn:p")) != -1)
    {
        switch(ch)
        {
            case 'c':
                count_flag = USER;
                count_user = strtoul(optarg,NULL,0);    
                if(errno != 0)
                {
                    print_error(exec_name, main_pid, __FILE__, __LINE__, errno);
                    free(exec_name);
                    return(errno);
                    break;
                }
                break;

            case 'd':
                dir_flag = USER;
                dir_len = strlen(optarg);
                dir_user = calloc(dir_len, sizeof(char) );
                if(dir_user == NULL)
                {
                    print_error(exec_name, main_pid, __FILE__, __LINE__, errno);
                    free(exec_name);
                    return(errno);
                    break;
                }
                stpncpy(dir_user, optarg, dir_len);
                break;

            case 'h':
                printf("%s -cdhnp\n", exec_name);
                printf("-c file count\n");
                printf("-d dir name\n");
                printf("-h help\n");
                printf("-n file name template\n");
                printf("-p print (verbose)\n");
                return(-1);
                break;

            case 'n':
                name_flag = USER;
                name_len = strlen(optarg);
                name_user = calloc(name_len, sizeof(char) );
                if(name_user == NULL)
                {
                    print_error(exec_name, main_pid, __FILE__, __LINE__, errno);
                    free(exec_name);
                    return(errno);
                    break;
                }
                stpncpy(name_user, optarg, name_len);
                break;

            case 'p':
                print_flag = USER;
                break;

            default:
                print_error(exec_name, main_pid, __FILE__, __LINE__, EINVAL);
                printf("Execute with -h for a usage menu.\n");
                free(exec_name);
                return(EINVAL);
                break;
        }
    }

    if(print_flag == USER)
    {
        printf("Determining filename length.\n");
    }
    if( dir_flag == USER && name_flag == USER)
    {
        fullname_len = dir_len + strlen("/") + name_len + strlen("-XXX") + 1;
    }
    else if(dir_flag == USER && name_flag == DFLT)
    {
        fullname_len = dir_len + strlen("/") + name_len_dflt + strlen("-XXX") + 1;
    }
    else if(dir_flag == DFLT && name_flag == USER)
    {
        fullname_len = dir_len_dflt + strlen("/") + name_len + strlen("-XXX") + 1;
    }
    else if(dir_flag == DFLT && name_flag == DFLT)
    {
        fullname_len = fullname_len_dflt;
    }

    if(print_flag == USER)
    {
        printf("Allocating space for filename: %zu = %zu(strlen) & %zu(char size)\n", fullname_len * sizeof(char), fullname_len, sizeof(char) );
    }
    char *fullname = calloc(fullname_len, sizeof(char));
    if(fullname == NULL)
    {
        print_error(exec_name, main_pid, __FILE__, __LINE__, errno);    
        free(exec_name);
        free(dir_user);
        free(name_user);
        return(uerrno);
    }

    if(print_flag == USER)
    {
        printf("Building filename:\n");
    }

    if(dir_flag == USER)
    {
        stpncpy(fullname, dir_user, dir_len);
    }
    else
    {
        stpncpy(fullname, dir_dflt, dir_len_dflt);
    }

    strcat(fullname,"/");

    if(name_flag == USER)
    {
        strncat(fullname, name_user, name_len);
    }
    else
    {
        strncat(fullname, name_dflt, name_len_dflt);
    }

    strncat(fullname, "-XXX", strlen("-XXX") );

    FILE *fd;
    if(count_flag == USER)
    {
        if(print_flag == USER)
        {
            printf("Creating %zu random files.\n", count_user);
        }   
        for( unsigned long idx = 0; idx < count_user; idx++ ) 
        {
            mkstemp(fullname);
            fd = fopen(fullname, "w");
            if(fd == NULL)
            {
                print_error(exec_name, main_pid, __FILE__, __LINE__, errno);
                free(exec_name);
                free(dir_user);
                free(name_user);
                free(fullname);
                return(errno);      
            }
            fclose(fd);

            fullname[fullname_len-2] = 'X';
            fullname[fullname_len-3] = 'X';
            fullname[fullname_len-4] = 'X';
        }
    }
    else
    {
        if(print_flag == USER)
        {
            printf("Creating random file.\n");
        }
        
        mkstemp(fullname);
        fd = fopen(fullname, "w");
        if(fd == NULL)
        {
            print_error(exec_name, main_pid, __FILE__, __LINE__, errno);
            free(exec_name);
            free(dir_user);
            free(name_user);
            free(fullname);
            return(errno);
        }
        fclose(fd);
    }

    free(exec_name);
    free(dir_user);
    free(name_user);
    free(fullname);
    return(uerrno); 
}
void
print_error(char *progname, pid_t main_pid, char *file, unsigned line, unsigned errnum)
{
    fprintf(stderr, "[%s, %u] [%s, line:%u], Errno: %u, Error: %s\n",
            progname, main_pid, file, line, errnum, strerror(errnum)
           );
}
```
代码语言:javascript
复制
EN

回答 1

Code Review用户

发布于 2023-05-06 20:57:52

编写模块化代码:

短期记忆和视野都很小.main()函数太大,做得太多,因此很难读懂。尽可能将小任务解释为纯函数。

一个例子如下:

代码语言:javascript
复制
static inline void usage (const char *exec_name)
{
    printf ("%s -cdhnp\n"
            "-c file count\n"
            "-d dir name\n"
            "-h help\n"
            "-n file name template\n"
            "-p print (verbose)\n", 
            exec_name);
}

现在我们可以取代:

代码语言:javascript
复制
case 'h':
    printf("%s -cdhnp\n", exec_name);
    printf("-c file count\n");
    printf("-d dir name\n");
    printf("-h help\n");
    printf("-n file name template\n");
    printf("-p print (verbose)\n");
    return(-1);
    break;

通过以下方式:

代码语言:javascript
复制
case 'h':
    usage();
    return EXIT_FAILURE;

代码可能会调用未定义的行为:

argv[0]被传递给print_error() in main(),但它从未被消毒过:

代码语言:javascript
复制
int 
main (int argc, char **argv) 
{
    /* POSIX requires the calling program to pass in a non-null argv[0]. */
    if (!argv) { // Add
       complain();
    }
}

简化代码

代码语言:javascript
复制
/* 
if( dir_flag == USER && name_flag == USER)
    {
        fullname_len = dir_len + strlen("/") + name_len + strlen("-XXX") + 1;
    }
    else if(dir_flag == USER && name_flag == DFLT)
    {
        fullname_len = dir_len + strlen("/") + name_len_dflt + strlen("-XXX") + 1;
    }
    else if(dir_flag == DFLT && name_flag == USER)
    {
        fullname_len = dir_len_dflt + strlen("/") + name_len + strlen("-XXX") + 1;
    }
    else if(dir_flag == DFLT && name_flag == DFLT)
    {
        fullname_len = fullname_len_dflt;
    }
*/

if (dir_flag == USER) {
    size_t len = name_flag == USER ? name_len : name_len_dflt;
    fullname_len = dir_len + strlen("/") + size + strlen("-XXX") + 1;
} else {
    fullname_len = name_flag == USER ?
                   dir_len_dflt + strlen("/") + name_len + strlen("-XXX") + 1 :
                   fullname_len_dflt;
}

/*
fullname[fullname_len-2] = 'X';
fullname[fullname_len-3] = 'X';
fullname[fullname_len-4] = 'X';
        
*/

memcpy (fullname + fullname_len - 4, "XXX", 3);

减少复制:

我不喜欢这样的冗馀:

代码语言:javascript
复制
free(exec_name);
free(dir_user);
free(name_user);
free(fullname);

但我们可以通过一些宏观手段来清理:

代码语言:javascript
复制
#define Fn_apply(type, fn, ...) {                                      \
    void *stopper_for_apply = (int[]){0};                              \
    type **list_for_apply = (type*[]){__VA_ARGS__, stopper_for_apply}; \
    for (size_t i = 0; list_for_apply[i] != stopper_for_apply; i++)         \
         fn(list_for_apply[i]);                                        \
}

#define Free_all(...) Fn_apply(void, free, __VA_ARGS__);

/* @Credit: Ben Klemens */
/* Could the above have a non-standard practice? Perhaps, but it's one option. */

现在我们可以简单地写:

代码语言:javascript
复制
Free_all (exec_name, dir_user, name_user, fullname);

或者,如果这对您来说不是一个可行的解决方案,我们可以使用goto

代码语言:javascript
复制
  out_free_all:
    free(exec_name);
    free(dir_user);
    free(name_user);
    free(fullname);
    return(errno);

现在我们可以简单地写:

代码语言:javascript
复制
goto out_free_all;

而不是在每次错误时重复对free()的所有调用。

类似的标签可用于:

代码语言:javascript
复制
print_error(exec_name, main_pid, __FILE__, __LINE__, errno);
free(exec_name);
return(errno);
break;

这在几乎所有的switch()案例中都是重复的。

stderr:

写入错误消息

代码语言:javascript
复制
// printf("Execute with -h for a usage menu.\n");
   fputs ("Execute with -h for a usage menu.\n", stderr);

stderr没有缓冲,stdout没有缓冲。

潜在缺陷:

代码在调用errno之后对strtoul进行测试,但在此之前并不清除errno

代码语言:javascript
复制
#if 0
   count_user = strtoul(optarg,NULL,0);                  
   if(errno != 0)
#else
   errno = 0; // Add
   count_user = strtoul(optarg,NULL,0);                  
   if(errno != 0)
#endif

另见:正确使用strtol()

相邻字符串文本连接为一个:

因此,一旦调用printf()就足够了:

代码语言:javascript
复制
#if 0
   printf("%s -cdhnp\n", exec_name);
   printf("-c file count\n");
   printf("-d dir name\n");
   printf("-h help\n");
   printf("-n file name template\n");
   printf("-p print (verbose)\n");             
#else
   printf ("%s -cdhnp\n"
           "-c file count\n"
           "-d dir name\n"
           "-h help\n"
           "-n file name template\n"
           "-p print (verbose)\n", exec_name);
#endif

文件-作用域变量被反对:

代码语言:javascript
复制
unsigned uerrno = 0;

没有必要将uerrno声明在main()之外。

不应该修改的

参数应该用const限定符:

声明。

const-qualified的一个对象是只读的。

代码语言:javascript
复制
void
print_error(const char *progname, pid_t main_pid, const char *file, unsigned line, unsigned errnum)

restrict可以允许选择优化:

代码语言:javascript
复制
void
print_error(const char *restrict progname, pid_t main_pid, const char *restrict file, unsigned line, unsigned errnum)

由于这一职能仅用于这一翻译单位,因此应以内部联系的方式宣布:

代码语言:javascript
复制
static void print_error (print_error(const char *restrict progname, pid_t main_pid, const char *restrict file, unsigned line, unsigned errnum) 

/* Yes, it could be marked inline too. But the compiler can take care of that. */

使用更多的const:

代码语言:javascript
复制
#if 0
    //  Default settings:
    const char *dir_dflt = "/tmp";
    const char *name_dflt = "rfile";
    const char *fullname_dflt = "/tmp/rfile-XXX";
#else    
    //  Default settings:
    const char *const dir_dflt = "/tmp";
    const char *const name_dflt = "rfile";
    const char *const fullname_dflt = "/tmp/rfile-XXX";
#endif

正在返回BSD约定?

errno值。

代码语言:javascript
复制
return(errno); // ??

我建议:

代码语言:javascript
复制
return EXIT_FAILURE;

使用标准的bool类型:

包括:

代码语言:javascript
复制
#include <stdbool.h>

我们可以声明类型为bool的变量,并使用truefalse代替USERDFLT,这两个变量并不是非常明确的。

代码语言:javascript
复制
#if 0
    int count_flag =0;
    int dir_flag = 0;
    int name_flag = 0;
    int print_flag = 0;
#else 
    bool count_flag = dir_flag = name_flag = print_flag = false;
#endif

检查失败的库/系统调用的返回值:

代码语言:javascript
复制
#if 0
    mkstemp (fullname);
#else
    if (mkstemp (fullname) == -1) {
        complain ();
    }
#endif

未成年人

  • 如果我们简单地在print_error()之前定义它,那么我们就不需要main()的前向声明。
  • 我们不需要包含<sys/errno.h>,标准的<errno.h>就足够了。
  • 在最初的C规范中,返回值周围需要括号。情况不再是这样,它使return看起来像一个函数调用。简单地说:
代码语言:javascript
复制
   return a;

而不是:

代码语言:javascript
复制
   return(a);
  • stpncpy()是一个非标准函数.考虑使用strcpy() / strncpy()代替。
  • 标准将sizeof(char)定义为1,因此可以忽略它。
  • 通过这样定义一个struct,我们可以将目录名和它们的长度组合在一起:
代码语言:javascript
复制
    struct dir_info {
        const char *const dir_name;
        const size_t len;
    };
  • fd通常用于保存文件描述符,而不是文件指针。考虑将其命名为streamfp
  • stpncpy()返回指向dest中终止空字节的指针,如果dest不是以空终止,则返回dest + n。保存它并将其用于进一步的连接,而不是调用strcat()
票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/284843

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档