(procdir): If name_scan() returns something, check if it was explicitely given in...
[debian/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 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)) && 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 void
640 get_gnu_dumpdir ()
641 {
642   size_t size;
643   size_t copied;
644   union block *data_block;
645   char *to;
646   char *archive_dir;
647   
648   size = current_stat_info.stat.st_size;
649   if (size != current_stat_info.stat.st_size)
650     xalloc_die ();
651
652   archive_dir = xmalloc (size);
653   to = archive_dir;
654
655   set_next_block_after (current_header);
656   mv_begin (&current_stat_info);
657
658   for (; size > 0; size -= copied)
659     {
660       mv_size_left (size);
661       data_block = find_next_block ();
662       if (!data_block)
663         ERROR ((1, 0, _("Unexpected EOF in archive")));
664       copied = available_space_after (data_block);
665       if (copied > size)
666         copied = size;
667       memcpy (to, data_block->buffer, copied);
668       to += copied;
669       set_next_block_after ((union block *)
670                             (data_block->buffer + copied - 1));
671     }
672
673   mv_end ();
674   
675   current_stat_info.stat.st_size = 0; /* For skip_member() and friends
676                                          to work correctly */
677   current_stat_info.dumpdir = archive_dir;
678 }
679
680
681 /* Examine the directories under directory_name and delete any
682    files that were not there at the time of the back-up. */
683 void
684 purge_directory (char const *directory_name)
685 {
686   char *current_dir;
687   char *cur, *arc;
688
689   if (!current_stat_info.dumpdir)
690     {
691       skip_member ();
692       return;
693     }
694   
695   current_dir = savedir (directory_name);
696
697   if (!current_dir)
698     {
699       /* The directory doesn't exist now.  It'll be created.  In any
700          case, we don't have to delete any files out of it.  */
701
702       skip_member ();
703       return;
704     }
705
706   for (cur = current_dir; *cur; cur += strlen (cur) + 1)
707     {
708       for (arc = current_stat_info.dumpdir; *arc; arc += strlen (arc) + 1)
709         {
710           arc++;
711           if (!strcmp (arc, cur))
712             break;
713         }
714       if (*arc == '\0')
715         {
716           struct stat st;
717           char *p = new_name (directory_name, cur);
718
719           if (deref_stat (false, p, &st))
720             {
721               stat_diag (p);
722               WARN((0, 0, _("%s: Not purging directory: unable to stat"),
723                     quotearg_colon (p)));
724               continue;
725             }
726           else if (one_file_system_option && st.st_dev != root_device)
727             {
728               WARN((0, 0,
729                     _("%s: directory is on a different device: not purging"),
730                     quotearg_colon (p)));
731               continue;
732             }
733
734           if (! interactive_option || confirm ("delete", p))
735             {
736               if (verbose_option)
737                 fprintf (stdlis, _("%s: Deleting %s\n"),
738                          program_name, quote (p));
739               if (! remove_any_file (p, RECURSIVE_REMOVE_OPTION))
740                 {
741                   int e = errno;
742                   ERROR ((0, e, _("%s: Cannot remove"), quotearg_colon (p)));
743                 }
744             }
745           free (p);
746         }
747
748     }
749   free (current_dir);
750 }
751
752 void
753 list_dumpdir (char *buffer, size_t size)
754 {
755   while (size)
756     {
757       switch (*buffer)
758         {
759         case 'Y':
760         case 'N':
761         case 'D':
762           fprintf (stdlis, "%c ", *buffer);
763           buffer++;
764           size--;
765           break;
766           
767         case 0:
768           fputc ('\n', stdlis);
769           buffer++;
770           size--;
771           break;
772           
773         default:
774           fputc (*buffer, stdlis);
775           buffer++;
776           size--;
777         }
778     }
779 }