From a7b03af9ccbfef06b837f2601a376a65f66d5da5 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Wed, 16 Oct 2013 18:42:38 +0200 Subject: [PATCH] Improve implementation of cat(1). --- utils/cat.cpp | 178 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 120 insertions(+), 58 deletions(-) diff --git a/utils/cat.cpp b/utils/cat.cpp index 08bbfb9b..93aadbe3 100644 --- a/utils/cat.cpp +++ b/utils/cat.cpp @@ -1,6 +1,6 @@ /******************************************************************************* - Copyright(C) Jonas 'Sortie' Termansen 2011, 2012. + Copyright(C) Jonas 'Sortie' Termansen 2013. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free @@ -16,94 +16,156 @@ this program. If not, see . cat.cpp - Concatenate files and print on the standard output. + Concatenate and print files to the standard output. *******************************************************************************/ -#include -#include -#include -#include -#include +#include + #include #include +#include +#include +#include +#include +#include +#include -int docat(const char* inputname, int fd) +#if !defined(VERSIONSTR) +#define VERSIONSTR "unknown version" +#endif + +static bool cat_fd(int fd, const char* path) { - do + struct stat st; + if ( fstat(fd, &st) == 0 ) { - const size_t BUFFER_SIZE = 255; - char buffer[BUFFER_SIZE+1]; - ssize_t bytesread = read(fd, buffer, BUFFER_SIZE); - if ( bytesread == 0 ) { break; } - if ( bytesread < 0 ) + if ( S_ISDIR(st.st_mode) ) + return error(0, EISDIR, "`%s'", path), false; + } + + const size_t BUFFER_SIZE = 16 * 1024; + uint8_t buffer[BUFFER_SIZE]; + + ssize_t buffer_used; + while ( 0 < (buffer_used = read(fd, buffer, BUFFER_SIZE)) ) + { + size_t so_far = 0; + while ( so_far < (size_t) buffer_used ) { - error(0, errno, "read: %s", inputname); - return 1; + ssize_t amount = write(1, buffer + so_far, buffer_used - so_far); + if ( amount <= 0 ) + return error(0, errno, "`%s'", ""), false; + so_far += amount; } - if ( (ssize_t) writeall(1, buffer, bytesread) < bytesread ) + } + + if ( buffer_used < 0 ) + return error(0, errno, "`%s'", path), false; + + return true; +} + +static bool cat_path(const char* path) +{ + if ( !strcmp("-", path) ) + return cat_fd(0, ""); + int fd = open(path, O_RDONLY); + if ( fd < 0 ) + { + error(0, errno, "`%s'", path); + return false; + } + bool result = cat_fd(fd, path); + close(fd); + return result; +} + +static bool cat_arguments(int argc, char* argv[]) +{ + if ( argc <= 1 ) + return cat_path("-"); + bool success = true; + for ( int i = 1; i < argc; i++ ) + if ( !cat_path(argv[i]) ) + success = false; + return success; +} + +static void compact_arguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + while ( i < *argc && !(*argv)[i] ) { - error(0, errno, "write: ", inputname); - return 1; + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; } - } while ( true ); - return 0; } -void usage(FILE* fp, const char* argv0) +static void usage(FILE* fp, const char* argv0) { - fprintf(fp, "usage: %s [FILE ...]\n", argv0); - fprintf(fp, "Concatenate files and print on the standard output.\n"); + fprintf(fp, "Usage: %s [OPTION]... [FILE]...\n", argv0); + fprintf(fp, "Concatenate FILE(s), or standard input, to standard output.\n"); + fprintf(fp, "\n"); + fprintf(fp, " -u (ignored)\n"); + fprintf(fp, " --help display this help and exit\n"); + fprintf(fp, " --usage display this help and exit\n"); + fprintf(fp, " --version output version information and exit\n"); + fprintf(fp, "\n"); + fprintf(fp, "With no FILE, or when FILE is -, read standard input.\n"); } -void help(FILE* fp, const char* argv0) +static void help(FILE* fp, const char* argv0) { - return usage(fp, argv0); + usage(fp, argv0); } -void version(FILE* fp, const char* argv0) +static void version(FILE* fp, const char* argv0) { - return usage(fp, argv0); + fprintf(fp, "%s (Sortix) %s\n", argv0, VERSIONSTR); + fprintf(fp, "License GPLv3+: GNU GPL version 3 or later .\n"); + fprintf(fp, "This is free software: you are free to change and redistribute it.\n"); + fprintf(fp, "There is NO WARRANTY, to the extent permitted by law.\n"); } int main(int argc, char* argv[]) { const char* argv0 = argv[0]; - - for ( int i = 1; i < argc; i++ ) + for ( int i = 0; i < argc; i++ ) { const char* arg = argv[i]; - if ( arg[0] != '-' ) { continue; } - argv[i] = NULL; - if ( !strcmp(arg, "--") ) { break; } - if ( !strcmp(arg, "--help") ) { help(stdout, argv0); exit(0); } - if ( !strcmp(arg, "--usage") ) { usage(stdout, argv0); exit(0); } - if ( !strcmp(arg, "--version") ) { version(stdout, argv0); exit(0); } - error(0, 0, "unrecognized option: %s", arg); - usage(stderr, argv0); - exit(1); - } - - int result = 0; - - bool any = false; - for ( int i = 1; i < argc; i++ ) - { - if ( !argv[i] ) { continue; } - any = true; - int fd = open(argv[i], O_RDONLY); - if ( fd < 0 ) - { - error(0, errno, "%s", argv[i]); - result = 1; + if ( arg[0] != '-' || !arg[1] ) continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + case 'u': /* Ignored, POSIX compatibility. */ break; + default: + fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); + usage(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) + help(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--usage") ) + usage(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--version") ) + version(stdout, argv0), exit(0); + else + { + fprintf(stderr, "%s: unknown option: %s\n", argv0, arg); + usage(stderr, argv0); + exit(1); } - - result |= docat(argv[i], fd); - close(fd); } - if ( !any ) { result = docat("", 0); } + compact_arguments(&argc, &argv); - return result; + return cat_arguments(argc, argv) ? 0 : 1; }