├── .clang-format ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── fastcat.c └── simple.c /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | Language: Cpp 3 | MaxEmptyLinesToKeep: 3 4 | IndentCaseLabels: false 5 | AllowShortIfStatementsOnASingleLine: false 6 | AllowShortCaseLabelsOnASingleLine: false 7 | AllowShortLoopsOnASingleLine: false 8 | DerivePointerAlignment: false 9 | PointerAlignment: Right 10 | SpaceAfterCStyleCast: true 11 | TabWidth: 4 12 | UseTab: Never 13 | IndentWidth: 4 14 | BreakBeforeBraces: Linux 15 | AccessModifierOffset: -4 16 | ForEachMacros: 17 | - foreach 18 | - Q_FOREACH 19 | - BOOST_FOREACH 20 | - list_for_each 21 | - list_for_each_safe 22 | - list_for_each_entry 23 | - list_for_each_entry_safe 24 | - hlist_for_each_entry 25 | - rb_list_foreach 26 | - rb_list_foreach_safe 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | fastcat 2 | simple 3 | 100-0.txt 4 | 100-0.txt.md5 5 | out 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 National Cheng Kung University, Taiwan. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | override CFLAGS := -Wall -pedantic -std=c99 -O2 $(CFLAGS) 2 | override LDFLAGS := $(LDFLAGS) 3 | 4 | EXE = fastcat 5 | 6 | all: $(EXE) simple 7 | 8 | $(EXE): $(EXE).c 9 | $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ 10 | 11 | simple: simple.c 12 | $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@ 13 | 14 | clean: 15 | $(RM) fastcat simple out 16 | 17 | distclean: clean 18 | $(RM) 100-0.txt 100-0.txt.md5 19 | 20 | 100-0.txt: 21 | @echo "Downloading the complete works of William Shakespeare..." 22 | @wget --quiet http://www.gutenberg.org/files/100/100-0.txt 23 | @md5sum $@ > $@.md5 24 | 25 | check: $(EXE) 100-0.txt 26 | @$(RM) out 27 | @./fastcat 100-0.txt > out 28 | @diff 100-0.txt out 29 | @./fastcat 100-0.txt | md5sum --status -c 100-0.txt.md5 30 | @echo "OK!" 31 | 32 | bench: $(EXE) simple 100-0.txt 33 | @printf "simple: " 34 | @./simple simple 100-0.txt | pv -r > /dev/null 35 | @bench './simple 100-0.txt' 36 | @printf "cat from coreutils: " 37 | @cat simple 100-0.txt | pv -r > /dev/null 38 | @bench 'cat 100-0.txt' 39 | @printf "fastcat: " 40 | @./fastcat simple 100-0.txt | pv -r > /dev/null 41 | @bench './fastcat 100-0.txt' 42 | 43 | .PHONY: clean 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fastcat 2 | 3 | `fastcat` is an implementation of [cat(1)](http://man7.org/linux/man-pages/man1/cat.1.html) 4 | that uses Linux-specific system calls [splice](http://man7.org/linux/man-pages/man2/splice.2.html) 5 | and [sendfile](http://man7.org/linux/man-pages/man2/sendfile.2.html) to write to stdout. 6 | In situations where stdout is piped, `splice` is used; in all other cases, 7 | `sendfile` is used. The combination of these calls avoids unnecessary copying to userspace. 8 | 9 | One unfortunate side effect is that 10 | ```shell 11 | $ fastcat FILE >> OUT 12 | ``` 13 | is not supported as neither `splice` nor `sendfile` support appending to files. 14 | 15 | ## Benchmarking 16 | 17 | [bench](https://hackage.haskell.org/package/bench) is a powerful alternative to the time command, 18 | and `fastcat` uses it for benchmarking. 19 | ```shell 20 | $ make bench 21 | ``` 22 | 23 | ## License 24 | `fastcat` is released under the MIT License. Use of this source code is governed 25 | by a MIT License that can be found in the LICENSE file. 26 | -------------------------------------------------------------------------------- /fastcat.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | /* Removes the O_APPEND flag from a given fd */ 15 | static void remove_append(int fd) 16 | { 17 | int flags = fcntl(fd, F_GETFL); 18 | if (flags >= 0) 19 | fcntl(fd, F_SETFL, flags ^ O_APPEND); 20 | } 21 | 22 | /* Returns true if STDOUT_FILENO has O_APPEND set; false otherwise */ 23 | static bool stdout_append() 24 | { 25 | return fcntl(STDOUT_FILENO, F_GETFL) & O_APPEND; 26 | } 27 | 28 | int main(int argc, char **argv) 29 | { 30 | if (argc < 2) { /* No file was given. */ 31 | fprintf(stderr, "Usage: %s FILE [FILE...]\n", argv[0]); 32 | return EXIT_FAILURE; 33 | } 34 | 35 | if (stdout_append()) { 36 | /* In some cases, stdout can have O_APPEND set but not be pointing 37 | * to a particular file. 38 | * FIXME: It is not safe to remove O_APPEND when STDOUT_FILENO is not 39 | * a tty. We should not overwrite files. That is, we have to 40 | * add "&& isatty(STDOUT_FILENO)" for the above if-statement. 41 | */ 42 | remove_append(STDOUT_FILENO); 43 | } 44 | 45 | /* Neither splice nor sendfile support file descriptors with 46 | * O_APPEND. Either removing O_APPEND failed or we're not redirected to 47 | * a tty. 48 | */ 49 | if (stdout_append()) { 50 | fprintf(stderr, "Unable to append to files.\n"); 51 | return EXIT_FAILURE; 52 | } 53 | 54 | int num_files = argc - 1; 55 | int fds[num_files]; 56 | struct stat stats[num_files]; 57 | 58 | /* Open all files and add them to the array of file descriptors. */ 59 | for (int i = 0; i < num_files; i++) { 60 | char *in_filename = argv[i + 1]; 61 | fds[i] = open(in_filename, O_RDONLY); 62 | if (fds[i] < 0) { 63 | fprintf(stderr, "Unable to open %s.\n", in_filename); 64 | if (errno == ENOENT) 65 | fprintf(stderr, "File does not exist.\n"); 66 | return EXIT_FAILURE; 67 | } 68 | 69 | fstat(fds[i], (stats + i)); 70 | if (S_ISDIR(stats[i].st_mode)) { 71 | fprintf(stderr, "%s is a directory.\n", in_filename); 72 | return EXIT_FAILURE; 73 | } 74 | } 75 | 76 | /* Copy the files to stdout. Close the files when done. Exit with a 77 | * non-zero status on error. 78 | */ 79 | for (int i = 0; i < num_files; i++) { 80 | /* Copy between the file descriptors without an intermediate copy into 81 | * userspace. Try splice in case one is a pipe (which stdout often is); 82 | * if not, then use sendfile if splice set errno to EINVAL (as it does 83 | * when neither argument is a pipe). 84 | */ 85 | ssize_t bytes = 86 | splice(fds[i], NULL, STDOUT_FILENO, NULL, stats[i].st_size, 0); 87 | if (bytes == -1 && errno == EINVAL) 88 | bytes = sendfile(STDOUT_FILENO, fds[i], NULL, stats[i].st_size); 89 | 90 | /* If things still failed, exit with errno as the exit status. */ 91 | if (bytes < 0) 92 | return errno; 93 | close(fds[i]); 94 | } 95 | 96 | return EXIT_SUCCESS; 97 | } 98 | -------------------------------------------------------------------------------- /simple.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | for (int count = 1; count < argc; count++) { 9 | FILE *file = fopen(argv[count], "r"); 10 | if (!file) { 11 | fprintf(stderr, "%s: %s : %s\n", argv[0], argv[count], 12 | strerror(errno)); 13 | continue; 14 | } 15 | int chr; 16 | while ((chr = getc(file)) != EOF) 17 | fprintf(stdout, "%c", chr); 18 | fclose(file); 19 | } 20 | return 0; 21 | } 22 | --------------------------------------------------------------------------------