Commit | Line | Data |
---|---|---|
f87489ac WJ |
1 | /* Create a tar archive. |
2 | Copyright (C) 1988 Free Software Foundation | |
3 | ||
4 | This file is part of GNU Tar. | |
5 | ||
6 | GNU Tar is free software; you can redistribute it and/or modify | |
7 | it under the terms of the GNU General Public License as published by | |
8 | the Free Software Foundation; either version 1, or (at your option) | |
9 | any later version. | |
10 | ||
11 | GNU Tar is distributed in the hope that it will be useful, | |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | GNU General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU General Public License | |
17 | along with GNU Tar; see the file COPYING. If not, write to | |
18 | the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ | |
19 | ||
20 | /* | |
21 | * Create a tar archive. | |
22 | * | |
23 | * Written 25 Aug 1985 by John Gilmore, ihnp4!hoptoad!gnu. | |
24 | * | |
25 | * @(#)create.c 1.36 11/6/87 - gnu | |
26 | */ | |
27 | #include <sys/types.h> | |
28 | #include <sys/stat.h> | |
29 | #include <stdio.h> | |
30 | ||
31 | #ifndef V7 | |
32 | #include <fcntl.h> | |
33 | #endif | |
34 | ||
35 | #ifndef __MSDOS__ | |
36 | #include <sys/file.h> | |
37 | #include <sys/param.h> /* for MAXPATHLEN */ | |
38 | #include <pwd.h> | |
39 | #include <grp.h> | |
40 | #endif | |
41 | ||
42 | #ifdef BSD42 | |
43 | #include <sys/dir.h> | |
44 | #else | |
45 | #ifdef __MSDOS__ | |
46 | #include "msd_dir.h" | |
47 | #else | |
48 | #ifdef USG | |
49 | #ifdef NDIR | |
50 | #include <ndir.h> | |
51 | #else | |
52 | #include <dirent.h> | |
53 | #endif | |
54 | #ifndef DIRECT | |
55 | #define direct dirent | |
56 | #endif | |
57 | #define DP_NAMELEN(x) strlen((x)->d_name) | |
58 | #else | |
59 | /* | |
60 | * FIXME: On other systems there is no standard place for the header file | |
61 | * for the portable directory access routines. Change the #include line | |
62 | * below to bring it in from wherever it is. | |
63 | */ | |
64 | #include "ndir.h" | |
65 | #endif | |
66 | #endif | |
67 | #endif | |
68 | ||
69 | #ifndef DP_NAMELEN | |
70 | #define DP_NAMELEN(x) (x)->d_namlen | |
71 | #endif | |
72 | ||
73 | #ifdef USG | |
74 | #include <sys/sysmacros.h> /* major() and minor() defined here */ | |
75 | #endif | |
76 | ||
77 | /* | |
78 | * V7 doesn't have a #define for this. | |
79 | */ | |
80 | #ifndef O_RDONLY | |
81 | #define O_RDONLY 0 | |
82 | #endif | |
83 | ||
84 | /* | |
85 | * Most people don't have a #define for this. | |
86 | */ | |
87 | #ifndef O_BINARY | |
88 | #define O_BINARY 0 | |
89 | #endif | |
90 | ||
91 | #ifndef MAXPATHLEN | |
92 | #define MAXPATHLEN 1024 | |
93 | #endif | |
94 | ||
95 | #include "tar.h" | |
96 | #include "port.h" | |
97 | ||
98 | extern struct stat hstat; /* Stat struct corresponding */ | |
99 | ||
100 | #ifndef __MSDOS__ | |
101 | extern dev_t ar_dev; | |
102 | extern ino_t ar_ino; | |
103 | #endif | |
104 | ||
105 | /* JF */ | |
106 | extern struct name *gnu_list_name; | |
107 | ||
108 | /* | |
109 | * If there are no symbolic links, there is no lstat(). Use stat(). | |
110 | */ | |
111 | #ifndef S_IFLNK | |
112 | #define lstat stat | |
113 | #endif | |
114 | ||
115 | extern char *malloc(); | |
116 | extern char *strcpy(); | |
117 | extern char *strncpy(); | |
118 | extern void bzero(); | |
119 | extern void bcopy(); | |
120 | extern int errno; | |
121 | ||
122 | extern void print_header(); | |
123 | ||
124 | union record *start_header(); | |
125 | void finish_header(); | |
126 | void finduname(); | |
127 | void findgname(); | |
128 | char *name_next(); | |
129 | void to_oct(); | |
130 | void dump_file(); | |
131 | ||
132 | ||
133 | /* This code moved from tar.h since create.c is the only file that cares | |
134 | about 'struct link's. This means that other files might not have to | |
135 | include sys/types.h any more. | |
136 | */ | |
137 | ||
138 | struct link { | |
139 | struct link *next; | |
140 | dev_t dev; | |
141 | ino_t ino; | |
142 | short linkcount; | |
143 | char name[1]; | |
144 | }; | |
145 | ||
146 | struct link *linklist; /* Points to first link in list */ | |
147 | ||
148 | static nolinks; /* Gets set if we run out of RAM */ | |
149 | ||
150 | /* | |
151 | * "Scratch" space to store the information about a sparse file before | |
152 | * writing the info into the header or extended header | |
153 | */ | |
154 | /* struct sp_array *sparsearray;*/ | |
155 | ||
156 | /* number of elts storable in the sparsearray */ | |
157 | /*int sparse_array_size = 10;*/ | |
158 | ||
159 | void | |
160 | create_archive() | |
161 | { | |
162 | register char *p; | |
163 | char *name_from_list(); | |
164 | ||
165 | open_archive(0); /* Open for writing */ | |
166 | ||
167 | if(f_gnudump) { | |
168 | char buf[MAXNAMLEN],*q,*bufp; | |
169 | ||
170 | collect_and_sort_names(); | |
171 | ||
172 | while(p=name_from_list()) | |
173 | dump_file(p,-1); | |
174 | /* if(!f_dironly) { */ | |
175 | blank_name_list(); | |
176 | while(p=name_from_list()) { | |
177 | strcpy(buf,p); | |
178 | if(p[strlen(p)-1]!='/') | |
179 | strcat(buf,"/"); | |
180 | bufp=buf+strlen(buf); | |
181 | for(q=gnu_list_name->dir_contents;q && *q;q+=strlen(q)+1) { | |
182 | if(*q=='Y') { | |
183 | strcpy(bufp,q+1); | |
184 | dump_file(buf,-1); | |
185 | } | |
186 | } | |
187 | } | |
188 | /* } */ | |
189 | ||
190 | } else { | |
191 | p = name_next(1); | |
192 | if(!p) | |
193 | dump_file(".", -1); | |
194 | else { | |
195 | do dump_file(p, -1); | |
196 | while (p = name_next(1)); | |
197 | } | |
198 | } | |
199 | ||
200 | write_mangled(); | |
201 | write_eot(); | |
202 | close_archive(); | |
203 | if(f_gnudump) | |
204 | write_dir_file(); | |
205 | name_close(); | |
206 | } | |
207 | ||
208 | /* | |
209 | * Dump a single file. If it's a directory, recurse. | |
210 | * Result is 1 for success, 0 for failure. | |
211 | * Sets global "hstat" to stat() output for this file. | |
212 | */ | |
213 | void | |
214 | dump_file (p, curdev) | |
215 | char *p; /* File name to dump */ | |
216 | int curdev; /* Device our parent dir was on */ | |
217 | { | |
218 | union record *header; | |
219 | char type; | |
220 | extern char *save_name; /* JF for multi-volume support */ | |
221 | extern long save_totsize; | |
222 | extern long save_sizeleft; | |
223 | union record *exhdr; | |
224 | char save_linkflag; | |
225 | extern time_t new_time; | |
226 | int sparse_ind = 0; | |
227 | ||
228 | ||
229 | if(f_confirm && !confirm("add",p)) | |
230 | return; | |
231 | ||
232 | /* | |
233 | * Use stat if following (rather than dumping) 4.2BSD's | |
234 | * symbolic links. Otherwise, use lstat (which, on non-4.2 | |
235 | * systems, is #define'd to stat anyway. | |
236 | */ | |
237 | #ifdef AIX | |
238 | if (0 != f_follow_links ? | |
239 | statx (p, &hstat, STATSIZE, STX_HIDDEN): | |
240 | statx (p, &hstat, STATSIZE, STX_HIDDEN|STX_LINK)) | |
241 | #else | |
242 | if (0 != f_follow_links? stat(p, &hstat): lstat(p, &hstat)) | |
243 | #endif /* AIX */ | |
244 | { | |
245 | badperror: | |
246 | msg_perror("can't add file %s",p); | |
247 | badfile: | |
248 | errors++; | |
249 | return; | |
250 | } | |
251 | ||
252 | #ifdef AIX | |
253 | if (S_ISHIDDEN (hstat.st_mode)) { | |
254 | char *new = (char *)allocate (strlen (p) + 2); | |
255 | if (new) { | |
256 | strcpy (new, p); | |
257 | strcat (new, "@"); | |
258 | p = new; | |
259 | } | |
260 | } | |
261 | #endif /* AIX */ | |
262 | ||
263 | /* See if we only want new files, and check if this one is too old to | |
264 | put in the archive. */ | |
265 | if( f_new_files | |
266 | && !f_gnudump | |
267 | && new_time>hstat.st_mtime | |
268 | && (hstat.st_mode&S_IFMT)!=S_IFDIR | |
269 | && (f_new_files>1 || new_time>hstat.st_ctime)) { | |
270 | if(curdev<0) { | |
271 | msg("%s: is unchanged; not dumped",p); | |
272 | } | |
273 | return; | |
274 | } | |
275 | ||
276 | #ifndef __MSDOS__ | |
277 | /* See if we are trying to dump the archive */ | |
278 | if(ar_dev && hstat.st_dev==ar_dev && hstat.st_ino==ar_ino) { | |
279 | msg("%s is the archive; not dumped",p); | |
280 | return; | |
281 | } | |
282 | #endif | |
283 | /* | |
284 | * Check for multiple links. | |
285 | * | |
286 | * We maintain a list of all such files that we've written so | |
287 | * far. Any time we see another, we check the list and | |
288 | * avoid dumping the data again if we've done it once already. | |
289 | */ | |
290 | if (hstat.st_nlink > 1) switch (hstat.st_mode & S_IFMT) { | |
291 | register struct link *lp; | |
292 | ||
293 | case S_IFREG: /* Regular file */ | |
294 | #ifdef S_IFCTG | |
295 | case S_IFCTG: /* Contigous file */ | |
296 | #endif | |
297 | #ifdef S_IFCHR | |
298 | case S_IFCHR: /* Character special file */ | |
299 | #endif | |
300 | ||
301 | #ifdef S_IFBLK | |
302 | case S_IFBLK: /* Block special file */ | |
303 | #endif | |
304 | ||
305 | #ifdef S_IFIFO | |
306 | case S_IFIFO: /* Fifo special file */ | |
307 | #endif | |
308 | ||
309 | /* First quick and dirty. Hashing, etc later FIXME */ | |
310 | for (lp = linklist; lp; lp = lp->next) { | |
311 | if (lp->ino == hstat.st_ino && | |
312 | lp->dev == hstat.st_dev) { | |
313 | char *link_name = lp->name; | |
314 | ||
315 | /* We found a link. */ | |
316 | hstat.st_size = 0; | |
317 | header = start_header(p, &hstat); | |
318 | if (header == NULL) goto badfile; | |
319 | while(!f_absolute_paths && *link_name == '/') { | |
320 | static int link_warn = 0; | |
321 | ||
322 | if (!link_warn) { | |
323 | msg("Removing leading / from absolute links"); | |
324 | link_warn++; | |
325 | } | |
326 | link_name++; | |
327 | } | |
328 | strncpy(header->header.linkname, | |
329 | link_name,NAMSIZ); | |
330 | if(header->header.linkname[NAMSIZ-1]) { | |
331 | char *mangled; | |
332 | extern char *find_mangled(); | |
333 | ||
334 | mangled=find_mangled(link_name); | |
335 | msg("%s: link name too long: mangled to %s",link_name,mangled); | |
336 | strncpy(header->header.linkname,mangled,NAMSIZ); | |
337 | } | |
338 | header->header.linkflag = LF_LINK; | |
339 | finish_header(header); | |
340 | /* FIXME: Maybe remove from list after all links found? */ | |
341 | return; /* We dumped it */ | |
342 | } | |
343 | } | |
344 | ||
345 | /* Not found. Add it to the list of possible links. */ | |
346 | lp = (struct link *)malloc((unsigned)(sizeof(struct link)+strlen(p))); | |
347 | if (!lp) { | |
348 | if (!nolinks) { | |
349 | msg( | |
350 | "no memory for links, they will be dumped as separate files"); | |
351 | nolinks++; | |
352 | } | |
353 | } | |
354 | lp->ino = hstat.st_ino; | |
355 | lp->dev = hstat.st_dev; | |
356 | strcpy(lp->name, p); | |
357 | lp->next = linklist; | |
358 | linklist = lp; | |
359 | } | |
360 | ||
361 | /* | |
362 | * This is not a link to a previously dumped file, so dump it. | |
363 | */ | |
364 | switch (hstat.st_mode & S_IFMT) { | |
365 | ||
366 | case S_IFREG: /* Regular file */ | |
367 | #ifdef S_IFCTG | |
368 | case S_IFCTG: /* Contiguous file */ | |
369 | #endif | |
370 | { | |
371 | int f; /* File descriptor */ | |
372 | long bufsize, count; | |
373 | long sizeleft; | |
374 | register union record *start; | |
375 | int header_moved; | |
376 | char isextended = 0; | |
377 | int upperbound; | |
378 | int end_nulls = 0; | |
379 | ||
380 | header_moved = 0; | |
381 | ||
382 | #ifdef BSD42 | |
383 | if (f_sparse_files) { | |
384 | /* | |
385 | * JK - This is the test for sparseness: whether the | |
386 | * "size" of the file matches the number of blocks | |
387 | * allocated for it. If there is a smaller number | |
388 | * of blocks that would be necessary to accommodate | |
389 | * a file of this size, we have a sparse file, i.e., | |
390 | * at least one of those records in the file is just | |
391 | * a useless hole. | |
392 | */ | |
393 | #ifdef hpux /* Nice of HPUX to gratuitiously change it, huh? - mib */ | |
394 | if (hstat.st_size - (hstat.st_blocks * 1024) > 1024 ) { | |
395 | #else | |
396 | if (hstat.st_size - (hstat.st_blocks * RECORDSIZE) > RECORDSIZE) { | |
397 | #endif | |
398 | int filesize = hstat.st_size; | |
399 | register int i; | |
400 | ||
401 | header = start_header(p, &hstat); | |
402 | if (header == NULL) | |
403 | goto badfile; | |
404 | header->header.linkflag = LF_SPARSE; | |
405 | header_moved++; | |
406 | ||
407 | /* | |
408 | * Call the routine that figures out the | |
409 | * layout of the sparse file in question. | |
410 | * UPPERBOUND is the index of the last | |
411 | * element of the "sparsearray," i.e., | |
412 | * the number of elements it needed to | |
413 | * describe the file. | |
414 | */ | |
415 | ||
416 | upperbound = deal_with_sparse(p, header); | |
417 | ||
418 | /* | |
419 | * See if we'll need an extended header | |
420 | * later | |
421 | */ | |
422 | if (upperbound > SPARSE_IN_HDR-1) | |
423 | header->header.isextended++; | |
424 | /* | |
425 | * We store the "real" file size so | |
426 | * we can show that in case someone wants | |
427 | * to list the archive, i.e., tar tvf <file>. | |
428 | * It might be kind of disconcerting if the | |
429 | * shrunken file size was the one that showed | |
430 | * up. | |
431 | */ | |
432 | to_oct((long) hstat.st_size, 1+12, | |
433 | header->header.realsize); | |
434 | ||
435 | /* | |
436 | * This will be the new "size" of the | |
437 | * file, i.e., the size of the file | |
438 | * minus the records of holes that we're | |
439 | * skipping over. | |
440 | */ | |
441 | ||
442 | find_new_file_size(&filesize, upperbound); | |
443 | hstat.st_size = filesize; | |
444 | to_oct((long) filesize, 1+12, | |
445 | header->header.size); | |
446 | /* to_oct((long) end_nulls, 1+12, | |
447 | header->header.ending_blanks);*/ | |
448 | ||
449 | for (i = 0; i < SPARSE_IN_HDR; i++) { | |
450 | if (!sparsearray[i].numbytes) | |
451 | break; | |
452 | to_oct(sparsearray[i].offset, 1+12, | |
453 | header->header.sp[i].offset); | |
454 | to_oct(sparsearray[i].numbytes, 1+12, | |
455 | header->header.sp[i].numbytes); | |
456 | } | |
457 | ||
458 | } | |
459 | } | |
460 | #else | |
461 | upperbound=SPARSE_IN_HDR-1; | |
462 | #endif | |
463 | ||
464 | sizeleft = hstat.st_size; | |
465 | /* Don't bother opening empty, world readable files. */ | |
466 | if (sizeleft > 0 || 0444 != (0444 & hstat.st_mode)) { | |
467 | f = open(p, O_RDONLY|O_BINARY); | |
468 | if (f < 0) goto badperror; | |
469 | } else { | |
470 | f = -1; | |
471 | } | |
472 | ||
473 | /* If the file is sparse, we've already taken care of this */ | |
474 | if (!header_moved) { | |
475 | header = start_header(p, &hstat); | |
476 | if (header == NULL) { | |
477 | if(f>=0) | |
478 | (void)close(f); | |
479 | goto badfile; | |
480 | } | |
481 | } | |
482 | #ifdef S_IFCTG | |
483 | /* Mark contiguous files, if we support them */ | |
484 | if (f_standard && (hstat.st_mode & S_IFMT) == S_IFCTG) { | |
485 | header->header.linkflag = LF_CONTIG; | |
486 | } | |
487 | #endif | |
488 | isextended = header->header.isextended; | |
489 | save_linkflag = header->header.linkflag; | |
490 | finish_header(header); | |
491 | if (isextended) { | |
492 | int sum = 0; | |
493 | register int i; | |
494 | /* register union record *exhdr;*/ | |
495 | int arraybound = SPARSE_EXT_HDR; | |
496 | /* static */ int index_offset = SPARSE_IN_HDR; | |
497 | ||
498 | extend: exhdr = findrec(); | |
499 | ||
500 | if (exhdr == NULL) goto badfile; | |
501 | bzero(exhdr->charptr, RECORDSIZE); | |
502 | for (i = 0; i < SPARSE_EXT_HDR; i++) { | |
503 | if (i+index_offset > upperbound) | |
504 | break; | |
505 | to_oct((long) sparsearray[i+index_offset].numbytes, | |
506 | 1+12, | |
507 | exhdr->ext_hdr.sp[i].numbytes); | |
508 | to_oct((long) sparsearray[i+index_offset].offset, | |
509 | 1+12, | |
510 | exhdr->ext_hdr.sp[i].offset); | |
511 | } | |
512 | userec(exhdr); | |
513 | /* sum += i; | |
514 | if (sum < upperbound) | |
515 | goto extend;*/ | |
516 | if (index_offset+i < upperbound) { | |
517 | index_offset += i; | |
518 | exhdr->ext_hdr.isextended++; | |
519 | goto extend; | |
520 | } | |
521 | ||
522 | } | |
523 | if (save_linkflag == LF_SPARSE) { | |
524 | if (finish_sparse_file(f, &sizeleft, hstat.st_size, p)) | |
525 | goto padit; | |
526 | } | |
527 | else | |
528 | while (sizeleft > 0) { | |
529 | ||
530 | if(f_multivol) { | |
531 | save_name = p; | |
532 | save_sizeleft = sizeleft; | |
533 | save_totsize = hstat.st_size; | |
534 | } | |
535 | start = findrec(); | |
536 | ||
537 | bufsize = endofrecs()->charptr - start->charptr; | |
538 | ||
539 | if (sizeleft < bufsize) { | |
540 | /* Last read -- zero out area beyond */ | |
541 | bufsize = (int)sizeleft; | |
542 | count = bufsize % RECORDSIZE; | |
543 | if (count) | |
544 | bzero(start->charptr + sizeleft, | |
545 | (int)(RECORDSIZE - count)); | |
546 | } | |
547 | count = read(f, start->charptr, bufsize); | |
548 | if (count < 0) { | |
549 | msg_perror("read error at byte %ld, reading\ | |
550 | %d bytes, in file %s", hstat.st_size - sizeleft, bufsize,p); | |
551 | goto padit; | |
552 | } | |
553 | sizeleft -= count; | |
554 | ||
555 | /* This is nonportable (the type of userec's arg). */ | |
556 | userec(start+(count-1)/RECORDSIZE); | |
557 | ||
558 | if (count == bufsize) continue; | |
559 | msg( "file %s shrunk by %d bytes, padding with zeros.", p, sizeleft); | |
560 | goto padit; /* Short read */ | |
561 | } | |
562 | ||
563 | if(f_multivol) | |
564 | save_name = 0; | |
565 | ||
566 | if (f >= 0) | |
567 | (void)close(f); | |
568 | ||
569 | break; | |
570 | ||
571 | /* | |
572 | * File shrunk or gave error, pad out tape to match | |
573 | * the size we specified in the header. | |
574 | */ | |
575 | padit: | |
576 | while(sizeleft>0) { | |
577 | save_sizeleft=sizeleft; | |
578 | start=findrec(); | |
579 | bzero(start->charptr,RECORDSIZE); | |
580 | userec(start); | |
581 | sizeleft-=RECORDSIZE; | |
582 | } | |
583 | if(f_multivol) | |
584 | save_name=0; | |
585 | if(f>=0) | |
586 | (void)close(f); | |
587 | break; | |
588 | /* abort(); */ | |
589 | } | |
590 | ||
591 | #ifdef S_IFLNK | |
592 | case S_IFLNK: /* Symbolic link */ | |
593 | { | |
594 | int size; | |
595 | ||
596 | hstat.st_size = 0; /* Force 0 size on symlink */ | |
597 | header = start_header(p, &hstat); | |
598 | if (header == NULL) goto badfile; | |
599 | size = readlink(p, header->header.linkname, NAMSIZ); | |
600 | if (size < 0) goto badperror; | |
601 | if (size == NAMSIZ) { | |
602 | char buf[MAXPATHLEN]; | |
603 | ||
604 | readlink(p,buf,MAXPATHLEN); | |
605 | /* next_mangle(header->header.linkname); */ | |
606 | add_symlink_mangle(buf,p,header->header.linkname); | |
607 | msg("symbolic link %s too long: mangling to %s",p, header->header.linkname); | |
608 | /* size=strlen(header->header.linkname); */ | |
609 | } else | |
610 | header->header.linkname[size] = '\0'; | |
611 | header->header.linkflag = LF_SYMLINK; | |
612 | finish_header(header); /* Nothing more to do to it */ | |
613 | } | |
614 | break; | |
615 | #endif | |
616 | ||
617 | case S_IFDIR: /* Directory */ | |
618 | { | |
619 | register DIR *dirp; | |
620 | register struct direct *d; | |
621 | char *namebuf; | |
622 | int buflen; | |
623 | register int len; | |
624 | int our_device = hstat.st_dev; | |
625 | extern char *ck_malloc(),*ck_realloc(); | |
626 | ||
627 | /* Build new prototype name */ | |
628 | len = strlen(p); | |
629 | buflen=len+NAMSIZ; | |
630 | namebuf=ck_malloc(buflen+1); | |
631 | strncpy(namebuf, p, buflen); | |
632 | while (len >= 1 && '/' == namebuf[len-1]) | |
633 | len--; /* Delete trailing slashes */ | |
634 | namebuf[len++] = '/'; /* Now add exactly one back */ | |
635 | namebuf[len] = '\0'; /* Make sure null-terminated */ | |
636 | ||
637 | /* | |
638 | * Output directory header record with permissions | |
639 | * FIXME, do this AFTER files, to avoid R/O dir problems? | |
640 | * If old archive format, don't write record at all. | |
641 | */ | |
642 | if (!f_oldarch) { | |
643 | hstat.st_size = 0; /* Force 0 size on dir */ | |
644 | /* | |
645 | * If people could really read standard archives, | |
646 | * this should be: (FIXME) | |
647 | header = start_header(f_standard? p: namebuf, &hstat); | |
648 | * but since they'd interpret LF_DIR records as | |
649 | * regular files, we'd better put the / on the name. | |
650 | */ | |
651 | header = start_header(namebuf, &hstat); | |
652 | if (header == NULL) | |
653 | goto badfile; /* eg name too long */ | |
654 | ||
655 | if (f_gnudump) | |
656 | header->header.linkflag = LF_DUMPDIR; | |
657 | else if (f_standard) | |
658 | header->header.linkflag = LF_DIR; | |
659 | ||
660 | /* If we're gnudumping, we aren't done yet so don't close it. */ | |
661 | if(!f_gnudump) | |
662 | finish_header(header); /* Done with directory header */ | |
663 | } | |
664 | ||
665 | if(f_gnudump) { | |
666 | int sizeleft; | |
667 | int totsize; | |
668 | int bufsize; | |
669 | union record *start; | |
670 | int count; | |
671 | char *buf,*p_buf; | |
672 | ||
673 | buf=gnu_list_name->dir_contents; /* FOO */ | |
674 | totsize=0; | |
675 | for(p_buf=buf;p_buf && *p_buf;) { | |
676 | int tmp; | |
677 | ||
678 | tmp=strlen(p_buf)+1; | |
679 | totsize+=tmp; | |
680 | p_buf+=tmp; | |
681 | } | |
682 | totsize++; | |
683 | to_oct((long)totsize,1+12,header->header.size); | |
684 | finish_header(header); | |
685 | p_buf=buf; | |
686 | sizeleft=totsize; | |
687 | while(sizeleft>0) { | |
688 | if(f_multivol) { | |
689 | save_name=p; | |
690 | save_sizeleft=sizeleft; | |
691 | save_totsize=totsize; | |
692 | } | |
693 | start=findrec(); | |
694 | bufsize=endofrecs()->charptr - start->charptr; | |
695 | if(sizeleft<bufsize) { | |
696 | bufsize=sizeleft; | |
697 | count=bufsize%RECORDSIZE; | |
698 | if(count) | |
699 | bzero(start->charptr+sizeleft,RECORDSIZE-count); | |
700 | } | |
701 | bcopy(p_buf,start->charptr,bufsize); | |
702 | sizeleft-=bufsize; | |
703 | p_buf+=bufsize; | |
704 | userec(start+(bufsize-1)/RECORDSIZE); | |
705 | } | |
706 | if(f_multivol) | |
707 | save_name = 0; | |
708 | break; | |
709 | } | |
710 | ||
711 | /* Now output all the files in the directory */ | |
712 | /* if (f_dironly) | |
713 | break; /* Unless the cmdline said not to */ | |
714 | /* | |
715 | * See if we are crossing from one file system to another, | |
716 | * and avoid doing so if the user only wants to dump one file system. | |
717 | */ | |
718 | if (f_local_filesys && curdev >= 0 && curdev != hstat.st_dev) { | |
719 | if(f_verbose) | |
720 | msg("%s: is on a different filesystem; not dumped",p); | |
721 | break; | |
722 | } | |
723 | ||
724 | ||
725 | errno = 0; | |
726 | dirp = opendir(p); | |
727 | if (!dirp) { | |
728 | if (errno) { | |
729 | msg_perror ("can't open directory %s",p); | |
730 | } else { | |
731 | msg("error opening directory %s", | |
732 | p); | |
733 | } | |
734 | break; | |
735 | } | |
736 | ||
737 | /* Hack to remove "./" from the front of all the file names */ | |
738 | if (len == 2 && namebuf[0] == '.' && namebuf[1]=='/') | |
739 | len = 0; | |
740 | ||
741 | /* Should speed this up by cd-ing into the dir, FIXME */ | |
742 | while (NULL != (d=readdir(dirp))) { | |
743 | /* Skip . and .. */ | |
744 | if(is_dot_or_dotdot(d->d_name)) | |
745 | continue; | |
746 | ||
747 | if (DP_NAMELEN(d) + len >= buflen) { | |
748 | buflen=len+DP_NAMELEN(d); | |
749 | namebuf=ck_realloc(namebuf,buflen+1); | |
750 | /* namebuf[len]='\0'; | |
751 | msg("file name %s%s too long", | |
752 | namebuf, d->d_name); | |
753 | continue; */ | |
754 | } | |
755 | strcpy(namebuf+len, d->d_name); | |
756 | if(f_exclude && check_exclude(namebuf)) | |
757 | continue; | |
758 | dump_file(namebuf, our_device); | |
759 | } | |
760 | ||
761 | closedir(dirp); | |
762 | free(namebuf); | |
763 | } | |
764 | break; | |
765 | ||
766 | #ifdef S_IFCHR | |
767 | case S_IFCHR: /* Character special file */ | |
768 | type = LF_CHR; | |
769 | goto easy; | |
770 | #endif | |
771 | ||
772 | #ifdef S_IFBLK | |
773 | case S_IFBLK: /* Block special file */ | |
774 | type = LF_BLK; | |
775 | goto easy; | |
776 | #endif | |
777 | ||
778 | /* Avoid screwy apollo lossage where S_IFIFO == S_IFSOCK */ | |
779 | #if ((_ISP__M68K == 0) && (_ISP__A88K == 0)) | |
780 | #ifdef S_IFIFO | |
781 | case S_IFIFO: /* Fifo special file */ | |
782 | ||
783 | type = LF_FIFO; | |
784 | goto easy; | |
785 | #endif | |
786 | #endif | |
787 | ||
788 | #ifdef S_IFSOCK | |
789 | case S_IFSOCK: /* Socket pretend its a fifo? */ | |
790 | type = LF_FIFO; | |
791 | goto easy; | |
792 | #endif | |
793 | ||
794 | easy: | |
795 | if (!f_standard) goto unknown; | |
796 | ||
797 | hstat.st_size = 0; /* Force 0 size */ | |
798 | header = start_header(p, &hstat); | |
799 | if (header == NULL) goto badfile; /* eg name too long */ | |
800 | ||
801 | header->header.linkflag = type; | |
802 | if (type != LF_FIFO) { | |
803 | to_oct((long) major(hstat.st_rdev), 8, | |
804 | header->header.devmajor); | |
805 | to_oct((long) minor(hstat.st_rdev), 8, | |
806 | header->header.devminor); | |
807 | } | |
808 | ||
809 | finish_header(header); | |
810 | break; | |
811 | ||
812 | default: | |
813 | unknown: | |
814 | msg("%s: Unknown file type; file ignored.", p); | |
815 | break; | |
816 | } | |
817 | } | |
818 | ||
819 | int | |
820 | finish_sparse_file(fd, sizeleft, fullsize, name) | |
821 | int fd; | |
822 | long *sizeleft, | |
823 | fullsize; | |
824 | char *name; | |
825 | { | |
826 | union record *start; | |
827 | char tempbuf[RECORDSIZE]; | |
828 | int bufsize, | |
829 | sparse_ind = 0, | |
830 | count; | |
831 | long pos; | |
832 | long nwritten = 0; | |
833 | ||
834 | ||
835 | while (*sizeleft > 0) { | |
836 | start = findrec(); | |
837 | bzero(start->charptr, RECORDSIZE); | |
838 | bufsize = sparsearray[sparse_ind].numbytes; | |
839 | if (!bufsize) { /* we blew it, maybe */ | |
840 | msg("Wrote %ld of %ld bytes to file %s", | |
841 | fullsize - *sizeleft, fullsize, name); | |
842 | break; | |
843 | } | |
844 | pos = lseek(fd, sparsearray[sparse_ind++].offset, 0); | |
845 | /* | |
846 | * If the number of bytes to be written here exceeds | |
847 | * the size of the temporary buffer, do it in steps. | |
848 | */ | |
849 | while (bufsize > RECORDSIZE) { | |
850 | /* if (amt_read) { | |
851 | count = read(fd, start->charptr+amt_read, RECORDSIZE-amt_read); | |
852 | bufsize -= RECORDSIZE - amt_read; | |
853 | amt_read = 0; | |
854 | userec(start); | |
855 | start = findrec(); | |
856 | bzero(start->charptr, RECORDSIZE); | |
857 | }*/ | |
858 | /* store the data */ | |
859 | count = read(fd, start->charptr, RECORDSIZE); | |
860 | if (count < 0) { | |
861 | msg_perror("read error at byte %ld, reading %d bytes, in file %s", | |
862 | fullsize - *sizeleft, bufsize, name); | |
863 | return 1; | |
864 | } | |
865 | bufsize -= count; | |
866 | *sizeleft -= count; | |
867 | userec(start); | |
868 | nwritten += RECORDSIZE; /* XXX */ | |
869 | start = findrec(); | |
870 | bzero(start->charptr, RECORDSIZE); | |
871 | } | |
872 | ||
873 | ||
874 | clear_buffer(tempbuf); | |
875 | count = read(fd, tempbuf, bufsize); | |
876 | bcopy(tempbuf, start->charptr, RECORDSIZE); | |
877 | if (count < 0) { | |
878 | msg_perror("read error at byte %ld, reading %d bytes, in file %s", | |
879 | fullsize - *sizeleft, bufsize, name); | |
880 | return 1; | |
881 | } | |
882 | /* if (amt_read >= RECORDSIZE) { | |
883 | amt_read = 0; | |
884 | userec(start+(count-1)/RECORDSIZE); | |
885 | if (count != bufsize) { | |
886 | msg("file %s shrunk by %d bytes, padding with zeros.", name, sizeleft); | |
887 | return 1; | |
888 | } | |
889 | start = findrec(); | |
890 | } else | |
891 | amt_read += bufsize;*/ | |
892 | nwritten += count; /* XXX */ | |
893 | *sizeleft -= count; | |
894 | userec(start); | |
895 | ||
896 | } | |
897 | free(sparsearray); | |
898 | printf ("Amount actually written is (I hope) %d.\n", nwritten); | |
899 | /* userec(start+(count-1)/RECORDSIZE);*/ | |
900 | return 0; | |
901 | ||
902 | } | |
903 | ||
904 | init_sparsearray() | |
905 | { | |
906 | register int i; | |
907 | ||
908 | sp_array_size = 10; | |
909 | /* | |
910 | * Make room for our scratch space -- initially is 10 elts long | |
911 | */ | |
912 | sparsearray = (struct sp_array *) malloc(sp_array_size * sizeof(struct sp_array)); | |
913 | for (i = 0; i < sp_array_size; i++) { | |
914 | sparsearray[i].offset = 0; | |
915 | sparsearray[i].numbytes = 0; | |
916 | } | |
917 | } | |
918 | ||
919 | ||
920 | ||
921 | /* | |
922 | * Okay, we've got a sparse file on our hands -- now, what we need to do is | |
923 | * make a pass through the file and carefully note where any data is, i.e., | |
924 | * we want to find how far into the file each instance of data is, and how | |
925 | * many bytes are there. We store this information in the sparsearray, | |
926 | * which will later be translated into header information. For now, we use | |
927 | * the sparsearray as convenient storage. | |
928 | * | |
929 | * As a side note, this routine is a mess. If I could have found a cleaner | |
930 | * way to do it, I would have. If anyone wants to find a nicer way to do | |
931 | * this, feel free. | |
932 | */ | |
933 | ||
934 | /* There is little point in trimming small amounts of null data at the */ | |
935 | /* head and tail of blocks -- it's ok if we only avoid dumping blocks */ | |
936 | /* of complete null data */ | |
937 | int | |
938 | deal_with_sparse(name, header, nulls_at_end) | |
939 | char *name; | |
940 | union record *header; | |
941 | ||
942 | { | |
943 | long numbytes = 0; | |
944 | long offset = 0; | |
945 | long save_offset; | |
946 | int fd; | |
947 | int current_size = hstat.st_size; | |
948 | int sparse_ind = 0, | |
949 | cc; | |
950 | char buf[RECORDSIZE]; | |
951 | int read_last_data = 0; /* did we just read the last record? */ | |
952 | int amidst_data = 0; | |
953 | ||
954 | header->header.isextended = 0; | |
955 | /* | |
956 | * Can't open the file -- this problem will be caught later on, | |
957 | * so just return. | |
958 | */ | |
959 | if ((fd = open(name, O_RDONLY)) < 0) | |
960 | return 0; | |
961 | ||
962 | init_sparsearray(); | |
963 | clear_buffer(buf); | |
964 | ||
965 | while ((cc = read(fd, buf, sizeof buf)) != 0) { | |
966 | ||
967 | if (sparse_ind > sp_array_size-1) { | |
968 | ||
969 | /* | |
970 | * realloc the scratch area, since we've run out of room -- | |
971 | */ | |
972 | sparsearray = (struct sp_array *) | |
973 | realloc(sparsearray, | |
974 | 2 * sp_array_size * (sizeof(struct sp_array))); | |
975 | sp_array_size *= 2; | |
976 | } | |
977 | if (cc == sizeof buf) { | |
978 | if (zero_record(buf)) { | |
979 | if (amidst_data) { | |
980 | sparsearray[sparse_ind++].numbytes | |
981 | = numbytes; | |
982 | amidst_data = 0; | |
983 | } | |
984 | } else { /* !zero_record(buf) */ | |
985 | if (amidst_data) | |
986 | numbytes += cc; | |
987 | else { | |
988 | amidst_data = 1; | |
989 | numbytes = cc; | |
990 | sparsearray[sparse_ind].offset | |
991 | = offset; | |
992 | } | |
993 | } | |
994 | } else if (cc < sizeof buf) { | |
995 | /* This has to be the last bit of the file, so this */ | |
996 | /* is somewhat shorter than the above. */ | |
997 | if (!zero_record(buf)) { | |
998 | if (!amidst_data) { | |
999 | amidst_data = 1; | |
1000 | numbytes = cc; | |
1001 | sparsearray[sparse_ind].offset | |
1002 | = offset; | |
1003 | } else | |
1004 | numbytes += cc; | |
1005 | } | |
1006 | } | |
1007 | offset += cc; | |
1008 | clear_buffer(buf); | |
1009 | } | |
1010 | if (amidst_data) | |
1011 | sparsearray[sparse_ind++].numbytes = numbytes; | |
1012 | close(fd); | |
1013 | ||
1014 | return sparse_ind - 1; | |
1015 | } | |
1016 | ||
1017 | /* | |
1018 | * Just zeroes out the buffer so we don't confuse ourselves with leftover | |
1019 | * data. | |
1020 | */ | |
1021 | clear_buffer(buf) | |
1022 | char *buf; | |
1023 | { | |
1024 | register int i; | |
1025 | ||
1026 | for (i = 0; i < RECORDSIZE; i++) | |
1027 | buf[i] = '\0'; | |
1028 | } | |
1029 | ||
1030 | #if 0 /* I'm leaving this as a monument to Joy Kendall, who wrote it */ | |
1031 | /* | |
1032 | * JK - | |
1033 | * This routine takes a character array, and tells where within that array | |
1034 | * the data can be found. It skips over any zeros, and sets the first | |
1035 | * non-zero point in the array to be the "start", and continues until it | |
1036 | * finds non-data again, which is marked as the "end." This routine is | |
1037 | * mainly for 1) seeing how far into a file we must lseek to data, given | |
1038 | * that we have a sparse file, and 2) determining the "real size" of the | |
1039 | * file, i.e., the number of bytes in the sparse file that are data, as | |
1040 | * opposed to the zeros we are trying to skip. | |
1041 | */ | |
1042 | where_is_data(from, to, buffer) | |
1043 | int *from, | |
1044 | *to; | |
1045 | char *buffer; | |
1046 | { | |
1047 | register int i = 0; | |
1048 | register int save_to = *to; | |
1049 | int amidst_data = 0; | |
1050 | ||
1051 | ||
1052 | while (!buffer[i]) | |
1053 | i++; | |
1054 | *from = i; | |
1055 | ||
1056 | if (*from < 16) /* don't bother */ | |
1057 | *from = 0; | |
1058 | /* keep going to make sure there isn't more real | |
1059 | data in this record */ | |
1060 | while (i < RECORDSIZE) { | |
1061 | if (!buffer[i]) { | |
1062 | if (amidst_data) { | |
1063 | save_to = i; | |
1064 | amidst_data = 0; | |
1065 | } | |
1066 | i++; | |
1067 | } | |
1068 | else if (buffer[i]) { | |
1069 | if (!amidst_data) | |
1070 | amidst_data = 1; | |
1071 | i++; | |
1072 | } | |
1073 | } | |
1074 | if (i == RECORDSIZE) | |
1075 | *to = i; | |
1076 | else | |
1077 | *to = save_to; | |
1078 | ||
1079 | } | |
1080 | #endif | |
1081 | ||
1082 | /* Note that this routine is only called if zero_record returned true */ | |
1083 | #if 0 /* But we actually don't need it at all. */ | |
1084 | where_is_data (from, to, buffer) | |
1085 | int *from, *to; | |
1086 | char *buffer; | |
1087 | { | |
1088 | char *fp, *tp; | |
1089 | ||
1090 | for (fp = buffer; ! *fp; fp++) | |
1091 | ; | |
1092 | for (tp = buffer + RECORDSIZE - 1; ! *tp; tp--) | |
1093 | ; | |
1094 | *from = fp - buffer; | |
1095 | *to = tp - buffer + 1; | |
1096 | } | |
1097 | #endif | |
1098 | ||
1099 | ||
1100 | ||
1101 | /* | |
1102 | * Takes a recordful of data and basically cruises through it to see if | |
1103 | * it's made *entirely* of zeros, returning a 0 the instant it finds | |
1104 | * something that is a non-zero, i.e., useful data. | |
1105 | */ | |
1106 | zero_record(buffer) | |
1107 | char *buffer; | |
1108 | { | |
1109 | register int i; | |
1110 | ||
1111 | for (i = 0; i < RECORDSIZE; i++) | |
1112 | if (buffer[i] != '\000') | |
1113 | return 0; | |
1114 | return 1; | |
1115 | } | |
1116 | ||
1117 | find_new_file_size(filesize, highest_index) | |
1118 | int *filesize; | |
1119 | int highest_index; | |
1120 | { | |
1121 | register int i; | |
1122 | ||
1123 | *filesize = 0; | |
1124 | for (i = 0; sparsearray[i].numbytes && i <= highest_index; i++) | |
1125 | *filesize += sparsearray[i].numbytes; | |
1126 | } | |
1127 | ||
1128 | /* | |
1129 | * Make a header block for the file name whose stat info is st . | |
1130 | * Return header pointer for success, NULL if the name is too long. | |
1131 | */ | |
1132 | union record * | |
1133 | start_header(name, st) | |
1134 | char *name; | |
1135 | register struct stat *st; | |
1136 | { | |
1137 | register union record *header; | |
1138 | ||
1139 | header = (union record *) findrec(); | |
1140 | bzero(header->charptr, sizeof(*header)); /* XXX speed up */ | |
1141 | ||
1142 | /* | |
1143 | * Check the file name and put it in the record. | |
1144 | */ | |
1145 | if(!f_absolute_paths) { | |
1146 | static int warned_once = 0; | |
1147 | #ifdef __MSDOS__ | |
1148 | if(name[1]==':') { | |
1149 | name+=2; | |
1150 | if(!warned_once++) | |
1151 | msg("Removing drive spec from names in the archive"); | |
1152 | } | |
1153 | #endif | |
1154 | while ('/' == *name) { | |
1155 | name++; /* Force relative path */ | |
1156 | if (!warned_once++) | |
1157 | msg("Removing leading / from absolute path names in the archive."); | |
1158 | } | |
1159 | } | |
1160 | strncpy(header->header.name, name, NAMSIZ); | |
1161 | if (header->header.name[NAMSIZ-1]) { | |
1162 | char *mangled; | |
1163 | ||
1164 | /* next_mangle(header->header.name); */ | |
1165 | add_mangle(name,header->header.name); | |
1166 | msg("%s: is too long: mangling to %s", name, header->header.name); | |
1167 | } | |
1168 | ||
1169 | to_oct((long) (st->st_mode & ~S_IFMT), | |
1170 | 8, header->header.mode); | |
1171 | to_oct((long) st->st_uid, 8, header->header.uid); | |
1172 | to_oct((long) st->st_gid, 8, header->header.gid); | |
1173 | to_oct((long) st->st_size, 1+12, header->header.size); | |
1174 | to_oct((long) st->st_mtime, 1+12, header->header.mtime); | |
1175 | /* header->header.linkflag is left as null */ | |
1176 | if(f_gnudump) { | |
1177 | to_oct((long) st->st_atime, 1+12, header->header.atime); | |
1178 | to_oct((long) st->st_ctime, 1+12, header->header.ctime); | |
1179 | } | |
1180 | ||
1181 | #ifndef NONAMES | |
1182 | /* Fill in new Unix Standard fields if desired. */ | |
1183 | if (f_standard) { | |
1184 | header->header.linkflag = LF_NORMAL; /* New default */ | |
1185 | strcpy(header->header.magic, TMAGIC); /* Mark as Unix Std */ | |
1186 | finduname(header->header.uname, st->st_uid); | |
1187 | findgname(header->header.gname, st->st_gid); | |
1188 | } | |
1189 | #endif | |
1190 | return header; | |
1191 | } | |
1192 | ||
1193 | /* | |
1194 | * Finish off a filled-in header block and write it out. | |
1195 | * We also print the file name and/or full info if verbose is on. | |
1196 | */ | |
1197 | void | |
1198 | finish_header(header) | |
1199 | register union record *header; | |
1200 | { | |
1201 | register int i, sum; | |
1202 | register char *p; | |
1203 | void bcopy(); | |
1204 | ||
1205 | bcopy(CHKBLANKS, header->header.chksum, sizeof(header->header.chksum)); | |
1206 | ||
1207 | sum = 0; | |
1208 | p = header->charptr; | |
1209 | for (i = sizeof(*header); --i >= 0; ) { | |
1210 | /* | |
1211 | * We can't use unsigned char here because of old compilers, | |
1212 | * e.g. V7. | |
1213 | */ | |
1214 | sum += 0xFF & *p++; | |
1215 | } | |
1216 | ||
1217 | /* | |
1218 | * Fill in the checksum field. It's formatted differently | |
1219 | * from the other fields: it has [6] digits, a null, then a | |
1220 | * space -- rather than digits, a space, then a null. | |
1221 | * We use to_oct then write the null in over to_oct's space. | |
1222 | * The final space is already there, from checksumming, and | |
1223 | * to_oct doesn't modify it. | |
1224 | * | |
1225 | * This is a fast way to do: | |
1226 | * (void) sprintf(header->header.chksum, "%6o", sum); | |
1227 | */ | |
1228 | to_oct((long) sum, 8, header->header.chksum); | |
1229 | header->header.chksum[6] = '\0'; /* Zap the space */ | |
1230 | ||
1231 | userec(header); | |
1232 | ||
1233 | if (f_verbose) { | |
1234 | extern union record *head; /* Points to current tape header */ | |
1235 | extern int head_standard; /* Tape header is in ANSI format */ | |
1236 | ||
1237 | /* These globals are parameters to print_header, sigh */ | |
1238 | head = header; | |
1239 | /* hstat is already set up */ | |
1240 | head_standard = f_standard; | |
1241 | print_header(); | |
1242 | } | |
1243 | ||
1244 | return; | |
1245 | } | |
1246 | ||
1247 | ||
1248 | /* | |
1249 | * Quick and dirty octal conversion. | |
1250 | * Converts long "value" into a "digs"-digit field at "where", | |
1251 | * including a trailing space and room for a null. "digs"==3 means | |
1252 | * 1 digit, a space, and room for a null. | |
1253 | * | |
1254 | * We assume the trailing null is already there and don't fill it in. | |
1255 | * This fact is used by start_header and finish_header, so don't change it! | |
1256 | * | |
1257 | * This should be equivalent to: | |
1258 | * (void) sprintf(where, "%*lo ", digs-2, value); | |
1259 | * except that sprintf fills in the trailing null and we don't. | |
1260 | */ | |
1261 | void | |
1262 | to_oct(value, digs, where) | |
1263 | register long value; | |
1264 | register int digs; | |
1265 | register char *where; | |
1266 | { | |
1267 | ||
1268 | --digs; /* Trailing null slot is left alone */ | |
1269 | where[--digs] = ' '; /* Put in the space, though */ | |
1270 | ||
1271 | /* Produce the digits -- at least one */ | |
1272 | do { | |
1273 | where[--digs] = '0' + (char)(value & 7); /* one octal digit */ | |
1274 | value >>= 3; | |
1275 | } while (digs > 0 && value != 0); | |
1276 | ||
1277 | /* Leading spaces, if necessary */ | |
1278 | while (digs > 0) | |
1279 | where[--digs] = ' '; | |
1280 | ||
1281 | } | |
1282 | ||
1283 | ||
1284 | /* | |
1285 | * Write the EOT record(s). | |
1286 | * We actually zero at least one record, through the end of the block. | |
1287 | * Old tar writes garbage after two zeroed records -- and PDtar used to. | |
1288 | */ | |
1289 | write_eot() | |
1290 | { | |
1291 | union record *p; | |
1292 | int bufsize; | |
1293 | void bzero(); | |
1294 | ||
1295 | p = findrec(); | |
1296 | if (p) | |
1297 | { | |
1298 | bufsize = endofrecs()->charptr - p->charptr; | |
1299 | bzero(p->charptr, bufsize); | |
1300 | userec(p); | |
1301 | } | |
1302 | } |