Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cat #27

Open
wants to merge 53 commits into
base: main
Choose a base branch
from
Open

Cat #27

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
9f3a1c9
Implemented cat base
SzAkos04 Apr 17, 2024
1690fe8
Implemented new arguments
SzAkos04 Apr 17, 2024
ba66476
Implemented reading from stdin
SzAkos04 Apr 17, 2024
7bccf6a
Implemented -n
SzAkos04 Apr 17, 2024
4a08480
Improved code maintainabilitiy
SzAkos04 Apr 17, 2024
220a375
Updated author info
SzAkos04 Apr 17, 2024
b64992c
Forgot to free memory
SzAkos04 Apr 18, 2024
64adb9c
Improved `--number` logic
SzAkos04 Apr 18, 2024
2d17a2b
Fixed stdin bug
SzAkos04 Apr 18, 2024
042874d
Optimized print loop
SzAkos04 Apr 18, 2024
a88450d
Removed unnecessary newline
SzAkos04 Apr 18, 2024
3c824ba
Implemented
SzAkos04 Apr 18, 2024
e340d71
Improved variable name readability
SzAkos04 Apr 18, 2024
cc303d8
Improved error handling, fixed whitespace bug
SzAkos04 Apr 18, 2024
14084b1
Fixed line number bug
SzAkos04 Apr 18, 2024
393e0d9
Implemented usage without files
SzAkos04 Apr 19, 2024
c74d61e
Improved print_stdin function
SzAkos04 Apr 19, 2024
56b7061
Fixed --squeeze-blank unexpected behaviours
SzAkos04 Apr 19, 2024
00e00e7
Added comments
SzAkos04 Apr 19, 2024
25f22dc
Fixed argument parsing, improved code consistency
SzAkos04 Apr 20, 2024
6c71cd0
Fixed print_stdin function segfault
SzAkos04 Apr 20, 2024
1881f9b
Implemented --show-nonprinting, fixed argument parsing
SzAkos04 Apr 20, 2024
d0909cd
Removed done TODO comments
SzAkos04 Apr 20, 2024
068e3ab
Fixed bug where if the args are -nE, the numbers don't show up
SzAkos04 Apr 20, 2024
de075b1
Improved show-nonprinting
SzAkos04 Apr 20, 2024
430e402
Implemented new argument options
SzAkos04 Apr 20, 2024
c35d036
Improved error handling
SzAkos04 Apr 20, 2024
4d492f4
Removed global flag variables
SzAkos04 Apr 20, 2024
a10e25d
Improved code consistency, removed unnecessary code
SzAkos04 Apr 20, 2024
cd2a364
Got rid of most of the manual memory management, fixed flags bug
SzAkos04 Apr 20, 2024
6d24d36
Simplified argument parsing
SzAkos04 Apr 20, 2024
571430d
Added back accidentally deleted argument parsing
SzAkos04 Apr 20, 2024
62cbbf3
Improved error handling
SzAkos04 Apr 20, 2024
656fb15
Improved stridx() function
SzAkos04 Apr 20, 2024
b4e7309
Switched from stack to heap memory allocations
SzAkos04 Apr 21, 2024
6ab5746
Optimized memory usage
SzAkos04 Apr 21, 2024
0a82d46
Added return to main()
SzAkos04 Apr 21, 2024
a06eb21
Merge branch 'proh14:main' into cat
SzAkos04 Apr 21, 2024
6ea8832
Implemented -u flag
SzAkos04 Apr 21, 2024
d267e9a
Improved code readability
SzAkos04 Apr 21, 2024
c31738c
Resolved requested changes
SzAkos04 Apr 21, 2024
0855d3f
Resolved code changes with file reading
SzAkos04 Apr 21, 2024
de49561
Removed logging message
SzAkos04 Apr 21, 2024
6501435
Removed unnecessary include
SzAkos04 Apr 21, 2024
515d7aa
Removed TODO comment
SzAkos04 Apr 21, 2024
88b6745
Moved help message to variable, fixed FLAGLIST
SzAkos04 Apr 21, 2024
abeb60d
Removed unnecessary code
SzAkos04 Apr 21, 2024
2465bfa
Improved error messages, removed more unused code
SzAkos04 Apr 21, 2024
430f65d
Merge branch 'proh14:main' into cat
SzAkos04 Apr 22, 2024
a3eb958
Improved file reading
SzAkos04 Apr 22, 2024
3c3441b
Optimized print_buffer() function
SzAkos04 Apr 22, 2024
2410bf6
Moved buffer clearing
SzAkos04 Apr 22, 2024
1865a48
Merge branch 'proh14:main' into cat
SzAkos04 Apr 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/cat/cat.1
Original file line number Diff line number Diff line change
@@ -1 +1 @@
\" TODO
\" TODO
348 changes: 345 additions & 3 deletions src/cat/cat.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,348 @@
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

int main(void) {
printf("Hello, World!\n");
#define NAME "cat (canoutils)"
#define VERSION "1.0.0"
#define AUTHOR "Akos Szijgyarto (SzAkos04)"

#include "version_info.h"

static const char HELP[] = {
"Usage: cat [OPTION]... [FILE]...\n"
"Concatenate FILE(s) to standard output.\n"
"\n"
"With no FILE, or when FILE is -, read standard input.\n"
"\n"
" -A, --show-all equivalent to -vET\n"
" -b, --number-nonblank number nonempty output lines, overrides -n\n"
" -e equivalent to -vE\n"
" -E, --show-ends display $ at end of each line\n"
" -n, --number number all output lines\n"
" -s, --squeeze-blank suppress repeated empty output lines\n"
" -t equivalent to -vT\n"
" -T, --show-tabs display TAB characters as ^I\n"
" -u (ignored)\n"
" -v, --show-nonprinting use ^ and M- notation, except for LFD and TAB\n"
" --help display this help and exit\n"
" --version output version information and exit\n"
"\n"
"Examples:\n"
" cat f - g Output f's contents, then standard input, then g's "
"contents.\n"
" cat Copy standard input to standard output.\n"};

#define print_help() \
do { \
printf("%s", HELP); \
} while (0)

// only used when reading from stdin
#define BUF_MAX 65535 // max length of a buffer in bytes

#define CONST_ATTR __attribute__((const))

typedef enum {
NumberNonblank = (1 << 0), // number nonempty output lines, overrides -n
ShowEnds = (1 << 1), // display $ at end of each line
Number = (1 << 2), // number all output lines
SqueezeBlank = (1 << 3), // suppress repeated empty output lines
ShowTabs = (1 << 4), // display TAB characters as ^I
ShowNonprinting = (1 << 5), // use ^ and M- notation, except for LFD and TAB
} Flag;

static const char FLAGLIST[] = "bEnsTv";

int cat(int filec, char **paths, unsigned int flags);
int print_buffer(char *buf, ssize_t buf_size, unsigned int flags);
int print_stdin(unsigned int flags);

inline CONST_ATTR int stridx(const char *str, char c);

void free_paths(int filec, char **paths);

int main(int argc, char **argv) {
int filec = 0; // file count
// if argc == 1, then it is allocated 0 bytes
char **paths = (char **)malloc((argc - 1) * sizeof(char *));
if (!paths && argc > 1) {
perror("cat: could not allocate memory");
return EXIT_FAILURE;
}

unsigned int flags = 0;

// parse arguments
for (int i = 1; i < argc; ++i) {
int len = strlen(argv[i]);
if (len > 1 && argv[i][0] == '-') {
for (int j = 1; j < len; ++j) {
switch (argv[i][j]) {
// flags that are multiple flags in one
case 'A':
flags |= ShowNonprinting | ShowEnds | ShowTabs;
break;
case 'b':
flags &= ~Number;
flags |= NumberNonblank;
break;
case 'e':
flags |= ShowNonprinting | ShowEnds;
break;
case 'n':
flags &= ~NumberNonblank;
flags |= Number;
break;
case 't':
flags |= ShowNonprinting | ShowTabs;
break;
case 'u':
// disable buffering for the output stream
// NOTE: for some reason this makes the program run slower on my
// machine. Running the program with the `-u` flag on this source
// code, the average runtime increases by approximately 10ms
if (setvbuf(stdout, NULL, _IONBF, 0) != 0) {
perror("cat: error setting output buffer mode");
return EXIT_FAILURE;
}
break;
case '-':
if (!strcmp(argv[i], "--show-all")) {
flags |= ShowNonprinting | ShowEnds | ShowTabs;
} else if (!strcmp(argv[i], "--number-nonblank")) {
flags &= ~Number;
flags |= NumberNonblank;
} else if (!strcmp(argv[i], "--show-ends")) {
flags |= ShowEnds;
} else if (!strcmp(argv[i], "--number")) {
flags &= ~NumberNonblank;
flags |= Number;
} else if (!strcmp(argv[i], "--squeeze-blank")) {
flags |= SqueezeBlank;
} else if (!strcmp(argv[i], "--show-tabs")) {
flags |= ShowTabs;
} else if (!strcmp(argv[i], "--show-nonprinting")) {
flags |= ShowNonprinting;
} else if (!strcmp(argv[i], "--help")) {
print_help();
free_paths(filec, paths);
return EXIT_SUCCESS;
} else if (!strcmp(argv[i], "--version")) {
print_version();
free_paths(filec, paths);
return EXIT_SUCCESS;
}
break;
default: {
int flag = stridx(FLAGLIST, argv[i][j]);
if (flag == -1) {
fprintf(stderr, "cat: invalid option `-%c`\n", argv[i][j]);
fprintf(stderr, "Try 'cat --help' for more information.\n");
free_paths(filec, paths);
return EXIT_FAILURE;
}
flags |= 1 << (flag);
break;
}
}
}
} else {
// check if the file is accessible
if (access(argv[i], F_OK | R_OK) != 0 && strcmp(argv[i], "-") != 0) {
fprintf(stderr, "cat: file `%s` not found\n", argv[i]);
free_paths(filec, paths);
return EXIT_FAILURE;
}

paths[filec] = (char *)malloc((strlen(argv[i]) + 1) * sizeof(char));
if (!paths[filec]) {
perror("cat: could not allocate memory");
free_paths(filec, paths);
return EXIT_FAILURE;
}

strcpy(paths[filec], argv[i]);
filec++;
}
}

paths = (char **)realloc(paths, filec * sizeof(char *));
if (!paths && argc > 1) {
perror("cat: could not allocate memory");
return EXIT_FAILURE;
}

if (cat(filec, paths, flags) != 0) {
free_paths(filec, paths);
return EXIT_FAILURE;
}

free_paths(filec, paths);
return EXIT_SUCCESS;
}

int cat(int filec, char **paths, unsigned int flags) {
// print stdin
if (filec == 0 || !paths) {
return print_stdin(flags);
}

for (int i = 0; i < filec; ++i) {
// print stdin
if (!strcmp(paths[i], "-")) {
if (print_stdin(flags) != 0) {
return 1;
}

continue;
}

// read file
int fd = open(paths[i], O_RDONLY);
if (fd == -1) {
perror("cat: could not open device");
return 1;
}

// get the size of the file
struct stat st;
if (fstat(fd, &st) == -1) {
perror("cat: could not get file size");
close(fd);
return 1;
}
off_t file_size = st.st_size;

char buf[file_size + 1];
ssize_t total_bytes_read = 0;
while (total_bytes_read < file_size) {
ssize_t bytes_read =
read(fd, buf + total_bytes_read, file_size - total_bytes_read);
if (bytes_read == -1) {
perror("cat: error reading file");
close(fd);
return 1;
} else if (bytes_read == 0) {
break; // end of file
}
total_bytes_read += bytes_read;
}
if (total_bytes_read != file_size) {
perror("cat: could not read file");
close(fd);
return 1;
}

buf[total_bytes_read] = '\0'; // null-terminate the string

if (print_buffer(buf, total_bytes_read, flags) != 0) {
close(fd);
return 1;
}
close(fd);
}
return 0;
}
}

#define BEFORE_NUMBER 6 // number of spaces before line numbers

int print_buffer(char *buf, ssize_t buf_size, unsigned int flags) {
int lines = 1;
if ((flags & Number)) {
// print number before the first line
printf("%*d ", BEFORE_NUMBER, lines);
} else if ((flags & NumberNonblank)) {
if (buf[0] != '\n' && buf[1] != '\0') {
// print number before the first line
printf("%*d ", BEFORE_NUMBER, lines);
}
}
for (int i = 0; i < buf_size; ++i) {
// higher priority
// NOTE: not the prettiest code, but it works
if ((flags & SqueezeBlank) && buf[i] == '\n') {
// skip over consecutive '\n' characters
if (i + 1 < buf_size && buf[i + 1] == '\n') {
// if the consecutive '\n' characters are over
if (i + 2 < buf_size && buf[i + 2] != '\n') {
if ((flags & Number)) {
printf("\n%*d ", BEFORE_NUMBER, ++lines);
} else {
putchar('\n');
}
}
continue;
}
}

if ((flags & NumberNonblank) && buf[i] == '\n' && buf[i + 1] != '\n' &&
buf[i + 1] != '\0') {
printf("\n%*d ", BEFORE_NUMBER, ++lines);
continue;
}
if ((flags & ShowEnds) && buf[i] == '\n') {
putchar('$');
}
if ((flags & Number) && buf[i] == '\n' && buf[i + 1] != '\0') {
printf("\n%*d ", BEFORE_NUMBER, ++lines);
continue;
}
if ((flags & ShowTabs) && buf[i] == '\t') {
printf("^I");
continue;
}
if ((flags & ShowNonprinting) && !isprint(buf[i]) && buf[i] != 9 &&
buf[i] != 10) {
if (buf[i] & 0x80) {
// meta (M-) notation for characters with the eighth bit set
printf("M-");
char printable_char = buf[i] & 0x7F; // clear the eighth bit
printf("^%c", '@' + printable_char);
} else {
// regular non-printable character notation
printf("^%c", '@' + buf[i]);
}
continue;
}

putchar(buf[i]);
}

// clear the buffer after printing it
memset(buf, 0, buf_size);
return 0;
}

int print_stdin(unsigned int flags) {
char buf[BUF_MAX];
ssize_t bytes_read;

while ((bytes_read = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
if (print_buffer(buf, bytes_read, flags) != 0) {
return 1;
}
}

if (bytes_read == -1) {
perror("cat: read error");
return 1;
}

return 0;
}

inline CONST_ATTR int stridx(const char *str, char c) {
const char *p = strchr(str, c);
return (p) ? (int)(p - str) : -1;
}

void free_paths(int filec, char **paths) {
for (int i = 0; i < filec; i++) {
free(paths[i]);
}
free(paths);
}