]> Dogcows Code - chaz/tar/blob - src/incremen.c
Update copyright year to 2006.
[chaz/tar] / src / incremen.c
1 /* GNU dump extensions to tar.
2
3 Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
4 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any later
9 version.
10
11 This program is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
14 Public License for more details.
15
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
19
20 #include <system.h>
21 #include <getline.h>
22 #include <hash.h>
23 #include <quotearg.h>
24 #include "common.h"
25
26 /* Incremental dump specialities. */
27
28 /* Which child files to save under a directory. */
29 enum children {NO_CHILDREN, CHANGED_CHILDREN, ALL_CHILDREN};
30
31 /* Directory attributes. */
32 struct directory
33 {
34 struct timespec mtime; /* Modification time */
35 dev_t device_number; /* device number for directory */
36 ino_t inode_number; /* inode number for directory */
37 enum children children;
38 bool nfs;
39 bool found;
40 char name[1]; /* file name of directory */
41 };
42
43 static Hash_table *directory_table;
44
45 #if HAVE_ST_FSTYPE_STRING
46 static char const nfs_string[] = "nfs";
47 # define NFS_FILE_STAT(st) (strcmp ((st).st_fstype, nfs_string) == 0)
48 #else
49 # define ST_DEV_MSB(st) (~ (dev_t) 0 << (sizeof (st).st_dev * CHAR_BIT - 1))
50 # define NFS_FILE_STAT(st) (((st).st_dev & ST_DEV_MSB (st)) != 0)
51 #endif
52
53 /* Calculate the hash of a directory. */
54 static size_t
55 hash_directory (void const *entry, size_t n_buckets)
56 {
57 struct directory const *directory = entry;
58 return hash_string (directory->name, n_buckets);
59 }
60
61 /* Compare two directories for equality. */
62 static bool
63 compare_directories (void const *entry1, void const *entry2)
64 {
65 struct directory const *directory1 = entry1;
66 struct directory const *directory2 = entry2;
67 return strcmp (directory1->name, directory2->name) == 0;
68 }
69
70 /* Create and link a new directory entry for directory NAME, having a
71 device number DEV and an inode number INO, with NFS indicating
72 whether it is an NFS device and FOUND indicating whether we have
73 found that the directory exists. */
74 static struct directory *
75 note_directory (char const *name, struct timespec mtime,
76 dev_t dev, ino_t ino, bool nfs, bool found)
77 {
78 size_t size = offsetof (struct directory, name) + strlen (name) + 1;
79 struct directory *directory = xmalloc (size);
80
81 directory->mtime = mtime;
82 directory->device_number = dev;
83 directory->inode_number = ino;
84 directory->children = CHANGED_CHILDREN;
85 directory->nfs = nfs;
86 directory->found = found;
87 strcpy (directory->name, name);
88
89 if (! ((directory_table
90 || (directory_table = hash_initialize (0, 0, hash_directory,
91 compare_directories, 0)))
92 && hash_insert (directory_table, directory)))
93 xalloc_die ();
94
95 return directory;
96 }
97
98 /* Return a directory entry for a given file NAME, or zero if none found. */
99 static struct directory *
100 find_directory (char *name)
101 {
102 if (! directory_table)
103 return 0;
104 else
105 {
106 size_t size = offsetof (struct directory, name) + strlen (name) + 1;
107 struct directory *dir = alloca (size);
108 strcpy (dir->name, name);
109 return hash_lookup (directory_table, dir);
110 }
111 }
112
113 void
114 update_parent_directory (const char *name)
115 {
116 struct directory *directory;
117 char *p, *name_buffer;
118
119 p = dir_name (name);
120 name_buffer = xmalloc (strlen (p) + 2);
121 strcpy (name_buffer, p);
122 if (! ISSLASH (p[strlen (p) - 1]))
123 strcat (name_buffer, "/");
124
125 directory = find_directory (name_buffer);
126 free (name_buffer);
127 if (directory)
128 {
129 struct stat st;
130 if (deref_stat (dereference_option, p, &st) != 0)
131 stat_diag (name);
132 else
133 directory->mtime = get_stat_mtime (&st);
134 }
135 free (p);
136 }
137
138 static int
139 compare_dirents (const void *first, const void *second)
140 {
141 return strcmp ((*(char *const *) first) + 1,
142 (*(char *const *) second) + 1);
143 }
144
145 enum children
146 procdir (char *name_buffer, struct stat *stat_data,
147 dev_t device,
148 enum children children,
149 bool verbose)
150 {
151 struct directory *directory;
152 bool nfs = NFS_FILE_STAT (*stat_data);
153 struct name *np;
154
155 if ((directory = find_directory (name_buffer)) != NULL)
156 {
157 /* With NFS, the same file can have two different devices
158 if an NFS directory is mounted in multiple locations,
159 which is relatively common when automounting.
160 To avoid spurious incremental redumping of
161 directories, consider all NFS devices as equal,
162 relying on the i-node to establish differences. */
163
164 if (! (((directory->nfs & nfs)
165 || directory->device_number == stat_data->st_dev)
166 && directory->inode_number == stat_data->st_ino))
167 {
168 if (verbose)
169 WARN ((0, 0, _("%s: Directory has been renamed"),
170 quotearg_colon (name_buffer)));
171 directory->children = ALL_CHILDREN;
172 directory->nfs = nfs;
173 directory->device_number = stat_data->st_dev;
174 directory->inode_number = stat_data->st_ino;
175 }
176 else if (listed_incremental_option)
177 /* Newer modification time can mean that new files were
178 created in the directory or some of the existing files
179 were renamed. */
180 directory->children =
181 timespec_cmp (get_stat_mtime (stat_data), directory->mtime) > 0
182 ? ALL_CHILDREN : CHANGED_CHILDREN;
183
184 directory->found = true;
185 }
186 else
187 {
188 if (verbose)
189 WARN ((0, 0, _("%s: Directory is new"),
190 quotearg_colon (name_buffer)));
191 directory = note_directory (name_buffer,
192 get_stat_mtime(stat_data),
193 stat_data->st_dev,
194 stat_data->st_ino,
195 nfs,
196 true);
197
198 directory->children =
199 (listed_incremental_option
200 || (OLDER_STAT_TIME (*stat_data, m)
201 || (after_date_option
202 && OLDER_STAT_TIME (*stat_data, c))))
203 ? ALL_CHILDREN
204 : CHANGED_CHILDREN;
205 }
206
207 /* If the directory is on another device and --one-file-system was given,
208 omit it... */
209 if (one_file_system_option && device != stat_data->st_dev
210 /* ... except if it was explicitely given in the command line */
211 && !((np = name_scan (name_buffer, true)) && np->explicit))
212 directory->children = NO_CHILDREN;
213 else if (children == ALL_CHILDREN)
214 directory->children = ALL_CHILDREN;
215
216 return directory->children;
217 }
218
219
220 /* Recursively scan the given directory. */
221 static void
222 scan_directory (struct obstack *stk, char *dir_name, dev_t device)
223 {
224 char *dirp = savedir (dir_name); /* for scanning directory */
225 char const *entry; /* directory entry being scanned */
226 size_t entrylen; /* length of directory entry */
227 char *name_buffer; /* directory, `/', and directory member */
228 size_t name_buffer_size; /* allocated size of name_buffer, minus 2 */
229 size_t name_length; /* used length in name_buffer */
230 enum children children;
231 struct stat stat_data;
232
233 if (! dirp)
234 savedir_error (dir_name);
235
236 name_buffer_size = strlen (dir_name) + NAME_FIELD_SIZE;
237 name_buffer = xmalloc (name_buffer_size + 2);
238 strcpy (name_buffer, dir_name);
239 if (! ISSLASH (dir_name[strlen (dir_name) - 1]))
240 strcat (name_buffer, "/");
241 name_length = strlen (name_buffer);
242
243 if (deref_stat (dereference_option, name_buffer, &stat_data))
244 {
245 stat_diag (name_buffer);
246 children = CHANGED_CHILDREN;
247 }
248 else
249 children = procdir (name_buffer, &stat_data, device, NO_CHILDREN, false);
250
251 if (dirp && children != NO_CHILDREN)
252 for (entry = dirp;
253 (entrylen = strlen (entry)) != 0;
254 entry += entrylen + 1)
255 {
256 if (name_buffer_size <= entrylen + name_length)
257 {
258 do
259 name_buffer_size += NAME_FIELD_SIZE;
260 while (name_buffer_size <= entrylen + name_length);
261 name_buffer = xrealloc (name_buffer, name_buffer_size + 2);
262 }
263 strcpy (name_buffer + name_length, entry);
264
265 if (excluded_name (name_buffer))
266 obstack_1grow (stk, 'N');
267 else
268 {
269
270 if (deref_stat (dereference_option, name_buffer, &stat_data))
271 {
272 stat_diag (name_buffer);
273 continue;
274 }
275
276 if (S_ISDIR (stat_data.st_mode))
277 {
278 procdir (name_buffer, &stat_data, device, children,
279 verbose_option);
280 obstack_1grow (stk, 'D');
281 }
282
283 else if (one_file_system_option && device != stat_data.st_dev)
284 obstack_1grow (stk, 'N');
285
286 #ifdef S_ISHIDDEN
287 else if (S_ISHIDDEN (stat_data.st_mode))
288 {
289 obstack_1grow (stk, 'D');
290 obstack_grow (stk, entry, entrylen);
291 obstack_grow (stk, "A", 2);
292 continue;
293 }
294 #endif
295
296 else
297 if (children == CHANGED_CHILDREN
298 && OLDER_STAT_TIME (stat_data, m)
299 && (!after_date_option || OLDER_STAT_TIME (stat_data, c)))
300 obstack_1grow (stk, 'N');
301 else
302 obstack_1grow (stk, 'Y');
303 }
304
305 obstack_grow (stk, entry, entrylen + 1);
306 }
307
308 obstack_grow (stk, "\000\000", 2);
309
310 free (name_buffer);
311 if (dirp)
312 free (dirp);
313 }
314
315 /* Sort the contents of the obstack, and convert it to the char * */
316 static char *
317 sort_obstack (struct obstack *stk)
318 {
319 char *pointer = obstack_finish (stk);
320 size_t counter;
321 char *cursor;
322 char *buffer;
323 char **array;
324 char **array_cursor;
325
326 counter = 0;
327 for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1)
328 counter++;
329
330 if (!counter)
331 return NULL;
332
333 array = obstack_alloc (stk, sizeof (char *) * (counter + 1));
334
335 array_cursor = array;
336 for (cursor = pointer; *cursor; cursor += strlen (cursor) + 1)
337 *array_cursor++ = cursor;
338 *array_cursor = 0;
339
340 qsort (array, counter, sizeof (char *), compare_dirents);
341
342 buffer = xmalloc (cursor - pointer + 2);
343
344 cursor = buffer;
345 for (array_cursor = array; *array_cursor; array_cursor++)
346 {
347 char *string = *array_cursor;
348
349 while ((*cursor++ = *string++))
350 continue;
351 }
352 *cursor = '\0';
353 return buffer;
354 }
355
356 char *
357 get_directory_contents (char *dir_name, dev_t device)
358 {
359 struct obstack stk;
360 char *buffer;
361
362 obstack_init (&stk);
363 scan_directory (&stk, dir_name, device);
364 buffer = sort_obstack (&stk);
365 obstack_free (&stk, NULL);
366 return buffer;
367 }
368
369 size_t
370 dumpdir_size (const char *p)
371 {
372 size_t totsize = 0;
373
374 while (*p)
375 {
376 size_t size = strlen (p) + 1;
377 totsize += size;
378 p += size;
379 }
380 return totsize + 1;
381 }
382
383 \f
384
385 static FILE *listed_incremental_stream;
386
387 /* Version of incremental format snapshots (directory files) used by this
388 tar. Currently it is supposed to be a single decimal number. 0 means
389 incremental snapshots as per tar version before 1.15.2.
390
391 The current tar version supports incremental versions from
392 0 up to TAR_INCREMENTAL_VERSION, inclusive.
393 It is able to create only snapshots of TAR_INCREMENTAL_VERSION */
394
395 #define TAR_INCREMENTAL_VERSION 1
396
397 /* Read incremental snapshot file (directory file).
398 If the file has older incremental version, make sure that it is processed
399 correctly and that tar will use the most conservative backup method among
400 possible alternatives (i.e. prefer ALL_CHILDREN over CHANGED_CHILDREN,
401 etc.) This ensures that the snapshots are updated to the recent version
402 without any loss of data. */
403 void
404 read_directory_file (void)
405 {
406 int fd;
407 FILE *fp;
408 char *buf = 0;
409 size_t bufsize;
410
411 /* Open the file for both read and write. That way, we can write
412 it later without having to reopen it, and don't have to worry if
413 we chdir in the meantime. */
414 fd = open (listed_incremental_option, O_RDWR | O_CREAT, MODE_RW);
415 if (fd < 0)
416 {
417 open_error (listed_incremental_option);
418 return;
419 }
420
421 fp = fdopen (fd, "r+");
422 if (! fp)
423 {
424 open_error (listed_incremental_option);
425 close (fd);
426 return;
427 }
428
429 listed_incremental_stream = fp;
430
431 if (0 < getline (&buf, &bufsize, fp))
432 {
433 char *ebuf;
434 int n;
435 long lineno = 1;
436 uintmax_t u;
437 time_t t = u;
438 int incremental_version;
439
440 if (strncmp (buf, PACKAGE_NAME, sizeof PACKAGE_NAME - 1) == 0)
441 {
442 ebuf = buf + sizeof PACKAGE_NAME - 1;
443 if (*ebuf++ != '-')
444 ERROR((1, 0, _("Bad incremental file format")));
445 for (; *ebuf != '-'; ebuf++)
446 if (!*ebuf)
447 ERROR((1, 0, _("Bad incremental file format")));
448
449 incremental_version = (errno = 0, strtoumax (ebuf+1, &ebuf, 10));
450 if (getline (&buf, &bufsize, fp) <= 0)
451 {
452 read_error (listed_incremental_option);
453 free (buf);
454 return;
455 }
456 ++lineno;
457 }
458 else
459 incremental_version = 0;
460
461 if (incremental_version > TAR_INCREMENTAL_VERSION)
462 ERROR((1, 0, _("Unsupported incremental format version: %d"),
463 incremental_version));
464
465 t = u = (errno = 0, strtoumax (buf, &ebuf, 10));
466 if (buf == ebuf || (u == 0 && errno == EINVAL))
467 ERROR ((0, 0, "%s:%ld: %s",
468 quotearg_colon (listed_incremental_option),
469 lineno,
470 _("Invalid time stamp")));
471 else if (t != u)
472 ERROR ((0, 0, "%s:%ld: %s",
473 quotearg_colon (listed_incremental_option),
474 lineno,
475 _("Time stamp out of range")));
476 else if (incremental_version == 1)
477 {
478 newer_mtime_option.tv_sec = t;
479
480 t = u = (errno = 0, strtoumax (buf, &ebuf, 10));
481 if (buf == ebuf || (u == 0 && errno == EINVAL))
482 ERROR ((0, 0, "%s:%ld: %s",
483 quotearg_colon (listed_incremental_option),
484 lineno,
485 _("Invalid time stamp")));
486 else if (t != u)
487 ERROR ((0, 0, "%s:%ld: %s",
488 quotearg_colon (listed_incremental_option),
489 lineno,
490 _("Time stamp out of range")));
491 newer_mtime_option.tv_nsec = t;
492 }
493 else
494 {
495 /* pre-1 incremental format does not contain nanoseconds */
496 newer_mtime_option.tv_sec = t;
497 newer_mtime_option.tv_nsec = 0;
498 }
499
500 while (0 < (n = getline (&buf, &bufsize, fp)))
501 {
502 dev_t dev;
503 ino_t ino;
504 bool nfs = buf[0] == '+';
505 char *strp = buf + nfs;
506 struct timespec mtime;
507
508 lineno++;
509
510 if (buf[n - 1] == '\n')
511 buf[n - 1] = '\0';
512
513 if (incremental_version == 1)
514 {
515 errno = 0;
516 mtime.tv_sec = u = strtoumax (strp, &ebuf, 10);
517 if (!isspace (*ebuf))
518 ERROR ((0, 0, "%s:%ld: %s",
519 quotearg_colon (listed_incremental_option), lineno,
520 _("Invalid modification time (seconds)")));
521 else if (mtime.tv_sec != u)
522 ERROR ((0, 0, "%s:%ld: %s",
523 quotearg_colon (listed_incremental_option), lineno,
524 _("Modification time (seconds) out of range")));
525 strp = ebuf;
526
527 errno = 0;
528 mtime.tv_nsec = u = strtoumax (strp, &ebuf, 10);
529 if (!isspace (*ebuf))
530 ERROR ((0, 0, "%s:%ld: %s",
531 quotearg_colon (listed_incremental_option), lineno,
532 _("Invalid modification time (nanoseconds)")));
533 else if (mtime.tv_nsec != u)
534 ERROR ((0, 0, "%s:%ld: %s",
535 quotearg_colon (listed_incremental_option), lineno,
536 _("Modification time (nanoseconds) out of range")));
537 strp = ebuf;
538 }
539 else
540 memset (&mtime, 0, sizeof mtime);
541
542 errno = 0;
543 dev = u = strtoumax (strp, &ebuf, 10);
544 if (!isspace (*ebuf))
545 ERROR ((0, 0, "%s:%ld: %s",
546 quotearg_colon (listed_incremental_option), lineno,
547 _("Invalid device number")));
548 else if (dev != u)
549 ERROR ((0, 0, "%s:%ld: %s",
550 quotearg_colon (listed_incremental_option), lineno,
551 _("Device number out of range")));
552 strp = ebuf;
553
554 errno = 0;
555 ino = u = strtoumax (strp, &ebuf, 10);
556 if (!isspace (*ebuf))
557 ERROR ((0, 0, "%s:%ld: %s",
558 quotearg_colon (listed_incremental_option), lineno,
559 _("Invalid inode number")));
560 else if (ino != u)
561 ERROR ((0, 0, "%s:%ld: %s",
562 quotearg_colon (listed_incremental_option), lineno,
563 _("Inode number out of range")));
564 strp = ebuf;
565
566 strp++;
567 unquote_string (strp);
568 note_directory (strp, mtime, dev, ino, nfs, 0);
569 }
570 }
571
572 if (ferror (fp))
573 read_error (listed_incremental_option);
574 if (buf)
575 free (buf);
576 }
577
578 /* Output incremental data for the directory ENTRY to the file DATA.
579 Return nonzero if successful, preserving errno on write failure. */
580 static bool
581 write_directory_file_entry (void *entry, void *data)
582 {
583 struct directory const *directory = entry;
584 FILE *fp = data;
585
586 if (directory->found)
587 {
588 int e;
589 char buf[UINTMAX_STRSIZE_BOUND];
590 char *str = quote_copy_string (directory->name);
591
592 if (directory->nfs)
593 fprintf (fp, "+");
594 fprintf (fp, "%s ", umaxtostr (directory->mtime.tv_sec, buf));
595 fprintf (fp, "%s ", umaxtostr (directory->mtime.tv_nsec, buf));
596 fprintf (fp, "%s ", umaxtostr (directory->device_number, buf));
597 fprintf (fp, "%s ", umaxtostr (directory->inode_number, buf));
598 fprintf (fp, "%s\n", str ? str : directory->name);
599
600 e = errno;
601 if (str)
602 free (str);
603 errno = e;
604 }
605
606 return ! ferror (fp);
607 }
608
609 void
610 write_directory_file (void)
611 {
612 FILE *fp = listed_incremental_stream;
613
614 if (! fp)
615 return;
616
617 if (fseek (fp, 0L, SEEK_SET) != 0)
618 seek_error (listed_incremental_option);
619 if (sys_truncate (fileno (fp)) != 0)
620 truncate_error (listed_incremental_option);
621
622 fprintf (fp, "%s-%s-%d\n", PACKAGE_NAME, PACKAGE_VERSION,
623 TAR_INCREMENTAL_VERSION);
624
625 fprintf (fp, "%lu %lu\n",
626 (unsigned long int) start_time.tv_sec,
627 (unsigned long int) start_time.tv_nsec);
628 if (! ferror (fp) && directory_table)
629 hash_do_for_each (directory_table, write_directory_file_entry, fp);
630 if (ferror (fp))
631 write_error (listed_incremental_option);
632 if (fclose (fp) != 0)
633 close_error (listed_incremental_option);
634 }
635
636 \f
637 /* Restoration of incremental dumps. */
638
639 static void
640 get_gnu_dumpdir (struct tar_stat_info *stat_info)
641 {
642 size_t size;
643 size_t copied;
644 union block *data_block;
645 char *to;
646 char *archive_dir;
647
648 size = stat_info->stat.st_size;
649
650 archive_dir = xmalloc (size);
651 to = archive_dir;
652
653 set_next_block_after (current_header);
654 mv_begin (stat_info);
655
656 for (; size > 0; size -= copied)
657 {
658 mv_size_left (size);
659 data_block = find_next_block ();
660 if (!data_block)
661 ERROR ((1, 0, _("Unexpected EOF in archive")));
662 copied = available_space_after (data_block);
663 if (copied > size)
664 copied = size;
665 memcpy (to, data_block->buffer, copied);
666 to += copied;
667 set_next_block_after ((union block *)
668 (data_block->buffer + copied - 1));
669 }
670
671 mv_end ();
672
673 stat_info->dumpdir = archive_dir;
674 stat_info->skipped = true; /* For skip_member() and friends
675 to work correctly */
676 }
677
678 /* Return T if STAT_INFO represents a dumpdir archive member.
679 Note: can invalidate current_header. It happens if flush_archive()
680 gets called within get_gnu_dumpdir() */
681 bool
682 is_dumpdir (struct tar_stat_info *stat_info)
683 {
684 if (stat_info->is_dumpdir && !stat_info->dumpdir)
685 get_gnu_dumpdir (stat_info);
686 return stat_info->is_dumpdir;
687 }
688
689 /* Examine the directories under directory_name and delete any
690 files that were not there at the time of the back-up. */
691 void
692 purge_directory (char const *directory_name)
693 {
694 char *current_dir;
695 char *cur, *arc;
696
697 if (!is_dumpdir (&current_stat_info))
698 {
699 skip_member ();
700 return;
701 }
702
703 current_dir = savedir (directory_name);
704
705 if (!current_dir)
706 {
707 /* The directory doesn't exist now. It'll be created. In any
708 case, we don't have to delete any files out of it. */
709
710 skip_member ();
711 return;
712 }
713
714 for (cur = current_dir; *cur; cur += strlen (cur) + 1)
715 {
716 for (arc = current_stat_info.dumpdir; *arc; arc += strlen (arc) + 1)
717 {
718 arc++;
719 if (!strcmp (arc, cur))
720 break;
721 }
722 if (*arc == '\0')
723 {
724 struct stat st;
725 char *p = new_name (directory_name, cur);
726
727 if (deref_stat (false, p, &st))
728 {
729 stat_diag (p);
730 WARN((0, 0, _("%s: Not purging directory: unable to stat"),
731 quotearg_colon (p)));
732 continue;
733 }
734 else if (one_file_system_option && st.st_dev != root_device)
735 {
736 WARN((0, 0,
737 _("%s: directory is on a different device: not purging"),
738 quotearg_colon (p)));
739 continue;
740 }
741
742 if (! interactive_option || confirm ("delete", p))
743 {
744 if (verbose_option)
745 fprintf (stdlis, _("%s: Deleting %s\n"),
746 program_name, quote (p));
747 if (! remove_any_file (p, RECURSIVE_REMOVE_OPTION))
748 {
749 int e = errno;
750 ERROR ((0, e, _("%s: Cannot remove"), quotearg_colon (p)));
751 }
752 }
753 free (p);
754 }
755
756 }
757 free (current_dir);
758 }
759
760 void
761 list_dumpdir (char *buffer, size_t size)
762 {
763 while (size)
764 {
765 switch (*buffer)
766 {
767 case 'Y':
768 case 'N':
769 case 'D':
770 fprintf (stdlis, "%c ", *buffer);
771 buffer++;
772 size--;
773 break;
774
775 case 0:
776 fputc ('\n', stdlis);
777 buffer++;
778 size--;
779 break;
780
781 default:
782 fputc (*buffer, stdlis);
783 buffer++;
784 size--;
785 }
786 }
787 }
This page took 0.067588 seconds and 5 git commands to generate.