replay-log: add validations for corrupt log entries
[xfstests-dev.git] / src / log-writes / log-writes.c
1 #include <linux/fs.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <sys/ioctl.h>
5 #include <fcntl.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <errno.h>
9 #include <unistd.h>
10 #include <string.h>
11 #include "log-writes.h"
12
13 int log_writes_verbose = 0;
14
15 /*
16  * @log: the log to free.
17  *
18  * This will close any open fd's the log has and free up its memory.
19  */
20 void log_free(struct log *log)
21 {
22         if (log->replayfd >= 0)
23                 close(log->replayfd);
24         if (log->logfd >= 0)
25                 close(log->logfd);
26         free(log);
27 }
28
29 static int discard_range(struct log *log, u64 start, u64 len)
30 {
31         u64 range[2] = { start, len };
32
33         if (ioctl(log->replayfd, BLKDISCARD, &range) < 0) {
34                 if (log_writes_verbose)
35                         printf("replay device doesn't support discard, "
36                                "switching to writing zeros\n");
37                 log->flags |= LOG_DISCARD_NOT_SUPP;
38         }
39         return 0;
40 }
41
42 static int zero_range(struct log *log, u64 start, u64 len)
43 {
44         u64 bufsize = len;
45         ssize_t ret;
46         char *buf = NULL;
47
48         if (log->max_zero_size < len) {
49                 if (log_writes_verbose)
50                         printf("discard len %llu larger than max %llu\n",
51                                (unsigned long long)len,
52                                (unsigned long long)log->max_zero_size);
53                 return 0;
54         }
55
56         while (!buf) {
57                 buf = malloc(bufsize);
58                 if (!buf)
59                         bufsize >>= 1;
60                 if (!bufsize) {
61                         fprintf(stderr, "Couldn't allocate zero buffer");
62                         return -1;
63                 }
64         }
65
66         memset(buf, 0, bufsize);
67         while (len) {
68                 ret = pwrite(log->replayfd, buf, bufsize, start);
69                 if (ret != bufsize) {
70                         fprintf(stderr, "Error zeroing file: %d\n", errno);
71                         free(buf);
72                         return -1;
73                 }
74                 len -= ret;
75                 start += ret;
76         }
77         free(buf);
78         return 0;
79 }
80
81 /*
82  * @log: the log we are replaying.
83  * @entry: the discard entry.
84  *
85  * Discard the given length.  If the device supports discard we will call that
86  * ioctl, otherwise we will write 0's to emulate discard.  If the discard size
87  * is larger than log->max_zero_size then we will simply skip the zero'ing if
88  * the drive doesn't support discard.
89  */
90 int log_discard(struct log *log, struct log_write_entry *entry)
91 {
92         u64 start = le64_to_cpu(entry->sector) * log->sectorsize;
93         u64 size = le64_to_cpu(entry->nr_sectors) * log->sectorsize;
94         u64 max_chunk = 1 * 1024 * 1024 * 1024;
95
96         if (log->flags & LOG_IGNORE_DISCARD)
97                 return 0;
98
99         while (size) {
100                 u64 len = size > max_chunk ? max_chunk : size;
101                 int ret;
102
103                 /*
104                  * Do this check first in case it is our first discard, that way
105                  * if we return EOPNOTSUPP we will fall back to the 0 method
106                  * automatically.
107                  */
108                 if (!(log->flags & LOG_DISCARD_NOT_SUPP))
109                         ret = discard_range(log, start, len);
110                 if (log->flags & LOG_DISCARD_NOT_SUPP)
111                         ret = zero_range(log, start, len);
112                 if (ret)
113                         return -1;
114                 size -= len;
115                 start += len;
116         }
117         return 0;
118 }
119
120 /*
121  * @entry: entry to be replayed.
122  *
123  * @return: 1 if the entry is sane, 0 if it is invalid.
124  *
125  * Check if this is a sane log entry.
126  */
127 int log_entry_valid(struct log_write_entry *entry)
128 {
129         u64 flags = le64_to_cpu(entry->flags);
130
131         /* Suspect all zeroes entry */
132         if (!flags && !entry->nr_sectors)
133                 return 0;
134         /* Suspect non zero padded entry */
135         if (flags != LOG_MARK_FLAG && entry->data[0] != 0)
136                 return 0;
137         return 1;
138 }
139
140 /*
141  * @log: the log we are replaying.
142  * @entry: where we put the entry.
143  * @read_data: read the entry data as well, entry must be log->sectorsize sized
144  * if this is set.
145  *
146  * @return: 0 if we replayed, 1 if we are at the end, -1 if there was an error.
147  *
148  * Replay the next entry in our log onto the replay device.
149  */
150 int log_replay_next_entry(struct log *log, struct log_write_entry *entry,
151                           int read_data)
152 {
153         u64 size;
154         u64 flags;
155         size_t read_size = read_data ? log->sectorsize :
156                 sizeof(struct log_write_entry);
157         char *buf;
158         ssize_t ret;
159         off_t offset;
160
161         if (log->cur_entry >= log->nr_entries)
162                 return 1;
163
164         ret = read(log->logfd, entry, read_size);
165         if (ret != read_size) {
166                 fprintf(stderr, "Error reading entry: %d\n", errno);
167                 return -1;
168         }
169         if (!log_entry_valid(entry)) {
170                 fprintf(stderr, "Malformed entry @%llu\n",
171                                 log->cur_pos / log->sectorsize);
172                 return -1;
173         }
174         log->cur_entry++;
175
176         size = le64_to_cpu(entry->nr_sectors) * log->sectorsize;
177         if (read_size < log->sectorsize) {
178                 log->cur_pos = lseek(log->logfd,
179                         log->sectorsize - sizeof(struct log_write_entry), SEEK_CUR);
180                 if (log->cur_pos == (off_t)-1) {
181                         fprintf(stderr, "Error seeking in log: %d\n", errno);
182                         return -1;
183                 }
184         } else {
185                 log->cur_pos += read_size;
186         }
187
188         if (log_writes_verbose) {
189                 printf("replaying %d@%llu: sector %llu, size %llu, flags %llu\n",
190                        (int)log->cur_entry - 1, log->cur_pos / log->sectorsize,
191                        (unsigned long long)le64_to_cpu(entry->sector),
192                        (unsigned long long)size,
193                        (unsigned long long)le64_to_cpu(entry->flags));
194         }
195         if (!size)
196                 return 0;
197
198         flags = le64_to_cpu(entry->flags);
199         if (flags & LOG_DISCARD_FLAG)
200                 return log_discard(log, entry);
201
202         buf = malloc(size);
203         if (!buf) {
204                 fprintf(stderr, "Error allocating buffer %llu entry %llu\n", (unsigned long long)size, (unsigned long long)log->cur_entry - 1);
205                 return -1;
206         }
207
208         ret = read(log->logfd, buf, size);
209         if (ret != size) {
210                 fprintf(stderr, "Error reading data: %d\n", errno);
211                 free(buf);
212                 return -1;
213         }
214         log->cur_pos += size;
215
216         offset = le64_to_cpu(entry->sector) * log->sectorsize;
217         ret = pwrite(log->replayfd, buf, size, offset);
218         free(buf);
219         if (ret != size) {
220                 fprintf(stderr, "Error writing data: %d\n", errno);
221                 return -1;
222         }
223
224         return 0;
225 }
226
227 /*
228  * @log: the log we are manipulating.
229  * @entry_num: the entry we want.
230  *
231  * Seek to the given entry in the log, starting at 0 and ending at
232  * log->nr_entries - 1.
233  */
234 int log_seek_entry(struct log *log, u64 entry_num)
235 {
236         u64 i = 0;
237
238         if (entry_num >= log->nr_entries) {
239                 fprintf(stderr, "Invalid entry number\n");
240                 return -1;
241         }
242
243         /* Skip the first sector containing the log super block */
244         log->cur_pos = lseek(log->logfd, log->sectorsize, SEEK_SET);
245         if (log->cur_pos == (off_t)-1) {
246                 fprintf(stderr, "Error seeking in file: %d\n", errno);
247                 return -1;
248         }
249
250         log->cur_entry = 0;
251         for (i = 0; i < entry_num; i++) {
252                 struct log_write_entry entry;
253                 ssize_t ret;
254                 off_t seek_size;
255                 u64 flags;
256
257                 ret = read(log->logfd, &entry, sizeof(entry));
258                 if (ret != sizeof(entry)) {
259                         fprintf(stderr, "Error reading entry: %d\n", errno);
260                         return -1;
261                 }
262                 if (!log_entry_valid(&entry)) {
263                         fprintf(stderr, "Malformed entry @%llu\n",
264                                         log->cur_pos / log->sectorsize);
265                         return -1;
266                 }
267                 if (log_writes_verbose > 1)
268                         printf("seek entry %d@%llu: %llu, size %llu, flags %llu\n",
269                                (int)i, log->cur_pos / log->sectorsize,
270                                (unsigned long long)le64_to_cpu(entry.sector),
271                                (unsigned long long)le64_to_cpu(entry.nr_sectors),
272                                (unsigned long long)le64_to_cpu(entry.flags));
273                 flags = le64_to_cpu(entry.flags);
274                 seek_size = log->sectorsize - sizeof(entry);
275                 if (!(flags & LOG_DISCARD_FLAG))
276                         seek_size += le64_to_cpu(entry.nr_sectors) *
277                                 log->sectorsize;
278                 log->cur_pos = lseek(log->logfd, seek_size, SEEK_CUR);
279                 if (log->cur_pos == (off_t)-1) {
280                         fprintf(stderr, "Error seeking in file: %d\n", errno);
281                         return -1;
282                 }
283                 log->cur_entry++;
284         }
285
286         return 0;
287 }
288
289 /*
290  * @log: the log we are manipulating.
291  * @entry: the entry we read.
292  * @read_data: read the extra data for the entry, your entry must be
293  * log->sectorsize large.
294  *
295  * @return: 1 if we hit the end of the log, 0 we got the next entry, < 0 if
296  * there was an error.
297  *
298  * Seek to the next entry in the log.
299  */
300 int log_seek_next_entry(struct log *log, struct log_write_entry *entry,
301                         int read_data)
302 {
303         size_t read_size = read_data ? log->sectorsize :
304                 sizeof(struct log_write_entry);
305         u64 flags;
306         ssize_t ret;
307
308         if (log->cur_entry >= log->nr_entries)
309                 return 1;
310
311         ret = read(log->logfd, entry, read_size);
312         if (ret != read_size) {
313                 fprintf(stderr, "Error reading entry: %d\n", errno);
314                 return -1;
315         }
316         if (!log_entry_valid(entry)) {
317                 fprintf(stderr, "Malformed entry @%llu\n",
318                                 log->cur_pos / log->sectorsize);
319                 return -1;
320         }
321         log->cur_entry++;
322
323         if (read_size < log->sectorsize) {
324                 log->cur_pos = lseek(log->logfd,
325                         log->sectorsize - sizeof(struct log_write_entry), SEEK_CUR);
326                 if (log->cur_pos == (off_t)-1) {
327                         fprintf(stderr, "Error seeking in log: %d\n", errno);
328                         return -1;
329                 }
330         } else {
331                 log->cur_pos += read_size;
332         }
333         if (log_writes_verbose > 1)
334                 printf("seek entry %d@%llu: %llu, size %llu, flags %llu\n",
335                        (int)log->cur_entry - 1, log->cur_pos / log->sectorsize,
336                        (unsigned long long)le64_to_cpu(entry->sector),
337                        (unsigned long long)le64_to_cpu(entry->nr_sectors),
338                        (unsigned long long)le64_to_cpu(entry->flags));
339
340         flags = le64_to_cpu(entry->flags);
341         read_size = le64_to_cpu(entry->nr_sectors) * log->sectorsize;
342         if (!read_size || (flags & LOG_DISCARD_FLAG))
343                 return 0;
344
345         log->cur_pos = lseek(log->logfd, read_size, SEEK_CUR);
346         if (log->cur_pos == (off_t)-1) {
347                 fprintf(stderr, "Error seeking in log: %d\n", errno);
348                 return -1;
349         }
350
351         return 0;
352 }
353
354 /*
355  * @logfile: the file that contains the write log.
356  * @replayfile: the file/device to replay onto, can be NULL.
357  *
358  * Opens a logfile and makes sure it is valid and returns a struct log.
359  */
360 struct log *log_open(char *logfile, char *replayfile)
361 {
362         struct log *log;
363         struct log_write_super super;
364         ssize_t ret;
365
366         log = malloc(sizeof(struct log));
367         if (!log) {
368                 fprintf(stderr, "Couldn't alloc log\n");
369                 return NULL;
370         }
371
372         log->replayfd = -1;
373
374         log->logfd = open(logfile, O_RDONLY);
375         if (log->logfd < 0) {
376                 fprintf(stderr, "Couldn't open log %s: %d\n", logfile,
377                         errno);
378                 log_free(log);
379                 return NULL;
380         }
381
382         if (replayfile) {
383                 log->replayfd = open(replayfile, O_WRONLY);
384                 if (log->replayfd < 0) {
385                         fprintf(stderr, "Couldn't open replay file %s: %d\n",
386                                 replayfile, errno);
387                         log_free(log);
388                         return NULL;
389                 }
390         }
391
392         ret = read(log->logfd, &super, sizeof(struct log_write_super));
393         if (ret < sizeof(struct log_write_super)) {
394                 fprintf(stderr, "Error reading super: %d\n", errno);
395                 log_free(log);
396                 return NULL;
397         }
398
399         if (le64_to_cpu(super.magic) != WRITE_LOG_MAGIC) {
400                 fprintf(stderr, "Magic doesn't match\n");
401                 log_free(log);
402                 return NULL;
403         }
404
405         if (le64_to_cpu(super.version) != WRITE_LOG_VERSION) {
406                 fprintf(stderr, "Version mismatch, wanted %d, have %d\n",
407                         WRITE_LOG_VERSION, (int)le64_to_cpu(super.version));
408                 log_free(log);
409                 return NULL;
410         }
411
412         log->sectorsize = le32_to_cpu(super.sectorsize);
413         log->nr_entries = le64_to_cpu(super.nr_entries);
414         log->max_zero_size = 128 * 1024 * 1024;
415
416         log->cur_pos = lseek(log->logfd, log->sectorsize - sizeof(super), SEEK_CUR);
417         if (log->cur_pos == (off_t) -1) {
418                 fprintf(stderr, "Error seeking to first entry: %d\n", errno);
419                 log_free(log);
420                 return NULL;
421         }
422         log->cur_entry = 0;
423
424         return log;
425 }