f9ffc3fc9b820428f3b603a0235f7902b0beda72
[febootstrap.git] / helper / cpio.c
1 /* febootstrap-supermin-helper reimplementation in C.
2  * Copyright (C) 2009-2010 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <limits.h>
26 #include <fcntl.h>
27 #include <errno.h>
28 #include <sys/stat.h>
29 #include <assert.h>
30
31 #include "error.h"
32 #include "fts_.h"
33 #include "full-write.h"
34 #include "xalloc.h"
35 #include "xvasprintf.h"
36
37 #include "helper.h"
38
39 /* Buffer size used in copy operations throughout.  Large for
40  * greatest efficiency.
41  */
42 #define BUFFER_SIZE 65536
43
44 static int out_fd = -1;
45 static off_t out_offset = 0;
46
47 static void write_file_to_fd (const char *filename);
48 static void write_file_len_to_fd (const char *filename, size_t len);
49 static void write_padding (size_t len);
50 static void cpio_append_fts_entry (FTSENT *entry);
51 static void cpio_append_stat (const char *filename, const struct stat *);
52 static void cpio_append (const char *filename);
53 static void cpio_append_trailer (void);
54
55 /* Copy contents of buffer to out_fd and keep out_offset correct. */
56 static void
57 write_to_fd (const void *buffer, size_t len)
58 {
59   if (full_write (out_fd, buffer, len) != len)
60     error (EXIT_FAILURE, errno, "write");
61   out_offset += len;
62 }
63
64 /* Copy contents of file to out_fd. */
65 static void
66 write_file_to_fd (const char *filename)
67 {
68   char buffer[BUFFER_SIZE];
69   int fd2;
70   ssize_t r;
71
72   if (verbose >= 2)
73     fprintf (stderr, "write_file_to_fd %s -> %d\n", filename, out_fd);
74
75   fd2 = open (filename, O_RDONLY);
76   if (fd2 == -1)
77     error (EXIT_FAILURE, errno, "open: %s", filename);
78   for (;;) {
79     r = read (fd2, buffer, sizeof buffer);
80     if (r == 0)
81       break;
82     if (r == -1) {
83       if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
84         continue;
85       error (EXIT_FAILURE, errno, "read: %s", filename);
86     }
87     write_to_fd (buffer, r);
88   }
89
90   if (close (fd2) == -1)
91     error (EXIT_FAILURE, errno, "close: %s", filename);
92 }
93
94 /* Copy file of given length to output, and fail if the file has
95  * changed size.
96  */
97 static void
98 write_file_len_to_fd (const char *filename, size_t len)
99 {
100   char buffer[BUFFER_SIZE];
101   size_t count = 0;
102
103   if (verbose >= 2)
104     fprintf (stderr, "write_file_to_fd %s -> %d\n", filename, out_fd);
105
106   int fd2 = open (filename, O_RDONLY);
107   if (fd2 == -1)
108     error (EXIT_FAILURE, errno, "open: %s", filename);
109   for (;;) {
110     ssize_t r = read (fd2, buffer, sizeof buffer);
111     if (r == 0)
112       break;
113     if (r == -1) {
114       if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
115         continue;
116       error (EXIT_FAILURE, errno, "read: %s", filename);
117     }
118     write_to_fd (buffer, r);
119     count += r;
120     if (count > len)
121       error (EXIT_FAILURE, 0, "write_file_len_to_fd: %s: file has increased in size\n", filename);
122   }
123
124   if (close (fd2) == -1)
125     error (EXIT_FAILURE, errno, "close: %s", filename);
126
127   if (count != len)
128     error (EXIT_FAILURE, 0, "febootstrap-supermin-helper: write_file_len_to_fd: %s: file has changed size\n", filename);
129 }
130
131 /* Append the file pointed to by FTSENT to the cpio output. */
132 static void
133 cpio_append_fts_entry (FTSENT *entry)
134 {
135   if (entry->fts_info & FTS_NS || entry->fts_info & FTS_NSOK)
136     cpio_append (entry->fts_path);
137   else
138     cpio_append_stat (entry->fts_path, entry->fts_statp);
139 }
140
141 /* Append the file named 'filename' to the cpio output. */
142 static void
143 cpio_append (const char *filename)
144 {
145   struct stat statbuf;
146
147   if (lstat (filename, &statbuf) == -1)
148     error (EXIT_FAILURE, errno, "lstat: %s", filename);
149   cpio_append_stat (filename, &statbuf);
150 }
151
152 /* Append the file to the cpio output. */
153 #define PADDING(len) ((((len) + 3) & ~3) - (len))
154
155 #define CPIO_HEADER_LEN (6 + 13*8)
156
157 static void
158 cpio_append_stat (const char *filename, const struct stat *statbuf)
159 {
160   const char *orig_filename = filename;
161
162   if (*filename == '/')
163     filename++;
164   if (*filename == '\0')
165     filename = ".";
166
167   if (verbose >= 2)
168     fprintf (stderr, "cpio_append_stat %s 0%o -> %d\n",
169              orig_filename, statbuf->st_mode, out_fd);
170
171   /* Regular files and symlinks are the only ones that have a "body"
172    * in this cpio entry.
173    */
174   int has_body = S_ISREG (statbuf->st_mode) || S_ISLNK (statbuf->st_mode);
175
176   size_t len = strlen (filename) + 1;
177
178   char header[CPIO_HEADER_LEN + 1];
179   snprintf (header, sizeof header,
180             "070701"            /* magic */
181             "%08X"              /* inode */
182             "%08X"              /* mode */
183             "%08X" "%08X"       /* uid, gid */
184             "%08X"              /* nlink */
185             "%08X"              /* mtime */
186             "%08X"              /* file length */
187             "%08X" "%08X"       /* device holding file major, minor */
188             "%08X" "%08X"       /* for specials, device major, minor */
189             "%08X"              /* name length (including \0 byte) */
190             "%08X",             /* checksum (not used by the kernel) */
191             (unsigned) statbuf->st_ino, statbuf->st_mode,
192             statbuf->st_uid, statbuf->st_gid,
193             (unsigned) statbuf->st_nlink, (unsigned) statbuf->st_mtime,
194             has_body ? (unsigned) statbuf->st_size : 0,
195             major (statbuf->st_dev), minor (statbuf->st_dev),
196             major (statbuf->st_rdev), minor (statbuf->st_rdev),
197             (unsigned) len, 0);
198
199   /* Write the header. */
200   write_to_fd (header, CPIO_HEADER_LEN);
201
202   /* Follow with the filename, and pad it. */
203   write_to_fd (filename, len);
204   size_t padding_len = PADDING (CPIO_HEADER_LEN + len);
205   write_padding (padding_len);
206
207   /* Follow with the file or symlink content, and pad it. */
208   if (has_body) {
209     if (S_ISREG (statbuf->st_mode))
210       write_file_len_to_fd (orig_filename, statbuf->st_size);
211     else if (S_ISLNK (statbuf->st_mode)) {
212       char tmp[PATH_MAX];
213       if (readlink (orig_filename, tmp, sizeof tmp) == -1)
214         error (EXIT_FAILURE, errno, "readlink: %s", orig_filename);
215       write_to_fd (tmp, statbuf->st_size);
216     }
217
218     padding_len = PADDING (statbuf->st_size);
219     write_padding (padding_len);
220   }
221 }
222
223 /* CPIO voodoo. */
224 static void
225 cpio_append_trailer (void)
226 {
227   struct stat statbuf;
228   memset (&statbuf, 0, sizeof statbuf);
229   statbuf.st_nlink = 1;
230   cpio_append_stat ("TRAILER!!!", &statbuf);
231
232   /* CPIO seems to pad up to the next block boundary, ie. up to
233    * the next 512 bytes.
234    */
235   write_padding (((out_offset + 511) & ~511) - out_offset);
236   assert ((out_offset & 511) == 0);
237 }
238
239 /* Write 'len' bytes of zeroes out. */
240 static void
241 write_padding (size_t len)
242 {
243   static const char buffer[512] = { 0 };
244
245   while (len > 0) {
246     size_t n = len < sizeof buffer ? len : sizeof buffer;
247     write_to_fd (buffer, n);
248     len -= n;
249   }
250 }
251
252 static void
253 cpio_start (const char *appliance)
254 {
255   out_fd = open (appliance, O_WRONLY | O_CREAT | O_TRUNC | O_NOCTTY, 0644);
256   if (out_fd == -1)
257     error (EXIT_FAILURE, errno, "open: %s", appliance);
258   out_offset = 0;
259 }
260
261 static void
262 cpio_end (void)
263 {
264   cpio_append_trailer ();
265
266   /* Finish off and close output file. */
267   if (close (out_fd) == -1)
268     error (EXIT_FAILURE, errno, "close");
269 }
270
271 struct writer cpio_writer = {
272   .wr_start = cpio_start,
273   .wr_end = cpio_end,
274   .wr_file = cpio_append,
275   .wr_file_stat = cpio_append_stat,
276   .wr_fts_entry = cpio_append_fts_entry,
277   .wr_cpio_file = write_file_to_fd,
278 };