Line data Source code
1 : /* Copyright (C) 1989-2020 Free Software Foundation, Inc.
2 : Written by James Clark (jjc@jclark.com)
3 :
4 : This file is part of groff, the GNU roff typesetting system.
5 :
6 : groff is free software; you can redistribute it and/or modify it under
7 : the terms of the GNU General Public License as published by the Free
8 : Software Foundation, either version 3 of the License, or
9 : (at your option) any later version.
10 :
11 : groff is distributed in the hope that it will be useful, but WITHOUT ANY
12 : WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 : FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 : for more details.
15 :
16 : You should have received a copy of the GNU General Public License
17 : along with this program. If not, see <http://www.gnu.org/licenses/>. */
18 :
19 : #ifdef HAVE_CONFIG_H
20 : #include <config.h>
21 : #endif
22 :
23 : #include <errno.h>
24 : #include <stdlib.h> // free(), malloc()
25 :
26 : #include "lib.h"
27 :
28 : #include "posix.h"
29 : #include "cset.h"
30 : #include "cmap.h"
31 : #include "errarg.h"
32 : #include "error.h"
33 :
34 : #include "refid.h"
35 : #include "search.h"
36 : #include "index.h"
37 : #include "defs.h"
38 :
39 : #include "nonposix.h"
40 :
41 : // Interface to mmap.
42 : extern "C" {
43 : void *mapread(int fd, int len);
44 : int unmap(void *, int len);
45 : }
46 :
47 : #if 0
48 : const
49 : #endif
50 : int minus_one = -1;
51 :
52 : bool do_verify = false;
53 :
54 : struct word_list;
55 :
56 : class index_search_item : public search_item {
57 : search_item *out_of_date_files;
58 : index_header header;
59 : char *buffer;
60 : void *map_addr;
61 : int map_len;
62 : tag *tags;
63 : int *table;
64 : int *lists;
65 : char *pool;
66 : char *key_buffer;
67 : char *filename_buffer;
68 : size_t filename_buflen;
69 : char **common_words_table;
70 : int common_words_table_size;
71 : const char *ignore_fields;
72 : time_t mtime;
73 :
74 : const char *get_invalidity_reason();
75 : const int *search1(const char **pp, const char *end);
76 : const int *search(const char *ptr, int length, int **temp_listp);
77 : const char *munge_filename(const char *);
78 : void read_common_words_file();
79 : void add_out_of_date_file(int fd, const char *filename, int fid);
80 : public:
81 : index_search_item(const char *, int);
82 : ~index_search_item();
83 : const char *check_header(index_header *, unsigned);
84 : bool load(int fd);
85 : search_item_iterator *make_search_item_iterator(const char *);
86 : bool is_valid();
87 : void check_files();
88 : int next_filename_id() const;
89 : friend class index_search_item_iterator;
90 : };
91 :
92 : class index_search_item_iterator : public search_item_iterator {
93 : index_search_item *indx;
94 : search_item_iterator *out_of_date_files_iter;
95 : search_item *next_out_of_date_file;
96 : const int *found_list;
97 : int *temp_list;
98 : char *buf;
99 : int buflen;
100 : linear_searcher searcher;
101 : char *query;
102 : int get_tag(int tagno, const linear_searcher &, const char **, int *,
103 : reference_id *);
104 : public:
105 : index_search_item_iterator(index_search_item *, const char *);
106 : ~index_search_item_iterator();
107 : int next(const linear_searcher &, const char **, int *, reference_id *);
108 : };
109 :
110 :
111 0 : index_search_item::index_search_item(const char *filename, int fid)
112 : : search_item(filename, fid), out_of_date_files(0), buffer(0), map_addr(0),
113 : map_len(0), key_buffer(0), filename_buffer(0), filename_buflen(0),
114 0 : common_words_table(0)
115 : {
116 0 : }
117 :
118 0 : index_search_item::~index_search_item()
119 : {
120 0 : if (buffer)
121 0 : free(buffer);
122 0 : if (map_addr) {
123 0 : if (unmap(map_addr, map_len) < 0)
124 0 : error("unmap: %1", strerror(errno));
125 : }
126 0 : while (out_of_date_files) {
127 0 : search_item *tem = out_of_date_files;
128 0 : out_of_date_files = out_of_date_files->next;
129 0 : delete tem;
130 : }
131 0 : delete[] filename_buffer;
132 0 : delete[] key_buffer;
133 0 : if (common_words_table) {
134 0 : for (int i = 0; i < common_words_table_size; i++)
135 0 : delete[] common_words_table[i];
136 0 : delete[] common_words_table;
137 : }
138 0 : }
139 :
140 : class file_closer {
141 : int *fdp;
142 : public:
143 0 : file_closer(int &fd) : fdp(&fd) { }
144 0 : ~file_closer() { close(*fdp); }
145 : };
146 :
147 : // Tell the compiler that a variable is intentionally unused.
148 0 : inline void unused(void *) { }
149 :
150 : // Validate the data reported in the header so that we don't overread on
151 : // the heap in the load() member function. Return null pointer if no
152 : // problems are detected.
153 0 : const char *index_search_item::check_header(index_header *file_header,
154 : unsigned file_size)
155 : {
156 0 : if (file_header->tags_size < 0)
157 0 : return "tag list length negative";
158 0 : if (file_header->lists_size < 0)
159 0 : return "reference list length negative";
160 : // The table and string pool sizes will not be zero, even in an empty
161 : // index.
162 0 : if (file_header->table_size < 1)
163 0 : return "table size nonpositive";
164 0 : if (file_header->strings_size < 1)
165 0 : return "string pool size nonpositive";
166 0 : size_t sz = (file_header->tags_size * sizeof(tag)
167 0 : + file_header->lists_size * sizeof(int)
168 0 : + file_header->table_size * sizeof(int)
169 0 : + file_header->strings_size
170 : + sizeof *file_header);
171 0 : if (sz != file_size)
172 0 : return("size mismatch between header and data");
173 0 : unsigned size_remaining = file_size;
174 0 : unsigned chunk_size = file_header->tags_size * sizeof(tag);
175 0 : if (chunk_size > size_remaining)
176 0 : return "claimed tag list length exceeds file size";
177 0 : size_remaining -= chunk_size;
178 0 : chunk_size = file_header->lists_size * sizeof(int);
179 0 : if (chunk_size > size_remaining)
180 0 : return "claimed reference list length exceeds file size";
181 0 : size_remaining -= chunk_size;
182 0 : chunk_size = file_header->table_size * sizeof(int);
183 0 : if (chunk_size > size_remaining)
184 0 : return "claimed table size exceeds file size";
185 0 : size_remaining -= chunk_size;
186 0 : chunk_size = file_header->strings_size;
187 0 : if (chunk_size > size_remaining)
188 0 : return "claimed string pool size exceeds file size";
189 0 : return 0;
190 : }
191 :
192 0 : bool index_search_item::load(int fd)
193 : {
194 0 : file_closer fd_closer(fd); // close fd on return
195 0 : unused(&fd_closer);
196 : struct stat sb;
197 0 : if (fstat(fd, &sb) < 0) {
198 0 : error("can't fstat index '%1': %2", name, strerror(errno));
199 0 : return false;
200 : }
201 0 : if (!S_ISREG(sb.st_mode)) {
202 0 : error("index '%1' is not a regular file", name);
203 0 : return false;
204 : }
205 0 : mtime = sb.st_mtime;
206 0 : unsigned size = unsigned(sb.st_size); // widening conversion
207 0 : if (size == 0) {
208 0 : error("index '%1' is an empty file", name);
209 0 : return false;
210 : }
211 : char *addr;
212 0 : map_addr = mapread(fd, size);
213 0 : if (map_addr) {
214 0 : addr = (char *)map_addr;
215 0 : map_len = size;
216 : }
217 : else {
218 0 : addr = buffer = (char *)malloc(size);
219 0 : if (buffer == 0) {
220 0 : error("can't allocate memory to process index '%1'", name);
221 0 : return false;
222 : }
223 0 : char *ptr = buffer;
224 0 : int bytes_to_read = size;
225 0 : while (bytes_to_read > 0) {
226 0 : int nread = read(fd, ptr, bytes_to_read);
227 0 : if (nread == 0) {
228 0 : error("unexpected end-of-file while reading index '%1'", name);
229 0 : return false;
230 : }
231 0 : if (nread < 0) {
232 0 : error("read error on index '%1': %2", name, strerror(errno));
233 0 : return false;
234 : }
235 0 : bytes_to_read -= nread;
236 0 : ptr += nread;
237 : }
238 : }
239 0 : header = *(index_header *)addr;
240 0 : if (header.magic != INDEX_MAGIC) {
241 0 : error("'%1' is not an index file: wrong magic number", name);
242 0 : return false;
243 : }
244 0 : if (header.version != INDEX_VERSION) {
245 0 : error("version number in index '%1' is wrong: was %2, should be %3",
246 0 : name, header.version, INDEX_VERSION);
247 0 : return false;
248 : }
249 0 : const char *problem = check_header(&header, size);
250 0 : if (problem != 0) {
251 0 : if (do_verify)
252 0 : error("corrupt header in index file '%1': %2", name, problem);
253 : else
254 0 : error("corrupt header in index file '%1'", name);
255 0 : return false;
256 : }
257 0 : tags = (tag *)(addr + sizeof(header));
258 0 : lists = (int *)(tags + header.tags_size);
259 0 : table = (int *)(lists + header.lists_size);
260 0 : pool = (char *)(table + header.table_size);
261 0 : ignore_fields = strchr(strchr(pool, '\0') + 1, '\0') + 1;
262 0 : key_buffer = new char[header.truncate];
263 0 : read_common_words_file();
264 0 : return true;
265 : }
266 :
267 0 : const char *index_search_item::get_invalidity_reason()
268 : {
269 0 : if (tags == 0)
270 0 : return "not loaded";
271 0 : if ((header.lists_size > 0) && (lists[header.lists_size - 1] >= 0))
272 0 : return "last list element not negative";
273 : int i;
274 0 : for (i = 0; i < header.table_size; i++) {
275 0 : int li = table[i];
276 0 : if (li >= header.lists_size)
277 0 : return "bad list index";
278 0 : if (li >= 0) {
279 0 : for (int *ptr = lists + li; *ptr >= 0; ptr++) {
280 0 : if (*ptr >= header.tags_size)
281 0 : return "bad tag index";
282 0 : if (*ptr >= ptr[1] && ptr[1] >= 0)
283 0 : return "list not ordered";
284 : }
285 : }
286 : }
287 0 : for (i = 0; i < header.tags_size; i++) {
288 0 : if (tags[i].filename_index >= header.strings_size)
289 0 : return "bad index in tags";
290 0 : if (tags[i].length < 0)
291 0 : return "bad length in tags";
292 0 : if (tags[i].start < 0)
293 0 : return "bad start in tags";
294 : }
295 0 : if (pool[header.strings_size - 1] != '\0')
296 0 : return "last character in string pool is not null";
297 0 : return 0;
298 : }
299 :
300 0 : bool index_search_item::is_valid()
301 : {
302 0 : const char *reason = get_invalidity_reason();
303 0 : if (!reason)
304 0 : return true;
305 0 : error("'%1' is bad: %2", name, reason);
306 0 : return false;
307 : }
308 :
309 0 : int index_search_item::next_filename_id() const
310 : {
311 0 : return filename_id + header.strings_size + 1;
312 : }
313 :
314 0 : search_item_iterator *index_search_item::make_search_item_iterator(
315 : const char *query)
316 : {
317 0 : return new index_search_item_iterator(this, query);
318 : }
319 :
320 2 : search_item *make_index_search_item(const char *filename, int fid)
321 : {
322 2 : char *index_filename = new char[strlen(filename) + sizeof(INDEX_SUFFIX)];
323 2 : strcpy(index_filename, filename);
324 2 : strcat(index_filename, INDEX_SUFFIX);
325 2 : int fd = open(index_filename, O_RDONLY | O_BINARY);
326 2 : if (fd < 0)
327 2 : return 0;
328 0 : index_search_item *item = new index_search_item(index_filename, fid);
329 0 : delete[] index_filename;
330 0 : if (!item->load(fd)) {
331 0 : close(fd);
332 0 : delete item;
333 0 : return 0;
334 : }
335 0 : else if (do_verify && !item->is_valid()) {
336 0 : delete item;
337 0 : return 0;
338 : }
339 : else {
340 0 : item->check_files();
341 0 : return item;
342 : }
343 : }
344 :
345 :
346 0 : index_search_item_iterator::index_search_item_iterator(index_search_item *ind,
347 0 : const char *q)
348 : : indx(ind), out_of_date_files_iter(0), next_out_of_date_file(0), temp_list(0),
349 : buf(0), buflen(0),
350 0 : searcher(q, strlen(q), ind->ignore_fields, ind->header.truncate),
351 0 : query(strsave(q))
352 : {
353 0 : found_list = indx->search(q, strlen(q), &temp_list);
354 0 : if (!found_list) {
355 0 : found_list = &minus_one;
356 0 : warning("all keys would have been discarded in constructing index '%1'",
357 0 : indx->name);
358 : }
359 0 : }
360 :
361 0 : index_search_item_iterator::~index_search_item_iterator()
362 : {
363 0 : delete[] temp_list;
364 0 : delete[] buf;
365 0 : delete[] query;
366 0 : delete out_of_date_files_iter;
367 0 : }
368 :
369 0 : int index_search_item_iterator::next(const linear_searcher &,
370 : const char **pp, int *lenp,
371 : reference_id *ridp)
372 : {
373 0 : if (found_list) {
374 : for (;;) {
375 0 : int tagno = *found_list;
376 0 : if (tagno == -1)
377 0 : break;
378 0 : found_list++;
379 0 : if (get_tag(tagno, searcher, pp, lenp, ridp))
380 0 : return 1;
381 0 : }
382 0 : found_list = 0;
383 0 : next_out_of_date_file = indx->out_of_date_files;
384 : }
385 0 : while (next_out_of_date_file) {
386 0 : if (out_of_date_files_iter == 0)
387 : out_of_date_files_iter
388 0 : = next_out_of_date_file->make_search_item_iterator(query);
389 0 : if (out_of_date_files_iter->next(searcher, pp, lenp, ridp))
390 0 : return 1;
391 0 : delete out_of_date_files_iter;
392 0 : out_of_date_files_iter = 0;
393 0 : next_out_of_date_file = next_out_of_date_file->next;
394 : }
395 0 : return 0;
396 : }
397 :
398 0 : int index_search_item_iterator::get_tag(int tagno,
399 : const linear_searcher &searchr,
400 : const char **pp, int *lenp,
401 : reference_id *ridp)
402 : {
403 0 : if (tagno < 0 || tagno >= indx->header.tags_size) {
404 0 : error("bad tag number");
405 0 : return 0;
406 : }
407 0 : tag *tp = indx->tags + tagno;
408 0 : const char *filename = indx->munge_filename(indx->pool + tp->filename_index);
409 0 : int fd = open(filename, O_RDONLY | O_BINARY);
410 0 : if (fd < 0) {
411 0 : error("can't open '%1': %2", filename, strerror(errno));
412 0 : return 0;
413 : }
414 : struct stat sb;
415 0 : if (fstat(fd, &sb) < 0) {
416 0 : error("can't fstat: %1", strerror(errno));
417 0 : close(fd);
418 0 : return 0;
419 : }
420 0 : time_t mtime = sb.st_mtime;
421 0 : if (mtime > indx->mtime) {
422 0 : indx->add_out_of_date_file(fd, filename,
423 0 : indx->filename_id + tp->filename_index);
424 0 : return 0;
425 : }
426 0 : int res = 0;
427 0 : FILE *fp = fdopen(fd, FOPEN_RB);
428 0 : if (!fp) {
429 0 : error("fdopen failed");
430 0 : close(fd);
431 0 : return 0;
432 : }
433 0 : if (tp->start != 0 && fseek(fp, long(tp->start), 0) < 0)
434 0 : error("can't seek on '%1': %2", filename, strerror(errno));
435 : else {
436 0 : int length = tp->length;
437 0 : int err = 0;
438 0 : if (length == 0) {
439 0 : if (fstat(fileno(fp), &sb) < 0) {
440 0 : error("can't stat '%1': %2", filename, strerror(errno));
441 0 : err = 1;
442 : }
443 0 : else if (!S_ISREG(sb.st_mode)) {
444 0 : error("'%1' is not a regular file", filename);
445 0 : err = 1;
446 : }
447 : else
448 0 : length = int(sb.st_size);
449 : }
450 0 : if (!err) {
451 0 : if (length + 2 > buflen) {
452 0 : delete[] buf;
453 0 : buflen = length + 2;
454 0 : buf = new char[buflen];
455 : }
456 0 : if (fread(buf + 1, 1, length, fp) != (size_t)length)
457 0 : error("fread on '%1' failed: %2", filename, strerror(errno));
458 : else {
459 0 : buf[0] = '\n';
460 : // Remove the CR characters from CRLF pairs.
461 0 : int sidx = 1, didx = 1;
462 0 : for ( ; sidx < length + 1; sidx++, didx++)
463 : {
464 0 : if (buf[sidx] == '\r')
465 : {
466 0 : if (buf[++sidx] != '\n')
467 0 : buf[didx++] = '\r';
468 : else
469 0 : length--;
470 : }
471 0 : if (sidx != didx)
472 0 : buf[didx] = buf[sidx];
473 : }
474 0 : buf[length + 1] = '\n';
475 0 : res = searchr.search(buf + 1, buf + 2 + length, pp, lenp);
476 0 : if (res && ridp)
477 0 : *ridp = reference_id(indx->filename_id + tp->filename_index,
478 0 : tp->start);
479 : }
480 : }
481 : }
482 0 : fclose(fp);
483 0 : return res;
484 : }
485 :
486 0 : const char *index_search_item::munge_filename(const char *filename)
487 : {
488 0 : if (IS_ABSOLUTE(filename))
489 0 : return filename;
490 0 : const char *cwd = pool;
491 0 : int need_slash = (cwd[0] != 0
492 0 : && strchr(DIR_SEPS, strchr(cwd, '\0')[-1]) == 0);
493 0 : size_t len = strlen(cwd) + strlen(filename) + need_slash + 1;
494 0 : if (len > filename_buflen) {
495 0 : delete[] filename_buffer;
496 0 : filename_buflen = len;
497 0 : filename_buffer = new char[len];
498 : }
499 0 : strcpy(filename_buffer, cwd);
500 0 : if (need_slash)
501 0 : strcat(filename_buffer, "/");
502 0 : strcat(filename_buffer, filename);
503 0 : return filename_buffer;
504 : }
505 :
506 0 : const int *index_search_item::search1(const char **pp, const char *end)
507 : {
508 0 : while (*pp < end && !csalnum(**pp))
509 0 : *pp += 1;
510 0 : if (*pp >= end)
511 0 : return 0;
512 0 : const char *start = *pp;
513 0 : while (*pp < end && csalnum(**pp))
514 0 : *pp += 1;
515 0 : int len = *pp - start;
516 0 : if (len < header.shortest)
517 0 : return 0;
518 0 : if (len > header.truncate)
519 0 : len = header.truncate;
520 0 : int is_number = 1;
521 0 : for (int i = 0; i < len; i++)
522 0 : if (csdigit(start[i]))
523 0 : key_buffer[i] = start[i];
524 : else {
525 0 : key_buffer[i] = cmlower(start[i]);
526 0 : is_number = 0;
527 : }
528 0 : if (is_number && !(len == 4 && start[0] == '1' && start[1] == '9'))
529 0 : return 0;
530 0 : unsigned hc = hash(key_buffer, len);
531 0 : if (common_words_table) {
532 0 : for (int h = hc % common_words_table_size;
533 0 : common_words_table[h];
534 : --h) {
535 0 : if (strlen(common_words_table[h]) == (size_t)len
536 0 : && memcmp(common_words_table[h], key_buffer, len) == 0)
537 0 : return 0;
538 0 : if (h == 0)
539 0 : h = common_words_table_size;
540 : }
541 : }
542 0 : int li = table[int(hc % header.table_size)];
543 0 : return li < 0 ? &minus_one : lists + li;
544 : }
545 :
546 0 : static void merge(int *result, const int *s1, const int *s2)
547 : {
548 0 : for (; *s1 >= 0; s1++) {
549 0 : while (*s2 >= 0 && *s2 < *s1)
550 0 : s2++;
551 0 : if (*s2 == *s1)
552 0 : *result++ = *s2;
553 : }
554 0 : *result++ = -1;
555 0 : }
556 :
557 0 : const int *index_search_item::search(const char *ptr, int length,
558 : int **temp_listp)
559 : {
560 0 : const char *end = ptr + length;
561 0 : if (*temp_listp) {
562 0 : delete[] *temp_listp;
563 0 : *temp_listp = 0;
564 : }
565 0 : const int *first_list = 0;
566 0 : while (ptr < end && (first_list = search1(&ptr, end)) == 0)
567 : ;
568 0 : if (!first_list)
569 0 : return 0;
570 0 : if (*first_list < 0)
571 0 : return first_list;
572 0 : const int *second_list = 0;
573 0 : while (ptr < end && (second_list = search1(&ptr, end)) == 0)
574 : ;
575 0 : if (!second_list)
576 0 : return first_list;
577 0 : if (*second_list < 0)
578 0 : return second_list;
579 : const int *p;
580 0 : for (p = first_list; *p >= 0; p++)
581 : ;
582 0 : int len = p - first_list;
583 0 : for (p = second_list; *p >= 0; p++)
584 : ;
585 0 : if (p - second_list < len)
586 0 : len = p - second_list;
587 0 : int *matches = new int[len + 1];
588 0 : merge(matches, first_list, second_list);
589 0 : while (ptr < end) {
590 0 : const int *list = search1(&ptr, end);
591 0 : if (list != 0) {
592 0 : if (*list < 0) {
593 0 : delete[] matches;
594 0 : return list;
595 : }
596 0 : merge(matches, matches, list);
597 0 : if (*matches < 0) {
598 0 : delete[] matches;
599 0 : return &minus_one;
600 : }
601 : }
602 : }
603 0 : *temp_listp = matches;
604 0 : return matches;
605 : }
606 :
607 0 : void index_search_item::read_common_words_file()
608 : {
609 0 : if (header.common <= 0)
610 0 : return;
611 0 : const char *common_words_file = munge_filename(strchr(pool, '\0') + 1);
612 0 : errno = 0;
613 0 : FILE *fp = fopen(common_words_file, "r");
614 0 : if (!fp) {
615 0 : error("can't open '%1': %2", common_words_file, strerror(errno));
616 0 : return;
617 : }
618 0 : common_words_table_size = ceil_prime(2 * header.common);
619 0 : common_words_table = new char *[common_words_table_size];
620 0 : for (int i = 0; i < common_words_table_size; i++)
621 0 : common_words_table[i] = 0;
622 0 : int count = 0;
623 0 : int key_len = 0;
624 : for (;;) {
625 0 : int c = getc(fp);
626 0 : while (c != EOF && !csalnum(c))
627 0 : c = getc(fp);
628 0 : if (c == EOF)
629 0 : break;
630 0 : do {
631 0 : if (key_len < header.truncate)
632 0 : key_buffer[key_len++] = cmlower(c);
633 0 : c = getc(fp);
634 0 : } while (c != EOF && csalnum(c));
635 0 : if (key_len >= header.shortest) {
636 0 : int h = hash(key_buffer, key_len) % common_words_table_size;
637 0 : while (common_words_table[h]) {
638 0 : if (h == 0)
639 0 : h = common_words_table_size;
640 0 : --h;
641 : }
642 0 : common_words_table[h] = new char[key_len + 1];
643 0 : memcpy(common_words_table[h], key_buffer, key_len);
644 0 : common_words_table[h][key_len] = '\0';
645 : }
646 0 : if (++count >= header.common)
647 0 : break;
648 0 : key_len = 0;
649 0 : if (c == EOF)
650 0 : break;
651 0 : }
652 0 : fclose(fp);
653 : }
654 :
655 0 : void index_search_item::add_out_of_date_file(int fd, const char *filename,
656 : int fid)
657 : {
658 : search_item **pp;
659 0 : for (pp = &out_of_date_files; *pp; pp = &(*pp)->next)
660 0 : if ((*pp)->is_named(filename))
661 0 : return;
662 0 : *pp = make_linear_search_item(fd, filename, fid);
663 0 : warning("'%1' modified since index '%2' created", filename, name);
664 : }
665 :
666 0 : void index_search_item::check_files()
667 : {
668 0 : const char *pool_end = pool + header.strings_size;
669 0 : for (const char *ptr = strchr(ignore_fields, '\0') + 1;
670 0 : ptr < pool_end;
671 0 : ptr = strchr(ptr, '\0') + 1) {
672 0 : const char *path = munge_filename(ptr);
673 : struct stat sb;
674 0 : if (stat(path, &sb) < 0)
675 0 : error("can't stat '%1': %2", path, strerror(errno));
676 0 : else if (sb.st_mtime > mtime) {
677 0 : int fd = open(path, O_RDONLY | O_BINARY);
678 0 : if (fd < 0)
679 0 : error("can't open '%1': %2", path, strerror(errno));
680 : else
681 0 : add_out_of_date_file(fd, path, filename_id + (ptr - pool));
682 : }
683 : }
684 0 : }
685 :
686 : // Local Variables:
687 : // fill-column: 72
688 : // mode: C++
689 : // End:
690 : // vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
|