Break out of an infinite loop if we're not making progress.
[make.git] / arscan.c
blob4fcf060ee473c0c6277489664784d93234caebf2
1 /* Library function for scanning an archive file.
2 Copyright (C) 1987-2012 Free Software Foundation, Inc.
3 This file is part of GNU Make.
5 GNU Make is free software; you can redistribute it and/or modify it under the
6 terms of the GNU General Public License as published by the Free Software
7 Foundation; either version 3 of the License, or (at your option) any later
8 version.
10 GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
11 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License along with
15 this program. If not, see <http://www.gnu.org/licenses/>. */
17 #include "make.h"
19 #ifdef HAVE_FCNTL_H
20 #include <fcntl.h>
21 #else
22 #include <sys/file.h>
23 #endif
25 #ifndef NO_ARCHIVES
27 #ifdef VMS
28 #include <lbrdef.h>
29 #include <mhddef.h>
30 #include <credef.h>
31 #include <descrip.h>
32 #include <ctype.h>
33 #if __DECC
34 #include <unixlib.h>
35 #include <lbr$routines.h>
36 #endif
38 static void *VMS_lib_idx;
40 static char *VMS_saved_memname;
42 static time_t VMS_member_date;
44 static long int (*VMS_function) ();
46 static int
47 VMS_get_member_info (struct dsc$descriptor_s *module, unsigned long *rfa)
49 int status, i;
50 long int fnval;
52 time_t val;
54 static struct dsc$descriptor_s bufdesc =
55 { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };
57 struct mhddef *mhd;
58 char filename[128];
60 bufdesc.dsc$a_pointer = filename;
61 bufdesc.dsc$w_length = sizeof (filename);
63 status = lbr$set_module (&VMS_lib_idx, rfa, &bufdesc,
64 &bufdesc.dsc$w_length, 0);
65 if (! (status & 1))
67 error (NILF, _("lbr$set_module() failed to extract module info, status = %d"),
68 status);
70 lbr$close (&VMS_lib_idx);
72 return 0;
75 mhd = (struct mhddef *) filename;
77 #ifdef __DECC
78 /* John Fowler <jfowler@nyx.net> writes this is needed in his environment,
79 * but that decc$fix_time() isn't documented to work this way. Let me
80 * know if this causes problems in other VMS environments.
83 /* Modified by M. Gehre at 11-JAN-2008 because old formula is wrong:
84 * val = decc$fix_time (&mhd->mhd$l_datim) + timezone - daylight*3600;
85 * a) daylight specifies, if the timezone has daylight saving enabled, not
86 * if it is active
87 * b) what we need is the information, if daylight saving was active, if
88 * the library module was replaced. This information we get using the
89 * localtime function
92 struct tm *tmp;
94 /* Conversion from VMS time to C time */
95 val = decc$fix_time (&mhd->mhd$l_datim);
98 * Conversion from local time (stored in library) to GMT (needed for gmake)
99 * Note: The tm_gmtoff element is a VMS extension to the ANSI standard.
101 tmp = localtime (&val);
102 val -= tmp->tm_gmtoff;
104 #endif
106 for (i = 0; i < module->dsc$w_length; i++)
107 filename[i] = _tolower ((unsigned char)module->dsc$a_pointer[i]);
109 filename[i] = '\0';
111 VMS_member_date = (time_t) -1;
113 fnval =
114 (*VMS_function) (-1, filename, 0, 0, 0, 0, val, 0, 0, 0,
115 VMS_saved_memname);
117 if (fnval)
119 VMS_member_date = fnval;
120 return 0;
122 else
123 return 1;
126 /* Takes three arguments ARCHIVE, FUNCTION and ARG.
128 Open the archive named ARCHIVE, find its members one by one,
129 and for each one call FUNCTION with the following arguments:
130 archive file descriptor for reading the data,
131 member name,
132 member name might be truncated flag,
133 member header position in file,
134 member data position in file,
135 member data size,
136 member date,
137 member uid,
138 member gid,
139 member protection mode,
140 ARG.
142 NOTE: on VMS systems, only name, date, and arg are meaningful!
144 The descriptor is poised to read the data of the member
145 when FUNCTION is called. It does not matter how much
146 data FUNCTION reads.
148 If FUNCTION returns nonzero, we immediately return
149 what FUNCTION returned.
151 Returns -1 if archive does not exist,
152 Returns -2 if archive has invalid format.
153 Returns 0 if have scanned successfully. */
155 long int
156 ar_scan (const char *archive, ar_member_func_t function, const void *arg)
158 char *p;
160 static struct dsc$descriptor_s libdesc =
161 { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };
163 unsigned long func = LBR$C_READ;
164 unsigned long type = LBR$C_TYP_UNK;
165 unsigned long index = 1;
167 int status;
169 status = lbr$ini_control (&VMS_lib_idx, &func, &type, 0);
171 if (! (status & 1))
173 error (NILF, _("lbr$ini_control() failed with status = %d"), status);
174 return -2;
177 /* there is no such descriptor with "const char *dsc$a_pointer" */
178 libdesc.dsc$a_pointer = (char *)archive;
179 libdesc.dsc$w_length = strlen (archive);
181 status = lbr$open (&VMS_lib_idx, &libdesc, 0, 0, 0, 0, 0);
183 if (! (status & 1))
185 error (NILF, _("unable to open library '%s' to lookup member '%s'"),
186 archive, (char *)arg);
187 return -1;
190 VMS_saved_memname = (char *)arg;
192 /* For comparison, delete .obj from arg name. */
194 p = strrchr (VMS_saved_memname, '.');
195 if (p)
196 *p = '\0';
198 VMS_function = function;
200 VMS_member_date = (time_t) -1;
201 lbr$get_index (&VMS_lib_idx, &index, VMS_get_member_info, 0);
203 /* Undo the damage. */
204 if (p)
205 *p = '.';
207 lbr$close (&VMS_lib_idx);
209 return VMS_member_date > 0 ? VMS_member_date : 0;
212 #else /* !VMS */
214 /* SCO Unix's compiler defines both of these. */
215 #ifdef M_UNIX
216 #undef M_XENIX
217 #endif
219 /* On the sun386i and in System V rel 3, ar.h defines two different archive
220 formats depending upon whether you have defined PORTAR (normal) or PORT5AR
221 (System V Release 1). There is no default, one or the other must be defined
222 to have a nonzero value. */
224 #if (!defined (PORTAR) || PORTAR == 0) && (!defined (PORT5AR) || PORT5AR == 0)
225 #undef PORTAR
226 #ifdef M_XENIX
227 /* According to Jim Sievert <jas1@rsvl.unisys.com>, for SCO XENIX defining
228 PORTAR to 1 gets the wrong archive format, and defining it to 0 gets the
229 right one. */
230 #define PORTAR 0
231 #else
232 #define PORTAR 1
233 #endif
234 #endif
236 /* On AIX, define these symbols to be sure to get both archive formats.
237 AIX 4.3 introduced the "big" archive format to support 64-bit object
238 files, so on AIX 4.3 systems we need to support both the "normal" and
239 "big" archive formats. An archive's format is indicated in the
240 "fl_magic" field of the "FL_HDR" structure. For a normal archive,
241 this field will be the string defined by the AIAMAG symbol. For a
242 "big" archive, it will be the string defined by the AIAMAGBIG symbol
243 (at least on AIX it works this way).
245 Note: we'll define these symbols regardless of which AIX version
246 we're compiling on, but this is okay since we'll use the new symbols
247 only if they're present. */
248 #ifdef _AIX
249 # define __AR_SMALL__
250 # define __AR_BIG__
251 #endif
253 #ifndef WINDOWS32
254 # ifndef __BEOS__
255 # include <ar.h>
256 # else
257 /* BeOS 5 doesn't have <ar.h> but has archives in the same format
258 * as many other Unices. This was taken from GNU binutils for BeOS.
260 # define ARMAG "!<arch>\n" /* String that begins an archive file. */
261 # define SARMAG 8 /* Size of that string. */
262 # define ARFMAG "`\n" /* String in ar_fmag at end of each header. */
263 struct ar_hdr
265 char ar_name[16]; /* Member file name, sometimes / terminated. */
266 char ar_date[12]; /* File date, decimal seconds since Epoch. */
267 char ar_uid[6], ar_gid[6]; /* User and group IDs, in ASCII decimal. */
268 char ar_mode[8]; /* File mode, in ASCII octal. */
269 char ar_size[10]; /* File size, in ASCII decimal. */
270 char ar_fmag[2]; /* Always contains ARFMAG. */
272 # endif
273 # define TOCHAR(_m) (_m)
274 #else
275 /* These should allow us to read Windows (VC++) libraries (according to Frank
276 * Libbrecht <frankl@abzx.belgium.hp.com>)
278 # include <windows.h>
279 # include <windef.h>
280 # include <io.h>
281 # define ARMAG IMAGE_ARCHIVE_START
282 # define SARMAG IMAGE_ARCHIVE_START_SIZE
283 # define ar_hdr _IMAGE_ARCHIVE_MEMBER_HEADER
284 # define ar_name Name
285 # define ar_mode Mode
286 # define ar_size Size
287 # define ar_date Date
288 # define ar_uid UserID
289 # define ar_gid GroupID
290 /* In Windows the member names have type BYTE so we must cast them. */
291 # define TOCHAR(_m) ((char *)(_m))
292 #endif
294 /* Cray's <ar.h> apparently defines this. */
295 #ifndef AR_HDR_SIZE
296 # define AR_HDR_SIZE (sizeof (struct ar_hdr))
297 #endif
299 /* Takes three arguments ARCHIVE, FUNCTION and ARG.
301 Open the archive named ARCHIVE, find its members one by one,
302 and for each one call FUNCTION with the following arguments:
303 archive file descriptor for reading the data,
304 member name,
305 member name might be truncated flag,
306 member header position in file,
307 member data position in file,
308 member data size,
309 member date,
310 member uid,
311 member gid,
312 member protection mode,
313 ARG.
315 The descriptor is poised to read the data of the member
316 when FUNCTION is called. It does not matter how much
317 data FUNCTION reads.
319 If FUNCTION returns nonzero, we immediately return
320 what FUNCTION returned.
322 Returns -1 if archive does not exist,
323 Returns -2 if archive has invalid format.
324 Returns 0 if have scanned successfully. */
326 long int
327 ar_scan (const char *archive, ar_member_func_t function, const void *arg)
329 #ifdef AIAMAG
330 FL_HDR fl_header;
331 #ifdef AIAMAGBIG
332 int big_archive = 0;
333 FL_HDR_BIG fl_header_big;
334 #endif
335 #else
336 int long_name = 0;
337 #endif
338 char *namemap = 0;
339 int desc = open (archive, O_RDONLY, 0);
340 if (desc < 0)
341 return -1;
342 #ifdef SARMAG
344 char buf[SARMAG];
345 register int nread = read (desc, buf, SARMAG);
346 if (nread != SARMAG || memcmp (buf, ARMAG, SARMAG))
348 (void) close (desc);
349 return -2;
352 #else
353 #ifdef AIAMAG
355 register int nread = read (desc, &fl_header, FL_HSZ);
357 if (nread != FL_HSZ)
359 (void) close (desc);
360 return -2;
362 #ifdef AIAMAGBIG
363 /* If this is a "big" archive, then set the flag and
364 re-read the header into the "big" structure. */
365 if (!memcmp (fl_header.fl_magic, AIAMAGBIG, SAIAMAG))
367 big_archive = 1;
369 /* seek back to beginning of archive */
370 if (lseek (desc, 0, 0) < 0)
372 (void) close (desc);
373 return -2;
376 /* re-read the header into the "big" structure */
377 nread = read (desc, &fl_header_big, FL_HSZ_BIG);
378 if (nread != FL_HSZ_BIG)
380 (void) close (desc);
381 return -2;
384 else
385 #endif
386 /* Check to make sure this is a "normal" archive. */
387 if (memcmp (fl_header.fl_magic, AIAMAG, SAIAMAG))
389 (void) close (desc);
390 return -2;
393 #else
395 #ifndef M_XENIX
396 int buf;
397 #else
398 unsigned short int buf;
399 #endif
400 register int nread = read(desc, &buf, sizeof (buf));
401 if (nread != sizeof (buf) || buf != ARMAG)
403 (void) close (desc);
404 return -2;
407 #endif
408 #endif
410 /* Now find the members one by one. */
412 #ifdef SARMAG
413 register long int member_offset = SARMAG;
414 #else
415 #ifdef AIAMAG
416 long int member_offset;
417 long int last_member_offset;
419 #ifdef AIAMAGBIG
420 if ( big_archive )
422 sscanf (fl_header_big.fl_fstmoff, "%20ld", &member_offset);
423 sscanf (fl_header_big.fl_lstmoff, "%20ld", &last_member_offset);
425 else
426 #endif
428 sscanf (fl_header.fl_fstmoff, "%12ld", &member_offset);
429 sscanf (fl_header.fl_lstmoff, "%12ld", &last_member_offset);
432 if (member_offset == 0)
434 /* Empty archive. */
435 close (desc);
436 return 0;
438 #else
439 #ifndef M_XENIX
440 register long int member_offset = sizeof (int);
441 #else /* Xenix. */
442 register long int member_offset = sizeof (unsigned short int);
443 #endif /* Not Xenix. */
444 #endif
445 #endif
447 while (1)
449 register int nread;
450 struct ar_hdr member_header;
451 #ifdef AIAMAGBIG
452 struct ar_hdr_big member_header_big;
453 #endif
454 #ifdef AIAMAG
455 char name[256];
456 int name_len;
457 long int dateval;
458 int uidval, gidval;
459 long int data_offset;
460 #else
461 char namebuf[sizeof member_header.ar_name + 1];
462 char *name;
463 int is_namemap; /* Nonzero if this entry maps long names. */
464 #endif
465 long int eltsize;
466 int eltmode;
467 long int fnval;
469 if (lseek (desc, member_offset, 0) < 0)
471 (void) close (desc);
472 return -2;
475 #ifdef AIAMAG
476 #define AR_MEMHDR_SZ(x) (sizeof(x) - sizeof (x._ar_name))
478 #ifdef AIAMAGBIG
479 if (big_archive)
481 nread = read (desc, &member_header_big,
482 AR_MEMHDR_SZ(member_header_big) );
484 if (nread != AR_MEMHDR_SZ(member_header_big))
486 (void) close (desc);
487 return -2;
490 sscanf (member_header_big.ar_namlen, "%4d", &name_len);
491 nread = read (desc, name, name_len);
493 if (nread != name_len)
495 (void) close (desc);
496 return -2;
499 name[name_len] = 0;
501 sscanf (member_header_big.ar_date, "%12ld", &dateval);
502 sscanf (member_header_big.ar_uid, "%12d", &uidval);
503 sscanf (member_header_big.ar_gid, "%12d", &gidval);
504 sscanf (member_header_big.ar_mode, "%12o", &eltmode);
505 sscanf (member_header_big.ar_size, "%20ld", &eltsize);
507 data_offset = (member_offset + AR_MEMHDR_SZ(member_header_big)
508 + name_len + 2);
510 else
511 #endif
513 nread = read (desc, &member_header,
514 AR_MEMHDR_SZ(member_header) );
516 if (nread != AR_MEMHDR_SZ(member_header))
518 (void) close (desc);
519 return -2;
522 sscanf (member_header.ar_namlen, "%4d", &name_len);
523 nread = read (desc, name, name_len);
525 if (nread != name_len)
527 (void) close (desc);
528 return -2;
531 name[name_len] = 0;
533 sscanf (member_header.ar_date, "%12ld", &dateval);
534 sscanf (member_header.ar_uid, "%12d", &uidval);
535 sscanf (member_header.ar_gid, "%12d", &gidval);
536 sscanf (member_header.ar_mode, "%12o", &eltmode);
537 sscanf (member_header.ar_size, "%12ld", &eltsize);
539 data_offset = (member_offset + AR_MEMHDR_SZ(member_header)
540 + name_len + 2);
542 data_offset += data_offset % 2;
544 fnval =
545 (*function) (desc, name, 0,
546 member_offset, data_offset, eltsize,
547 dateval, uidval, gidval,
548 eltmode, arg);
550 #else /* Not AIAMAG. */
551 nread = read (desc, &member_header, AR_HDR_SIZE);
552 if (nread == 0)
553 /* No data left means end of file; that is OK. */
554 break;
556 if (nread != AR_HDR_SIZE
557 #if defined(ARFMAG) || defined(ARFZMAG)
558 || (
559 # ifdef ARFMAG
560 memcmp (member_header.ar_fmag, ARFMAG, 2)
561 # else
563 # endif
565 # ifdef ARFZMAG
566 memcmp (member_header.ar_fmag, ARFZMAG, 2)
567 # else
569 # endif
571 #endif
574 (void) close (desc);
575 return -2;
578 name = namebuf;
579 memcpy (name, member_header.ar_name, sizeof member_header.ar_name);
581 register char *p = name + sizeof member_header.ar_name;
583 *p = '\0';
584 while (p > name && *--p == ' ');
586 #ifndef AIAMAG
587 /* If the member name is "//" or "ARFILENAMES/" this may be
588 a list of file name mappings. The maximum file name
589 length supported by the standard archive format is 14
590 characters. This member will actually always be the
591 first or second entry in the archive, but we don't check
592 that. */
593 is_namemap = (!strcmp (name, "//")
594 || !strcmp (name, "ARFILENAMES/"));
595 #endif /* Not AIAMAG. */
596 /* On some systems, there is a slash after each member name. */
597 if (*p == '/')
598 *p = '\0';
600 #ifndef AIAMAG
601 /* If the member name starts with a space or a slash, this
602 is an index into the file name mappings (used by GNU ar).
603 Otherwise if the member name looks like #1/NUMBER the
604 real member name appears in the element data (used by
605 4.4BSD). */
606 if (! is_namemap
607 && (name[0] == ' ' || name[0] == '/')
608 && namemap != 0)
610 name = namemap + atoi (name + 1);
611 long_name = 1;
613 else if (name[0] == '#'
614 && name[1] == '1'
615 && name[2] == '/')
617 int namesize = atoi (name + 3);
619 name = alloca (namesize + 1);
620 nread = read (desc, name, namesize);
621 if (nread != namesize)
623 close (desc);
624 return -2;
626 name[namesize] = '\0';
628 long_name = 1;
630 #endif /* Not AIAMAG. */
633 #ifndef M_XENIX
634 sscanf (TOCHAR (member_header.ar_mode), "%o", &eltmode);
635 eltsize = atol (TOCHAR (member_header.ar_size));
636 #else /* Xenix. */
637 eltmode = (unsigned short int) member_header.ar_mode;
638 eltsize = member_header.ar_size;
639 #endif /* Not Xenix. */
641 fnval =
642 (*function) (desc, name, ! long_name, member_offset,
643 member_offset + AR_HDR_SIZE, eltsize,
644 #ifndef M_XENIX
645 atol (TOCHAR (member_header.ar_date)),
646 atoi (TOCHAR (member_header.ar_uid)),
647 atoi (TOCHAR (member_header.ar_gid)),
648 #else /* Xenix. */
649 member_header.ar_date,
650 member_header.ar_uid,
651 member_header.ar_gid,
652 #endif /* Not Xenix. */
653 eltmode, arg);
655 #endif /* AIAMAG. */
657 if (fnval)
659 (void) close (desc);
660 return fnval;
663 #ifdef AIAMAG
664 if (member_offset == last_member_offset)
665 /* End of the chain. */
666 break;
668 #ifdef AIAMAGBIG
669 if (big_archive)
670 sscanf (member_header_big.ar_nxtmem, "%20ld", &member_offset);
671 else
672 #endif
673 sscanf (member_header.ar_nxtmem, "%12ld", &member_offset);
675 if (lseek (desc, member_offset, 0) != member_offset)
677 (void) close (desc);
678 return -2;
680 #else
682 /* If this member maps archive names, we must read it in. The
683 name map will always precede any members whose names must
684 be mapped. */
685 if (is_namemap)
687 char *clear;
688 char *limit;
690 namemap = alloca (eltsize);
691 nread = read (desc, namemap, eltsize);
692 if (nread != eltsize)
694 (void) close (desc);
695 return -2;
698 /* The names are separated by newlines. Some formats have
699 a trailing slash. Null terminate the strings for
700 convenience. */
701 limit = namemap + eltsize;
702 for (clear = namemap; clear < limit; clear++)
704 if (*clear == '\n')
706 *clear = '\0';
707 if (clear[-1] == '/')
708 clear[-1] = '\0';
712 is_namemap = 0;
715 member_offset += AR_HDR_SIZE + eltsize;
716 if (member_offset % 2 != 0)
717 member_offset++;
718 #endif
722 close (desc);
723 return 0;
725 #endif /* !VMS */
727 /* Return nonzero iff NAME matches MEM.
728 If TRUNCATED is nonzero, MEM may be truncated to
729 sizeof (struct ar_hdr.ar_name) - 1. */
732 ar_name_equal (const char *name, const char *mem, int truncated)
734 const char *p;
736 p = strrchr (name, '/');
737 if (p != 0)
738 name = p + 1;
740 #ifndef VMS
741 if (truncated)
743 #ifdef AIAMAG
744 /* TRUNCATED should never be set on this system. */
745 abort ();
746 #else
747 struct ar_hdr hdr;
748 #if !defined (__hpux) && !defined (cray)
749 return strneq (name, mem, sizeof(hdr.ar_name) - 1);
750 #else
751 return strneq (name, mem, sizeof(hdr.ar_name) - 2);
752 #endif /* !__hpux && !cray */
753 #endif /* !AIAMAG */
755 #endif /* !VMS */
757 return !strcmp (name, mem);
760 #ifndef VMS
761 /* ARGSUSED */
762 static long int
763 ar_member_pos (int desc UNUSED, const char *mem, int truncated,
764 long int hdrpos, long int datapos UNUSED, long int size UNUSED,
765 long int date UNUSED, int uid UNUSED, int gid UNUSED,
766 int mode UNUSED, const void *name)
768 if (!ar_name_equal (name, mem, truncated))
769 return 0;
770 return hdrpos;
773 /* Set date of member MEMNAME in archive ARNAME to current time.
774 Returns 0 if successful,
775 -1 if file ARNAME does not exist,
776 -2 if not a valid archive,
777 -3 if other random system call error (including file read-only),
778 1 if valid but member MEMNAME does not exist. */
781 ar_member_touch (const char *arname, const char *memname)
783 long int pos = ar_scan (arname, ar_member_pos, memname);
784 int fd;
785 struct ar_hdr ar_hdr;
786 int i;
787 unsigned int ui;
788 struct stat statbuf;
790 if (pos < 0)
791 return (int) pos;
792 if (!pos)
793 return 1;
795 fd = open (arname, O_RDWR, 0666);
796 if (fd < 0)
797 return -3;
798 /* Read in this member's header */
799 if (lseek (fd, pos, 0) < 0)
800 goto lose;
801 if (AR_HDR_SIZE != read (fd, &ar_hdr, AR_HDR_SIZE))
802 goto lose;
803 /* Write back the header, thus touching the archive file. */
804 if (lseek (fd, pos, 0) < 0)
805 goto lose;
806 if (AR_HDR_SIZE != write (fd, &ar_hdr, AR_HDR_SIZE))
807 goto lose;
808 /* The file's mtime is the time we we want. */
809 EINTRLOOP (i, fstat (fd, &statbuf));
810 if (i < 0)
811 goto lose;
812 #if defined(ARFMAG) || defined(ARFZMAG) || defined(AIAMAG) || defined(WINDOWS32)
813 /* Advance member's time to that time */
814 for (ui = 0; ui < sizeof ar_hdr.ar_date; ui++)
815 ar_hdr.ar_date[ui] = ' ';
816 sprintf (TOCHAR (ar_hdr.ar_date), "%ld", (long int) statbuf.st_mtime);
817 #ifdef AIAMAG
818 ar_hdr.ar_date[strlen(ar_hdr.ar_date)] = ' ';
819 #endif
820 #else
821 ar_hdr.ar_date = statbuf.st_mtime;
822 #endif
823 /* Write back this member's header */
824 if (lseek (fd, pos, 0) < 0)
825 goto lose;
826 if (AR_HDR_SIZE != write (fd, &ar_hdr, AR_HDR_SIZE))
827 goto lose;
828 close (fd);
829 return 0;
831 lose:
832 i = errno;
833 close (fd);
834 errno = i;
835 return -3;
837 #endif
839 #ifdef TEST
841 long int
842 describe_member (int desc, const char *name, int truncated,
843 long int hdrpos, long int datapos, long int size,
844 long int date, int uid, int gid, int mode, const void *arg)
846 extern char *ctime ();
848 printf (_("Member '%s'%s: %ld bytes at %ld (%ld).\n"),
849 name, truncated ? _(" (name might be truncated)") : "",
850 size, hdrpos, datapos);
851 printf (_(" Date %s"), ctime (&date));
852 printf (_(" uid = %d, gid = %d, mode = 0%o.\n"), uid, gid, mode);
854 return 0;
858 main (int argc, char **argv)
860 ar_scan (argv[1], describe_member, NULL);
861 return 0;
864 #endif /* TEST. */
865 #endif /* NO_ARCHIVES. */