-
Notifications
You must be signed in to change notification settings - Fork 0
/
tail.c
246 lines (203 loc) · 6.35 KB
/
tail.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// tail.c
// Řešení IJC-DU2, příklad 1), 25.3. 2023
// Autor: Jakub Antonín Štigler, FIT
// Přeloženo: clang 15.0.7
// Funguje s gcc 12.2.1
// C standard: C11
#include <stdio.h> // size_t, FILE, fprintf, fopen, fclose, fscanf, fputs,
// NULL
#include <stdlib.h> // strtoul, EXIT_FAILURE, EXIT_SUCCESS, free, malloc,
// calloc
#include <assert.h> // assert
#include <string.h> // strcmp
// ANSI escape codes colorful prints
#ifdef NO_COLOR_PRINT
#define MAGENTA
#define RESET
#define RED
#else
#define RED "\x1b[91m"
#define MAGENTA "\x1b[95m"
#define RESET "\x1b[0m"
#endif
// Circular buffer headers >>==================================================
// curcular buffer
typedef struct {
size_t size;
size_t write;
size_t read;
char **buffer;
} cb_t;
// creates new circular buffer that can hold up to 'n' items at once, n must
// be at least 1. returns buffer with size 0 if the creation fails
cb_t cb_create(size_t n);
// adds line to the buffer. Returns the overwritten line or NULL if no line
// was overwritten
char *cb_put(cb_t *cb, char *line);
// gets the line from the read position and moves to the next position, returns
// null if the buffer is empty
char *cb_get(cb_t *cb);
// frees the memory allocated by the budder DOESN'T FREE THE ITEMS
void cb_free(cb_t cb);
// Tail implementation >>======================================================
// line length implementation limit
#define MAX_LINE_LEN ((size_t)4095)
// writes the last 'line_count' lines from 'in' to 'out'. returns true on
// success, otherwise false
_Bool tail(FILE *in, FILE* out, size_t line_count);
// my implementation of gets that returns the position of the last readed
// character. Len must be at least 1 (the behavior is undefined for 0) as
// it also includes the newline character
char *getline(FILE *in, char *buffer, size_t len);
int main(int argc, char **argv) {
(void)argc; // ignore unused parameter warning
char *filename = NULL;
size_t line_count = 10;
// parse the arguments, only one filename allowed
// multiple -n arguments allowed, the last takes effect
while (*++argv) { // C standard says that argv[argc] shall be NULL
if (strcmp(*argv, "-n") == 0) {
if (!*++argv) {
fprintf(
stderr,
RED "failure:" RESET " Expected positive integer after "
"'-n', but there are no more arguments\n"
);
return EXIT_FAILURE;
}
char *res;
line_count = strtoul(*argv, &res, 10);
if (*res || **argv == '-') {
fprintf(
stderr,
RED "failure:" RESET " Expected positive integer after "
"'-n', found '%s'\n",
*argv
);
return EXIT_FAILURE;
}
} else if (filename) {
fprintf(
stderr,
RED "failure:" RESET " Multiple files specified\n"
);
return EXIT_FAILURE;
} else {
filename = *argv;
}
}
FILE *in = filename ? fopen(filename, "r") : stdin;
int ret = EXIT_SUCCESS;
if (!tail(in, stdout, line_count)) {
fprintf(
stderr,
RED "failure:" RESET " An unrecoverable failure occured (maybe "
"not enough memory)\n"
);
ret = EXIT_FAILURE;
}
if (filename)
fclose(in);
return ret;
}
_Bool tail(FILE *in, FILE *out, size_t line_count) {
_Static_assert(MAX_LINE_LEN, "Invalid max line length (was 0)");
cb_t lines = cb_create(line_count);
if (lines.size == 0)
return 0;
char *buffer = NULL;
// whether to print long line warning at the end
_Bool warn = 0;
size_t warndist = 0;
// read the lines
for (;; ++warndist) {
// allocate the buffer if it isn't
if (!buffer) {
buffer = malloc(MAX_LINE_LEN);
// when allocation fails it is time to panic
if (!buffer)
goto panic;
}
char *end = getline(in, buffer, MAX_LINE_LEN);
if (end == buffer) // reached EOF
break;
// check if line is too long
if (end[-1] != '\n' && end - buffer + 1 == MAX_LINE_LEN) {
// skip all characters on the line (including newline)
fscanf(in, "%*[^\n]");
fscanf(in, "%*c");
// make sure that all lines end with newline
end[-1] = '\n';
warn = 1;
warndist = 0;
}
buffer = cb_put(&lines, buffer);
}
// print the lines and free the memory
if (buffer)
free(buffer);
while ((buffer = cb_get(&lines))) {
fputs(buffer, out);
free(buffer);
}
cb_free(lines);
// print the warning only if the long lines are actually printed
if (warn && warndist <= line_count) {
fprintf(
stderr,
"tail: " MAGENTA "warning:" RESET " some long lines were trimmed "
"to %zu characters\n",
MAX_LINE_LEN - 2
);
}
return 1;
panic: // on error, free all memory and return false
if (buffer)
free(buffer);
while ((buffer = cb_get(&lines)))
free(buffer);
cb_free(lines);
return 0;
}
char *getline(FILE *in, char *buffer, size_t len) {
assert(len);
int c = 0;
while (--len && c != '\n' && (c = fgetc(in)) != EOF)
*buffer++ = c;
*buffer = 0;
return buffer;
}
// Curcular buffer implementation >>===========================================
cb_t cb_create(size_t n) {
cb_t cb = {
.size = n + 1,
.write = 0,
.read = 0,
.buffer = NULL,
};
if (n < 1) {
cb.size = 0;
return cb;
}
cb.buffer = calloc(n + 1, sizeof(char *));
if (!cb.buffer)
cb.size = 0;
return cb;
}
char *cb_put(cb_t *cb, char *line) {
char *ret = (cb->write + 1) % cb->size == cb->read ? cb_get(cb) : NULL;
cb->buffer[cb->write] = line;
cb->write = (cb->write + 1) % cb->size;
return ret;
}
char *cb_get(cb_t *cb) {
if (cb->read == cb->write)
return NULL;
char *ret = cb->buffer[cb->read];
cb->read = (cb->read + 1) % cb->size;
return ret;
}
void cb_free(cb_t cb) {
if (cb.buffer)
free(cb.buffer);
}