Use standard POSIX "ustar" format for the scratch disk.
[pintos-anon] / src / lib / ustar.c
1 #include <ustar.h>
2 #include <limits.h>
3 #include <packed.h>
4 #include <stdio.h>
5 #include <string.h>
6
7 /* Header for ustar-format tar archive.  See the documentation of
8    the "pax" utility in [SUSv3] for the the "ustar" format
9    specification. */
10 struct ustar_header
11   {
12     char name[100];             /* File name.  Null-terminated if room. */
13     char mode[8];               /* Permissions as octal string. */
14     char uid[8];                /* User ID as octal string. */
15     char gid[8];                /* Group ID as octal string. */
16     char size[12];              /* File size in bytes as octal string. */
17     char mtime[12];             /* Modification time in seconds
18                                    from Jan 1, 1970, as octal string. */
19     char chksum[8];             /* Sum of octets in header as octal string. */
20     char typeflag;              /* An enum ustar_type value. */
21     char linkname[100];         /* Name of link target.
22                                    Null-terminated if room. */
23     char magic[6];              /* "ustar\0" */
24     char version[2];            /* "00" */
25     char uname[32];             /* User name, always null-terminated. */
26     char gname[32];             /* Group name, always null-terminated. */
27     char devmajor[8];           /* Device major number as octal string. */
28     char devminor[8];           /* Device minor number as octal string. */
29     char prefix[155];           /* Prefix to file name.
30                                    Null-terminated if room. */
31     char padding[12];           /* Pad to 512 bytes. */
32   }
33 PACKED;
34
35 /* Returns the checksum for the given ustar format HEADER. */
36 static unsigned int
37 calculate_chksum (const struct ustar_header *h)
38 {
39   const uint8_t *header = (const uint8_t *) h;
40   unsigned int chksum;
41   size_t i;
42
43   chksum = 0;
44   for (i = 0; i < USTAR_HEADER_SIZE; i++)
45     {
46       /* The ustar checksum is calculated as if the chksum field
47          were all spaces. */
48       const size_t chksum_start = offsetof (struct ustar_header, chksum);
49       const size_t chksum_end = chksum_start + sizeof h->chksum;
50       bool in_chksum_field = i >= chksum_start && i < chksum_end;
51       chksum += in_chksum_field ? ' ' : header[i];
52     }
53   return chksum;
54 }
55
56 /* Drop possibly dangerous prefixes from FILE_NAME and return the
57    stripped name.  An archive with file names that start with "/"
58    or "../" could cause a naive tar extractor to write to
59    arbitrary parts of the file system, not just the destination
60    directory.  We don't want to create such archives or be such a
61    naive extractor.
62
63    The return value can be a suffix of FILE_NAME or a string
64    literal. */
65 static const char *
66 strip_antisocial_prefixes (const char *file_name)
67 {
68   while (*file_name == '/'
69          || !memcmp (file_name, "./", 2)
70          || !memcmp (file_name, "../", 3))
71     file_name = strchr (file_name, '/') + 1;
72   return *file_name == '\0' || !strcmp (file_name, "..") ? "." : file_name;
73 }
74
75 /* Composes HEADER as a USTAR_HEADER_SIZE (512)-byte archive
76    header in ustar format for a SIZE-byte file named FILE_NAME of
77    the given TYPE.  The caller is responsible for writing the
78    header to a file or device.
79
80    If successful, returns true.  On failure (due to an
81    excessively long file name), returns false. */
82 bool
83 ustar_make_header (const char *file_name, enum ustar_type type,
84                    int size, char header[USTAR_HEADER_SIZE])
85 {
86   struct ustar_header *h = (struct ustar_header *) header;
87
88   ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE);
89   ASSERT (type == USTAR_REGULAR || type == USTAR_DIRECTORY);
90
91   /* Check file name. */
92   file_name = strip_antisocial_prefixes (file_name);
93   if (strlen (file_name) > 99)
94     {
95       printf ("%s: file name too long\n", file_name);
96       return false;
97     }
98
99   /* Fill in header except for final checksum. */
100   memset (h, 0, sizeof *h);
101   strlcpy (h->name, file_name, sizeof h->name);
102   snprintf (h->mode, sizeof h->mode, "%07o",
103             type == USTAR_REGULAR ? 0644 : 0755);
104   strlcpy (h->uid, "0000000", sizeof h->uid);
105   strlcpy (h->gid, "0000000", sizeof h->gid);
106   snprintf (h->size, sizeof h->size, "%011o", size);
107   snprintf (h->mtime, sizeof h->size, "%011o", 1136102400);
108   h->typeflag = type;
109   strlcpy (h->magic, "ustar", sizeof h->magic);
110   h->version[0] = h->version[1] = '0';
111   strlcpy (h->gname, "root", sizeof h->gname);
112   strlcpy (h->uname, "root", sizeof h->uname);
113
114   /* Compute and fill in final checksum. */
115   snprintf (h->chksum, sizeof h->chksum, "%07o", calculate_chksum (h));
116
117   return true;
118 }
119
120 /* Parses a SIZE-byte octal field in S in the format used by
121    ustar format.  If successful, stores the field's value in
122    *VALUE and returns true; on failure, returns false.
123
124    ustar octal fields consist of a sequence of octal digits
125    terminated by a space or a null byte.  The ustar specification
126    seems ambiguous as to whether these fields must be padded on
127    the left with '0's, so we accept any field that fits in the
128    available space, regardless of whether it fills the space. */
129 static bool
130 parse_octal_field (const char *s, size_t size, unsigned long int *value)
131 {
132   size_t ofs;
133
134   *value = 0;
135   for (ofs = 0; ofs < size; ofs++)
136     {
137       char c = s[ofs];
138       if (c >= '0' && c <= '7')
139         {
140           if (*value > ULONG_MAX / 8)
141             {
142               /* Overflow. */
143               return false;
144             }
145           *value = c - '0' + *value * 8;
146         }
147       else if (c == ' ' || c == '\0')
148         {
149           /* End of field, but disallow completely empty
150              fields. */
151           return ofs > 0;
152         }
153       else
154         {
155           /* Bad character. */
156           return false;
157         }
158     }
159
160   /* Field did not end in space or null byte. */
161   return false;
162 }
163
164 /* Returns true if the CNT bytes starting at BLOCK are all zero,
165    false otherwise. */
166 static bool
167 is_all_zeros (const char *block, size_t cnt)
168 {
169   while (cnt-- > 0)
170     if (*block++ != 0)
171       return false;
172   return true;
173 }
174
175 /* Parses HEADER as a ustar-format archive header for a regular
176    file or directory.  If successful, stores the archived file's
177    name in *FILE_NAME (as a pointer into HEADER or a string
178    literal), its type in *TYPE, and its size in bytes in *SIZE,
179    and returns a null pointer.  On failure, returns a
180    human-readable error message. */
181 const char *
182 ustar_parse_header (const char header[USTAR_HEADER_SIZE],
183                     const char **file_name, enum ustar_type *type, int *size)
184 {
185   const struct ustar_header *h = (const struct ustar_header *) header;
186   unsigned long int chksum, size_ul;
187
188   ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE);
189
190   /* Detect end of archive. */
191   if (is_all_zeros (header, USTAR_HEADER_SIZE))
192     {
193       *file_name = NULL;
194       *type = USTAR_EOF;
195       *size = 0;
196       return NULL;
197     }
198
199   /* Validate ustar header. */
200   if (memcmp (h->magic, "ustar", 6))
201     return "not a ustar archive";
202   else if (h->version[0] != '0' || h->version[1] != '0')
203     return "invalid ustar version";
204   else if (!parse_octal_field (h->chksum, sizeof h->chksum, &chksum))
205     return "corrupt chksum field";
206   else if (chksum != calculate_chksum (h))
207     return "checksum mismatch";
208   else if (h->name[sizeof h->name - 1] != '\0' || h->prefix[0] != '\0')
209     return "file name too long";
210   else if (h->typeflag != USTAR_REGULAR && h->typeflag != USTAR_DIRECTORY)
211     return "unimplemented file type";
212   if (h->typeflag == USTAR_REGULAR)
213     {
214       if (!parse_octal_field (h->size, sizeof h->size, &size_ul))
215         return "corrupt file size field";
216       else if (size_ul > INT_MAX)
217         return "file too large";
218     }
219   else
220     size_ul = 0;
221
222   /* Success. */
223   *file_name = strip_antisocial_prefixes (h->name);
224   *type = h->typeflag;
225   *size = size_ul;
226   return NULL;
227 }
228