-
Notifications
You must be signed in to change notification settings - Fork 0
/
ctail.c
301 lines (269 loc) · 8.61 KB
/
ctail.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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
// ctail.c
// Author: Michal Krulich
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#define LINE_LENGTH_LIMIT 16383 // Maximum length of a single line
#define DEFAULT_LINES_BUFFER_SIZE 10 // Default number of last lines to be displayed
#define BUFFER_SIZE_LIMIT 4200000000u // Limit for total number of lines in a file
// Cyclic buffer that contains pointers to strings.
// The size of the cyclic buffer is always bigger by one than the maximum count of held objects.
// The buffer is full, when the end index stands right in front of the start index.
// The buffer is empty, when the start and end index are equal.
struct circ_bfr // Circular buffer
{
unsigned size; // Usable size of the buffer, how many objects can this buffer hold.
unsigned start; // Number indexing the first object.
unsigned end; // Number indexing place where will be placed next object.
char *lines[]; // Array containing the lines.
};
typedef struct circ_bfr cbfr_t;
// The real size of the cyclic buffer
#define cb_true_size(cbfr_pointer) ((cbfr_pointer)->size + 1)
// Increments the count of objects inside the cyclic buffer
// Moves the start index
#define cb_inc_start(cbfr) \
do \
{ \
(cbfr)->start = ((cbfr)->start + 1) % cb_true_size(cbfr); \
} while (0)
// Decrements the count of objects inside the cyclic buffer
// Moves the end index
#define cb_inc_end(cbfr) \
do \
{ \
(cbfr)->end = ((cbfr)->end + 1) % cb_true_size(cbfr); \
} while (0)
/*
* Allocates the memory space for the cyclic buffer and initializes it.
* Returns the pointer to the buffer or NULL in case of an error.
*/
cbfr_t *cb_create(unsigned n);
/*
* Inserts the the dynamically allocated string into the buffer.
* If the buffer is already full, the first line in the buffer is removed and deallocated.
*/
void cb_put(cbfr_t *cb, char *line);
/*
* Returns pointer to the first line in the buffer and removes it from the buffer.
* Returns NULL, if the buffer is empty.
*/
char *cb_get(cbfr_t *cb);
/*
* Frees the cyclic buffer and its content from the memory.
*/
void cb_free(cbfr_t *cb);
/*
* Prints out a string ended with '\0' or '\n' to stdout.
*/
void print_line(char *line);
/*
* Safely closes a file.
*/
void safe_file_close(const char *name, FILE *file)
{
if (name != NULL)
{
fclose(file);
}
}
int main(int argc, char *argv[])
{
unsigned buffer_size = DEFAULT_LINES_BUFFER_SIZE;
char *file_to_be_openned = NULL; // Name of the file to be openned.
bool process_switches = true;
// Argument processing, similiar to shell command "shift" logic
for (int next_argument = 1; next_argument < argc; next_argument++)
{
// help text
if (strcmp(argv[next_argument], "--help") == 0 && process_switches)
{
printf("Usage: ctail [OPTION]... [FILE]...\n"
"Print the last 10 lines of each FILE to standard output.\n"
"With no FILE, or when FILE is -, read standard input.\n"
"Options:\n"
" -n [X] Prints the last X lines.\n"
" --help Prints out this help.\n"
" -i Ignores switches/options after this one.\n"
" ctail -i --help ... reads file with name \"--help\"\n"
"This program tries to mimic the behaviour of the classic UNIX tail utillity");
return EXIT_SUCCESS;
}
else if(strcmp(argv[next_argument], "-i") == 0 && process_switches)
{
process_switches = false;
}
// switch -n, controls the number of lines to be displayed
else if (strcmp(argv[next_argument], "-n") == 0 && process_switches)
{
next_argument++;
if (next_argument >= argc)
{
fprintf(stderr, "ERROR: Expected a number after -n\n");
return EXIT_FAILURE;
}
if (sscanf(argv[next_argument], "%u", &buffer_size) != 1)
{
fprintf(stderr, "ERROR: Expected a number after -n\n");
return EXIT_FAILURE;
}
if (buffer_size > BUFFER_SIZE_LIMIT)
{
fprintf(stderr, "ERROR: Number is too large or smaller than 0\n");
return EXIT_FAILURE;
}
if (buffer_size == 0) // No line should be printed
{
return EXIT_SUCCESS;
}
}
else
{
file_to_be_openned = argv[next_argument];
}
}
FILE *input = stdin;
if (file_to_be_openned != NULL)
{
input = fopen(file_to_be_openned, "r");
if (input == NULL)
{
fprintf(stderr, "ERROR: Could not open file \"%s\"\n", file_to_be_openned);
return EXIT_FAILURE;
}
}
cbfr_t *buffer = cb_create(buffer_size);
if (buffer == NULL)
{
fprintf(stderr, "cb_create: Memory error\n");
safe_file_close(file_to_be_openned, input);
return EXIT_FAILURE;
}
int c; // read character
int idx = 0; // position of the next character
bool overflow = false; // states wheter the LINE_LENGTH_LIMIT was reached
char *line = malloc(sizeof(char) * (LINE_LENGTH_LIMIT + 1));
if (line == NULL)
{
fprintf(stderr, "main: Memory error\n");
cb_free(buffer);
safe_file_close(file_to_be_openned, input);
return EXIT_FAILURE;
}
int overflow_happened = 0;
while (true)
{
c = fgetc(input);
if (c == '\n' || c == EOF) // line processing
{
if (c == EOF && idx == 0) // classic ending of an UNIX file (...,LF,EOF)
{
free(line);
break;
}
line[idx] = '\0';
line[idx] = (c == '\n') ? '\n' : '\0';
cb_put(buffer, line);
idx = 0;
overflow = false;
if (c == EOF) // Last line does not end with LF but with EOF
{
break;
}
line = malloc(sizeof(char) * (LINE_LENGTH_LIMIT + 1));
if (line == NULL)
{
fprintf(stderr, "main: Memory error\n");
cb_free(buffer);
safe_file_close(file_to_be_openned, input);
return EXIT_FAILURE;
}
}
else if (!overflow) // save the read character
{
if (idx == LINE_LENGTH_LIMIT) // overflow detected
{
if (overflow_happened == 0)
{
fprintf(stderr, "WARNING: One or more lines are longer than %d, so their whole content could not be displayed.\n", LINE_LENGTH_LIMIT);
}
overflow = true;
overflow_happened++;
continue;
}
line[idx] = (char)c;
idx++;
}
}
// Print last N lines
while ((line = cb_get(buffer)) != NULL)
{
print_line(line);
free(line);
}
cb_free(buffer);
safe_file_close(file_to_be_openned, input);
return 0;
}
cbfr_t *cb_create(unsigned n)
{
assert(n != 0);
cbfr_t *cb = malloc(sizeof(cbfr_t) + sizeof(char *) * (n + 1));
if (cb == NULL)
{
return NULL;
}
cb->size = n;
cb->start = 0;
cb->end = 0;
return cb;
}
void cb_put(cbfr_t *cb, char *line)
{
assert(cb != NULL);
assert(line != NULL);
// Buffer is full. First line must be removed and deallocated.
if ((cb->end + 1) % cb_true_size(cb) == cb->start)
{
free(cb->lines[cb->start]);
cb_inc_start(cb);
}
cb->lines[cb->end] = line;
cb_inc_end(cb);
}
char *cb_get(cbfr_t *cb)
{
assert(cb != NULL);
if (cb->start == cb->end) // Empty buffer
{
return NULL;
}
char *line = cb->lines[cb->start];
cb_inc_start(cb);
return line;
}
void cb_free(cbfr_t *cb)
{
assert(cb != NULL);
char *line;
while (cb->start != cb->end)
{
line = cb_get(cb);
free(line);
}
free(cb);
}
void print_line(char *line)
{
assert(line != NULL);
for (int i = 0; line[i] != '\0'; i++)
{
putchar(line[i]);
if (line[i] == '\n')
{
return;
}
}
}