From f00a825e2d1fd5f2144fff036e85a04c384cff95 Mon Sep 17 00:00:00 2001 From: Erik Hofman Date: Mon, 15 Mar 2021 10:58:17 +0100 Subject: [PATCH] Add a cross-platform file mmap class and use it to access the timezone file, removing the need to keep the database in memory. --- simgear/io/sg_mmap.cxx | 325 +++++++++++++++++++++++++++++++++++++++++ simgear/io/sg_mmap.hxx | 122 ++++++++++++++++ 2 files changed, 447 insertions(+) create mode 100644 simgear/io/sg_mmap.cxx create mode 100644 simgear/io/sg_mmap.hxx diff --git a/simgear/io/sg_mmap.cxx b/simgear/io/sg_mmap.cxx new file mode 100644 index 00000000..0ca6dc40 --- /dev/null +++ b/simgear/io/sg_mmap.cxx @@ -0,0 +1,325 @@ +// sg_mmap.cxx -- File I/O routines +// +// Written by Curtis Olson, started November 1999. +// +// Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// $Id$ + +#include +#include + +#include + +#ifdef _WIN32 +# include +#else +# include +#endif + +#include + +#include +#include +#include + +#if !defined(_MSC_VER) +# include +#endif + +#include +#include + +#include "sg_mmap.hxx" + + +#ifdef WIN32 +/* + * map 'filename' and return a pointer to it. + */ +static void *simple_mmap(int, size_t, SIMPLE_UNMMAP *); +static void simple_unmmap(void*, size_t, SIMPLE_UNMMAP *); +#else +# define simple_mmap(a, b, c) mmap(0, (b), PROT_READ, MAP_PRIVATE, (a), 0L) +# define simple_unmmap(a, b, c) munmap((a), (b)) +#endif + +SGMMapFile::SGMMapFile( ) +{ + set_type( sgFileType ); +} + +SGMMapFile::SGMMapFile(const SGPath &file, int repeat_, int extraoflags_ ) + : file_name(file), repeat(repeat_), extraoflags(extraoflags_) +{ + set_type( sgFileType ); +} + +SGMMapFile::SGMMapFile( int existingFd ) : + fp(existingFd) +{ + set_type( sgFileType ); +} + +SGMMapFile::~SGMMapFile() { + close(); +} + + // open the file based on specified direction +bool SGMMapFile::open( const SGPath& file, const SGProtocolDir d ) { + file_name = file; + return open(d); +} + +// open the file based on specified direction +bool SGMMapFile::open( const SGProtocolDir d ) { + set_dir( d ); + +#if defined(SG_WINDOWS) + std::wstring n = file_name.wstr(); +#else + std::string n = file_name.utf8Str(); +#endif + + + if ( get_dir() == SG_IO_OUT ) { +#if defined(SG_WINDOWS) + int mode = _S_IREAD | _S_IWRITE; + fp = ::_wopen(n.c_str(), O_WRONLY | O_CREAT | O_TRUNC | extraoflags, mode); +#else + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + fp = ::open( n.c_str(), O_WRONLY | O_CREAT | O_TRUNC | extraoflags, mode ); +#endif + } else if ( get_dir() == SG_IO_IN ) { +#if defined(SG_WINDOWS) + fp = ::_wopen( n.c_str(), O_RDONLY | extraoflags ); +#else + fp = ::open( n.c_str(), O_RDONLY | extraoflags ); +#endif + } else { + SG_LOG( SG_IO, SG_ALERT, + "Error: bidirection mode not available for files." ); + return false; + } + + if ( fp == -1 ) { + SG_LOG( SG_IO, SG_ALERT, "Error opening file: " << file_name ); + return false; + } + + // mmap + struct stat statbuf; + fstat(fp, &statbuf); + + size = (size_t)statbuf.st_size; + buffer = (char*)simple_mmap(fp, size, &un); + if (buffer == (char*)-1) + { + SG_LOG( SG_IO, SG_ALERT, "Error mmapping file: " << file_name ); + return false; + } + + eof_flag = false; + + return true; +} + + +// read a block of data of specified size +int SGMMapFile::read( char *buf, int length ) { + size_t read_size = length; + size_t result = length; + size_t pos = offset; + + if (read_size > size - offset) { + read_size = size - offset; + result = 0; // eof + } + + // read a chunk + memcpy(buf, buffer+offset, read_size); + offset += read_size; + + if ( length > 0 && result == 0 ) { + if (repeat < 0 || iteration < repeat - 1) { + iteration++; + // loop reading the file, unless it is empty + + off_t fileLen = pos; + if (fileLen == 0) { + eof_flag = true; + return 0; + } else { + offset = 0; + if (read_size > size) { + read_size = size; + result = 0; // eof + } + memcpy(buf, buffer, read_size); + offset += read_size; + return result; + } + } else { + eof_flag = true; + } + } + return result; +} + + +// read a line of data, length is max size of input buffer +int SGMMapFile::readline( char *buf, int length ) { + size_t read_size = length; + size_t result = length; + size_t pos = offset; + + if (read_size > size - offset) { + read_size = size - offset; + result = 0; // eof + } + + // read a chunk + memcpy(buf, buffer+offset, read_size); + offset += read_size; + + if ( length > 0 && result == 0 ) { + if ((repeat < 0 || iteration < repeat - 1) && pos != 0) { + iteration++; + + pos = 0; + result = length; + read_size = length; + if (read_size > size) { + read_size = size; + result = 0; // eof + } + + memcpy(buf, buffer, read_size); + offset += read_size; + } else { + eof_flag = true; + } + } + + // find the end of line and reset position + size_t i; + for ( i = 0; i < result && buf[i] != '\n'; ++i ); + if ( buf[i] == '\n' ) { + result = i + 1; + } else { + result = i; + } + offset = pos + result; + + // just in case ... + buf[ result ] = '\0'; + + return result; +} + +std::string SGMMapFile::read_all() +{ + return std::string(buffer, size); +} + +// write data to a file +int SGMMapFile::write( const char *buf, const int length ) { + size_t write_size = length; + + if (write_size > size - offset) { + SG_LOG( SG_IO, SG_ALERT, "Attempting to write beyond the mmap buffer size: " << file_name ); + write_size = size - offset; + } + + memcpy(buffer+offset, buf, write_size); + offset += write_size; + + return write_size; +} + + +// write null terminated string to a file +int SGMMapFile::writestring( const char *str ) { + size_t write_size = std::strlen( str ); + if (write_size > size - offset) { + SG_LOG( SG_IO, SG_ALERT, "Attempting to write beyond the mmap buffer size: " << file_name ); + write_size = size - offset; + } + + memcpy(buffer+offset, str, write_size); + offset += write_size; + + return write_size; +} + + +// close the port +bool SGMMapFile::close() { + if (fp != -1 ) { + simple_unmmap(buffer, size, &un); + if ( ::close( fp ) == -1 ) { + return false; + } + eof_flag = true; + return true; + } + + return false; +} + +#ifdef WIN32 +/* Source: + * https://mollyrocket.com/forums/viewtopic.php?p=2529 + */ + +void * +simple_mmap(int fd, size_t length, SIMPLE_UNMMAP *un) +{ + HANDLE f; + HANDLE m; + void *p; + + f = (HANDLE)_get_osfhandle(fd); + if (!f) return (void *)-1; + + m = CreateFileMapping(f, NULL, PAGE_READONLY, 0, 0, NULL); + if (!m) return (void *)-1; + + p = MapViewOfFile(m, FILE_MAP_READ, 0, 0, 0); + if (!p) + { + CloseHandle(m); + return (void *)-1; + } + + if (un) + { + un->m = m; + un->p = p; + } + + return p; +} + +void +simple_unmmap(void *addr, size_t len, SIMPLE_UNMMAP *un) +{ + UnmapViewOfFile(un->p); + CloseHandle(un->m); +} +#endif + diff --git a/simgear/io/sg_mmap.hxx b/simgear/io/sg_mmap.hxx new file mode 100644 index 00000000..df597c65 --- /dev/null +++ b/simgear/io/sg_mmap.hxx @@ -0,0 +1,122 @@ +///@mmap +/// File I/O routines. +// +// Written by Curtis Olson, started November 1999. +// +// Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +#ifndef _SG_MMAP_HXX +#define _SG_MMAP_HXX + +#include + +#include + +#include "iochannel.hxx" + +#include + +#ifdef WIN32 +# define WIN32_LEAN_AND_MEAN +# include +#endif + +/** + * A file I/O class based on SGIOChannel. + */ +class SGMMapFile : public SGIOChannel { + + SGPath file_name; + int fp = -1; + bool eof_flag = true; + // Number of repetitions to play. -1 means loop infinitely. + const int repeat = 1; + int iteration = 0; // number of current repetition, + // starting at 0 + int extraoflags = 0; + + char *buffer = nullptr; + size_t offset = 0; + size_t size = 0; +#ifdef WIN32 + typedef struct + { + HANDLE m; + void *p; + } SIMPLE_UNMMAP; + SIMPLE_UNMMAP un; +#else + int un; // referenced but not used +#endif + +public: + + SGMMapFile(); + + /* + * Create an instance of SGMMapFile. + * When calling the constructor you need to provide a file + * name. This file is not opened immediately, but instead will be + * opened when the open() method is called. + * @param file name of file to open + * @param repeat On eof restart at the beginning of the file + */ + SGMMapFile( const SGPath& file, int repeat_ = 1, int extraoflags = 0); + + /** + * Create an SGMMapFile from an existing, open file-descriptor + */ + SGMMapFile( int existingFd ); + + /** Destructor */ + virtual ~SGMMapFile(); + + // open the file based on specified direction + bool open( const SGPath& file, const SGProtocolDir dir ); + bool open( const SGProtocolDir dir ); + + // read a block of data of specified size + int read( char *buf, int length ); + + // read a line of data, length is max size of input buffer + int readline( char *buf, int length ); + + // reads the whole file into a buffer + // note: this really defeats the purpose of mmapping a file + std::string read_all(); + + inline const char* get() { return buffer; } + + inline size_t get_size() { return size; } + + // write data to a file + int write( const char *buf, const int length ); + + // write null terminated string to a file + int writestring( const char *str ); + + // close file + bool close(); + + /** @return the name of the file being manipulated. */ + std::string get_file_name() const { return file_name.utf8Str(); } + + /** @return true of eof conditions exists */ + virtual bool eof() const { return eof_flag; }; +}; + +#endif // _SG_MMAP_HXX