I'm working on a problem to create my own C version of unix ar. Right now I'm trying to create the header and write it to the file.
Initially, I used a stat struct and printed the contents using an fprintf. However, when I try to use the ar command on a file printed with such a method, it does not work. I was told that the better solution would be to use an fwrite to write the struct directly to the file. So I'm now trying to implement that.
I've tried to populate then fwrite my ar_hdr struct with the stat info, however when I write to the file, I get garbage.
Update 2: using just an fprintf based upon @fvu's recommendations. Last item on fprintf is the constant ARFMAG from ar.h which I believe is the same as printing the two characters.
void header(char *archive, char *read_file){
struct stat sb;
FILE *fp;
if (stat(read_file, &sb) == -1)
fail('s');
fp = fopen(archive, "a");
if (!fp)
fail('f');
fprintf(fp, "%-16s%-12ld%-6ld%-6ld%-8d%-10lld%s", read_file, (long)sb.st_mtimespec.tv_sec,
(long)sb.st_uid, (long)sb.st_gid, sb.st_mode, (long long)sb.st_size, ARFMAG);
fclose(fp);
}
The test output of my program is now something like this:
!<arch>
b.txt 1359332639 502 20 33188 28 `
Appending B. shortb long b
d.txt 1359332655 502 20 33188 28 `
Appending D. shortb long b
c.txt 1359332646 502 20 33188 17 `
COpy this.
When I try the unix command: ar -tv myfile.a
result is: Inappropriate file type or format
If I use nano to look at test.a this is the result
!<arch>
^@b.txt 1359332639 502 20 100644 28 `
Appending B. shortb long b
d.txt 1359332655 502 20 100644 28 `
Appending D. shortb long b
c.txt 1359332646 502 20 100644 17 `
COpy this shit.
There's a weird shift@ symbol before the first header. Here's my code to write the overall file header, any hints would be appreciated.
char title[] = ARMAG; //constant defined in ar.h
//open or create the output file
sf = open(argv[2], O_WRONLY | O_CREAT | O_APPEND, perms);
if (sf == -1)
fail('o'); //open fail
title_num = write(sf, title, sizeof(title));
Adding results from od -x file | head -n 2:
0000000 3c21 7261 6863 0a3e 2e62 7874 2074 2020
0000020 2020 2020 2020 2020 3331 3935 3333 3632
Not the cause of your error (see below) but very odd nevertheless
struct ar_hdr d;
struct ar_hdr* bob = &d;
...
bob = malloc(sizeof(struct ar_hdr));
...
fwrite(bob, sizeof(d),1, fp);
is quite convoluted, I'd just drop everything into d and that's it.
Now, as your ar_hdr looks like this:
struct ar_hdr /* file member header */
{
char ar_name[16]; /* '/' terminated file member name */
char ar_date[12]; /* file member date */
char ar_uid[6] /* file member user identification */
char ar_gid[6] /* file member group identification */
char ar_mode[8] /* file member mode (octal) */
char ar_size[10]; /* file member size */
char ar_fmag[2]; /* header trailer string */
};
The Wikipedia article mentions that a textual header is the most common format being used:
Offset Length Name Format
0 16 File name ASCII
16 12 File modification timestamp Decimal
28 6 Owner ID Decimal
34 6 Group ID Decimal
40 8 File mode Octal
48 10 File size in bytes Decimal
58 2 File magic 0x60 0x0A
which means that fwrite'ing the struct directly to the file is not what you need to do: C strings as produced by individual sprintf's are null terminated, and what's behind the null (up to the length of the array) is garbage. The file format wants space characters instead of that garbage, and also no null's....
Actually I think your initial fprintf was quite close:
fprintf(fp, "%-16s%-12ld%-6ld%-6ld%-8o%-10lld%c%c",
fname, (long)sb.st_mtimespec.tv_sec,
(long)sb.st_uid, (long)sb.st_gid, sb.st_mode, (long long)sb.st_size,0x60,0xa);
As I'm unable to quickly test that here, there may still be some minor issues but it should be pretty close.
Addendum: for this kind of work a tool to look at the file content byte per byte (like od that comes with all linux distros) and a tool to bytewise compare two files (like dhex) are really a must, because just testing what ar is willing to open is just too hard and time-consuming.