1228753Smm/*-
2228753Smm * Copyright (c) 2003-2007 Tim Kientzle
3228753Smm * All rights reserved.
4228753Smm *
5228753Smm * Redistribution and use in source and binary forms, with or without
6228753Smm * modification, are permitted provided that the following conditions
7228753Smm * are met:
8228753Smm * 1. Redistributions of source code must retain the above copyright
9228753Smm *    notice, this list of conditions and the following disclaimer.
10228753Smm * 2. Redistributions in binary form must reproduce the above copyright
11228753Smm *    notice, this list of conditions and the following disclaimer in the
12228753Smm *    documentation and/or other materials provided with the distribution.
13228753Smm *
14228753Smm * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15228753Smm * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16228753Smm * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17228753Smm * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18228753Smm * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19228753Smm * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20228753Smm * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21228753Smm * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22228753Smm * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23228753Smm * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24228753Smm */
25228753Smm
26228753Smm#include "archive_platform.h"
27228763Smm__FBSDID("$FreeBSD$");
28228753Smm
29228753Smm#ifdef HAVE_SYS_STAT_H
30228753Smm#include <sys/stat.h>
31228753Smm#endif
32228753Smm#ifdef HAVE_ERRNO_H
33228753Smm#include <errno.h>
34228753Smm#endif
35228753Smm#ifdef HAVE_FCNTL_H
36228753Smm#include <fcntl.h>
37228753Smm#endif
38228753Smm#ifdef HAVE_IO_H
39228753Smm#include <io.h>
40228753Smm#endif
41228753Smm#ifdef HAVE_STDLIB_H
42228753Smm#include <stdlib.h>
43228753Smm#endif
44228753Smm#ifdef HAVE_STRING_H
45228753Smm#include <string.h>
46228753Smm#endif
47228753Smm#ifdef HAVE_UNISTD_H
48228753Smm#include <unistd.h>
49228753Smm#endif
50228753Smm
51228753Smm#include "archive.h"
52228753Smm
53228753Smmstruct read_fd_data {
54228753Smm	int	 fd;
55228753Smm	size_t	 block_size;
56232153Smm	char	 use_lseek;
57228753Smm	void	*buffer;
58228753Smm};
59228753Smm
60228753Smmstatic int	file_close(struct archive *, void *);
61228753Smmstatic ssize_t	file_read(struct archive *, void *, const void **buff);
62302001Smmstatic int64_t	file_seek(struct archive *, void *, int64_t request, int);
63232153Smmstatic int64_t	file_skip(struct archive *, void *, int64_t request);
64228753Smm
65228753Smmint
66228753Smmarchive_read_open_fd(struct archive *a, int fd, size_t block_size)
67228753Smm{
68228753Smm	struct stat st;
69228753Smm	struct read_fd_data *mine;
70228753Smm	void *b;
71228753Smm
72228753Smm	archive_clear_error(a);
73228753Smm	if (fstat(fd, &st) != 0) {
74228753Smm		archive_set_error(a, errno, "Can't stat fd %d", fd);
75228753Smm		return (ARCHIVE_FATAL);
76228753Smm	}
77228753Smm
78232153Smm	mine = (struct read_fd_data *)calloc(1, sizeof(*mine));
79228753Smm	b = malloc(block_size);
80228753Smm	if (mine == NULL || b == NULL) {
81228753Smm		archive_set_error(a, ENOMEM, "No memory");
82228753Smm		free(mine);
83228753Smm		free(b);
84228753Smm		return (ARCHIVE_FATAL);
85228753Smm	}
86228753Smm	mine->block_size = block_size;
87228753Smm	mine->buffer = b;
88228753Smm	mine->fd = fd;
89228753Smm	/*
90228753Smm	 * Skip support is a performance optimization for anything
91228753Smm	 * that supports lseek().  On FreeBSD, only regular files and
92228753Smm	 * raw disk devices support lseek() and there's no portable
93228753Smm	 * way to determine if a device is a raw disk device, so we
94228753Smm	 * only enable this optimization for regular files.
95228753Smm	 */
96228753Smm	if (S_ISREG(st.st_mode)) {
97228753Smm		archive_read_extract_set_skip_file(a, st.st_dev, st.st_ino);
98232153Smm		mine->use_lseek = 1;
99232153Smm	}
100228753Smm#if defined(__CYGWIN__) || defined(_WIN32)
101228753Smm	setmode(mine->fd, O_BINARY);
102228753Smm#endif
103228753Smm
104232153Smm	archive_read_set_read_callback(a, file_read);
105232153Smm	archive_read_set_skip_callback(a, file_skip);
106302001Smm	archive_read_set_seek_callback(a, file_seek);
107232153Smm	archive_read_set_close_callback(a, file_close);
108232153Smm	archive_read_set_callback_data(a, mine);
109232153Smm	return (archive_read_open1(a));
110228753Smm}
111228753Smm
112228753Smmstatic ssize_t
113228753Smmfile_read(struct archive *a, void *client_data, const void **buff)
114228753Smm{
115228753Smm	struct read_fd_data *mine = (struct read_fd_data *)client_data;
116228753Smm	ssize_t bytes_read;
117228753Smm
118228753Smm	*buff = mine->buffer;
119228753Smm	for (;;) {
120228753Smm		bytes_read = read(mine->fd, mine->buffer, mine->block_size);
121228753Smm		if (bytes_read < 0) {
122228753Smm			if (errno == EINTR)
123228753Smm				continue;
124248616Smm			archive_set_error(a, errno, "Error reading fd %d",
125248616Smm			    mine->fd);
126228753Smm		}
127228753Smm		return (bytes_read);
128228753Smm	}
129228753Smm}
130228753Smm
131232153Smmstatic int64_t
132232153Smmfile_skip(struct archive *a, void *client_data, int64_t request)
133228753Smm{
134228753Smm	struct read_fd_data *mine = (struct read_fd_data *)client_data;
135238856Smm	int64_t skip = request;
136238856Smm	int64_t old_offset, new_offset;
137232153Smm	int skip_bits = sizeof(skip) * 8 - 1;  /* off_t is a signed type. */
138228753Smm
139232153Smm	if (!mine->use_lseek)
140228753Smm		return (0);
141228753Smm
142232153Smm	/* Reduce a request that would overflow the 'skip' variable. */
143232153Smm	if (sizeof(request) > sizeof(skip)) {
144232153Smm		int64_t max_skip =
145232153Smm		    (((int64_t)1 << (skip_bits - 1)) - 1) * 2 + 1;
146232153Smm		if (request > max_skip)
147232153Smm			skip = max_skip;
148232153Smm	}
149232153Smm
150228753Smm	/* Reduce request to the next smallest multiple of block_size */
151228753Smm	request = (request / mine->block_size) * mine->block_size;
152228753Smm	if (request == 0)
153228753Smm		return (0);
154228753Smm
155232153Smm	if (((old_offset = lseek(mine->fd, 0, SEEK_CUR)) >= 0) &&
156232153Smm	    ((new_offset = lseek(mine->fd, skip, SEEK_CUR)) >= 0))
157232153Smm		return (new_offset - old_offset);
158232153Smm
159232153Smm	/* If seek failed once, it will probably fail again. */
160232153Smm	mine->use_lseek = 0;
161232153Smm
162232153Smm	/* Let libarchive recover with read+discard. */
163232153Smm	if (errno == ESPIPE)
164232153Smm		return (0);
165232153Smm
166228753Smm	/*
167232153Smm	 * There's been an error other than ESPIPE. This is most
168232153Smm	 * likely caused by a programmer error (too large request)
169232153Smm	 * or a corrupted archive file.
170228753Smm	 */
171232153Smm	archive_set_error(a, errno, "Error seeking");
172232153Smm	return (-1);
173228753Smm}
174228753Smm
175302001Smm/*
176302001Smm * TODO: Store the offset and use it in the read callback.
177302001Smm */
178302001Smmstatic int64_t
179302001Smmfile_seek(struct archive *a, void *client_data, int64_t request, int whence)
180302001Smm{
181302001Smm	struct read_fd_data *mine = (struct read_fd_data *)client_data;
182302001Smm	int64_t r;
183302001Smm
184302001Smm	/* We use off_t here because lseek() is declared that way. */
185302001Smm	/* See above for notes about when off_t is less than 64 bits. */
186302001Smm	r = lseek(mine->fd, request, whence);
187302001Smm	if (r >= 0)
188302001Smm		return r;
189302001Smm
190302001Smm	if (errno == ESPIPE) {
191302001Smm		archive_set_error(a, errno,
192302001Smm		    "A file descriptor(%d) is not seekable(PIPE)", mine->fd);
193302001Smm		return (ARCHIVE_FAILED);
194302001Smm	} else {
195302001Smm		/* If the input is corrupted or truncated, fail. */
196302001Smm		archive_set_error(a, errno,
197302001Smm		    "Error seeking in a file descriptor(%d)", mine->fd);
198302001Smm		return (ARCHIVE_FATAL);
199302001Smm	}
200302001Smm}
201302001Smm
202228753Smmstatic int
203228753Smmfile_close(struct archive *a, void *client_data)
204228753Smm{
205228753Smm	struct read_fd_data *mine = (struct read_fd_data *)client_data;
206228753Smm
207228753Smm	(void)a; /* UNUSED */
208228753Smm	free(mine->buffer);
209228753Smm	free(mine);
210228753Smm	return (ARCHIVE_OK);
211228753Smm}
212