/*
* file.c
* Data source for files and block devices.
*
* Copyright (c) 2003 Christoph Pfisterer
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "global.h"
#define USE_BINARY_SEARCH 0
#define DEBUG_SIZE 0
#ifdef USE_IOCTL_LINUX
#include <sys/ioctl.h>
#include <linux/fs.h>
#endif
#ifdef USE_IOCTL_FREEBSD
#include <sys/disklabel.h>
#endif
#ifdef USE_IOCTL_DARWIN
#include <sys/ioctl.h>
#include <sys/disk.h>
#endif
/*
* types
*/
typedef struct file_source {
SOURCE c;
int fd;
} FILE_SOURCE;
/*
* helper functions
*/
static int analyze_file(SOURCE *s, int level);
static u8 read_file(SOURCE *s, u8 pos, u8 len, void *buf);
static void close_file(SOURCE *s);
#if USE_BINARY_SEARCH
static int check_position(int fd, u8 pos);
#endif
/*
* initialize the file source
*/
SOURCE *init_file_source(int fd, int filekind)
{
FILE_SOURCE *fs;
off_t result;
fs = (FILE_SOURCE *)malloc(sizeof(FILE_SOURCE));
if (fs == NULL)
bailout("Out of memory");
memset(fs, 0, sizeof(FILE_SOURCE));
if (filekind != 0) /* special treatment hook for devices */
fs->c.analyze = analyze_file;
fs->c.read_bytes = read_file;
fs->c.close = close_file;
fs->fd = fd;
/*
* Determine the size using various methods. The first method that
* works is used.
*/
/*
* lseek() to the end:
* Works on files. On some systems (Linux), this also works on devices.
*/
if (!fs->c.size_known) {
result = lseek(fd, 0, SEEK_END);
#if DEBUG_SIZE
printf("Size: lseek returned %lld\n", result);
#endif
if (result > 0) {
fs->c.size_known = 1;
fs->c.size = result;
}
}
#ifdef USE_IOCTL_LINUX
/*
* ioctl, Linux style:
* Works on certain devices.
*/
#ifdef BLKGETSIZE64
#define u64 __u64 /* workaround for broken header file */
if (!fs->c.size_known && filekind != 0) {
u8 devsize;
if (ioctl(fd, BLKGETSIZE64, (void *)&devsize) >= 0) {
fs->c.size_known = 1;
fs->c.size = devsize;
#if DEBUG_SIZE
printf("Size: Linux 64-bit ioctl reports %llu\n", fs->c.size);
#endif
}
}
#undef u64
#endif
if (!fs->c.size_known && filekind != 0) {
u4 blockcount;
if (ioctl(fd, BLKGETSIZE, (void *)&blockcount) >= 0) {
fs->c.size_known = 1;
fs->c.size = (u8)blockcount * 512;
#if DEBUG_SIZE
printf("Size: Linux 32-bit ioctl reports %llu (%lu blocks)\n",
fs->c.size, blockcount);
#endif
}
}
#endif
#ifdef USE_IOCTL_FREEBSD
/*
* ioctl, FreeBSD style:
* Works on partitioned hard disks or somthing like that.
*/
if (!fs->c.size_known && filekind != 0) {
struct disklabel dl;
if (ioctl(fd, DIOCGDINFO, &dl) >= 0) {
fs->c.size_known = 1;
fs->c.size = (u8) dl.d_ncylinders * dl.d_secpercyl * dl.d_secsize;
/* TODO: check this, it's the whole disk size... */
#if DEBUG_SIZE
printf("Size: FreeBSD ioctl reports %llu\n", fs->c.size);
#endif
}
}
#endif
#ifdef USE_IOCTL_DARWIN
/*
* ioctl, Darwin style:
* Works on certain devices.
*/
if (!fs->c.size_known && filekind != 0) {
u4 blocksize;
u8 blockcount;
if (ioctl(fd, DKIOCGETBLOCKSIZE, (void *)&blocksize) >= 0) {
if (ioctl(fd, DKIOCGETBLOCKCOUNT, (void *)&blockcount) >= 0) {
fs->c.size_known = 1;
fs->c.size = blockcount * blocksize;
#if DEBUG_SIZE
printf("Size: Darwin ioctl reports %llu (%llu blocks of %lu bytes)\n",
fs->c.size, blockcount, blocksize);
#endif
}
}
}
#endif
#if USE_BINARY_SEARCH
/*
* binary search:
* Works on anything that can seek, but is quite expensive.
*/
if (!fs->c.size_known) {
u8 lower, upper, current;
/* TODO: check that the target can seek at all */
#if DEBUG_SIZE
printf("Size: Doing a binary search\n");
#endif
/* first, find an upper bound starting from a reasonable guess */
lower = 0;
upper = 1024 * 1024; /* start with 1MB */
while (check_position(fd, upper)) {
lower = upper;
upper <<= 2;
}
/* second, nail down the size between the lower and upper bounds */
while (upper > lower + 1) {
current = (lower + upper) >> 1;
if (check_position(fd, current))
lower = current;
else
upper = current;
}
fs->c.size_known = 1;
fs->c.size = lower + 1;
#if DEBUG_SIZE
printf("Size: Binary search reports %llu\n", fs->c.size);
#endif
}
#endif
return (SOURCE *)fs;
}
/*
* special handling hook: devices may have out-of-band structure
*/
static int analyze_file(SOURCE *s, int level)
{
if (analyze_cdaccess(((FILE_SOURCE *)s)->fd, s, level))
return 1;
return 0;
}
/*
* raw read
*/
static u8 read_file(SOURCE *s, u8 pos, u8 len, void *buf)
{
off_t result_seek;
ssize_t result_read;
char *p;
u8 got;
int fd = ((FILE_SOURCE *)s)->fd;
/* seek to the requested position */
result_seek = lseek(fd, pos, SEEK_SET);
if (result_seek != pos) {
errore("Seek to %llu failed", pos);
return 0;
}
/* read from there */
p = (char *)buf;
got = 0;
while (len > 0) {
result_read = read(fd, p, len);
if (result_read < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
errore("Data read failed at position %llu", pos + got);
break;
} else if (result_read == 0) {
/* simple EOF, no message */
break;
} else {
len -= result_read;
got += result_read;
p += result_read;
}
}
return got;
}
/*
* dispose of everything
*/
static void close_file(SOURCE *s)
{
int fd = ((FILE_SOURCE *)s)->fd;
if (fd >= 0)
close(fd);
}
/*
* check if the given position is inside the file's size
*/
#if USE_BINARY_SEARCH
static int check_position(int fd, u8 pos)
{
char buf[2];
#if DEBUG_SIZE
printf(" Probing %llu\n", pos);
#endif
if (lseek(fd, pos, SEEK_SET) != pos)
return 0;
if (read(fd, buf, 1) != 1)
return 0;
return 1;
}
#endif
/* EOF */
syntax highlighted by Code2HTML, v. 0.9.1