// -*- mode: c++; c-basic-offset: 4 -*-
/*
* fromfile.{cc,hh} -- provides convenient, fast access to files
* Eddie Kohler
*
* Copyright (c) 1999-2000 Massachusetts Institute of Technology
* Copyright (c) 2001-2003 International Computer Science Institute
* Copyright (c) 2004 The Regents of the University of California
*
* 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, subject to the conditions
* listed in the Click LICENSE file. These conditions include: you must
* preserve this copyright notice, and you cannot mention the copyright
* holders in advertising related to the Software without their permission.
* The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
* notice is a summary of the Click LICENSE file; the license in that file is
* legally binding.
*/
#include <click/config.h>
#include "fromfile.hh"
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/element.hh>
#include <click/straccum.hh>
#include <click/userutils.hh>
#include "fakepcap.hh"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef ALLOW_MMAP
# include <sys/mman.h>
#endif
CLICK_DECLS
FromFile::FromFile()
: _fd(-1), _buffer(0), _data_packet(0),
#ifdef ALLOW_MMAP
# ifdef __linux__
_mmap(false),
# else
_mmap(true),
# endif
#endif
_filename(), _pipe(0), _landmark_pattern("%f"), _lineno(0)
{
}
int
FromFile::configure_keywords(Vector<String> &conf, int first_keyword, Element *e, ErrorHandler *errh)
{
#ifndef ALLOW_MMAP
bool mmap = false;
#else
bool mmap = _mmap;
#endif
if (cp_va_parse_remove_keywords(conf, first_keyword, e, errh,
"MMAP", cpBool, "access file with mmap()?", &mmap,
cpEnd) < 0)
return -1;
#ifdef ALLOW_MMAP
_mmap = mmap;
#else
if (mmap)
errh->warning("'MMAP true' is not supported on this platform");
#endif
return 0;
}
String
FromFile::print_filename() const
{
if (!_filename || _filename == "-")
return String::stable_string("<stdin>", 7);
else
return _filename;
}
String
FromFile::landmark(const String &landmark_pattern) const
{
StringAccum sa;
const char *e = landmark_pattern.end();
for (const char *s = landmark_pattern.begin(); s < e; s++)
if (s < e - 1 && s[0] == '%' && s[1] == 'f') {
sa << print_filename();
s++;
} else if (s < e - 1 && s[0] == '%' && s[1] == 'l') {
sa << _lineno;
s++;
} else if (s < e - 1 && s[0] == '%' && s[1] == '%') {
sa << '%';
s++;
} else
sa << *s;
return sa.take_string();
}
int
FromFile::error(ErrorHandler *errh, const char *format, ...) const
{
if (!errh)
errh = ErrorHandler::default_handler();
va_list val;
va_start(val, format);
errh->verror(ErrorHandler::ERR_ERROR, landmark(), format, val);
va_end(val);
return ErrorHandler::ERROR_RESULT;
}
int
FromFile::warning(ErrorHandler *errh, const char *format, ...) const
{
if (!errh)
errh = ErrorHandler::default_handler();
va_list val;
va_start(val, format);
errh->verror(ErrorHandler::ERR_WARNING, landmark(), format, val);
va_end(val);
return ErrorHandler::ERROR_RESULT;
}
#ifdef ALLOW_MMAP
static void
munmap_destructor(unsigned char *data, size_t amount)
{
if (munmap((caddr_t)data, amount) < 0)
click_chatter("FromFile: munmap: %s", strerror(errno));
}
int
FromFile::read_buffer_mmap(ErrorHandler *errh)
{
if (_mmap_unit == 0) {
size_t page_size = getpagesize();
_mmap_unit = (WANT_MMAP_UNIT / page_size) * page_size;
_mmap_off = 0;
// don't report most errors on the first time through
errh = ErrorHandler::silent_handler();
}
// get length of file
struct stat statbuf;
if (fstat(_fd, &statbuf) < 0)
return error(errh, "stat: %s", strerror(errno));
// check for end of file
// But return -1 if we have not mmaped before: it might be a pipe, not
// true EOF.
if (_mmap_off >= statbuf.st_size)
return (_mmap_off == 0 ? -1 : 0);
// actually mmap
_len = _mmap_unit;
if ((off_t)(_mmap_off + _len) > statbuf.st_size)
_len = statbuf.st_size - _mmap_off;
void *mmap_data = mmap(0, _len, PROT_READ, MAP_SHARED, _fd, _mmap_off);
if (mmap_data == MAP_FAILED)
return error(errh, "mmap: %s", strerror(errno));
_data_packet = Packet::make((unsigned char *)mmap_data, _len, munmap_destructor);
_buffer = _data_packet->data();
_file_offset = _mmap_off;
_mmap_off += _len;
# ifdef HAVE_MADVISE
// don't care about errors
(void) madvise((caddr_t)mmap_data, _len, MADV_SEQUENTIAL);
# endif
return 1;
}
#endif
int
FromFile::read_buffer(ErrorHandler *errh)
{
if (_data_packet)
_data_packet->kill();
_data_packet = 0;
_file_offset += _len;
_pos -= _len; // adjust _pos by _len: it might validly point
// beyond _len
_len = 0;
#ifdef ALLOW_MMAP
if (_mmap) {
int result = read_buffer_mmap(errh);
if (result >= 0)
return result;
// else, try a regular read
_mmap = false;
(void) lseek(_fd, _mmap_off, SEEK_SET);
_len = 0;
}
#endif
_data_packet = Packet::make(0, 0, BUFFER_SIZE, 0);
if (!_data_packet)
return error(errh, strerror(ENOMEM));
_buffer = _data_packet->data();
unsigned char *data = _data_packet->data();
assert(_data_packet->headroom() == 0);
while (_len < BUFFER_SIZE) {
ssize_t got = ::read(_fd, data + _len, BUFFER_SIZE - _len);
if (got > 0)
_len += got;
else if (got == 0) // premature end of file
return _len;
else if (got < 0 && errno != EINTR && errno != EAGAIN)
return error(errh, strerror(errno));
}
return _len;
}
int
FromFile::read(void *vdata, uint32_t dlen, ErrorHandler *errh)
{
unsigned char *data = reinterpret_cast<unsigned char *>(vdata);
uint32_t dpos = 0;
while (dpos < dlen) {
if (_pos < _len) {
uint32_t howmuch = dlen - dpos;
if (howmuch > _len - _pos)
howmuch = _len - _pos;
memcpy(data + dpos, _buffer + _pos, howmuch);
dpos += howmuch;
_pos += howmuch;
}
if (dpos < dlen && read_buffer(errh) <= 0)
return dpos;
}
return dlen;
}
int
FromFile::read_line(String &result, ErrorHandler *errh)
{
// first, try to read a line from the current buffer
const unsigned char *s = _buffer + _pos;
const unsigned char *e = _buffer + _len;
while (s < e && *s != '\n' && *s != '\r')
s++;
if (s < e && (*s == '\n' || s + 1 < e)) {
s += (*s == '\r' && s[1] == '\n' ? 2 : 1);
int new_pos = s - _buffer;
result = String::stable_string((const char *) (_buffer + _pos), new_pos - _pos);
_pos = new_pos;
_lineno++;
return 1;
}
// otherwise, build up a line
StringAccum sa;
sa.append(_buffer + _pos, _len - _pos);
while (1) {
int errcode = read_buffer(errh);
if (errcode < 0 || (errcode == 0 && !sa))
return errcode;
// check doneness
bool done;
if (sa && sa.back() == '\r') {
if (_len > 0 && _buffer[0] == '\n')
sa << '\n', _pos++;
done = true;
} else if (errcode == 0) {
_pos = _len;
done = true;
} else {
s = _buffer, e = _buffer + _len;
while (s < e && *s != '\n' && *s != '\r')
s++;
if (s < e && (*s == '\n' || s + 1 < e)) {
s += (*s == '\r' && s[1] == '\n' ? 2 : 1);
sa.append(_buffer, s - _buffer);
_pos = s - _buffer;
done = true;
} else {
sa.append(_buffer, _len);
done = false;
}
}
if (done) {
result = sa.take_string();
_lineno++;
return 1;
}
}
}
int
FromFile::peek_line(String &result, ErrorHandler *errh)
{
int before_pos = _pos;
int retval = read_line(result, errh);
if (retval > 0) {
_pos = before_pos;
_lineno--;
}
return retval;
}
int
FromFile::seek(off_t want, ErrorHandler* errh)
{
if (want >= _file_offset && want < _file_offset + _len) {
_pos = want;
return 0;
}
#ifdef ALLOW_MMAP
if (_mmap) {
_mmap_off = (want / _mmap_unit) * _mmap_unit;
_pos = _len + want - _mmap_off;
return 0;
}
#endif
// check length of file
struct stat statbuf;
if (fstat(_fd, &statbuf) < 0)
return error(errh, "stat: %s", strerror(errno));
if (S_ISREG(statbuf.st_mode) && statbuf.st_size && want > statbuf.st_size)
return errh->error("FILEPOS out of range");
// try to seek
if (lseek(_fd, want, SEEK_SET) != (off_t) -1) {
_pos = _len;
_file_offset = want - _len;
return 0;
}
// otherwise, read data
while (_file_offset + _len < want && _len)
if (read_buffer(errh) < 0)
return -1;
_pos = want - _file_offset;
return 0;
}
int
FromFile::initialize(ErrorHandler *errh)
{
// open file
if (!_filename || _filename == "-")
_fd = STDIN_FILENO;
else
_fd = open(_filename.c_str(), O_RDONLY);
if (_fd < 0)
return errh->error("%s: %s", print_filename().c_str(), strerror(errno));
retry_file:
#ifdef ALLOW_MMAP
_mmap_unit = 0;
#endif
_file_offset = 0;
_pos = _len = 0;
int result = read_buffer(errh);
if (result < 0)
return -1;
else if (result == 0)
return error(errh, "empty file");
// check for a gziped or bzip2d dump
if (_fd == STDIN_FILENO || _pipe)
/* cannot handle gzip or bzip2 */;
else if (compressed_data(_buffer, _len)) {
close(_fd);
_fd = -1;
if (!(_pipe = open_uncompress_pipe(_filename, _buffer, _len, errh)))
return -1;
_fd = fileno(_pipe);
goto retry_file;
}
return 0;
}
void
FromFile::take_state(FromFile &o, ErrorHandler *errh)
{
_fd = o._fd;
o._fd = -1;
_pipe = o._pipe;
o._pipe = 0;
_buffer = o._buffer;
_pos = o._pos;
_len = o._len;
_data_packet = o._data_packet;
o._data_packet = 0;
#ifdef ALLOW_MMAP
if (_mmap != o._mmap)
errh->warning("different MMAP states");
_mmap = o._mmap;
_mmap_unit = o._mmap_unit;
_mmap_off = o._mmap_off;
#endif
_file_offset = o._file_offset;
}
void
FromFile::cleanup()
{
if (_pipe)
pclose(_pipe);
else if (_fd >= 0 && _fd != STDIN_FILENO)
close(_fd);
_pipe = 0;
_fd = -1;
if (_data_packet)
_data_packet->kill();
_data_packet = 0;
}
const uint8_t *
FromFile::get_aligned(size_t size, void *buffer, ErrorHandler *errh)
{
// we may need to read bits of the file
if (_pos + size <= _len) {
const uint8_t *chunk = _buffer + _pos;
_pos += size;
#if HAVE_INDIFFERENT_ALIGNMENT
return reinterpret_cast<const uint8_t *>(chunk);
#else
// make a copy if required for alignment
if (((uintptr_t)(chunk) & 3) == 0)
return reinterpret_cast<const uint8_t *>(chunk);
else {
memcpy(buffer, chunk, size);
return reinterpret_cast<uint8_t *>(buffer);
}
#endif
} else if (read(buffer, size, errh) == (int)size)
return reinterpret_cast<uint8_t *>(buffer);
else
return 0;
}
const uint8_t *
FromFile::get_unaligned(size_t size, void *buffer, ErrorHandler *errh)
{
// we may need to read bits of the file
if (_pos + size <= _len) {
const uint8_t *chunk = _buffer + _pos;
_pos += size;
return reinterpret_cast<const uint8_t *>(chunk);
} else if (read(buffer, size, errh) == (int)size)
return reinterpret_cast<uint8_t *>(buffer);
else
return 0;
}
String
FromFile::get_string(size_t size, ErrorHandler *errh)
{
// we may need to read bits of the file
if (_pos + size <= _len) {
const uint8_t *chunk = _buffer + _pos;
_pos += size;
return String::stable_string((const char *) chunk, size);
} else {
String s = String::garbage_string(size);
if (read(s.mutable_data(), size, errh) == (int) size)
return s;
else
return String();
}
}
Packet *
FromFile::get_packet(size_t size, uint32_t sec, uint32_t subsec, ErrorHandler *errh)
{
if (_pos + size <= _len) {
if (Packet *p = _data_packet->clone()) {
p->shrink_data(_buffer + _pos, size);
p->timestamp_anno().set(sec, subsec);
_pos += size;
return p;
}
} else {
if (WritablePacket *p = Packet::make(0, 0, size, 0)) {
if (read(p->data(), size, errh) < (int)size) {
p->kill();
return 0;
} else {
p->timestamp_anno().set(sec, subsec);
return p;
}
}
}
error(errh, strerror(ENOMEM));
return 0;
}
Packet *
FromFile::get_packet_from_data(const void *data_void, size_t data_size, size_t size, uint32_t sec, uint32_t subsec, ErrorHandler *errh)
{
const uint8_t *data = reinterpret_cast<const uint8_t *>(data_void);
if (data >= _buffer && data + size <= _buffer + _len) {
if (Packet *p = _data_packet->clone()) {
p->shrink_data(data, size);
p->timestamp_anno().set(sec, subsec);
return p;
}
} else {
if (WritablePacket *p = Packet::make(0, 0, size, 0)) {
memcpy(p->data(), data, data_size);
if (data_size < size
&& read(p->data() + data_size, size - data_size, errh) != (int)(size - data_size)) {
p->kill();
return 0;
}
p->timestamp_anno().set(sec, subsec);
return p;
}
}
error(errh, strerror(ENOMEM));
return 0;
}
String
FromFile::filename_handler(Element *e, void *thunk)
{
FromFile *fd = reinterpret_cast<FromFile *>((uint8_t *)e + (intptr_t)thunk);
return fd->print_filename();
}
String
FromFile::filesize_handler(Element *e, void *thunk)
{
FromFile *fd = reinterpret_cast<FromFile *>((uint8_t *)e + (intptr_t)thunk);
struct stat s;
if (fd->_fd >= 0 && fstat(fd->_fd, &s) >= 0 && S_ISREG(s.st_mode))
return String(s.st_size);
else
return "-";
}
String
FromFile::filepos_handler(Element* e, void* thunk)
{
FromFile* fd = reinterpret_cast<FromFile*>((uint8_t*)e + (intptr_t)thunk);
return String(fd->_file_offset + fd->_pos);
}
int
FromFile::filepos_write_handler(const String& str, Element* e, void* thunk, ErrorHandler* errh)
{
off_t offset;
if (!cp_file_offset(cp_uncomment(str), &offset))
return errh->error("argument must be file offset");
FromFile* fd = reinterpret_cast<FromFile*>((uint8_t*)e + (intptr_t)thunk);
return fd->seek(offset, errh);
}
void
FromFile::add_handlers(Element* e, bool filepos_writable) const
{
intptr_t offset = (const uint8_t *)this - (const uint8_t *)e;
e->add_read_handler("filename", filename_handler, (void *)offset);
e->add_read_handler("filesize", filesize_handler, (void *)offset);
e->add_read_handler("filepos", filepos_handler, (void *)offset);
if (filepos_writable)
e->add_write_handler("filepos", filepos_write_handler, (void *)offset);
}
CLICK_ENDDECLS
ELEMENT_REQUIRES(userlevel|ns FakePcap)
ELEMENT_PROVIDES(FromFile)
syntax highlighted by Code2HTML, v. 0.9.1