1 /*
2  * Copyright 2016-2017 Frank Hunleth
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "archive_open.h"
18 #include "progress.h"
19 #include "util.h"
20 
21 #include <archive.h>
22 #include <errno.h>
23 #include <stdlib.h>
24 #include <limits.h>
25 #include <string.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 
30 #define DEFAULT_LIBARCHIVE_BLOCK_SIZE 16384
31 
32 struct fwup_archive_data {
33     size_t current_frame_remaining;
34     bool is_stdin;
35     bool is_eof;
36     int fd;
37     struct fwup_progress *progress;
38 
39     char name[PATH_MAX];
40     char buffer[DEFAULT_LIBARCHIVE_BLOCK_SIZE];
41 };
42 
normal_read(struct archive * a,void * client_data,const void ** buff)43 static ssize_t normal_read(struct archive *a, void *client_data, const void **buff)
44 {
45     struct fwup_archive_data *ad = (struct fwup_archive_data *) client_data;
46 
47     *buff = ad->buffer;
48     for (;;) {
49         ssize_t bytes_read = read(ad->fd, ad->buffer, sizeof(ad->buffer));
50         if (bytes_read < 0) {
51             if (errno == EINTR)
52                 continue;
53 
54             if (ad->is_stdin)
55                 archive_set_error(a, errno, "Error reading stdin");
56             else
57                 archive_set_error(a, errno, "Error reading '%s'", ad->name);
58 
59             return -1;
60         }
61 
62         if (ad->progress)
63             ad->progress->input_bytes += bytes_read;
64 
65         return bytes_read;
66     }
67 }
68 
normal_close(struct archive * a,void * client_data)69 static int normal_close(struct archive *a, void *client_data)
70 {
71     struct fwup_archive_data *ad = (struct fwup_archive_data *) client_data;
72     (void)a; /* UNUSED */
73 
74     // NOTE: One very important difference between libarchive's default file
75     // reading implementation and this one is that libarchive drains stdin
76     // before closing it. This is the friendly thing to do when working with
77     // pipes since if data isn't needed at the end, you still have to consume
78     // it or the source of the pipe gets an error. This is NOT the behavior
79     // we want. We want to leave it up to other code for whether to drain or
80     // not. That way if there's an error, that code can choose to exit immediately
81     // and not pay the cost of transferring all of those bytes through the pipe.
82     // The pipe data may originate from a remote source and stopping the
83     // transmission immediately saves time.
84 
85     // Only close files - not stdin.
86     if (ad->fd > 0)
87         close(ad->fd);
88 
89     free(ad);
90     return ARCHIVE_OK;
91 }
92 
framed_stdin_read(struct archive * a,void * client_data,const void ** buff)93 static ssize_t framed_stdin_read(struct archive *a, void *client_data, const void **buff)
94 {
95     struct fwup_archive_data *ad = (struct fwup_archive_data *) client_data;
96     if (ad->is_eof)
97         return 0;
98 
99     *buff = ad->buffer;
100 
101     size_t amount_read;
102 
103     if (ad->current_frame_remaining == 0) {
104         uint32_t be_len;
105         amount_read = fread(&be_len, 1, sizeof(be_len), stdin);
106         if (amount_read != sizeof(be_len)) {
107             archive_set_error(a, errno, "Error reading stdin");
108             return -1;
109         }
110 
111         if (be_len == 0) {
112             ad->is_eof = true;
113             return 0;
114         }
115         ad->current_frame_remaining = FROM_BIGENDIAN32(be_len);
116     }
117 
118     size_t amount_to_read = ad->current_frame_remaining;
119     if (amount_to_read > sizeof(ad->buffer))
120         amount_to_read = sizeof(ad->buffer);
121 
122     amount_read = fread(ad->buffer, 1, amount_to_read, stdin);
123     if (amount_read == 0) {
124         archive_set_error(a, EIO, "Received EOF even though framing indicated more bytes");
125         return -1;
126     } else {
127         ad->current_frame_remaining -= amount_read;
128         if (ad->progress)
129             ad->progress->input_bytes += amount_read;
130 
131         return amount_read;
132     }
133 }
134 
135 /**
136  * @brief Open the specified file for use with libarchive.
137  *
138  * This call sets up all of the libarchive callbacks properly for fwup.
139  *
140  * @param a a libarchive handle
141  * @param filename the file to open or NULL for stdin
142  * @param progress input progress is reported if non-NULL
143  * @return a libarchive error code (e.g., ARCHIVE_OK or ARCHIVE_FATAL)
144  */
fwup_archive_open_filename(struct archive * a,const char * filename,struct fwup_progress * progress)145 int fwup_archive_open_filename(struct archive *a, const char *filename, struct fwup_progress *progress)
146 {
147     struct fwup_archive_data *ad = (struct fwup_archive_data *) calloc(1, sizeof(struct fwup_archive_data));
148     if (ad == NULL) {
149         archive_set_error(a, ENOMEM, "No memory");
150         return ARCHIVE_FATAL;
151     }
152 
153     ad->current_frame_remaining = 0;
154     ad->is_eof = false;
155     ad->is_stdin = (filename == NULL || filename[0] == '\0');
156     if (!ad->is_stdin)
157         strncpy(ad->name, filename, sizeof(ad->name) - 1);
158     ad->progress = progress;
159 
160     if (ad->is_stdin) {
161         ad->fd = STDIN_FILENO;
162 #ifdef _WIN32
163         setmode(STDIN_FILENO, O_BINARY);
164 #endif
165     } else {
166         ad->fd = open(ad->name, O_RDONLY | O_WIN32_BINARY);
167         if (ad->fd < 0) {
168             archive_set_error(a, errno, "Failed to open '%s'", ad->name);
169             free(ad);
170             return ARCHIVE_FATAL;
171         }
172 #ifdef HAVE_FCNTL
173         (void) fcntl(ad->fd, F_SETFD, FD_CLOEXEC);
174 #endif
175     }
176 
177     archive_read_set_callback_data(a, ad);
178     archive_read_set_close_callback(a, normal_close);
179 
180     if (fwup_framing && ad->is_stdin) {
181         // If reading from standard in and framing is enabled, then
182         // it needs to be applied to the input too.
183         archive_read_set_read_callback(a, framed_stdin_read);
184     } else {
185         // Files and stdin w/o framing are handled similarly.
186         archive_read_set_read_callback(a, normal_read);
187     }
188 
189     return archive_read_open1(a);
190 }
191 
fwup_archive_read_data_block(struct archive * a,const void ** buff,size_t * s,int64_t * o)192 int fwup_archive_read_data_block(struct archive *a, const void **buff, size_t *s, int64_t *o)
193 {
194     // Handle case where archive_read_data_block returns a 0 byte read
195     // even though it's not at end of file. A second read gets past this.
196 
197     int rc;
198     do {
199         rc = archive_read_data_block(a, buff, s, o);
200     } while (rc == ARCHIVE_OK && *s == 0);
201     return rc;
202 }