Compare commits

..

14 Commits

Author SHA1 Message Date
Automatic Release Builder
332d9dfadb new version: 2020.3.8 2021-03-24 11:08:07 +00:00
Automatic Release Builder
c05802a498 new version: 2020.3.8 2021-03-24 11:04:18 +00:00
James Turner
0970bb1be2 Fix for local particle update
See issue at:
https://sourceforge.net/p/flightgear/codetickets/2568/
2021-03-24 09:59:13 +00:00
James Turner
c9d83fab6c TerraSync: allow an explicit osm2city server
Fixes an error case where a manual TerraSync server is specified; we
would attempt to use an empty string as the OSM2City server, with
hilarious consequences.

Sentry-Id: FLIGHTGEAR-NCZ
2021-03-16 20:14:34 +00:00
James Turner
edcce32f24 Remove stray include of std::filesystem 2021-03-07 10:53:36 +00:00
Automatic Release Builder
68d265f0e7 Set correct version files 2021-03-04 22:02:11 +00:00
Automatic Release Builder
b985bb5757 new version: 2020.3.7 2021-03-01 12:11:33 +00:00
legoboyvdlp R
f030816385 TerraSync: counter to fetch number of bytes which have been extracted from a tarball file 2021-03-01 09:56:48 +00:00
James Turner
6f9f694eff HTTPRepository: improving handling of archives
Avoid hard-coding the archive extension, and ensure the extracted
archive directory is not orphaned on update. Finally, use the
literal filename in the .dirindex when computing the hash, rather
than adding a .tgz extension.
2021-03-01 09:56:48 +00:00
James Turner
a0d7f0e172 TerraSync: allow separate OSM2City server
Lookup OSM2City using a separate service profile, and use this server
for requests for OSM2City suffix dirs.
2021-03-01 09:51:24 +00:00
James Turner
9f98e438cb Fix HTTPClient reset() behaviour
Ensure all data members are correctly re-initialzied when doing a reset.

This shoed up as negative ‘bytes downloaded’ counts after a TerraSync
abandon and retry.
2021-02-22 11:29:05 +00:00
James Turner
f029ca7b64 Particles: replace use of ParticleSystemUpdater
Extend our own particle manager to replace the OSG particle system
updater. This fixes thread-safety and also timing (better match to
simulation dt values). We also use weak pointers (observer_ptr in
OSG terminology) to ensure particle systems are released once their
frame is gone.
2021-02-22 11:29:05 +00:00
James Turner
ca6c6dd6d3 Text-animation: fix missing encoding specification
Ensure we can pass full UTF-8 strings into text animations. Will consider
for back-port after discussion on the devel list.

Ticket-Id: https://sourceforge.net/p/flightgear/codetickets/2512/
2021-02-22 11:10:37 +00:00
Automatic Release Builder
4ba4ea5602 Adding code to see if it improves crash on computeHash.
Also add a test case to verify behaviour on empty files is correct.

Sentry-Id: FLIGHTGEAR-AY2
2021-02-04 10:28:08 +00:00
22 changed files with 1231 additions and 640 deletions

View File

@@ -286,6 +286,7 @@ else()
find_package(ZLIB 1.2.4 REQUIRED)
endif()
find_package(LibLZMA REQUIRED)
find_package(CURL REQUIRED)
if (SYSTEM_EXPAT)

View File

@@ -0,0 +1,124 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
FindLibLZMA
-----------
Find LZMA compression algorithm headers and library.
Imported Targets
^^^^^^^^^^^^^^^^
This module defines :prop_tgt:`IMPORTED` target ``LibLZMA::LibLZMA``, if
liblzma has been found.
Result variables
^^^^^^^^^^^^^^^^
This module will set the following variables in your project:
``LIBLZMA_FOUND``
True if liblzma headers and library were found.
``LIBLZMA_INCLUDE_DIRS``
Directory where liblzma headers are located.
``LIBLZMA_LIBRARIES``
Lzma libraries to link against.
``LIBLZMA_HAS_AUTO_DECODER``
True if lzma_auto_decoder() is found (required).
``LIBLZMA_HAS_EASY_ENCODER``
True if lzma_easy_encoder() is found (required).
``LIBLZMA_HAS_LZMA_PRESET``
True if lzma_lzma_preset() is found (required).
``LIBLZMA_VERSION_MAJOR``
The major version of lzma
``LIBLZMA_VERSION_MINOR``
The minor version of lzma
``LIBLZMA_VERSION_PATCH``
The patch version of lzma
``LIBLZMA_VERSION_STRING``
version number as a string (ex: "5.0.3")
#]=======================================================================]
find_path(LIBLZMA_INCLUDE_DIR lzma.h )
if(NOT LIBLZMA_LIBRARY)
find_library(LIBLZMA_LIBRARY_RELEASE NAMES lzma liblzma NAMES_PER_DIR PATH_SUFFIXES lib)
find_library(LIBLZMA_LIBRARY_DEBUG NAMES lzmad liblzmad NAMES_PER_DIR PATH_SUFFIXES lib)
include(SelectLibraryConfigurations)
select_library_configurations(LIBLZMA)
else()
file(TO_CMAKE_PATH "${LIBLZMA_LIBRARY}" LIBLZMA_LIBRARY)
endif()
if(LIBLZMA_INCLUDE_DIR AND EXISTS "${LIBLZMA_INCLUDE_DIR}/lzma/version.h")
file(STRINGS "${LIBLZMA_INCLUDE_DIR}/lzma/version.h" LIBLZMA_HEADER_CONTENTS REGEX "#define LZMA_VERSION_[A-Z]+ [0-9]+")
string(REGEX REPLACE ".*#define LZMA_VERSION_MAJOR ([0-9]+).*" "\\1" LIBLZMA_VERSION_MAJOR "${LIBLZMA_HEADER_CONTENTS}")
string(REGEX REPLACE ".*#define LZMA_VERSION_MINOR ([0-9]+).*" "\\1" LIBLZMA_VERSION_MINOR "${LIBLZMA_HEADER_CONTENTS}")
string(REGEX REPLACE ".*#define LZMA_VERSION_PATCH ([0-9]+).*" "\\1" LIBLZMA_VERSION_PATCH "${LIBLZMA_HEADER_CONTENTS}")
set(LIBLZMA_VERSION_STRING "${LIBLZMA_VERSION_MAJOR}.${LIBLZMA_VERSION_MINOR}.${LIBLZMA_VERSION_PATCH}")
unset(LIBLZMA_HEADER_CONTENTS)
endif()
# We're using new code known now as XZ, even library still been called LZMA
# it can be found in http://tukaani.org/xz/
# Avoid using old codebase
if (LIBLZMA_LIBRARY)
include(CheckLibraryExists)
set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET})
set(CMAKE_REQUIRED_QUIET ${LibLZMA_FIND_QUIETLY})
if(NOT LIBLZMA_LIBRARY_RELEASE AND NOT LIBLZMA_LIBRARY_DEBUG)
set(LIBLZMA_LIBRARY_check ${LIBLZMA_LIBRARY})
elseif(LIBLZMA_LIBRARY_RELEASE)
set(LIBLZMA_LIBRARY_check ${LIBLZMA_LIBRARY_RELEASE})
elseif(LIBLZMA_LIBRARY_DEBUG)
set(LIBLZMA_LIBRARY_check ${LIBLZMA_LIBRARY_DEBUG})
endif()
CHECK_LIBRARY_EXISTS(${LIBLZMA_LIBRARY_check} lzma_auto_decoder "" LIBLZMA_HAS_AUTO_DECODER)
CHECK_LIBRARY_EXISTS(${LIBLZMA_LIBRARY_check} lzma_easy_encoder "" LIBLZMA_HAS_EASY_ENCODER)
CHECK_LIBRARY_EXISTS(${LIBLZMA_LIBRARY_check} lzma_lzma_preset "" LIBLZMA_HAS_LZMA_PRESET)
unset(LIBLZMA_LIBRARY_check)
set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_SAVE})
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LibLZMA REQUIRED_VARS LIBLZMA_LIBRARY
LIBLZMA_INCLUDE_DIR
LIBLZMA_HAS_AUTO_DECODER
LIBLZMA_HAS_EASY_ENCODER
LIBLZMA_HAS_LZMA_PRESET
VERSION_VAR LIBLZMA_VERSION_STRING
)
mark_as_advanced( LIBLZMA_INCLUDE_DIR LIBLZMA_LIBRARY )
if (LIBLZMA_FOUND)
set(LIBLZMA_LIBRARIES ${LIBLZMA_LIBRARY})
set(LIBLZMA_INCLUDE_DIRS ${LIBLZMA_INCLUDE_DIR})
if(NOT TARGET LibLZMA::LibLZMA)
add_library(LibLZMA::LibLZMA UNKNOWN IMPORTED)
set_target_properties(LibLZMA::LibLZMA PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${LIBLZMA_INCLUDE_DIR}
IMPORTED_LINK_INTERFACE_LANGUAGES C)
if(LIBLZMA_LIBRARY_RELEASE)
set_property(TARGET LibLZMA::LibLZMA APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(LibLZMA::LibLZMA PROPERTIES
IMPORTED_LOCATION_RELEASE "${LIBLZMA_LIBRARY_RELEASE}")
endif()
if(LIBLZMA_LIBRARY_DEBUG)
set_property(TARGET LibLZMA::LibLZMA APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(LibLZMA::LibLZMA PROPERTIES
IMPORTED_LOCATION_DEBUG "${LIBLZMA_LIBRARY_DEBUG}")
endif()
if(NOT LIBLZMA_LIBRARY_RELEASE AND NOT LIBLZMA_LIBRARY_DEBUG)
set_target_properties(LibLZMA::LibLZMA PROPERTIES
IMPORTED_LOCATION "${LIBLZMA_LIBRARY}")
endif()
endif()
endif ()

View File

@@ -1,6 +1,7 @@
include(CMakeFindDependencyMacro)
find_dependency(ZLIB)
find_dependency(LibLZMA)
find_dependency(Threads)
# OSG

View File

@@ -1 +1 @@
2020.3.6
2020.3.8

View File

@@ -168,7 +168,9 @@ target_link_libraries(SimGearCore PRIVATE
${COCOA_LIBRARY}
${CURL_LIBRARIES}
${WINSOCK_LIBRARY}
${SHLWAPI_LIBRARY})
${SHLWAPI_LIBRARY}
LibLZMA::LibLZMA
)
if(SYSTEM_EXPAT)
target_link_libraries(SimGearCore PRIVATE ${EXPAT_LIBRARIES})

View File

@@ -0,0 +1,90 @@
// Copyright (C) 2021 James Turner - <james@flightgear.org>
//
// 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 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.
//
#pragma once
#include "untar.hxx"
namespace simgear {
class ArchiveExtractorPrivate
{
public:
ArchiveExtractorPrivate(ArchiveExtractor* o) : outer(o)
{
assert(outer);
}
virtual ~ArchiveExtractorPrivate() = default;
typedef enum {
INVALID = 0,
READING_HEADER,
READING_FILE,
READING_PADDING,
READING_PAX_GLOBAL_ATTRIBUTES,
READING_PAX_FILE_ATTRIBUTES,
PRE_END_OF_ARCHVE,
END_OF_ARCHIVE,
ERROR_STATE, ///< states above this are error conditions
BAD_ARCHIVE,
BAD_DATA,
FILTER_STOPPED
} State;
State state = INVALID;
ArchiveExtractor* outer = nullptr;
virtual void extractBytes(const uint8_t* bytes, size_t count) = 0;
virtual void flush() = 0;
SGPath extractRootPath()
{
return outer->_rootPath;
}
ArchiveExtractor::PathResult filterPath(std::string& pathToExtract)
{
return outer->filterPath(pathToExtract);
}
bool isSafePath(const std::string& p) const
{
if (p.empty()) {
return false;
}
// reject absolute paths
if (p.at(0) == '/') {
return false;
}
// reject paths containing '..'
size_t doubleDot = p.find("..");
if (doubleDot != std::string::npos) {
return false;
}
// on POSIX could use realpath to sanity check
return true;
}
};
} // namespace simgear

View File

@@ -81,21 +81,8 @@ void Client::ClientPrivate::createCurlMulti() {
#endif
}
Client::Client() :
d(new ClientPrivate)
Client::Client()
{
d->proxyPort = 0;
d->maxConnections = 4;
d->maxHostConnections = 4;
d->bytesTransferred = 0;
d->lastTransferRate = 0;
d->timeTransferSample.stamp();
d->totalBytesDownloaded = 0;
d->maxPipelineDepth = 5;
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
d->tlsCertificatePath = SGPath::fromEnv("SIMGEAR_TLS_CERT_PATH");
static bool didInitCurlGlobal = false;
static std::mutex initMutex;
@@ -105,7 +92,7 @@ Client::Client() :
didInitCurlGlobal = true;
}
d->createCurlMulti();
reset();
}
Client::~Client()
@@ -139,8 +126,21 @@ void Client::setMaxPipelineDepth(unsigned int depth)
void Client::reset()
{
curl_multi_cleanup(d->curlMulti);
if (d.get()) {
curl_multi_cleanup(d->curlMulti);
}
d.reset(new ClientPrivate);
d->proxyPort = 0;
d->maxConnections = 4;
d->maxHostConnections = 4;
d->bytesTransferred = 0;
d->lastTransferRate = 0;
d->timeTransferSample.stamp();
d->totalBytesDownloaded = 0;
d->maxPipelineDepth = 5;
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
d->tlsCertificatePath = SGPath::fromEnv("SIMGEAR_TLS_CERT_PATH");
d->createCurlMulti();
}

View File

@@ -20,15 +20,15 @@
#include "HTTPRepository.hxx"
#include <iostream>
#include <cassert>
#include <algorithm>
#include <sstream>
#include <cassert>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <limits>
#include <map>
#include <set>
#include <fstream>
#include <limits>
#include <cstdlib>
#include <sstream>
#include <fcntl.h>
@@ -143,8 +143,8 @@ class HTTPDirectory
HTTPRepository::EntryType type;
std::string name, hash;
size_t sizeInBytes = 0;
SGPath path; // absolute path on disk
size_t sizeInBytes = 0;
SGPath path; // absolute path on disk
};
typedef std::vector<ChildInfo> ChildInfoList;
@@ -218,53 +218,66 @@ public:
return;
}
char* buf = nullptr;
size_t bufSize = 0;
char* buf = nullptr;
size_t bufSize = 0;
for (auto &child : children) {
if (child.type != HTTPRepository::FileType)
for (auto& child : children) {
if (child.type != HTTPRepository::FileType)
continue;
if (child.path.exists())
continue;
SGPath cp = _repository->installedCopyPath;
cp.append(relativePath());
cp.append(child.name);
if (!cp.exists()) {
continue;
}
SGBinaryFile src(cp);
SGBinaryFile dst(child.path);
src.open(SG_IO_IN);
dst.open(SG_IO_OUT);
if (bufSize < cp.sizeInBytes()) {
bufSize = cp.sizeInBytes();
free(buf);
buf = (char*)malloc(bufSize);
if (!buf) {
continue;
}
}
if (child.path.exists())
continue;
src.read(buf, cp.sizeInBytes());
dst.write(buf, cp.sizeInBytes());
src.close();
dst.close();
SGPath cp = _repository->installedCopyPath;
cp.append(relativePath());
cp.append(child.name);
if (!cp.exists()) {
continue;
}
// reset caching
child.path.set_cached(false);
child.path.set_cached(true);
SGBinaryFile src(cp);
SGBinaryFile dst(child.path);
src.open(SG_IO_IN);
dst.open(SG_IO_OUT);
if (bufSize < cp.sizeInBytes()) {
bufSize = cp.sizeInBytes();
free(buf);
buf = (char *)malloc(bufSize);
if (!buf) {
continue;
}
}
src.read(buf, cp.sizeInBytes());
dst.write(buf, cp.sizeInBytes());
src.close();
dst.close();
// reset caching
child.path.set_cached(false);
child.path.set_cached(true);
std::string hash = computeHashForPath(child.path);
updatedFileContents(child.path, hash);
std::string hash = computeHashForPath(child.path);
updatedFileContents(child.path, hash);
}
free(buf);
}
/// helper to check and erase 'fooBar' from paths, if passed fooBar.zip, fooBar.tgz, etc.
void removeExtractedDirectoryFromList(PathList& paths, const std::string& tarballName)
{
const auto directoryName = SGPath::fromUtf8(tarballName).file_base();
auto it = std::find_if(paths.begin(), paths.end(), [directoryName](const SGPath& p) {
return p.isDir() && (p.file() == directoryName);
});
if (it != paths.end()) {
paths.erase(it);
}
}
void updateChildrenBasedOnHash()
{
using SAct = HTTPRepository::SyncAction;
@@ -298,6 +311,11 @@ public:
orphans.end());
}
// ensure the extracted directory corresponding to a tarball, is *not* considered an orphan
if (c.type == HTTPRepository::TarballType) {
removeExtractedDirectoryFromList(orphans, c.name);
}
if (_repository->syncPredicate) {
const auto pathOnDisk = isNew ? absolutePath() / c.name : *p;
// never handle deletes here, do them at the end
@@ -320,7 +338,6 @@ public:
toBeUpdated.push_back(c);
} else {
// File/Directory exists and hash is valid.
if (c.type == HTTPRepository::DirectoryType) {
// If it's a directory,perform a recursive check.
HTTPDirectory *childDir = childDirectory(c.name);
@@ -432,6 +449,7 @@ public:
return;
}
compressedBytes = p.sizeInBytes();
buffer = (uint8_t *)malloc(bufferSize);
}
@@ -444,6 +462,7 @@ public:
}
size_t rd = file.read((char*)buffer, bufferSize);
repo->bytesExtracted += rd;
extractor.extractBytes(buffer, rd);
if (file.eof()) {
@@ -470,8 +489,14 @@ public:
return HTTPRepoPrivate::ProcessContinue;
}
size_t archiveSizeBytes() const
{
return compressedBytes;
}
~ArchiveExtractTask() { free(buffer); }
private:
// intentionally small so we extract incrementally on Windows
// where Defender throttles many small files, sorry
@@ -483,6 +508,7 @@ public:
uint8_t *buffer = nullptr;
SGBinaryFile file;
ArchiveExtractor extractor;
std::size_t compressedBytes;
};
using ArchiveExtractTaskPtr = std::shared_ptr<ArchiveExtractTask>;
@@ -509,42 +535,42 @@ public:
_repository->totalDownloaded += sz;
SGPath p = SGPath(absolutePath(), file);
if ((p.extension() == "tgz") || (p.extension() == "zip")) {
// We require that any compressed files have the same filename as the file or directory
// they expand to, so we can remove the old file/directory before extracting the new
// data.
SGPath removePath = SGPath(p.base());
bool pathAvailable = true;
if (removePath.exists()) {
if (removePath.isDir()) {
simgear::Dir pd(removePath);
pathAvailable = pd.removeChildren();
} else {
pathAvailable = removePath.remove();
if (it->type == HTTPRepository::TarballType) {
// We require that any compressed files have the same filename as the file or directory
// they expand to, so we can remove the old file/directory before extracting the new
// data.
SGPath removePath = SGPath(p.base());
bool pathAvailable = true;
if (removePath.exists()) {
if (removePath.isDir()) {
simgear::Dir pd(removePath);
pathAvailable = pd.removeChildren();
} else {
pathAvailable = removePath.remove();
}
}
}
if (pathAvailable) {
// we use a Task helper to extract tarballs incrementally.
// without this, archive extraction blocks here, which
// prevents other repositories downloading / updating.
// Unfortunately due Windows AV (Defender, etc) we cna block
// here for many minutes.
if (pathAvailable) {
// we use a Task helper to extract tarballs incrementally.
// without this, archive extraction blocks here, which
// prevents other repositories downloading / updating.
// Unfortunately due Windows AV (Defender, etc) we cna block
// here for many minutes.
// use a lambda to own this shared_ptr; this means when the
// lambda is destroyed, the ArchiveExtraTask will get
// cleaned up.
ArchiveExtractTaskPtr t =
std::make_shared<ArchiveExtractTask>(p, _relativePath);
auto cb = [t](HTTPRepoPrivate *repo) {
return t->run(repo);
};
_repository->addTask(cb);
} else {
SG_LOG(SG_TERRASYNC, SG_ALERT, "Unable to remove old file/directory " << removePath);
} // of pathAvailable
} // of handling tgz files
// use a lambda to own this shared_ptr; this means when the
// lambda is destroyed, the ArchiveExtraTask will get
// cleaned up.
ArchiveExtractTaskPtr t =
std::make_shared<ArchiveExtractTask>(p, _relativePath);
auto cb = [t](HTTPRepoPrivate* repo) {
return t->run(repo);
};
_repository->bytesToExtract += t->archiveSizeBytes();
_repository->addTask(cb);
} else {
SG_LOG(SG_TERRASYNC, SG_ALERT, "Unable to remove old file/directory " << removePath);
} // of pathAvailable
} // of handling archive files
} // of hash matches
} // of found in child list
}
@@ -736,11 +762,9 @@ private:
std::string hashForChild(const ChildInfo& child) const
{
SGPath p(child.path);
if (child.type == HTTPRepository::DirectoryType)
p.append(".dirindex");
if (child.type == HTTPRepository::TarballType)
p.concat(
".tgz"); // For tarballs the hash is against the tarball file itself
if (child.type == HTTPRepository::DirectoryType) {
p.append(".dirindex");
}
return hashForPath(p);
}
@@ -937,6 +961,11 @@ size_t HTTPRepository::bytesDownloaded() const
return result;
}
size_t HTTPRepository::bytesToExtract() const
{
return _d->bytesToExtract - _d->bytesExtracted;
}
void HTTPRepository::setInstalledCopyPath(const SGPath& copyPath)
{
_d->installedCopyPath = copyPath;
@@ -1293,7 +1322,7 @@ HTTPRepository::failure() const
}
Dir dir(absPath);
bool result = dir.remove(true);
bool result = dir.remove(true);
return result;
}

View File

@@ -73,6 +73,8 @@ public:
virtual size_t bytesDownloaded() const;
virtual size_t bytesToExtract() const;
/**
* optionally provide the location of an installer copy of this
* repository. When a file is missing it will be copied from this tree.

View File

@@ -60,22 +60,23 @@ public:
HTTPRepository::FailureVec failures;
int maxPermittedFailures = 16;
HTTPRepoPrivate(HTTPRepository *parent)
: p(parent), isUpdating(false), status(HTTPRepository::REPO_NO_ERROR),
totalDownloaded(0) {
;
HTTPRepoPrivate(HTTPRepository* parent)
: p(parent)
{
}
~HTTPRepoPrivate();
HTTPRepository *p; // link back to outer
HTTP::Client *http;
HTTP::Client* http = nullptr;
std::string baseUrl;
SGPath basePath;
bool isUpdating;
HTTPRepository::ResultCode status;
bool isUpdating = false;
HTTPRepository::ResultCode status = HTTPRepository::REPO_NO_ERROR;
HTTPDirectory_ptr rootDir;
size_t totalDownloaded;
size_t totalDownloaded = 0;
size_t bytesToExtract = 0;
size_t bytesExtracted = 0;
HTTPRepository::SyncPredicate syncPredicate;
HTTP::Request_ptr updateFile(HTTPDirectory *dir, const std::string &name,

View File

@@ -82,7 +82,9 @@ std::string SGFile::computeHash()
[](char* p) { free(p); }};
if (!buf) {
SG_LOG(SG_IO, SG_ALERT, "Failed to malloc buffer for SHA1 check");
// @TODO report out of memory error
SG_LOG(SG_IO, SG_ALERT, "Failed to malloc buffer for SHA1 check:" << file_name);
return {};
}
size_t readLen;

BIN
simgear/io/test.tar.xz Normal file

Binary file not shown.

View File

@@ -901,6 +901,27 @@ void testPersistentSocketFailure(HTTP::Client *cl) {
verifyRequestCount("dirD/subdirDB/fileDBA", 1);
}
void testHashOnEmptyFile()
{
std::unique_ptr<HTTPRepository> repo;
SGPath p(simgear::Dir::current().path());
p.append("sgfile_compute_hash");
simgear::Dir pd(p);
if (pd.exists()) {
pd.removeChildren();
} else {
pd.create(0700);
}
SGPath fPath = p / "test_empty_file";
SGFile file(fPath);
file.open(SG_IO_OUT);
file.close();
const auto hash = file.computeHash();
}
int main(int argc, char* argv[])
{
sglog().setLogLevels( SG_ALL, SG_INFO );
@@ -951,6 +972,8 @@ int main(int argc, char* argv[])
testRetryAfterSocketFailure(&cl);
testPersistentSocketFailure(&cl);
testHashOnEmptyFile();
std::cout << "all tests passed ok" << std::endl;
return 0;
}

View File

@@ -32,7 +32,7 @@ void testTarGz()
uint8_t* buf = (uint8_t*) alloca(8192);
size_t bufSize = f.read((char*) buf, 8192);
SG_VERIFY(ArchiveExtractor::determineType(buf, bufSize) == ArchiveExtractor::TarData);
SG_VERIFY(ArchiveExtractor::determineType(buf, bufSize) == ArchiveExtractor::GZData);
f.close();
}
@@ -174,6 +174,34 @@ void testPAXAttributes()
}
void testExtractXZ()
{
SGPath p = SGPath(SRC_DIR);
p.append("test.tar.xz");
SGBinaryFile f(p);
f.open(SG_IO_IN);
SGPath extractDir = simgear::Dir::current().path() / "test_extract_xz";
simgear::Dir pd(extractDir);
pd.removeChildren();
ArchiveExtractor ex(extractDir);
uint8_t* buf = (uint8_t*)alloca(128);
while (!f.eof()) {
size_t bufSize = f.read((char*)buf, 128);
ex.extractBytes(buf, bufSize);
}
ex.flush();
SG_VERIFY(ex.isAtEndOfArchive());
SG_VERIFY(ex.hasError() == false);
SG_VERIFY((extractDir / "testDir/hello.c").exists());
SG_VERIFY((extractDir / "testDir/foo.txt").exists());
}
int main(int ac, char ** av)
{
testTarGz();
@@ -181,7 +209,8 @@ int main(int ac, char ** av)
testFilterTar();
testExtractStreamed();
testExtractZip();
testExtractXZ();
// disabled to avoiding checking in large PAX archive
// testPAXAttributes();

View File

@@ -38,82 +38,11 @@
#include <simgear/package/unzip.h>
#include <simgear/structure/exception.hxx>
#include "ArchiveExtractor_private.hxx"
namespace simgear
{
class ArchiveExtractorPrivate
{
public:
ArchiveExtractorPrivate(ArchiveExtractor* o) :
outer(o)
{
assert(outer);
}
virtual ~ArchiveExtractorPrivate() = default;
typedef enum {
INVALID = 0,
READING_HEADER,
READING_FILE,
READING_PADDING,
READING_PAX_GLOBAL_ATTRIBUTES,
READING_PAX_FILE_ATTRIBUTES,
PRE_END_OF_ARCHVE,
END_OF_ARCHIVE,
ERROR_STATE, ///< states above this are error conditions
BAD_ARCHIVE,
BAD_DATA,
FILTER_STOPPED
} State;
State state = INVALID;
ArchiveExtractor* outer = nullptr;
virtual void extractBytes(const uint8_t* bytes, size_t count) = 0;
virtual void flush() = 0;
SGPath extractRootPath()
{
return outer->_rootPath;
}
ArchiveExtractor::PathResult filterPath(std::string& pathToExtract)
{
return outer->filterPath(pathToExtract);
}
bool isSafePath(const std::string& p) const
{
if (p.empty()) {
return false;
}
// reject absolute paths
if (p.at(0) == '/') {
return false;
}
// reject paths containing '..'
size_t doubleDot = p.find("..");
if (doubleDot != std::string::npos) {
return false;
}
// on POSIX could use realpath to sanity check
return true;
}
};
///////////////////////////////////////////////////////////////////////////////////////////////////
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
const int ZLIB_INFLATE_WINDOW_BITS = MAX_WBITS;
const int ZLIB_DECODE_GZIP_HEADER = 16;
/* tar Header Block, from POSIX 1003.1-1990. */
typedef struct
{
@@ -153,320 +82,410 @@ typedef struct
#define FIFOTYPE '6' /* FIFO special */
#define CONTTYPE '7' /* reserved */
const char PAX_GLOBAL_HEADER = 'g';
const char PAX_FILE_ATTRIBUTES = 'x';
const char PAX_GLOBAL_HEADER = 'g';
const char PAX_FILE_ATTRIBUTES = 'x';
class TarExtractorPrivate : public ArchiveExtractorPrivate
///////////////////////////////////////////////////////////////////////////////////////////////////
const int ZLIB_DECOMPRESS_BUFFER_SIZE = 32 * 1024;
const int ZLIB_INFLATE_WINDOW_BITS = MAX_WBITS;
const int ZLIB_DECODE_GZIP_HEADER = 16;
/* tar Header Block, from POSIX 1003.1-1990. */
class TarExtractorPrivate : public ArchiveExtractorPrivate
{
public:
union {
UstarHeaderBlock header;
uint8_t headerBytes[TAR_HEADER_BLOCK_SIZE];
};
size_t bytesRemaining;
std::unique_ptr<SGFile> currentFile;
size_t currentFileSize;
uint8_t* headerPtr;
bool skipCurrentEntry = false;
std::string paxAttributes;
std::string paxPathName;
TarExtractorPrivate(ArchiveExtractor* o) : ArchiveExtractorPrivate(o)
{
setState(TarExtractorPrivate::READING_HEADER);
}
~TarExtractorPrivate() = default;
void readPaddingIfRequired()
{
size_t pad = currentFileSize % TAR_HEADER_BLOCK_SIZE;
if (pad) {
bytesRemaining = TAR_HEADER_BLOCK_SIZE - pad;
setState(READING_PADDING);
} else {
setState(READING_HEADER);
}
}
void checkEndOfState()
{
if (bytesRemaining > 0) {
return;
}
if (state == READING_FILE) {
if (currentFile) {
currentFile->close();
currentFile.reset();
}
readPaddingIfRequired();
} else if (state == READING_HEADER) {
processHeader();
} else if (state == PRE_END_OF_ARCHVE) {
if (headerIsAllZeros()) {
setState(END_OF_ARCHIVE);
} else {
// what does the spec say here?
}
} else if (state == READING_PAX_GLOBAL_ATTRIBUTES) {
parsePAXAttributes(true);
readPaddingIfRequired();
} else if (state == READING_PAX_FILE_ATTRIBUTES) {
parsePAXAttributes(false);
readPaddingIfRequired();
} else if (state == READING_PADDING) {
setState(READING_HEADER);
}
}
void setState(State newState)
{
if ((newState == READING_HEADER) || (newState == PRE_END_OF_ARCHVE)) {
bytesRemaining = TAR_HEADER_BLOCK_SIZE;
headerPtr = headerBytes;
}
state = newState;
}
void extractBytes(const uint8_t* bytes, size_t count) override
{
// uncompressed, just pass through directly
processBytes((const char*)bytes, count);
}
void flush() override
{
// no-op for tar files, we process everything greedily
}
void processHeader()
{
if (headerIsAllZeros()) {
if (state == PRE_END_OF_ARCHVE) {
setState(END_OF_ARCHIVE);
} else {
setState(PRE_END_OF_ARCHVE);
}
return;
}
if (strncmp(header.magic, TMAGIC, TMAGLEN) != 0) {
SG_LOG(SG_IO, SG_WARN, "Untar: magic is wrong");
state = BAD_ARCHIVE;
return;
}
skipCurrentEntry = false;
std::string tarPath = std::string(header.prefix) + std::string(header.fileName);
if (!paxPathName.empty()) {
tarPath = paxPathName;
paxPathName.clear(); // clear for next file
}
if (!isSafePath(tarPath)) {
SG_LOG(SG_IO, SG_WARN, "unsafe tar path, skipping::" << tarPath);
skipCurrentEntry = true;
}
auto result = filterPath(tarPath);
if (result == ArchiveExtractor::Stop) {
setState(FILTER_STOPPED);
return;
} else if (result == ArchiveExtractor::Skipped) {
skipCurrentEntry = true;
}
SGPath p = extractRootPath() / tarPath;
if (header.typeflag == DIRTYPE) {
if (!skipCurrentEntry) {
Dir dir(p);
dir.create(0755);
}
setState(READING_HEADER);
} else if ((header.typeflag == REGTYPE) || (header.typeflag == AREGTYPE)) {
currentFileSize = ::strtol(header.size, NULL, 8);
bytesRemaining = currentFileSize;
if (!skipCurrentEntry) {
currentFile.reset(new SGBinaryFile(p));
currentFile->open(SG_IO_OUT);
}
setState(READING_FILE);
} else if (header.typeflag == PAX_GLOBAL_HEADER) {
setState(READING_PAX_GLOBAL_ATTRIBUTES);
currentFileSize = ::strtol(header.size, NULL, 8);
bytesRemaining = currentFileSize;
paxAttributes.clear();
} else if (header.typeflag == PAX_FILE_ATTRIBUTES) {
setState(READING_PAX_FILE_ATTRIBUTES);
currentFileSize = ::strtol(header.size, NULL, 8);
bytesRemaining = currentFileSize;
paxAttributes.clear();
} else if ((header.typeflag == SYMTYPE) || (header.typeflag == LNKTYPE)) {
SG_LOG(SG_IO, SG_WARN, "Tarball contains a link or symlink, will be skipped:" << tarPath);
skipCurrentEntry = true;
setState(READING_HEADER);
} else {
SG_LOG(SG_IO, SG_WARN, "Unsupported tar file type:" << header.typeflag);
state = BAD_ARCHIVE;
}
}
void processBytes(const char* bytes, size_t count)
{
if ((state >= ERROR_STATE) || (state == END_OF_ARCHIVE)) {
return;
}
size_t curBytes = std::min(bytesRemaining, count);
if (state == READING_FILE) {
if (currentFile) {
currentFile->write(bytes, curBytes);
}
bytesRemaining -= curBytes;
} else if ((state == READING_HEADER) || (state == PRE_END_OF_ARCHVE) || (state == END_OF_ARCHIVE)) {
memcpy(headerPtr, bytes, curBytes);
bytesRemaining -= curBytes;
headerPtr += curBytes;
} else if (state == READING_PADDING) {
bytesRemaining -= curBytes;
} else if ((state == READING_PAX_FILE_ATTRIBUTES) || (state == READING_PAX_GLOBAL_ATTRIBUTES)) {
bytesRemaining -= curBytes;
paxAttributes.append(bytes, curBytes);
}
checkEndOfState();
if (count > curBytes) {
// recurse with unprocessed bytes
processBytes(bytes + curBytes, count - curBytes);
}
}
bool headerIsAllZeros() const
{
char* headerAsChar = (char*)&header;
for (size_t i = 0; i < offsetof(UstarHeaderBlock, magic); ++i) {
if (*headerAsChar++ != 0) {
return false;
}
}
return true;
}
// https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxa500/paxex.htm#paxex
void parsePAXAttributes(bool areGlobal)
{
auto lineStart = 0;
for (;;) {
auto firstSpace = paxAttributes.find(' ', lineStart);
auto firstEq = paxAttributes.find('=', lineStart);
if ((firstEq == std::string::npos) || (firstSpace == std::string::npos)) {
SG_LOG(SG_IO, SG_WARN, "Malfroemd PAX attributes in tarfile");
break;
}
uint32_t lengthBytes = std::stoul(paxAttributes.substr(lineStart, firstSpace));
uint32_t dataBytes = lengthBytes - (firstEq + 1);
std::string name = paxAttributes.substr(firstSpace + 1, firstEq - (firstSpace + 1));
// dataBytes - 1 here to trim off the trailing newline
std::string data = paxAttributes.substr(firstEq + 1, dataBytes - 1);
processPAXAttribute(areGlobal, name, data);
lineStart += lengthBytes;
}
}
void processPAXAttribute(bool isGlobalAttr, const std::string& attrName, const std::string& data)
{
if (!isGlobalAttr && (attrName == "path")) {
// data is UTF-8 encoded path name
paxPathName = data;
}
}
};
///////////////////////////////////////////////////////////////////////////////
class GZTarExtractor : public TarExtractorPrivate
{
public:
union {
UstarHeaderBlock header;
uint8_t headerBytes[TAR_HEADER_BLOCK_SIZE];
};
size_t bytesRemaining;
std::unique_ptr<SGFile> currentFile;
size_t currentFileSize;
z_stream zlibStream;
uint8_t* zlibOutput;
bool haveInitedZLib = false;
bool uncompressedData = false; // set if reading a plain .tar (not tar.gz)
uint8_t* headerPtr;
bool skipCurrentEntry = false;
std::string paxAttributes;
std::string paxPathName;
TarExtractorPrivate(ArchiveExtractor* o) :
ArchiveExtractorPrivate(o)
GZTarExtractor(ArchiveExtractor* outer) : TarExtractorPrivate(outer)
{
memset(&zlibStream, 0, sizeof(z_stream));
zlibOutput = (unsigned char*)malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
zlibStream.zalloc = Z_NULL;
zlibStream.zfree = Z_NULL;
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
zlibStream.next_out = zlibOutput;
memset(&zlibStream, 0, sizeof(z_stream));
zlibOutput = (unsigned char*)malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
zlibStream.zalloc = Z_NULL;
zlibStream.zfree = Z_NULL;
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
zlibStream.next_out = zlibOutput;
}
~TarExtractorPrivate()
~GZTarExtractor()
{
free(zlibOutput);
}
void readPaddingIfRequired()
void extractBytes(const uint8_t* bytes, size_t count) override
{
size_t pad = currentFileSize % TAR_HEADER_BLOCK_SIZE;
if (pad) {
bytesRemaining = TAR_HEADER_BLOCK_SIZE - pad;
setState(READING_PADDING);
} else {
setState(READING_HEADER);
}
}
void checkEndOfState()
{
if (bytesRemaining > 0) {
return;
}
zlibStream.next_in = (uint8_t*)bytes;
zlibStream.avail_in = count;
if (state == READING_FILE) {
if (currentFile) {
currentFile->close();
currentFile.reset();
}
readPaddingIfRequired();
} else if (state == READING_HEADER) {
processHeader();
} else if (state == PRE_END_OF_ARCHVE) {
if (headerIsAllZeros()) {
setState(END_OF_ARCHIVE);
if (!haveInitedZLib) {
// now we have data, see if we're dealing with GZ-compressed data or not
if ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)) {
// GZIP identification bytes
if (inflateInit2(&zlibStream, ZLIB_INFLATE_WINDOW_BITS | ZLIB_DECODE_GZIP_HEADER) != Z_OK) {
SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
state = TarExtractorPrivate::BAD_DATA;
return;
}
} else {
// what does the spec say here?
// set error state
state = TarExtractorPrivate::BAD_DATA;
return;
}
} else if (state == READING_PAX_GLOBAL_ATTRIBUTES) {
parsePAXAttributes(true);
readPaddingIfRequired();
} else if (state == READING_PAX_FILE_ATTRIBUTES) {
parsePAXAttributes(false);
readPaddingIfRequired();
} else if (state == READING_PADDING) {
setState(READING_HEADER);
}
}
void setState(State newState)
{
if ((newState == READING_HEADER) || (newState == PRE_END_OF_ARCHVE)) {
bytesRemaining = TAR_HEADER_BLOCK_SIZE;
headerPtr = headerBytes;
}
haveInitedZLib = true;
setState(TarExtractorPrivate::READING_HEADER);
} // of init on first-bytes case
state = newState;
}
size_t writtenSize;
// loop, running zlib() inflate and sending output bytes to
// our request body handler. Keep calling inflate until no bytes are
// written, and ZLIB has consumed all available input
do {
zlibStream.next_out = zlibOutput;
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
int result = inflate(&zlibStream, Z_NO_FLUSH);
if (result == Z_OK || result == Z_STREAM_END) {
// nothing to do
void extractBytes(const uint8_t* bytes, size_t count) override
{
zlibStream.next_in = (uint8_t*) bytes;
zlibStream.avail_in = count;
if (!haveInitedZLib) {
// now we have data, see if we're dealing with GZ-compressed data or not
if ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)) {
// GZIP identification bytes
if (inflateInit2(&zlibStream, ZLIB_INFLATE_WINDOW_BITS | ZLIB_DECODE_GZIP_HEADER) != Z_OK) {
SG_LOG(SG_IO, SG_WARN, "inflateInit2 failed");
state = TarExtractorPrivate::BAD_DATA;
return;
}
} else {
UstarHeaderBlock* header = (UstarHeaderBlock*)bytes;
if (strncmp(header->magic, TMAGIC, TMAGLEN) != 0) {
SG_LOG(SG_IO, SG_WARN, "didn't find tar magic in header");
state = TarExtractorPrivate::BAD_DATA;
return;
}
uncompressedData = true;
}
haveInitedZLib = true;
setState(TarExtractorPrivate::READING_HEADER);
} // of init on first-bytes case
if (uncompressedData) {
processBytes((const char*) bytes, count);
} else {
size_t writtenSize;
// loop, running zlib() inflate and sending output bytes to
// our request body handler. Keep calling inflate until no bytes are
// written, and ZLIB has consumed all available input
do {
zlibStream.next_out = zlibOutput;
zlibStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
int result = inflate(&zlibStream, Z_NO_FLUSH);
if (result == Z_OK || result == Z_STREAM_END) {
// nothing to do
}
else if (result == Z_BUF_ERROR) {
// transient error, fall through
}
else {
// _error = result;
SG_LOG(SG_IO, SG_WARN, "Permanent ZLib error:" << zlibStream.msg);
state = TarExtractorPrivate::BAD_DATA;
return;
}
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - zlibStream.avail_out;
if (writtenSize > 0) {
processBytes((const char*) zlibOutput, writtenSize);
}
if (result == Z_STREAM_END) {
break;
}
} while ((zlibStream.avail_in > 0) || (writtenSize > 0));
} // of Zlib-compressed data
}
void flush() override
{
// no-op for tar files, we process everything greedily
}
void processHeader()
{
if (headerIsAllZeros()) {
if (state == PRE_END_OF_ARCHVE) {
setState(END_OF_ARCHIVE);
} else if (result == Z_BUF_ERROR) {
// transient error, fall through
} else {
setState(PRE_END_OF_ARCHVE);
// _error = result;
SG_LOG(SG_IO, SG_WARN, "Permanent ZLib error:" << zlibStream.msg);
state = TarExtractorPrivate::BAD_DATA;
return;
}
return;
}
if (strncmp(header.magic, TMAGIC, TMAGLEN) != 0) {
SG_LOG(SG_IO, SG_WARN, "Untar: magic is wrong");
state = BAD_ARCHIVE;
return;
}
skipCurrentEntry = false;
std::string tarPath = std::string(header.prefix) + std::string(header.fileName);
if (!paxPathName.empty()) {
tarPath = paxPathName;
paxPathName.clear(); // clear for next file
}
if (!isSafePath(tarPath)) {
SG_LOG(SG_IO, SG_WARN, "unsafe tar path, skipping::" << tarPath);
skipCurrentEntry = true;
}
auto result = filterPath(tarPath);
if (result == ArchiveExtractor::Stop) {
setState(FILTER_STOPPED);
return;
} else if (result == ArchiveExtractor::Skipped) {
skipCurrentEntry = true;
}
SGPath p = extractRootPath() / tarPath;
if (header.typeflag == DIRTYPE) {
if (!skipCurrentEntry) {
Dir dir(p);
dir.create(0755);
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - zlibStream.avail_out;
if (writtenSize > 0) {
processBytes((const char*)zlibOutput, writtenSize);
}
setState(READING_HEADER);
} else if ((header.typeflag == REGTYPE) || (header.typeflag == AREGTYPE)) {
currentFileSize = ::strtol(header.size, NULL, 8);
bytesRemaining = currentFileSize;
if (!skipCurrentEntry) {
currentFile.reset(new SGBinaryFile(p));
currentFile->open(SG_IO_OUT);
}
setState(READING_FILE);
} else if (header.typeflag == PAX_GLOBAL_HEADER) {
setState(READING_PAX_GLOBAL_ATTRIBUTES);
currentFileSize = ::strtol(header.size, NULL, 8);
bytesRemaining = currentFileSize;
paxAttributes.clear();
} else if (header.typeflag == PAX_FILE_ATTRIBUTES) {
setState(READING_PAX_FILE_ATTRIBUTES);
currentFileSize = ::strtol(header.size, NULL, 8);
bytesRemaining = currentFileSize;
paxAttributes.clear();
} else if ((header.typeflag == SYMTYPE) || (header.typeflag == LNKTYPE)) {
SG_LOG(SG_IO, SG_WARN, "Tarball contains a link or symlink, will be skipped:" << tarPath);
skipCurrentEntry = true;
setState(READING_HEADER);
} else {
SG_LOG(SG_IO, SG_WARN, "Unsupported tar file type:" << header.typeflag);
state = BAD_ARCHIVE;
}
}
void processBytes(const char* bytes, size_t count)
{
if ((state >= ERROR_STATE) || (state == END_OF_ARCHIVE)) {
return;
}
size_t curBytes = std::min(bytesRemaining, count);
if (state == READING_FILE) {
if (currentFile) {
currentFile->write(bytes, curBytes);
}
bytesRemaining -= curBytes;
} else if ((state == READING_HEADER) || (state == PRE_END_OF_ARCHVE) || (state == END_OF_ARCHIVE)) {
memcpy(headerPtr, bytes, curBytes);
bytesRemaining -= curBytes;
headerPtr += curBytes;
} else if (state == READING_PADDING) {
bytesRemaining -= curBytes;
} else if ((state == READING_PAX_FILE_ATTRIBUTES) || (state == READING_PAX_GLOBAL_ATTRIBUTES)) {
bytesRemaining -= curBytes;
paxAttributes.append(bytes, curBytes);
}
checkEndOfState();
if (count > curBytes) {
// recurse with unprocessed bytes
processBytes(bytes + curBytes, count - curBytes);
}
}
bool headerIsAllZeros() const
{
char* headerAsChar = (char*) &header;
for (size_t i=0; i < offsetof(UstarHeaderBlock, magic); ++i) {
if (*headerAsChar++ != 0) {
return false;
}
}
return true;
}
// https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxa500/paxex.htm#paxex
void parsePAXAttributes(bool areGlobal)
{
auto lineStart = 0;
for (;;) {
auto firstSpace = paxAttributes.find(' ', lineStart);
auto firstEq = paxAttributes.find('=', lineStart);
if ((firstEq == std::string::npos) || (firstSpace == std::string::npos)) {
SG_LOG(SG_IO, SG_WARN, "Malfroemd PAX attributes in tarfile");
if (result == Z_STREAM_END) {
break;
}
uint32_t lengthBytes = std::stoul(paxAttributes.substr(lineStart, firstSpace));
uint32_t dataBytes = lengthBytes - (firstEq + 1);
std::string name = paxAttributes.substr(firstSpace+1, firstEq - (firstSpace + 1));
// dataBytes - 1 here to trim off the trailing newline
std::string data = paxAttributes.substr(firstEq+1, dataBytes - 1);
processPAXAttribute(areGlobal, name, data);
lineStart += lengthBytes;
}
}
void processPAXAttribute(bool isGlobalAttr, const std::string& attrName, const std::string& data)
{
if (!isGlobalAttr && (attrName == "path")) {
// data is UTF-8 encoded path name
paxPathName = data;
}
} while ((zlibStream.avail_in > 0) || (writtenSize > 0));
}
private:
z_stream zlibStream;
uint8_t* zlibOutput;
bool haveInitedZLib = false;
};
///////////////////////////////////////////////////////////////////////////////
#if 1 || defined(HAVE_XZ)
#include <lzma.h>
class XZTarExtractor : public TarExtractorPrivate
{
public:
XZTarExtractor(ArchiveExtractor* outer) : TarExtractorPrivate(outer)
{
_xzStream = LZMA_STREAM_INIT;
_outputBuffer = (uint8_t*)malloc(ZLIB_DECOMPRESS_BUFFER_SIZE);
auto ret = lzma_stream_decoder(&_xzStream, UINT64_MAX, LZMA_TELL_ANY_CHECK);
if (ret != LZMA_OK) {
setState(BAD_ARCHIVE);
return;
}
}
~XZTarExtractor()
{
free(_outputBuffer);
}
void extractBytes(const uint8_t* bytes, size_t count) override
{
lzma_action action = LZMA_RUN;
_xzStream.next_in = bytes;
_xzStream.avail_in = count;
size_t writtenSize;
do {
_xzStream.avail_out = ZLIB_DECOMPRESS_BUFFER_SIZE;
_xzStream.next_out = _outputBuffer;
const auto ret = lzma_code(&_xzStream, action);
writtenSize = ZLIB_DECOMPRESS_BUFFER_SIZE - _xzStream.avail_out;
if (writtenSize > 0) {
processBytes((const char*)_outputBuffer, writtenSize);
}
if (ret == LZMA_GET_CHECK) {
//
} else if (ret == LZMA_STREAM_END) {
setState(END_OF_ARCHIVE);
break;
} else if (ret != LZMA_OK) {
setState(BAD_ARCHIVE);
break;
}
} while ((_xzStream.avail_in > 0) || (writtenSize > 0));
}
void flush() override
{
const auto ret = lzma_code(&_xzStream, LZMA_FINISH);
if (ret != LZMA_STREAM_END) {
setState(BAD_ARCHIVE);
}
}
private:
lzma_stream _xzStream;
uint8_t* _outputBuffer = nullptr;
};
#endif
///////////////////////////////////////////////////////////////////////////////
extern "C" {
void fill_memory_filefunc(zlib_filefunc_def*);
}
@@ -637,17 +656,19 @@ void ArchiveExtractor::extractBytes(const uint8_t* bytes, size_t count)
if (r == TarData) {
d.reset(new TarExtractorPrivate(this));
}
else if (r == ZipData) {
d.reset(new ZipExtractorPrivate(this));
}
else {
SG_LOG(SG_IO, SG_WARN, "Invalid archive type");
} else if (r == GZData) {
d.reset(new GZTarExtractor(this));
} else if (r == XZData) {
d.reset(new XZTarExtractor(this));
} else if (r == ZipData) {
d.reset(new ZipExtractorPrivate(this));
} else {
SG_LOG(SG_IO, SG_WARN, "Invalid archive type");
_invalidDataType = true;
return;
}
}
// if hit here, we created the extractor. Feed the prefbuffer
// if hit here, we created the extractor. Feed the prefbuffer
// bytes through it
d->extractBytes((uint8_t*) _prebuffer.data(), _prebuffer.size());
_prebuffer.clear();
@@ -699,9 +720,18 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::determineType(const uint8_t*
return ZipData;
}
auto r = isTarData(bytes, count);
if ((r == TarData) || (r == InsufficientData))
return r;
if (count < 6) {
return InsufficientData;
}
const uint8_t XZ_HEADER[6] = {0xFD, '7', 'z', 'X', 'Z', 0x00};
if (memcmp(bytes, XZ_HEADER, 6) == 0) {
return XZData;
}
auto r = isTarData(bytes, count);
if ((r == TarData) || (r == InsufficientData) || (r == GZData))
return r;
return Invalid;
}
@@ -714,6 +744,8 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
}
UstarHeaderBlock* header = 0;
DetermineResult result = InsufficientData;
if ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)) {
// GZIP identification bytes
z_stream z;
@@ -731,11 +763,11 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
return Invalid;
}
int result = inflate(&z, Z_SYNC_FLUSH);
if ((result == Z_OK) || (result == Z_STREAM_END)) {
int zResult = inflate(&z, Z_SYNC_FLUSH);
if ((zResult == Z_OK) || (zResult == Z_STREAM_END)) {
// all good
} else {
SG_LOG(SG_IO, SG_WARN, "isTarData: Zlib inflate failed:" << result);
SG_LOG(SG_IO, SG_WARN, "isTarData: Zlib inflate failed:" << zResult);
inflateEnd(&z);
return Invalid; // not tar data
}
@@ -748,6 +780,7 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
header = reinterpret_cast<UstarHeaderBlock*>(zlibOutput);
inflateEnd(&z);
result = GZData;
} else {
// uncompressed tar
if (count < TAR_HEADER_BLOCK_SIZE) {
@@ -755,13 +788,14 @@ ArchiveExtractor::DetermineResult ArchiveExtractor::isTarData(const uint8_t* byt
}
header = (UstarHeaderBlock*) bytes;
result = TarData;
}
if (strncmp(header->magic, TMAGIC, TMAGLEN) != 0) {
return Invalid;
}
return TarData;
return result;
}
void ArchiveExtractor::extractLocalFile(const SGPath& archiveFile)

View File

@@ -36,15 +36,16 @@ public:
ArchiveExtractor(const SGPath& rootPath);
virtual ~ArchiveExtractor();
enum DetermineResult
{
Invalid,
InsufficientData,
TarData,
ZipData
};
enum DetermineResult {
Invalid,
InsufficientData,
TarData,
ZipData,
GZData, // Gzipped-tar
XZData // XZ (aka LZMA) tar
};
static DetermineResult determineType(const uint8_t* bytes, size_t count);
static DetermineResult determineType(const uint8_t* bytes, size_t count);
/**
* @brief API to extract a local zip or tar.gz

View File

@@ -80,7 +80,7 @@ void SGText::UpdateCallback::operator()(osg::Node * node, osg::NodeVisitor *nv )
// be lazy and set the text only if the property has changed.
// update() computes the glyph representation which looks
// more expensive than a the above string compare.
text->setText( buf );
text->setText( buf, osgText::String::ENCODING_UTF8 );
text->update();
}
traverse( node, nv );

View File

@@ -36,10 +36,10 @@
#include <osgParticle/MultiSegmentPlacer>
#include <osgParticle/SectorPlacer>
#include <osgParticle/ConstantRateCounter>
#include <osgParticle/ParticleSystemUpdater>
#include <osgParticle/ParticleSystem>
#include <osgParticle/FluidProgram>
#include <osgUtil/CullVisitor>
#include <osg/Geode>
#include <osg/Group>
#include <osg/MatrixTransform>
@@ -48,67 +48,250 @@
#include <simgear/scene/model/animation.hxx>
using ParticleSystemRef = osg::ref_ptr<osgParticle::ParticleSystem>;
namespace simgear
{
class ParticlesGlobalManager::ParticlesGlobalManagerPrivate : public osg::NodeCallback
{
public:
ParticlesGlobalManagerPrivate() : _updater(new osgParticle::ParticleSystemUpdater),
_commonGeode(new osg::Geode)
ParticlesGlobalManagerPrivate();
void operator()(osg::Node* node, osg::NodeVisitor* nv) override;
// only call this with the lock held!
osg::Group* internalGetCommonRoot();
void updateParticleSystemsFromCullCallback(int currentFrameNumber, osg::NodeVisitor* nv);
void addParticleSystem(osgParticle::ParticleSystem* ps, const osg::ref_ptr<osg::Group>& frame);
void registerNewLocalParticleSystem(osg::Node* node, ParticleSystemRef ps);
void registerNewWorldParticleSystem(osg::Node* node, ParticleSystemRef ps, osg::Group* frame);
std::mutex _lock;
bool _frozen = false;
double _simulationDt = 0.0;
osg::ref_ptr<osg::Group> _commonRoot;
osg::ref_ptr<osg::Geode> _commonGeode;
osg::Vec3 _wind;
bool _enabled = true;
osg::Vec3 _gravity;
// osg::Vec3 _localWind;
SGGeod _currentPosition;
SGConstPropertyNode_ptr _enabledNode;
using ParticleSystemWeakRef = osg::observer_ptr<osgParticle::ParticleSystem>;
using ParticleSystemsWeakRefVec = std::vector<ParticleSystemWeakRef>;
using ParticleSystemsStrongRefVec = std::vector<ParticleSystemRef>;
ParticleSystemsWeakRefVec _systems;
osg::ref_ptr<ParticlesGlobalManager::UpdaterCallback> _cullCallback;
using GroupRefVec = std::vector<osg::ref_ptr<osg::Group>>;
GroupRefVec _newWorldParticles;
};
/**
@brief this class replaces the need to use osgParticle::ParticleSystemUpdater, which has some
thread-safety and ownership complications in our us case
*/
class ParticlesGlobalManager::UpdaterCallback : public osg::NodeCallback
{
public:
UpdaterCallback(ParticlesGlobalManagerPrivate* p) : _manager(p)
{
}
void operator()(osg::Node* node, osg::NodeVisitor* nv) override
{
std::lock_guard<std::mutex> g(_lock);
_enabled = !_enabledNode || _enabledNode->getBoolValue();
if (!_enabled)
return;
const auto q = SGQuatd::fromLonLatDeg(_longitudeNode->getFloatValue(), _latitudeNode->getFloatValue());
osg::Matrix om(toOsg(q));
osg::Vec3 v(0, 0, 9.81);
_gravity = om.preMult(v);
// NOTE: THIS WIND COMPUTATION DOESN'T SEEM TO AFFECT PARTICLES
// const osg::Vec3& zUpWind = _wind;
// osg::Vec3 w(zUpWind.y(), zUpWind.x(), -zUpWind.z());
// _localWind = om.preMult(w);
}
// only call this with the lock held!
osg::Group* internalGetCommonRoot()
{
if (!_commonRoot.valid()) {
SG_LOG(SG_PARTICLES, SG_DEBUG, "Particle common root called.");
_commonRoot = new osg::Group;
_commonRoot->setName("common particle system root");
_commonGeode->setName("common particle system geode");
_commonRoot->addChild(_commonGeode);
_commonRoot->addChild(_updater);
_commonRoot->setNodeMask(~simgear::MODELLIGHT_BIT);
osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(nv);
if (cv && nv->getFrameStamp()) {
if (_frameNumber < nv->getFrameStamp()->getFrameNumber()) {
_frameNumber = nv->getFrameStamp()->getFrameNumber();
_manager->updateParticleSystemsFromCullCallback(_frameNumber, nv);
}
}
return _commonRoot.get();
// note, callback is responsible for scenegraph traversal so
// they must call traverse(node,nv) to ensure that the
// scene graph subtree (and associated callbacks) are traversed.
traverse(node, nv);
}
std::mutex _lock;
bool _frozen = false;
osg::ref_ptr<osgParticle::ParticleSystemUpdater> _updater;
osg::ref_ptr<osg::Group> _commonRoot;
osg::ref_ptr<osg::Geode> _commonGeode;
osg::Vec3 _wind;
bool _globalCallbackRegistered = false;
bool _enabled = true;
osg::Vec3 _gravity;
// osg::Vec3 _localWind;
SGConstPropertyNode_ptr _enabledNode;
SGConstPropertyNode_ptr _longitudeNode, _latitudeNode;
unsigned int _frameNumber = 0;
ParticlesGlobalManagerPrivate* _manager;
};
/**
single-shot node callback, used to register a particle system with the global manager. Once run, removes
itself. We used this to avoid updating a particle system until the load is complete and merged into the
main scene.
*/
class ParticlesGlobalManager::RegistrationCallback : public osg::NodeCallback
{
public:
void operator()(osg::Node* node, osg::NodeVisitor* nv) override
{
auto d = ParticlesGlobalManager::instance()->d;
d->addParticleSystem(_system, _frame);
node->removeUpdateCallback(this); // suicide
}
ParticleSystemRef _system;
osg::ref_ptr<osg::Group> _frame;
};
ParticlesGlobalManager::ParticlesGlobalManagerPrivate::ParticlesGlobalManagerPrivate() : _commonGeode(new osg::Geode),
_cullCallback(new UpdaterCallback(this))
{
// callbacks are registered in initFromMainThread : depending on timing,
// this constructor might be called from an osgDB thread
}
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::addParticleSystem(osgParticle::ParticleSystem* ps, const osg::ref_ptr<osg::Group>& frame)
{
std::lock_guard<std::mutex> g(_lock);
_systems.push_back(ps);
// we're inside an update callback here, so better not modify the
// scene structure; defer it to later
if (frame.get()) {
_newWorldParticles.push_back(frame);
}
}
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::registerNewLocalParticleSystem(osg::Node* node, ParticleSystemRef ps)
{
auto cb = new RegistrationCallback;
cb->_system = ps;
node->addUpdateCallback(cb);
}
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::registerNewWorldParticleSystem(osg::Node* node, ParticleSystemRef ps, osg::Group* frame)
{
auto cb = new RegistrationCallback;
cb->_system = ps;
cb->_frame = frame;
node->addUpdateCallback(cb);
}
// this is called from the main thread during scenery init
// after this, we beign updarting particle systems
void ParticlesGlobalManager::initFromMainThread()
{
std::lock_guard<std::mutex> g(d->_lock);
d->internalGetCommonRoot();
d->_commonRoot->addUpdateCallback(d.get());
d->_commonRoot->setCullingActive(false);
d->_commonRoot->addCullCallback(d->_cullCallback);
}
void ParticlesGlobalManager::update(double dt, const SGGeod& pos)
{
std::lock_guard<std::mutex> g(d->_lock);
d->_simulationDt = dt;
d->_currentPosition = pos;
for (auto f : d->_newWorldParticles) {
d->internalGetCommonRoot()->addChild(f);
}
d->_newWorldParticles.clear();
}
// this is called from the main thread, since it's an update callback
// lock any state used by updateParticleSystemsFromCullCallback, which
// runs during culling, potentialy on a different thread
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::operator()(osg::Node* node, osg::NodeVisitor* nv)
{
std::lock_guard<std::mutex> g(_lock);
_enabled = !_enabledNode || _enabledNode->getBoolValue();
if (!_enabled)
return;
const auto q = SGQuatd::fromLonLat(_currentPosition);
osg::Matrix om(toOsg(q));
osg::Vec3 v(0, 0, 9.81);
_gravity = om.preMult(v);
// NOTE: THIS WIND COMPUTATION DOESN'T SEEM TO AFFECT PARTICLES
// const osg::Vec3& zUpWind = _wind;
// osg::Vec3 w(zUpWind.y(), zUpWind.x(), -zUpWind.z());
// _localWind = om.preMult(w);
// while we have the lock, remove all expired systems
auto firstInvalid = std::remove_if(_systems.begin(), _systems.end(), [](const ParticleSystemWeakRef& weak) {
return !weak.valid();
});
_systems.erase(firstInvalid, _systems.end());
}
// only call this with the lock held!
osg::Group* ParticlesGlobalManager::ParticlesGlobalManagerPrivate::internalGetCommonRoot()
{
if (!_commonRoot.valid()) {
_commonRoot = new osg::Group;
_commonRoot->setName("common particle system root");
_commonGeode->setName("common particle system geode");
_commonRoot->addChild(_commonGeode);
_commonRoot->setNodeMask(~simgear::MODELLIGHT_BIT);
}
return _commonRoot.get();
}
void ParticlesGlobalManager::ParticlesGlobalManagerPrivate::updateParticleSystemsFromCullCallback(int frameNumber, osg::NodeVisitor* nv)
{
ParticleSystemsStrongRefVec activeSystems;
double dt = 0.0;
// begin locked section
{
std::lock_guard<std::mutex> g(_lock);
activeSystems.reserve(_systems.size());
for (const auto& psref : _systems) {
ParticleSystemRef owningRef;
if (!psref.lock(owningRef)) {
// pointed to system is gone, skip it
// we will clean these up in the update callback, don't
// worry about that here
continue;
}
// add to the list we will update now
activeSystems.push_back(owningRef);
}
dt = _simulationDt;
} // of locked section
// from here on, don't access class data; copy it all to local variables
// before this line. this is important so we're not holding _lock during
// culling, which might block osgDB threads for example.
if (dt <= 0.0) {
return;
}
for (const auto& ps : activeSystems) {
// code inside here is copied from osgParticle::ParticleSystemUpdate
osgParticle::ParticleSystem::ScopedWriteLock lock(*(ps->getReadWriteMutex()));
// We need to allow at least 2 frames difference, because the particle system's lastFrameNumber
// is updated in the draw thread which may not have completed yet.
if (!ps->isFrozen() &&
(!ps->getFreezeOnCull() || ((frameNumber - ps->getLastFrameNumber()) <= 2))) {
ps->update(dt, *nv);
}
// end of code copied from osgParticle::ParticleSystemUpdate
}
}
static std::mutex static_managerLock;
static std::unique_ptr<ParticlesGlobalManager> static_instance;
@@ -132,14 +315,6 @@ ParticlesGlobalManager::ParticlesGlobalManager() : d(new ParticlesGlobalManagerP
{
}
ParticlesGlobalManager::~ParticlesGlobalManager()
{
if (d->_globalCallbackRegistered) {
// is this actually necessary? possibly not
d->_updater->setUpdateCallback(nullptr);
}
}
bool ParticlesGlobalManager::isEnabled() const
{
std::lock_guard<std::mutex> g(d->_lock);
@@ -728,23 +903,10 @@ osg::ref_ptr<osg::Group> ParticlesGlobalManager::appendParticles(const SGPropert
emitter->setUpdateCallback(callback.get());
}
// touch shared data now (and not before)
{
std::lock_guard<std::mutex> g(d->_lock);
d->_updater->addParticleSystem(particleSys);
if (attach != "local") {
d->internalGetCommonRoot()->addChild(callback()->particleFrame);
}
if (!d->_globalCallbackRegistered) {
SG_LOG(SG_PARTICLES, SG_INFO, "Registering global particles callback");
d->_globalCallbackRegistered = true;
d->_longitudeNode = modelRoot->getNode("/position/longitude-deg", true);
d->_latitudeNode = modelRoot->getNode("/position/latitude-deg", true);
d->_updater->setUpdateCallback(d.get());
}
if (attach == "local") {
d->registerNewLocalParticleSystem(align, particleSys);
} else {
d->registerNewWorldParticleSystem(align, particleSys, callback()->particleFrame);
}
return align;

View File

@@ -41,7 +41,6 @@ class NodeVisitor;
namespace osgParticle
{
class ParticleSystem;
class ParticleSystemUpdater;
}
#include <simgear/scene/util/SGNodeMasks.hxx>
@@ -151,7 +150,7 @@ protected:
class ParticlesGlobalManager
{
public:
~ParticlesGlobalManager();
~ParticlesGlobalManager() = default;
static ParticlesGlobalManager* instance();
static void clear();
@@ -161,14 +160,18 @@ public:
osg::ref_ptr<osg::Group> appendParticles(const SGPropertyNode* configNode, SGPropertyNode* modelRoot, const osgDB::Options* options);
osg::Group* getCommonRoot();
osg::Geode* getCommonGeode();
osgParticle::ParticleSystemUpdater* getPSU();
void initFromMainThread();
void setFrozen(bool e);
bool isFrozen() const;
/**
@brief update function: call from the main thread , outside of OSG calls.
*/
void update(double dt, const SGGeod& pos);
void setSwitchNode(const SGPropertyNode* n);
/**
@@ -185,6 +188,8 @@ private:
ParticlesGlobalManager();
class ParticlesGlobalManagerPrivate;
class UpdaterCallback;
class RegistrationCallback;
// because Private inherits NodeCallback, we need to own it
// via an osg::ref_ptr

View File

@@ -109,13 +109,13 @@ bool hasWhitespace(string path)
class SyncItem
{
public:
enum Type
{
Stop = 0, ///< special item indicating to stop the SVNThread
enum Type {
Stop = 0, ///< special item indicating to stop the SVNThread
Tile,
AirportData,
SharedModels,
AIData
AIData,
OSMTile ///< OSm2City per-Tile data
};
enum Status
@@ -165,13 +165,16 @@ public:
SGTimeStamp stamp;
bool busy = false; ///< is the slot working or idle
unsigned int pendingKBytes = 0;
unsigned int pendingExtractKBytes = 0;
unsigned int nextWarnTimeout = 0;
};
static const int SYNC_SLOT_TILES = 0; ///< Terrain and Objects sync
static const int SYNC_SLOT_SHARED_DATA = 1; /// shared Models and Airport data
static const int SYNC_SLOT_AI_DATA = 2; /// AI traffic and models
static const unsigned int NUM_SYNC_SLOTS = 3;
static const int SYNC_SLOT_OSM_TILE_DATA = 3;
static const unsigned int NUM_SYNC_SLOTS = 4;
/**
* @brief translate a sync item type into one of the available slots.
@@ -186,7 +189,8 @@ static unsigned int syncSlotForType(SyncItem::Type ty)
return SYNC_SLOT_SHARED_DATA;
case SyncItem::AIData:
return SYNC_SLOT_AI_DATA;
case SyncItem::OSMTile:
return SYNC_SLOT_OSM_TILE_DATA;
default:
return SYNC_SLOT_SHARED_DATA;
}
@@ -194,18 +198,18 @@ static unsigned int syncSlotForType(SyncItem::Type ty)
struct TerrasyncThreadState
{
TerrasyncThreadState() :
_busy(false),
_stalled(false),
_hasServer(false),
_fail_count(0),
_updated_tile_count(0),
_success_count(0),
_consecutive_errors(0),
_cache_hits(0),
_transfer_rate(0),
_total_kb_downloaded(0),
_totalKbPending(0)
TerrasyncThreadState() : _busy(false),
_stalled(false),
_hasServer(false),
_fail_count(0),
_updated_tile_count(0),
_success_count(0),
_consecutive_errors(0),
_cache_hits(0),
_transfer_rate(0),
_total_kb_downloaded(0),
_totalKbPending(0),
_extractTotalKbPending(0)
{}
bool _busy;
@@ -220,6 +224,7 @@ struct TerrasyncThreadState
// kbytes, not bytes, because bytes might overflow 2^31
int _total_kb_downloaded;
unsigned int _totalKbPending;
unsigned int _extractTotalKbPending;
};
///////////////////////////////////////////////////////////////////////////////
@@ -275,9 +280,10 @@ public:
SyncItem getNewTile() { return _freshTiles.pop_front();}
void setHTTPServer(const std::string& server)
void setHTTPServer(const std::string& server, const std::string& osmServer)
{
_httpServer = stripPath(server);
_osmCityServer = stripPath(osmServer);
_isAutomaticServer = (server == "automatic");
}
@@ -296,6 +302,11 @@ public:
_sceneryVersion = simgear::strutils::strip(sceneryVersion);
}
void setOSMCityVersion(const std::string& osmCityVersion)
{
_osmCityService = osmCityVersion;
}
void setLocalDir(string dir) { _local_dir = stripPath(dir);}
string getLocalDir() { return _local_dir;}
@@ -322,10 +333,12 @@ public:
void setCachePath(const SGPath &p) { _persistentCachePath = p; }
private:
void incrementCacheHits()
{
std::lock_guard<std::mutex> g(_stateLock);
_state._cache_hits++;
std::string dnsSelectServerForService(const std::string& service);
void incrementCacheHits()
{
std::lock_guard<std::mutex> g(_stateLock);
_state._cache_hits++;
}
virtual void run();
@@ -334,9 +347,9 @@ public:
void runInternal();
void updateSyncSlot(SyncSlot& slot);
void beginSyncAirports(SyncSlot& slot);
void beginSyncTile(SyncSlot& slot);
void beginNormalSync(SyncSlot& slot);
bool beginSyncAirports(SyncSlot& slot);
bool beginSyncTile(SyncSlot& slot);
bool beginNormalSync(SyncSlot& slot);
void drainWaitingTiles();
@@ -363,6 +376,9 @@ public:
string _local_dir;
SGPath _persistentCachePath;
string _httpServer;
string _osmCityServer;
string _osmCityService = "o2c";
bool _isAutomaticServer;
SGPath _installRoot;
string _sceneryVersion;
@@ -474,8 +490,19 @@ bool SGTerraSync::WorkerThread::findServer()
{
if ( false == _isAutomaticServer ) return true;
_httpServer = dnsSelectServerForService(MakeQService(_protocol, _sceneryVersion));
if (!_osmCityService.empty()) {
_osmCityServer = dnsSelectServerForService(_osmCityService);
}
return !_httpServer.empty();
}
std::string SGTerraSync::WorkerThread::dnsSelectServerForService(const std::string& service)
{
DNS::NAPTRRequest * naptrRequest = new DNS::NAPTRRequest(_dnsdn);
naptrRequest->qservice = MakeQService(_protocol, _sceneryVersion);
naptrRequest->qservice = service;
naptrRequest->qflags = "U";
DNS::Request_ptr r(naptrRequest);
@@ -483,14 +510,15 @@ bool SGTerraSync::WorkerThread::findServer()
DNS::Client dnsClient;
dnsClient.makeRequest(r);
SG_LOG(SG_TERRASYNC,SG_DEBUG,"DNS NAPTR query for '" << _dnsdn << "' '" << naptrRequest->qservice << "'" );
while( !r->isComplete() && !r->isTimeout() )
dnsClient.update(0);
while (!r->isComplete() && !r->isTimeout()) {
dnsClient.update(0);
}
if( naptrRequest->entries.empty() ) {
SG_LOG(SG_TERRASYNC, SG_ALERT, "Warning: no DNS entry found for '" << _dnsdn << "' '" << naptrRequest->qservice << "'" );
_httpServer = "";
return false;
return {};
}
// walk through responses, they are ordered by 1. order and 2. preference
// For now, only take entries with lowest order
// TODO: try all available servers in the order given by preferenc and order
@@ -498,38 +526,33 @@ bool SGTerraSync::WorkerThread::findServer()
// get all servers with this order and the same (for now only lowest preference)
DNS::NAPTRRequest::NAPTR_list availableServers;
for( DNS::NAPTRRequest::NAPTR_list::const_iterator it = naptrRequest->entries.begin();
it != naptrRequest->entries.end();
++it ) {
if( (*it)->order != order )
for (const auto& entry : naptrRequest->entries) {
if (entry->order != order)
continue;
string regex = (*it)->regexp;
if( false == simgear::strutils::starts_with( (*it)->regexp, "!^.*$!" ) ) {
SG_LOG(SG_TERRASYNC,SG_WARN, "ignoring unsupported regexp: " << (*it)->regexp );
const string regex = entry->regexp;
if (false == simgear::strutils::starts_with(regex, "!^.*$!")) {
SG_LOG(SG_TERRASYNC, SG_WARN, "ignoring unsupported regexp: " << regex);
continue;
}
if( false == simgear::strutils::ends_with( (*it)->regexp, "!" ) ) {
SG_LOG(SG_TERRASYNC,SG_WARN, "ignoring unsupported regexp: " << (*it)->regexp );
if (false == simgear::strutils::ends_with(regex, "!")) {
SG_LOG(SG_TERRASYNC, SG_WARN, "ignoring unsupported regexp: " << regex);
continue;
}
// always use first entry
if( availableServers.empty() || (*it)->preference == availableServers[0]->preference) {
SG_LOG(SG_TERRASYNC,SG_DEBUG, "available server regexp: " << (*it)->regexp );
availableServers.push_back( *it );
if (availableServers.empty() || entry->preference == availableServers[0]->preference) {
SG_LOG(SG_TERRASYNC, SG_DEBUG, "available server regexp: " << regex);
availableServers.push_back(entry);
}
}
// now pick a random entry from the available servers
DNS::NAPTRRequest::NAPTR_list::size_type idx = sg_random() * availableServers.size();
_httpServer = availableServers[idx]->regexp;
_httpServer = _httpServer.substr( 6, _httpServer.length()-7 ); // strip search pattern and separators
SG_LOG(SG_TERRASYNC,SG_INFO, "picking entry # " << idx << ", server is " << _httpServer );
return true;
auto idx = static_cast<int>(sg_random() * availableServers.size());
const auto server = availableServers.at(idx)->regexp;
SG_LOG(SG_TERRASYNC, SG_INFO, "picking entry # " << idx << ", server is " << server.substr(6, server.length() - 7););
return server.substr(6, server.length() - 7);
}
void SGTerraSync::WorkerThread::run()
@@ -562,6 +585,7 @@ void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
#endif
// convert bytes to kbytes here
slot.pendingKBytes = (slot.repository->bytesToDownload() >> 10);
slot.pendingExtractKBytes = (slot.repository->bytesToExtract() >> 10);
return; // easy, still working
}
@@ -569,6 +593,9 @@ void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
HTTPRepository::ResultCode res = slot.repository->failure();
if (res == HTTPRepository::REPO_ERROR_NOT_FOUND) {
// not founds should never happen any more (unless the server-
// side data is incorrect), since we now check top-down that
// a 1x1 dir exists or not.
notFound(slot.currentItem);
} else if (res != HTTPRepository::REPO_NO_ERROR) {
fail(slot.currentItem);
@@ -591,6 +618,7 @@ void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
slot.busy = false;
slot.repository.reset();
slot.pendingKBytes = 0;
slot.pendingExtractKBytes = 0;
slot.currentItem = {};
}
@@ -604,12 +632,23 @@ void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
slot.isNewDirectory = !path.exists();
const auto type = slot.currentItem._type;
bool ok = false;
if (type == SyncItem::AirportData) {
beginSyncAirports(slot);
ok = beginSyncAirports(slot);
} else if (type == SyncItem::OSMTile) {
ok = beginSyncTile(slot);
} else if (type == SyncItem::Tile) {
beginSyncTile(slot);
ok = beginSyncTile(slot);
} else {
beginNormalSync(slot);
ok = beginNormalSync(slot);
}
if (!ok) {
SG_LOG(SG_TERRASYNC, SG_INFO, "sync of " << slot.currentItem._dir << " failed to start");
fail(slot.currentItem);
slot.busy = false;
slot.repository.reset();
return;
}
try {
@@ -626,17 +665,17 @@ void SGTerraSync::WorkerThread::updateSyncSlot(SyncSlot &slot)
slot.nextWarnTimeout = 30 * 1000;
slot.stamp.stamp();
slot.busy = true;
slot.pendingKBytes = slot.repository->bytesToDownload();
slot.pendingKBytes = slot.repository->bytesToDownload() >> 10;
slot.pendingExtractKBytes = slot.repository->bytesToExtract() >> 10;
SG_LOG(SG_TERRASYNC, SG_INFO, "sync of " << slot.repository->baseUrl() << " started, queue size is " << slot.queue.size());
SG_LOG(SG_TERRASYNC, SG_INFO, "sync of " << slot.repository->baseUrl() << ":" << slot.currentItem._dir << " started, queue size is " << slot.queue.size());
}
}
void SGTerraSync::WorkerThread::beginSyncAirports(SyncSlot& slot)
bool SGTerraSync::WorkerThread::beginSyncAirports(SyncSlot& slot)
{
if (!slot.isNewDirectory) {
beginNormalSync(slot);
return;
return beginNormalSync(slot);
}
SG_LOG(SG_TERRASYNC, SG_INFO, "doing Airports download via tarball");
@@ -656,9 +695,10 @@ void SGTerraSync::WorkerThread::beginSyncAirports(SyncSlot& slot)
};
slot.repository->setFilter(f);
return true;
}
void SGTerraSync::WorkerThread::beginSyncTile(SyncSlot& slot)
bool SGTerraSync::WorkerThread::beginSyncTile(SyncSlot& slot)
{
// avoid 404 requests by doing a sync which excludes all paths
// except our tile path. In the case of a missing 1x1 tile, we will
@@ -667,8 +707,7 @@ void SGTerraSync::WorkerThread::beginSyncTile(SyncSlot& slot)
auto comps = strutils::split(slot.currentItem._dir, "/");
if (comps.size() != 3) {
SG_LOG(SG_TERRASYNC, SG_ALERT, "Bad tile path:" << slot.currentItem._dir);
beginNormalSync(slot);
return;
return false;
}
const auto tileCategory = comps.front();
@@ -677,7 +716,17 @@ void SGTerraSync::WorkerThread::beginSyncTile(SyncSlot& slot)
const auto path = SGPath::fromUtf8(_local_dir) / tileCategory;
slot.repository.reset(new HTTPRepository(path, &_http));
slot.repository->setBaseUrl(_httpServer + "/" + tileCategory);
if (slot.currentItem._type == SyncItem::OSMTile) {
if (_osmCityServer.empty()) {
SG_LOG(SG_TERRASYNC, SG_WARN, "No OSM2City server defined for:" << slot.currentItem._dir);
return false;
}
slot.repository->setBaseUrl(_osmCityServer + "/" + tileCategory);
} else {
slot.repository->setBaseUrl(_httpServer + "/" + tileCategory);
}
if (_installRoot.exists()) {
SGPath p = _installRoot / tileCategory;
@@ -685,6 +734,7 @@ void SGTerraSync::WorkerThread::beginSyncTile(SyncSlot& slot)
}
const auto dirPrefix = tenByTenDir + "/" + oneByOneDir;
using simgear::strutils::starts_with;
// filter callback to *only* sync the 1x1 dir we want, if it exists
// if doesn't, we'll simply stop, which is what we want
@@ -693,7 +743,10 @@ void SGTerraSync::WorkerThread::beginSyncTile(SyncSlot& slot)
if (item.directory.empty()) {
return item.filename == tenByTenDir;
} else if (item.directory == tenByTenDir) {
return item.filename == oneByOneDir;
// allow 10x10 dir to contain 1x1_dir.tgz/.zip and still be accepted
// this does mean we'd also download oneByOneDir_foobar but that
// doesn't seem unreasonable either
return starts_with(item.filename, oneByOneDir);
}
// allow arbitrary children below dirPrefix, including sub-dirs
@@ -706,9 +759,10 @@ void SGTerraSync::WorkerThread::beginSyncTile(SyncSlot& slot)
};
slot.repository->setFilter(f);
return true;
}
void SGTerraSync::WorkerThread::beginNormalSync(SyncSlot& slot)
bool SGTerraSync::WorkerThread::beginNormalSync(SyncSlot& slot)
{
SGPath path(_local_dir);
path.append(slot.currentItem._dir);
@@ -720,6 +774,8 @@ void SGTerraSync::WorkerThread::beginNormalSync(SyncSlot& slot)
p.append(slot.currentItem._dir);
slot.repository->setInstalledCopyPath(p);
}
return true;
}
void SGTerraSync::WorkerThread::runInternal()
@@ -762,20 +818,24 @@ void SGTerraSync::WorkerThread::runInternal()
bool anySlotBusy = false;
unsigned int newPendingCount = 0;
unsigned int newExtractCount = 0; // how much is left to extract
// update each sync slot in turn
for (unsigned int slot=0; slot < NUM_SYNC_SLOTS; ++slot) {
updateSyncSlot(_syncSlots[slot]);
newPendingCount += _syncSlots[slot].pendingKBytes;
newExtractCount += _syncSlots[slot].pendingExtractKBytes;
anySlotBusy |= _syncSlots[slot].busy;
}
{
std::lock_guard<std::mutex> g(_stateLock);
_state._totalKbPending = newPendingCount; // approximately atomic update
_state._extractTotalKbPending = newExtractCount;
_state._busy = anySlotBusy;
}
if (!anySlotBusy) {
// wait on the blocking deque here, otherwise we spin
// the loop very fast, since _http::update with no connections
@@ -811,12 +871,19 @@ void SGTerraSync::WorkerThread::fail(SyncItem failedItem)
{
std::lock_guard<std::mutex> g(_stateLock);
time_t now = time(0);
_state._consecutive_errors++;
_state._fail_count++;
if (_osmCityServer.empty() && (failedItem._type == SyncItem::OSMTile)) {
// don't count these as errors, otherwise normla sync will keep
// being abandoned
} else {
_state._consecutive_errors++;
_state._fail_count++;
}
failedItem._status = SyncItem::Failed;
_freshTiles.push_back(failedItem);
// not we also end up here for partial syncs
SG_LOG(SG_TERRASYNC,SG_INFO,
SG_LOG(SG_TERRASYNC, SG_WARN,
"Failed to sync'" << failedItem._dir << "'");
_completedTiles[ failedItem._dir ] = now + UpdateInterval::FailedAttempt;
}
@@ -828,6 +895,8 @@ void SGTerraSync::WorkerThread::notFound(SyncItem item)
// we don't spam the server with lookups for models that don't
// exist
SG_LOG(SG_TERRASYNC, SG_WARN, "Not found for: '" << item._dir << "'");
time_t now = time(0);
item._status = SyncItem::NotFound;
_freshTiles.push_back(item);
@@ -1040,8 +1109,11 @@ void SGTerraSync::reinit()
if (enabled)
{
_availableNode->setBoolValue(true);
_workerThread->setHTTPServer( _terraRoot->getStringValue("http-server","automatic") );
_workerThread->setHTTPServer(
_terraRoot->getStringValue("http-server", "automatic"),
_terraRoot->getStringValue("osm2city-server", ""));
_workerThread->setSceneryVersion( _terraRoot->getStringValue("scenery-version","ws20") );
_workerThread->setOSMCityVersion(_terraRoot->getStringValue("osm2city-version", "o2c"));
_workerThread->setProtocol( _terraRoot->getStringValue("protocol","") );
#if 1
// leave it hardcoded for now, not sure about the security implications for now
@@ -1100,6 +1172,7 @@ void SGTerraSync::bind()
_transferRateBytesSecNode = _terraRoot->getNode("transfer-rate-bytes-sec", true);
_pendingKbytesNode = _terraRoot->getNode("pending-kbytes", true);
_downloadedKBtesNode = _terraRoot->getNode("downloaded-kbytes", true);
_extractPendingKbytesNode = _terraRoot->getNode("extract-pending-kbytes", true);
_enabledNode = _terraRoot->getNode("enabled", true);
_availableNode = _terraRoot->getNode("available", true);
_maxErrorsNode = _terraRoot->getNode("max-errors", true);
@@ -1125,6 +1198,7 @@ void SGTerraSync::unbind()
_transferRateBytesSecNode.clear();
_pendingKbytesNode.clear();
_downloadedKBtesNode.clear();
_extractPendingKbytesNode.clear();
_enabledNode.clear();
_availableNode.clear();
_maxErrorsNode.clear();
@@ -1161,6 +1235,7 @@ void SGTerraSync::update(double)
_transferRateBytesSecNode->setIntValue(copiedState._transfer_rate);
_pendingKbytesNode->setIntValue(copiedState._totalKbPending);
_downloadedKBtesNode->setIntValue(copiedState._total_kb_downloaded);
_extractPendingKbytesNode->setIntValue(copiedState._extractTotalKbPending);
_stalledNode->setBoolValue(_workerThread->isStalled());
_activeNode->setBoolValue(worker_running);
@@ -1210,6 +1285,10 @@ string_list SGTerraSync::getSceneryPathSuffixes() const
return scenerySuffixes;
}
bool isOSMSuffix(const std::string& suffix)
{
return (suffix == "Buildings") || (suffix == "Roads") || (suffix == "Pylons") || (suffix == "Details");
}
void SGTerraSync::syncAreaByPath(const std::string& aPath)
{
@@ -1222,8 +1301,7 @@ void SGTerraSync::syncAreaByPath(const std::string& aPath)
if (_workerThread->isDirActive(dir)) {
continue;
}
SyncItem w(dir, SyncItem::Tile);
SyncItem w(dir, isOSMSuffix(suffix) ? SyncItem::OSMTile : SyncItem::Tile);
_workerThread->request( w );
}
}
@@ -1242,6 +1320,11 @@ bool SGTerraSync::isTileDirPending(const std::string& sceneryDir) const
}
for (const auto& suffix : getSceneryPathSuffixes()) {
// don't wait on OSM dirs, even if enabled
if (isOSMSuffix(suffix)) {
continue;
}
const auto s = suffix + "/" + sceneryDir;
if (_workerThread->isDirActive(s)) {
return true;

View File

@@ -107,6 +107,7 @@ private:
SGPropertyNode_ptr _transferRateBytesSecNode;
SGPropertyNode_ptr _pendingKbytesNode;
SGPropertyNode_ptr _downloadedKBtesNode;
SGPropertyNode_ptr _extractPendingKbytesNode;
SGPropertyNode_ptr _maxErrorsNode;
// we manually bind+init TerraSync during early startup

1
version Normal file
View File

@@ -0,0 +1 @@
2020.3.8