Add VMS enhancements from Hartmut Becker.
[make.git] / arscan.c
blob771e394ebccac5fbbf36c9453e76ff005c61be11
1 /* Library function for scanning an archive file.
2 Copyright (C) 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997,
3 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free
4 Software Foundation, Inc.
5 This file is part of GNU Make.
7 GNU Make is free software; you can redistribute it and/or modify it under the
8 terms of the GNU General Public License as published by the Free Software
9 Foundation; either version 3 of the License, or (at your option) any later
10 version.
12 GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License along with
17 this program. If not, see <http://www.gnu.org/licenses/>. */
19 #include "make.h"
21 #ifdef HAVE_FCNTL_H
22 #include <fcntl.h>
23 #else
24 #include <sys/file.h>
25 #endif
27 #ifndef NO_ARCHIVES
29 #ifdef VMS
30 #include <lbrdef.h>
31 #include <mhddef.h>
32 #include <credef.h>
33 #include <descrip.h>
34 #include <ctype.h>
35 #if __DECC
36 #include <unixlib.h>
37 #include <lbr$routines.h>
38 #endif
40 static void *VMS_lib_idx;
42 static char *VMS_saved_memname;
44 static time_t VMS_member_date;
46 static long int (*VMS_function) ();
48 static int
49 VMS_get_member_info (struct dsc$descriptor_s *module, unsigned long *rfa)
51 int status, i;
52 long int fnval;
54 time_t val;
56 static struct dsc$descriptor_s bufdesc =
57 { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };
59 struct mhddef *mhd;
60 char filename[128];
62 bufdesc.dsc$a_pointer = filename;
63 bufdesc.dsc$w_length = sizeof (filename);
65 status = lbr$set_module (&VMS_lib_idx, rfa, &bufdesc,
66 &bufdesc.dsc$w_length, 0);
67 if (! (status & 1))
69 error (NILF, _("lbr$set_module() failed to extract module info, status = %d"),
70 status);
72 lbr$close (&VMS_lib_idx);
74 return 0;
77 mhd = (struct mhddef *) filename;
79 #ifdef __DECC
80 /* John Fowler <jfowler@nyx.net> writes this is needed in his environment,
81 * but that decc$fix_time() isn't documented to work this way. Let me
82 * know if this causes problems in other VMS environments.
85 /* Modified by M. Gehre at 11-JAN-2008 because old formula is wrong:
86 * val = decc$fix_time (&mhd->mhd$l_datim) + timezone - daylight*3600;
87 * a) daylight specifies, if the timezone has daylight saving enabled, not
88 * if it is active
89 * b) what we need is the information, if daylight saving was active, if
90 * the library module was replaced. This information we get using the
91 * localtime function
94 struct tm *tmp;
96 /* Conversion from VMS time to C time */
97 val = decc$fix_time (&mhd->mhd$l_datim);
100 * Conversion from local time (stored in library) to GMT (needed for gmake)
101 * Note: The tm_gmtoff element is a VMS extension to the ANSI standard.
103 tmp = localtime (&val);
104 val -= tmp->tm_gmtoff;
106 #endif
108 for (i = 0; i < module->dsc$w_length; i++)
109 filename[i] = _tolower ((unsigned char)module->dsc$a_pointer[i]);
111 filename[i] = '\0';
113 VMS_member_date = (time_t) -1;
115 fnval =
116 (*VMS_function) (-1, filename, 0, 0, 0, 0, val, 0, 0, 0,
117 VMS_saved_memname);
119 if (fnval)
121 VMS_member_date = fnval;
122 return 0;
124 else
125 return 1;
128 /* Takes three arguments ARCHIVE, FUNCTION and ARG.
130 Open the archive named ARCHIVE, find its members one by one,
131 and for each one call FUNCTION with the following arguments:
132 archive file descriptor for reading the data,
133 member name,
134 member name might be truncated flag,
135 member header position in file,
136 member data position in file,
137 member data size,
138 member date,
139 member uid,
140 member gid,
141 member protection mode,
142 ARG.
144 NOTE: on VMS systems, only name, date, and arg are meaningful!
146 The descriptor is poised to read the data of the member
147 when FUNCTION is called. It does not matter how much
148 data FUNCTION reads.
150 If FUNCTION returns nonzero, we immediately return
151 what FUNCTION returned.
153 Returns -1 if archive does not exist,
154 Returns -2 if archive has invalid format.
155 Returns 0 if have scanned successfully. */
157 long int
158 ar_scan (const char *archive, ar_member_func_t function, const void *arg)
160 char *p;
162 static struct dsc$descriptor_s libdesc =
163 { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };
165 unsigned long func = LBR$C_READ;
166 unsigned long type = LBR$C_TYP_UNK;
167 unsigned long index = 1;
169 int status;
171 status = lbr$ini_control (&VMS_lib_idx, &func, &type, 0);
173 if (! (status & 1))
175 error (NILF, _("lbr$ini_control() failed with status = %d"), status);
176 return -2;
179 /* there is no such descriptor with "const char *dsc$a_pointer" */
180 libdesc.dsc$a_pointer = (char *)archive;
181 libdesc.dsc$w_length = strlen (archive);
183 status = lbr$open (&VMS_lib_idx, &libdesc, 0, 0, 0, 0, 0);
185 if (! (status & 1))
187 error (NILF, _("unable to open library `%s' to lookup member `%s'"),
188 archive, (char *)arg);
189 return -1;
192 VMS_saved_memname = (char *)arg;
194 /* For comparison, delete .obj from arg name. */
196 p = strrchr (VMS_saved_memname, '.');
197 if (p)
198 *p = '\0';
200 VMS_function = function;
202 VMS_member_date = (time_t) -1;
203 lbr$get_index (&VMS_lib_idx, &index, VMS_get_member_info, 0);
205 /* Undo the damage. */
206 if (p)
207 *p = '.';
209 lbr$close (&VMS_lib_idx);
211 return VMS_member_date > 0 ? VMS_member_date : 0;
214 #else /* !VMS */
216 /* SCO Unix's compiler defines both of these. */
217 #ifdef M_UNIX
218 #undef M_XENIX
219 #endif
221 /* On the sun386i and in System V rel 3, ar.h defines two different archive
222 formats depending upon whether you have defined PORTAR (normal) or PORT5AR
223 (System V Release 1). There is no default, one or the other must be defined
224 to have a nonzero value. */
226 #if (!defined (PORTAR) || PORTAR == 0) && (!defined (PORT5AR) || PORT5AR == 0)
227 #undef PORTAR
228 #ifdef M_XENIX
229 /* According to Jim Sievert <jas1@rsvl.unisys.com>, for SCO XENIX defining
230 PORTAR to 1 gets the wrong archive format, and defining it to 0 gets the
231 right one. */
232 #define PORTAR 0
233 #else
234 #define PORTAR 1
235 #endif
236 #endif
238 /* On AIX, define these symbols to be sure to get both archive formats.
239 AIX 4.3 introduced the "big" archive format to support 64-bit object
240 files, so on AIX 4.3 systems we need to support both the "normal" and
241 "big" archive formats. An archive's format is indicated in the
242 "fl_magic" field of the "FL_HDR" structure. For a normal archive,
243 this field will be the string defined by the AIAMAG symbol. For a
244 "big" archive, it will be the string defined by the AIAMAGBIG symbol
245 (at least on AIX it works this way).
247 Note: we'll define these symbols regardless of which AIX version
248 we're compiling on, but this is okay since we'll use the new symbols
249 only if they're present. */
250 #ifdef _AIX
251 # define __AR_SMALL__
252 # define __AR_BIG__
253 #endif
255 #ifndef WINDOWS32
256 # ifndef __BEOS__
257 # include <ar.h>
258 # else
259 /* BeOS 5 doesn't have <ar.h> but has archives in the same format
260 * as many other Unices. This was taken from GNU binutils for BeOS.
262 # define ARMAG "!<arch>\n" /* String that begins an archive file. */
263 # define SARMAG 8 /* Size of that string. */
264 # define ARFMAG "`\n" /* String in ar_fmag at end of each header. */
265 struct ar_hdr
267 char ar_name[16]; /* Member file name, sometimes / terminated. */
268 char ar_date[12]; /* File date, decimal seconds since Epoch. */
269 char ar_uid[6], ar_gid[6]; /* User and group IDs, in ASCII decimal. */
270 char ar_mode[8]; /* File mode, in ASCII octal. */
271 char ar_size[10]; /* File size, in ASCII decimal. */
272 char ar_fmag[2]; /* Always contains ARFMAG. */
274 # endif
275 #else
276 /* These should allow us to read Windows (VC++) libraries (according to Frank
277 * Libbrecht <frankl@abzx.belgium.hp.com>)
279 # include <windows.h>
280 # include <windef.h>
281 # include <io.h>
282 # define ARMAG IMAGE_ARCHIVE_START
283 # define SARMAG IMAGE_ARCHIVE_START_SIZE
284 # define ar_hdr _IMAGE_ARCHIVE_MEMBER_HEADER
285 # define ar_name Name
286 # define ar_mode Mode
287 # define ar_size Size
288 # define ar_date Date
289 # define ar_uid UserID
290 # define ar_gid GroupID
291 #endif
293 /* Cray's <ar.h> apparently defines this. */
294 #ifndef AR_HDR_SIZE
295 # define AR_HDR_SIZE (sizeof (struct ar_hdr))
296 #endif
298 /* Takes three arguments ARCHIVE, FUNCTION and ARG.
300 Open the archive named ARCHIVE, find its members one by one,
301 and for each one call FUNCTION with the following arguments:
302 archive file descriptor for reading the data,
303 member name,
304 member name might be truncated flag,
305 member header position in file,
306 member data position in file,
307 member data size,
308 member date,
309 member uid,
310 member gid,
311 member protection mode,
312 ARG.
314 The descriptor is poised to read the data of the member
315 when FUNCTION is called. It does not matter how much
316 data FUNCTION reads.
318 If FUNCTION returns nonzero, we immediately return
319 what FUNCTION returned.
321 Returns -1 if archive does not exist,
322 Returns -2 if archive has invalid format.
323 Returns 0 if have scanned successfully. */
325 long int
326 ar_scan (const char *archive, ar_member_func_t function, const void *arg)
328 #ifdef AIAMAG
329 FL_HDR fl_header;
330 #ifdef AIAMAGBIG
331 int big_archive = 0;
332 FL_HDR_BIG fl_header_big;
333 #endif
334 #else
335 int long_name = 0;
336 #endif
337 char *namemap = 0;
338 int desc = open (archive, O_RDONLY, 0);
339 if (desc < 0)
340 return -1;
341 #ifdef SARMAG
343 char buf[SARMAG];
344 register int nread = read (desc, buf, SARMAG);
345 if (nread != SARMAG || memcmp (buf, ARMAG, SARMAG))
347 (void) close (desc);
348 return -2;
351 #else
352 #ifdef AIAMAG
354 register int nread = read (desc, &fl_header, FL_HSZ);
356 if (nread != FL_HSZ)
358 (void) close (desc);
359 return -2;
361 #ifdef AIAMAGBIG
362 /* If this is a "big" archive, then set the flag and
363 re-read the header into the "big" structure. */
364 if (!memcmp (fl_header.fl_magic, AIAMAGBIG, SAIAMAG))
366 big_archive = 1;
368 /* seek back to beginning of archive */
369 if (lseek (desc, 0, 0) < 0)
371 (void) close (desc);
372 return -2;
375 /* re-read the header into the "big" structure */
376 nread = read (desc, &fl_header_big, FL_HSZ_BIG);
377 if (nread != FL_HSZ_BIG)
379 (void) close (desc);
380 return -2;
383 else
384 #endif
385 /* Check to make sure this is a "normal" archive. */
386 if (memcmp (fl_header.fl_magic, AIAMAG, SAIAMAG))
388 (void) close (desc);
389 return -2;
392 #else
394 #ifndef M_XENIX
395 int buf;
396 #else
397 unsigned short int buf;
398 #endif
399 register int nread = read(desc, &buf, sizeof (buf));
400 if (nread != sizeof (buf) || buf != ARMAG)
402 (void) close (desc);
403 return -2;
406 #endif
407 #endif
409 /* Now find the members one by one. */
411 #ifdef SARMAG
412 register long int member_offset = SARMAG;
413 #else
414 #ifdef AIAMAG
415 long int member_offset;
416 long int last_member_offset;
418 #ifdef AIAMAGBIG
419 if ( big_archive )
421 sscanf (fl_header_big.fl_fstmoff, "%20ld", &member_offset);
422 sscanf (fl_header_big.fl_lstmoff, "%20ld", &last_member_offset);
424 else
425 #endif
427 sscanf (fl_header.fl_fstmoff, "%12ld", &member_offset);
428 sscanf (fl_header.fl_lstmoff, "%12ld", &last_member_offset);
431 if (member_offset == 0)
433 /* Empty archive. */
434 close (desc);
435 return 0;
437 #else
438 #ifndef M_XENIX
439 register long int member_offset = sizeof (int);
440 #else /* Xenix. */
441 register long int member_offset = sizeof (unsigned short int);
442 #endif /* Not Xenix. */
443 #endif
444 #endif
446 while (1)
448 register int nread;
449 struct ar_hdr member_header;
450 #ifdef AIAMAGBIG
451 struct ar_hdr_big member_header_big;
452 #endif
453 #ifdef AIAMAG
454 char name[256];
455 int name_len;
456 long int dateval;
457 int uidval, gidval;
458 long int data_offset;
459 #else
460 char namebuf[sizeof member_header.ar_name + 1];
461 char *name;
462 int is_namemap; /* Nonzero if this entry maps long names. */
463 #endif
464 long int eltsize;
465 int eltmode;
466 long int fnval;
468 if (lseek (desc, member_offset, 0) < 0)
470 (void) close (desc);
471 return -2;
474 #ifdef AIAMAG
475 #define AR_MEMHDR_SZ(x) (sizeof(x) - sizeof (x._ar_name))
477 #ifdef AIAMAGBIG
478 if (big_archive)
480 nread = read (desc, &member_header_big,
481 AR_MEMHDR_SZ(member_header_big) );
483 if (nread != AR_MEMHDR_SZ(member_header_big))
485 (void) close (desc);
486 return -2;
489 sscanf (member_header_big.ar_namlen, "%4d", &name_len);
490 nread = read (desc, name, name_len);
492 if (nread != name_len)
494 (void) close (desc);
495 return -2;
498 name[name_len] = 0;
500 sscanf (member_header_big.ar_date, "%12ld", &dateval);
501 sscanf (member_header_big.ar_uid, "%12d", &uidval);
502 sscanf (member_header_big.ar_gid, "%12d", &gidval);
503 sscanf (member_header_big.ar_mode, "%12o", &eltmode);
504 sscanf (member_header_big.ar_size, "%20ld", &eltsize);
506 data_offset = (member_offset + AR_MEMHDR_SZ(member_header_big)
507 + name_len + 2);
509 else
510 #endif
512 nread = read (desc, &member_header,
513 AR_MEMHDR_SZ(member_header) );
515 if (nread != AR_MEMHDR_SZ(member_header))
517 (void) close (desc);
518 return -2;
521 sscanf (member_header.ar_namlen, "%4d", &name_len);
522 nread = read (desc, name, name_len);
524 if (nread != name_len)
526 (void) close (desc);
527 return -2;
530 name[name_len] = 0;
532 sscanf (member_header.ar_date, "%12ld", &dateval);
533 sscanf (member_header.ar_uid, "%12d", &uidval);
534 sscanf (member_header.ar_gid, "%12d", &gidval);
535 sscanf (member_header.ar_mode, "%12o", &eltmode);
536 sscanf (member_header.ar_size, "%12ld", &eltsize);
538 data_offset = (member_offset + AR_MEMHDR_SZ(member_header)
539 + name_len + 2);
541 data_offset += data_offset % 2;
543 fnval =
544 (*function) (desc, name, 0,
545 member_offset, data_offset, eltsize,
546 dateval, uidval, gidval,
547 eltmode, arg);
549 #else /* Not AIAMAG. */
550 nread = read (desc, &member_header, AR_HDR_SIZE);
551 if (nread == 0)
552 /* No data left means end of file; that is OK. */
553 break;
555 if (nread != AR_HDR_SIZE
556 #if defined(ARFMAG) || defined(ARFZMAG)
557 || (
558 # ifdef ARFMAG
559 memcmp (member_header.ar_fmag, ARFMAG, 2)
560 # else
562 # endif
564 # ifdef ARFZMAG
565 memcmp (member_header.ar_fmag, ARFZMAG, 2)
566 # else
568 # endif
570 #endif
573 (void) close (desc);
574 return -2;
577 name = namebuf;
578 memcpy (name, member_header.ar_name, sizeof member_header.ar_name);
580 register char *p = name + sizeof member_header.ar_name;
582 *p = '\0';
583 while (p > name && *--p == ' ');
585 #ifndef AIAMAG
586 /* If the member name is "//" or "ARFILENAMES/" this may be
587 a list of file name mappings. The maximum file name
588 length supported by the standard archive format is 14
589 characters. This member will actually always be the
590 first or second entry in the archive, but we don't check
591 that. */
592 is_namemap = (!strcmp (name, "//")
593 || !strcmp (name, "ARFILENAMES/"));
594 #endif /* Not AIAMAG. */
595 /* On some systems, there is a slash after each member name. */
596 if (*p == '/')
597 *p = '\0';
599 #ifndef AIAMAG
600 /* If the member name starts with a space or a slash, this
601 is an index into the file name mappings (used by GNU ar).
602 Otherwise if the member name looks like #1/NUMBER the
603 real member name appears in the element data (used by
604 4.4BSD). */
605 if (! is_namemap
606 && (name[0] == ' ' || name[0] == '/')
607 && namemap != 0)
609 name = namemap + atoi (name + 1);
610 long_name = 1;
612 else if (name[0] == '#'
613 && name[1] == '1'
614 && name[2] == '/')
616 int namesize = atoi (name + 3);
618 name = alloca (namesize + 1);
619 nread = read (desc, name, namesize);
620 if (nread != namesize)
622 close (desc);
623 return -2;
625 name[namesize] = '\0';
627 long_name = 1;
629 #endif /* Not AIAMAG. */
632 #ifndef M_XENIX
633 sscanf (member_header.ar_mode, "%o", &eltmode);
634 eltsize = atol (member_header.ar_size);
635 #else /* Xenix. */
636 eltmode = (unsigned short int) member_header.ar_mode;
637 eltsize = member_header.ar_size;
638 #endif /* Not Xenix. */
640 fnval =
641 (*function) (desc, name, ! long_name, member_offset,
642 member_offset + AR_HDR_SIZE, eltsize,
643 #ifndef M_XENIX
644 atol (member_header.ar_date),
645 atoi (member_header.ar_uid),
646 atoi (member_header.ar_gid),
647 #else /* Xenix. */
648 member_header.ar_date,
649 member_header.ar_uid,
650 member_header.ar_gid,
651 #endif /* Not Xenix. */
652 eltmode, arg);
654 #endif /* AIAMAG. */
656 if (fnval)
658 (void) close (desc);
659 return fnval;
662 #ifdef AIAMAG
663 if (member_offset == last_member_offset)
664 /* End of the chain. */
665 break;
667 #ifdef AIAMAGBIG
668 if (big_archive)
669 sscanf (member_header_big.ar_nxtmem, "%20ld", &member_offset);
670 else
671 #endif
672 sscanf (member_header.ar_nxtmem, "%12ld", &member_offset);
674 if (lseek (desc, member_offset, 0) != member_offset)
676 (void) close (desc);
677 return -2;
679 #else
681 /* If this member maps archive names, we must read it in. The
682 name map will always precede any members whose names must
683 be mapped. */
684 if (is_namemap)
686 char *clear;
687 char *limit;
689 namemap = alloca (eltsize);
690 nread = read (desc, namemap, eltsize);
691 if (nread != eltsize)
693 (void) close (desc);
694 return -2;
697 /* The names are separated by newlines. Some formats have
698 a trailing slash. Null terminate the strings for
699 convenience. */
700 limit = namemap + eltsize;
701 for (clear = namemap; clear < limit; clear++)
703 if (*clear == '\n')
705 *clear = '\0';
706 if (clear[-1] == '/')
707 clear[-1] = '\0';
711 is_namemap = 0;
714 member_offset += AR_HDR_SIZE + eltsize;
715 if (member_offset % 2 != 0)
716 member_offset++;
717 #endif
721 close (desc);
722 return 0;
724 #endif /* !VMS */
726 /* Return nonzero iff NAME matches MEM.
727 If TRUNCATED is nonzero, MEM may be truncated to
728 sizeof (struct ar_hdr.ar_name) - 1. */
731 ar_name_equal (const char *name, const char *mem, int truncated)
733 const char *p;
735 p = strrchr (name, '/');
736 if (p != 0)
737 name = p + 1;
739 #ifndef VMS
740 if (truncated)
742 #ifdef AIAMAG
743 /* TRUNCATED should never be set on this system. */
744 abort ();
745 #else
746 struct ar_hdr hdr;
747 #if !defined (__hpux) && !defined (cray)
748 return strneq (name, mem, sizeof(hdr.ar_name) - 1);
749 #else
750 return strneq (name, mem, sizeof(hdr.ar_name) - 2);
751 #endif /* !__hpux && !cray */
752 #endif /* !AIAMAG */
754 #endif /* !VMS */
756 return !strcmp (name, mem);
759 #ifndef VMS
760 /* ARGSUSED */
761 static long int
762 ar_member_pos (int desc UNUSED, const char *mem, int truncated,
763 long int hdrpos, long int datapos UNUSED, long int size UNUSED,
764 long int date UNUSED, int uid UNUSED, int gid UNUSED,
765 int mode UNUSED, const void *name)
767 if (!ar_name_equal (name, mem, truncated))
768 return 0;
769 return hdrpos;
772 /* Set date of member MEMNAME in archive ARNAME to current time.
773 Returns 0 if successful,
774 -1 if file ARNAME does not exist,
775 -2 if not a valid archive,
776 -3 if other random system call error (including file read-only),
777 1 if valid but member MEMNAME does not exist. */
780 ar_member_touch (const char *arname, const char *memname)
782 long int pos = ar_scan (arname, ar_member_pos, memname);
783 int fd;
784 struct ar_hdr ar_hdr;
785 int i;
786 unsigned int ui;
787 struct stat statbuf;
789 if (pos < 0)
790 return (int) pos;
791 if (!pos)
792 return 1;
794 fd = open (arname, O_RDWR, 0666);
795 if (fd < 0)
796 return -3;
797 /* Read in this member's header */
798 if (lseek (fd, pos, 0) < 0)
799 goto lose;
800 if (AR_HDR_SIZE != read (fd, &ar_hdr, AR_HDR_SIZE))
801 goto lose;
802 /* Write back the header, thus touching the archive file. */
803 if (lseek (fd, pos, 0) < 0)
804 goto lose;
805 if (AR_HDR_SIZE != write (fd, &ar_hdr, AR_HDR_SIZE))
806 goto lose;
807 /* The file's mtime is the time we we want. */
808 EINTRLOOP (i, fstat (fd, &statbuf));
809 if (i < 0)
810 goto lose;
811 #if defined(ARFMAG) || defined(ARFZMAG) || defined(AIAMAG) || defined(WINDOWS32)
812 /* Advance member's time to that time */
813 for (ui = 0; ui < sizeof ar_hdr.ar_date; ui++)
814 ar_hdr.ar_date[ui] = ' ';
815 sprintf (ar_hdr.ar_date, "%ld", (long int) statbuf.st_mtime);
816 #ifdef AIAMAG
817 ar_hdr.ar_date[strlen(ar_hdr.ar_date)] = ' ';
818 #endif
819 #else
820 ar_hdr.ar_date = statbuf.st_mtime;
821 #endif
822 /* Write back this member's header */
823 if (lseek (fd, pos, 0) < 0)
824 goto lose;
825 if (AR_HDR_SIZE != write (fd, &ar_hdr, AR_HDR_SIZE))
826 goto lose;
827 close (fd);
828 return 0;
830 lose:
831 i = errno;
832 close (fd);
833 errno = i;
834 return -3;
836 #endif
838 #ifdef TEST
840 long int
841 describe_member (int desc, const char *name, int truncated,
842 long int hdrpos, long int datapos, long int size,
843 long int date, int uid, int gid, int mode, const void *arg)
845 extern char *ctime ();
847 printf (_("Member `%s'%s: %ld bytes at %ld (%ld).\n"),
848 name, truncated ? _(" (name might be truncated)") : "",
849 size, hdrpos, datapos);
850 printf (_(" Date %s"), ctime (&date));
851 printf (_(" uid = %d, gid = %d, mode = 0%o.\n"), uid, gid, mode);
853 return 0;
857 main (int argc, char **argv)
859 ar_scan (argv[1], describe_member, NULL);
860 return 0;
863 #endif /* TEST. */
864 #endif /* NO_ARCHIVES. */