summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorReinhard Tartler <siretart@tauware.de>2017-06-11 21:52:33 -0400
committerReinhard Tartler <siretart@tauware.de>2017-06-11 21:52:33 -0400
commite0eb815b67734abd09ff41e2271630d4b2a6d760 (patch)
tree0df971c34f98d2a1dfd0921524a17d561a4a6536
parent676c9e1c9d4ac8eb8a440d7f11c4ac44f98f4a6a (diff)
parente19a5db232e1ef90e9a02159d2fbd9707ffe4373 (diff)
merge upstream version 0.12
-rw-r--r--LICENSE.txt79
-rw-r--r--VERSION.txt4
-rw-r--r--bin/bbackupctl/bbackupctl.cpp8
-rw-r--r--bin/bbackupd/BackupClientContext.cpp171
-rw-r--r--bin/bbackupd/BackupClientContext.h20
-rw-r--r--bin/bbackupd/BackupClientDeleteList.cpp2
-rw-r--r--bin/bbackupd/BackupClientDirectoryRecord.cpp818
-rw-r--r--bin/bbackupd/BackupClientDirectoryRecord.h54
-rw-r--r--bin/bbackupd/BackupClientInodeToIDMap.cpp250
-rw-r--r--bin/bbackupd/BackupClientInodeToIDMap.h26
-rw-r--r--bin/bbackupd/BackupDaemon.cpp1366
-rw-r--r--bin/bbackupd/BackupDaemon.h101
-rw-r--r--bin/bbackupd/BackupDaemonInterface.h6
-rwxr-xr-xbin/bbackupd/bbackupd-config.in4
-rw-r--r--bin/bbackupd/bbackupd.cpp5
-rw-r--r--bin/bbackupd/win32/installer.iss51
-rw-r--r--bin/bbackupquery/BackupQueries.cpp688
-rw-r--r--bin/bbackupquery/BackupQueries.h119
-rw-r--r--bin/bbackupquery/BoxBackupCompareParams.h5
-rw-r--r--bin/bbackupquery/CommandCompletion.cpp602
-rw-r--r--bin/bbackupquery/bbackupquery.cpp282
-rw-r--r--bin/bbackupquery/documentation.txt7
-rw-r--r--bin/bbstoreaccounts/bbstoreaccounts.cpp500
-rw-r--r--bin/bbstored/BBStoreDHousekeeping.cpp37
-rw-r--r--bin/bbstored/BackupStoreDaemon.cpp19
-rw-r--r--bin/bbstored/BackupStoreDaemon.h5
-rw-r--r--bin/bbstored/bbstored.cpp8
-rw-r--r--configure.ac286
-rw-r--r--contrib/mac_osx/org.boxbackup.bbackupd.plist.in5
-rw-r--r--contrib/mac_osx/org.boxbackup.bbstored.plist.in8
-rw-r--r--contrib/suse/bbstored.service26
-rw-r--r--contrib/windows/installer/bbackupd.conf.template176
-rwxr-xr-xcontrib/windows/installer/boxbackup.mpi.in2646
-rw-r--r--distribution/boxbackup/DISTRIBUTION-MANIFEST.txt7
-rw-r--r--distribution/boxbackup/VERSION.txt4
-rw-r--r--docs/api-notes/win32_build_on_cygwin_using_mingw.txt96
-rw-r--r--docs/xsl-generic/html/biblio-iso690.xsl2
-rw-r--r--infrastructure/BoxPlatform.pm.in41
-rw-r--r--infrastructure/buildenv-testmain-template.cpp140
-rw-r--r--infrastructure/m4/ax_check_ssl.m45
-rw-r--r--infrastructure/m4/boxbackup_tests.m4315
-rw-r--r--infrastructure/m4/vl_lib_readline.m453
-rwxr-xr-xinfrastructure/makebuildenv.pl.in126
-rwxr-xr-xinfrastructure/makeparcels.pl.in69
-rwxr-xr-xinfrastructure/mingw/configure.sh18
-rw-r--r--infrastructure/msvc/2010/bbackupctl.vcxproj109
-rw-r--r--infrastructure/msvc/2010/bbackupd.vcxproj139
-rw-r--r--infrastructure/msvc/2010/bbstoreaccounts.vcxproj84
-rw-r--r--infrastructure/msvc/2010/bbstored.vcxproj93
-rw-r--r--infrastructure/msvc/2010/boxbackup.sln89
-rw-r--r--infrastructure/msvc/2010/boxquery.vcxproj123
-rw-r--r--infrastructure/msvc/2010/common.vcxproj251
-rw-r--r--infrastructure/msvc/2010/libbackupclient.vcxproj94
-rw-r--r--infrastructure/msvc/2010/libbackupstore.vcxproj151
-rw-r--r--infrastructure/msvc/2010/qdbm.vcxproj81
-rw-r--r--infrastructure/msvc/2010/win32test.vcxproj108
-rw-r--r--infrastructure/msvc/fake-config.sub.pl18
-rw-r--r--infrastructure/msvc/getversion.pl23
-rw-r--r--lib/backupclient/BackupClientRestore.cpp19
-rw-r--r--lib/backupclient/BackupClientRestore.h10
-rw-r--r--lib/backupclient/BackupDaemonConfigVerify.cpp13
-rw-r--r--lib/backupstore/BackupClientFileAttributes.cpp1223
-rw-r--r--lib/backupstore/BackupClientFileAttributes.h82
-rw-r--r--lib/backupstore/BackupCommands.cpp970
-rw-r--r--lib/backupstore/BackupConstants.h21
-rw-r--r--lib/backupstore/BackupStoreAccountDatabase.cpp8
-rw-r--r--lib/backupstore/BackupStoreAccountDatabase.h8
-rw-r--r--lib/backupstore/BackupStoreAccounts.cpp56
-rw-r--r--lib/backupstore/BackupStoreAccounts.h13
-rw-r--r--lib/backupstore/BackupStoreCheck.cpp482
-rw-r--r--lib/backupstore/BackupStoreCheck.h53
-rw-r--r--lib/backupstore/BackupStoreCheck2.cpp145
-rw-r--r--lib/backupstore/BackupStoreCheckData.cpp12
-rw-r--r--lib/backupstore/BackupStoreConfigVerify.cpp4
-rw-r--r--lib/backupstore/BackupStoreConstants.h44
-rw-r--r--lib/backupstore/BackupStoreContext.cpp1808
-rw-r--r--lib/backupstore/BackupStoreContext.h211
-rw-r--r--lib/backupstore/BackupStoreDirectory.cpp578
-rw-r--r--lib/backupstore/BackupStoreDirectory.h289
-rw-r--r--lib/backupstore/BackupStoreException.h17
-rw-r--r--lib/backupstore/BackupStoreException.txt72
-rw-r--r--lib/backupstore/BackupStoreFile.cpp1558
-rw-r--r--lib/backupstore/BackupStoreFile.h234
-rw-r--r--lib/backupstore/BackupStoreFileCmbDiff.cpp326
-rw-r--r--lib/backupstore/BackupStoreFileCmbIdx.cpp324
-rw-r--r--lib/backupstore/BackupStoreFileCombine.cpp410
-rw-r--r--lib/backupstore/BackupStoreFileCryptVar.cpp31
-rw-r--r--lib/backupstore/BackupStoreFileCryptVar.h39
-rw-r--r--lib/backupstore/BackupStoreFileDiff.cpp1047
-rw-r--r--lib/backupstore/BackupStoreFileEncodeStream.cpp717
-rw-r--r--lib/backupstore/BackupStoreFileEncodeStream.h137
-rw-r--r--lib/backupstore/BackupStoreFileRevDiff.cpp258
-rw-r--r--lib/backupstore/BackupStoreFileWire.h74
-rw-r--r--lib/backupstore/BackupStoreFilename.cpp281
-rw-r--r--lib/backupstore/BackupStoreFilename.h107
-rw-r--r--lib/backupstore/BackupStoreFilenameClear.cpp347
-rw-r--r--lib/backupstore/BackupStoreFilenameClear.h61
-rw-r--r--lib/backupstore/BackupStoreInfo.cpp385
-rw-r--r--lib/backupstore/BackupStoreInfo.h125
-rw-r--r--lib/backupstore/BackupStoreObjectMagic.h31
-rw-r--r--lib/backupstore/BackupStoreRefCountDatabase.cpp38
-rw-r--r--lib/backupstore/HousekeepStoreAccount.cpp1110
-rw-r--r--lib/backupstore/HousekeepStoreAccount.h113
-rw-r--r--lib/backupstore/Makefile.extra15
-rw-r--r--lib/backupstore/RunStatusProvider.h29
-rw-r--r--lib/backupstore/backupprotocol.txt235
-rw-r--r--lib/common/Archive.h48
-rw-r--r--lib/common/BannerText.h10
-rw-r--r--lib/common/Box.h63
-rw-r--r--lib/common/BoxConfig-MSVC.h10
-rw-r--r--lib/common/BoxException.h4
-rw-r--r--lib/common/BoxPlatform.h16
-rw-r--r--lib/common/BoxPortsAndFiles.h.in12
-rw-r--r--lib/common/BoxTime.cpp52
-rw-r--r--lib/common/BoxTime.h26
-rw-r--r--lib/common/CommonException.txt13
-rw-r--r--lib/common/Database.h31
-rw-r--r--lib/common/DebugMemLeakFinder.cpp144
-rw-r--r--lib/common/ExcludeList.cpp20
-rw-r--r--lib/common/FdGetLine.cpp64
-rw-r--r--lib/common/FdGetLine.h28
-rw-r--r--lib/common/FileModificationTime.cpp12
-rw-r--r--lib/common/FileModificationTime.h6
-rw-r--r--lib/common/FileStream.cpp81
-rw-r--r--lib/common/GetLine.cpp176
-rw-r--r--lib/common/GetLine.h67
-rw-r--r--lib/common/IOStreamGetLine.cpp34
-rw-r--r--lib/common/IOStreamGetLine.h34
-rw-r--r--lib/common/Logging.cpp79
-rw-r--r--lib/common/Logging.h184
-rw-r--r--lib/common/MainHelper.h21
-rw-r--r--lib/common/MemBlockStream.cpp20
-rw-r--r--lib/common/MemBlockStream.h9
-rw-r--r--lib/common/MemLeakFinder.h13
-rw-r--r--lib/common/RateLimitingStream.cpp95
-rw-r--r--lib/common/RateLimitingStream.h71
-rw-r--r--lib/common/StreamableMemBlock.cpp4
-rw-r--r--lib/common/StreamableMemBlock.h5
-rw-r--r--lib/common/Test.cpp15
-rw-r--r--lib/common/Test.h48
-rw-r--r--lib/common/Timer.cpp227
-rw-r--r--lib/common/Timer.h15
-rw-r--r--lib/common/UnixUser.cpp8
-rw-r--r--lib/common/UnixUser.h4
-rw-r--r--lib/common/Utils.cpp147
-rw-r--r--lib/common/Utils.h5
-rw-r--r--lib/common/ZeroStream.cpp4
-rwxr-xr-xlib/common/makeexception.pl.in4
-rw-r--r--lib/crypto/CipherAES.h12
-rw-r--r--lib/crypto/CipherBlowfish.h12
-rw-r--r--lib/crypto/CipherContext.cpp130
-rw-r--r--lib/crypto/CipherContext.h24
-rw-r--r--lib/crypto/CipherDescription.h24
-rw-r--r--lib/crypto/CryptoUtils.cpp46
-rw-r--r--lib/crypto/CryptoUtils.h27
-rw-r--r--lib/raidfile/RaidFileController.cpp13
-rw-r--r--lib/raidfile/RaidFileController.h4
-rw-r--r--lib/raidfile/RaidFileRead.cpp112
-rw-r--r--lib/raidfile/RaidFileWrite.cpp189
-rw-r--r--lib/raidfile/RaidFileWrite.h19
-rw-r--r--lib/server/Daemon.cpp169
-rw-r--r--lib/server/Daemon.h12
-rw-r--r--lib/server/Message.cpp125
-rw-r--r--lib/server/Message.h69
-rw-r--r--lib/server/Protocol.cpp99
-rw-r--r--lib/server/Protocol.h52
-rw-r--r--lib/server/SSLLib.cpp13
-rw-r--r--lib/server/SSLLib.h3
-rw-r--r--lib/server/ServerException.txt4
-rw-r--r--lib/server/ServerStream.h35
-rw-r--r--lib/server/Socket.cpp25
-rw-r--r--lib/server/SocketListen.h63
-rw-r--r--lib/server/SocketStream.cpp18
-rw-r--r--lib/server/SocketStream.h11
-rw-r--r--lib/server/SocketStreamTLS.cpp37
-rw-r--r--lib/server/TLSContext.cpp20
-rw-r--r--lib/server/TcpNice.cpp235
-rw-r--r--lib/server/TcpNice.h174
-rwxr-xr-xlib/server/makeprotocol.pl.in897
-rw-r--r--lib/win32/emu.cpp183
-rw-r--r--lib/win32/emu.h79
-rw-r--r--lib/win32/emu_winver.h37
-rwxr-xr-xlib/win32/getopt_long.cpp4
-rw-r--r--modules.txt18
-rw-r--r--parcels.txt11
-rw-r--r--qdbm/COPYING504
-rw-r--r--qdbm/ChangeLog990
-rw-r--r--qdbm/LTmakefile.in318
-rw-r--r--qdbm/Makefile.in646
-rw-r--r--qdbm/NEWS43
-rw-r--r--qdbm/NO-AUTO-GEN0
-rw-r--r--qdbm/README50
-rw-r--r--qdbm/RISCmakefile140
-rw-r--r--qdbm/THANKS45
-rw-r--r--qdbm/VCmakefile248
-rw-r--r--qdbm/cabin.c3529
-rw-r--r--qdbm/cabin.h1544
-rw-r--r--qdbm/cbcodec.c1079
-rw-r--r--qdbm/cbtest.c924
-rwxr-xr-xqdbm/configure3913
-rw-r--r--qdbm/configure.in312
-rw-r--r--qdbm/crmgr.c956
-rw-r--r--qdbm/crtest.c873
-rw-r--r--qdbm/crtsv.c266
-rw-r--r--qdbm/curia.c1192
-rw-r--r--qdbm/curia.h474
-rw-r--r--qdbm/depot.c2219
-rw-r--r--qdbm/depot.h492
-rw-r--r--qdbm/dpmgr.c916
-rw-r--r--qdbm/dptest.c836
-rw-r--r--qdbm/dptsv.c261
-rw-r--r--qdbm/hovel.c568
-rw-r--r--qdbm/hovel.h278
-rw-r--r--qdbm/hvmgr.c582
-rw-r--r--qdbm/hvtest.c272
-rw-r--r--qdbm/misc/COPYING.txt504
-rw-r--r--qdbm/misc/README-win32.txt101
-rw-r--r--qdbm/misc/VCmakefile-old169
-rw-r--r--qdbm/misc/benchmark.pdfbin0 -> 52196 bytes
-rw-r--r--qdbm/misc/icon16.pngbin0 -> 339 bytes
-rw-r--r--qdbm/misc/icon20.pngbin0 -> 275 bytes
-rw-r--r--qdbm/misc/index.html202
-rw-r--r--qdbm/misc/index.ja.html209
-rw-r--r--qdbm/misc/logo.pngbin0 -> 11430 bytes
-rwxr-xr-xqdbm/misc/makevcdef48
-rw-r--r--qdbm/misc/mymemo-ja.html34
-rw-r--r--qdbm/misc/tutorial-ja.html622
-rw-r--r--qdbm/misc/win32check.bat111
-rw-r--r--qdbm/myconf.c1113
-rw-r--r--qdbm/myconf.h593
-rw-r--r--qdbm/odeum.c2090
-rw-r--r--qdbm/odeum.h590
-rw-r--r--qdbm/odidx.c890
-rw-r--r--qdbm/odmgr.c1085
-rw-r--r--qdbm/odtest.c694
-rw-r--r--qdbm/qdbm.def424
-rw-r--r--qdbm/qdbm.pc.in14
-rw-r--r--qdbm/qdbm.spec.in218
-rw-r--r--qdbm/qmttest.c300
-rw-r--r--qdbm/relic.c266
-rw-r--r--qdbm/relic.h170
-rw-r--r--qdbm/rlmgr.c465
-rw-r--r--qdbm/rltest.c241
-rw-r--r--qdbm/spex-ja.html4348
-rw-r--r--qdbm/spex.html4343
-rw-r--r--qdbm/villa.c2666
-rw-r--r--qdbm/villa.h758
-rw-r--r--qdbm/vista.c171
-rw-r--r--qdbm/vista.h138
-rw-r--r--qdbm/vlmgr.c968
-rw-r--r--qdbm/vltest.c1507
-rw-r--r--qdbm/vltsv.c261
-rwxr-xr-xruntest.pl.in4
-rw-r--r--test/backupstore/Makefile.extra4
-rw-r--r--test/backupstore/testbackupstore.cpp1118
-rw-r--r--test/backupstorefix/testbackupstorefix.cpp746
-rwxr-xr-xtest/backupstorefix/testfiles/testbackupstorefix.pl.in4
-rw-r--r--test/backupstorepatch/testbackupstorepatch.cpp48
-rw-r--r--test/basicserver/Makefile.extra11
-rw-r--r--test/basicserver/TestCommands.cpp53
-rw-r--r--test/basicserver/testbasicserver.cpp37
-rw-r--r--test/bbackupd/Makefile.extra11
-rw-r--r--test/bbackupd/testbbackupd.cpp916
-rw-r--r--test/bbackupd/testfiles/bbackupd-temploc.conf.in58
-rw-r--r--test/common/testcommon.cpp40
-rw-r--r--win32.bat24
266 files changed, 81666 insertions, 1918 deletions
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 00000000..70b513ac
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,79 @@
+Box Backup, http://www.boxbackup.org/
+
+Copyright (c) 2003-2010, Ben Summers and contributors.
+All rights reserved.
+
+The license of the code was changed on 23-Jan-2010 in order to meet the
+Fedora Project's definition of Free Software, and therefore allow inclusion
+in Fedora, Red Hat Linux and CentOS. This also solves a long-standing
+incompatibility with the GNU Readline library that prevented us from
+distributing Box Backup binaries compiled against that library. You can
+review our discussions of the change in the mailing list archives at:
+http://lists.boxbackup.org/pipermail/boxbackup/2010-January/000005.html
+
+Note that this project uses mixed licensing. Different parts of the project
+may be used and distributed under different licenses, as described below.
+The two licenses used are "Box Backup GPL" and a BSD-style license.
+
+For full details of the license, please read the included COPYING.txt file.
+
+Unless stated otherwise in the file, all files in the following directories
+fall under the "Box Backup GPL" license, described below:
+
+bin/bbackupctl
+bin/bbackupd
+bin/bbackupobjdump
+bin/bbackupquery
+bin/bbstoreaccounts
+bin/bbstored
+bin/s3simulator
+lib/backupclient
+lib/backupstore
+test/backupdiff
+test/backupstore
+test/backupstorefix
+test/backupstorepatch
+test/bbackupd
+contrib/bbadmin
+contrib/bbreporter
+contrib/cygwin
+contrib/debian
+contrib/mac_osx
+contrib/redhat
+contrib/rpm
+contrib/solaris
+contrib/suse
+contrib/windows
+distribution/boxbackup
+
+The "Box Backup GPL" license text may be found in the file
+LICENSE-GPL.txt, or online at:
+[https://www.boxbackup.org/svn/box/trunk/LICENSE-GPL.txt]
+
+Unless stated otherwise in the file, all files in the following directories
+are dual licensed under the BSD and GPL licenses. You may use and distribute
+them providing that you comply EITHER with the terms of the BSD license,
+OR the GPL license. It is not necessary to comply with both licenses,
+only one.
+
+lib/common
+lib/compress
+lib/crypto
+lib/httpserver
+lib/intercept
+lib/raidfile
+lib/server
+lib/win32
+test/basicserver
+test/common
+test/compress
+test/crypto
+test/httpserver
+test/raidfile
+test/win32
+infrastructure
+distribution
+
+The dual license text may be found in the file
+LICENSE-DUAL.txt, or online at:
+[https://www.boxbackup.org/svn/box/trunk/LICENSE-DUAL.txt] \ No newline at end of file
diff --git a/VERSION.txt b/VERSION.txt
index c29321a1..2201711b 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1,2 +1,6 @@
+<<<<<<< HEAD
USE_SVN_VERSION
+=======
+0.12
+>>>>>>> 0.12
boxbackup
diff --git a/bin/bbackupctl/bbackupctl.cpp b/bin/bbackupctl/bbackupctl.cpp
index 8dc8f30e..39f15baf 100644
--- a/bin/bbackupctl/bbackupctl.cpp
+++ b/bin/bbackupctl/bbackupctl.cpp
@@ -67,13 +67,7 @@ int main(int argc, const char *argv[])
Logging::SetProgramName("bbackupctl");
// Filename for configuration file?
- std::string configFilename;
-
- #ifdef WIN32
- configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE;
- #else
- configFilename = BOX_FILE_BBACKUPD_DEFAULT_CONFIG;
- #endif
+ std::string configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE;
// Quiet?
bool quiet = false;
diff --git a/bin/bbackupd/BackupClientContext.cpp b/bin/bbackupd/BackupClientContext.cpp
index 6b51b9e8..26df04be 100644
--- a/bin/bbackupd/BackupClientContext.cpp
+++ b/bin/bbackupd/BackupClientContext.cpp
@@ -25,9 +25,10 @@
#include "BackupStoreConstants.h"
#include "BackupStoreException.h"
#include "BackupDaemon.h"
-#include "autogen_BackupProtocolClient.h"
+#include "autogen_BackupProtocol.h"
#include "BackupStoreFile.h"
#include "Logging.h"
+#include "TcpNice.h"
#include "MemLeakFindOn.h"
@@ -49,29 +50,30 @@ BackupClientContext::BackupClientContext
bool ExtendedLogging,
bool ExtendedLogToFile,
std::string ExtendedLogFile,
- ProgressNotifier& rProgressNotifier
+ ProgressNotifier& rProgressNotifier,
+ bool TcpNiceMode
)
- : mrResolver(rResolver),
- mrTLSContext(rTLSContext),
- mHostname(rHostname),
- mPort(Port),
- mAccountNumber(AccountNumber),
- mpSocket(0),
- mpConnection(0),
- mExtendedLogging(ExtendedLogging),
- mExtendedLogToFile(ExtendedLogToFile),
- mExtendedLogFile(ExtendedLogFile),
- mpExtendedLogFileHandle(NULL),
- mClientStoreMarker(ClientStoreMarker_NotKnown),
- mpDeleteList(0),
- mpCurrentIDMap(0),
- mpNewIDMap(0),
- mStorageLimitExceeded(false),
- mpExcludeFiles(0),
- mpExcludeDirs(0),
- mKeepAliveTimer(0, "KeepAliveTime"),
- mbIsManaged(false),
- mrProgressNotifier(rProgressNotifier)
+: mExperimentalSnapshotMode(false),
+ mrResolver(rResolver),
+ mrTLSContext(rTLSContext),
+ mHostname(rHostname),
+ mPort(Port),
+ mAccountNumber(AccountNumber),
+ mExtendedLogging(ExtendedLogging),
+ mExtendedLogToFile(ExtendedLogToFile),
+ mExtendedLogFile(ExtendedLogFile),
+ mpExtendedLogFileHandle(NULL),
+ mClientStoreMarker(ClientStoreMarker_NotKnown),
+ mpDeleteList(0),
+ mpCurrentIDMap(0),
+ mpNewIDMap(0),
+ mStorageLimitExceeded(false),
+ mpExcludeFiles(0),
+ mpExcludeDirs(0),
+ mKeepAliveTimer(0, "KeepAliveTime"),
+ mbIsManaged(false),
+ mrProgressNotifier(rProgressNotifier),
+ mTcpNiceMode(TcpNiceMode)
{
}
@@ -107,40 +109,44 @@ BackupClientContext::~BackupClientContext()
BackupProtocolClient &BackupClientContext::GetConnection()
{
// Already got it? Just return it.
- if(mpConnection != 0)
+ if(mapConnection.get())
{
- return *mpConnection;
+ return *mapConnection;
}
- // Get a socket connection
- if(mpSocket == 0)
- {
- mpSocket = new SocketStreamTLS;
- ASSERT(mpSocket != 0); // will have exceptioned if this was a problem
- }
+ // there shouldn't be a connection open
+ ASSERT(mapSocket.get() == 0);
+ // Defensive. Must close connection before releasing any old socket.
+ mapConnection.reset();
+ mapSocket.reset(new SocketStreamTLS);
try
{
// Defensive.
- if(mpConnection != 0)
- {
- delete mpConnection;
- mpConnection = 0;
- }
+ mapConnection.reset();
// Log intention
BOX_INFO("Opening connection to server '" <<
mHostname << "'...");
// Connect!
- mpSocket->Open(mrTLSContext, Socket::TypeINET,
- mHostname.c_str(), mPort);
+ ((SocketStreamTLS *)(mapSocket.get()))->Open(mrTLSContext,
+ Socket::TypeINET, mHostname, mPort);
- // And create a procotol object
- mpConnection = new BackupProtocolClient(*mpSocket);
+ if(mTcpNiceMode)
+ {
+ // Pass control of mapSocket to NiceSocketStream,
+ // which will take care of destroying it for us.
+ mapNice.reset(new NiceSocketStream(mapSocket));
+ mapConnection.reset(new BackupProtocolClient(*mapNice));
+ }
+ else
+ {
+ mapConnection.reset(new BackupProtocolClient(*mapSocket));
+ }
// Set logging option
- mpConnection->SetLogToSysLog(mExtendedLogging);
+ mapConnection->SetLogToSysLog(mExtendedLogging);
if (mExtendedLogToFile)
{
@@ -156,16 +162,17 @@ BackupProtocolClient &BackupClientContext::GetConnection()
}
else
{
- mpConnection->SetLogToFile(mpExtendedLogFileHandle);
+ mapConnection->SetLogToFile(mpExtendedLogFileHandle);
}
}
// Handshake
- mpConnection->Handshake();
+ mapConnection->Handshake();
// Check the version of the server
{
- std::auto_ptr<BackupProtocolClientVersion> serverVersion(mpConnection->QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ std::auto_ptr<BackupProtocolVersion> serverVersion(
+ mapConnection->QueryVersion(BACKUP_STORE_SERVER_VERSION));
if(serverVersion->GetVersion() != BACKUP_STORE_SERVER_VERSION)
{
THROW_EXCEPTION(BackupStoreException, WrongServerVersion)
@@ -173,7 +180,8 @@ BackupProtocolClient &BackupClientContext::GetConnection()
}
// Login -- if this fails, the Protocol will exception
- std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(mpConnection->QueryLogin(mAccountNumber, 0 /* read/write */));
+ std::auto_ptr<BackupProtocolLoginConfirmed> loginConf(
+ mapConnection->QueryLogin(mAccountNumber, 0 /* read/write */));
// Check that the client store marker is the one we expect
if(mClientStoreMarker != ClientStoreMarker_NotKnown)
@@ -183,9 +191,9 @@ BackupProtocolClient &BackupClientContext::GetConnection()
// Not good... finish the connection, abort, etc, ignoring errors
try
{
- mpConnection->QueryFinished();
- mpSocket->Shutdown();
- mpSocket->Close();
+ mapConnection->QueryFinished();
+ mapNice.reset();
+ mapSocket.reset();
}
catch(...)
{
@@ -213,14 +221,13 @@ BackupProtocolClient &BackupClientContext::GetConnection()
catch(...)
{
// Clean up.
- delete mpConnection;
- mpConnection = 0;
- delete mpSocket;
- mpSocket = 0;
+ mapConnection.reset();
+ mapNice.reset();
+ mapSocket.reset();
throw;
}
- return *mpConnection;
+ return *mapConnection;
}
// --------------------------------------------------------------------------
@@ -233,7 +240,7 @@ BackupProtocolClient &BackupClientContext::GetConnection()
// --------------------------------------------------------------------------
void BackupClientContext::CloseAnyOpenConnection()
{
- if(mpConnection)
+ if(mapConnection.get())
{
try
{
@@ -244,14 +251,14 @@ void BackupClientContext::CloseAnyOpenConnection()
box_time_t marker = GetCurrentBoxTime();
// Set it on the store
- mpConnection->QuerySetClientStoreMarker(marker);
+ mapConnection->QuerySetClientStoreMarker(marker);
// Record it so that it can be picked up later.
mClientStoreMarker = marker;
}
// Quit nicely
- mpConnection->QueryFinished();
+ mapConnection->QueryFinished();
}
catch(...)
{
@@ -259,26 +266,18 @@ void BackupClientContext::CloseAnyOpenConnection()
}
// Delete it anyway.
- delete mpConnection;
- mpConnection = 0;
+ mapConnection.reset();
}
-
- if(mpSocket)
+
+ try
{
- try
- {
- // Be nice about closing the socket
- mpSocket->Shutdown();
- mpSocket->Close();
- }
- catch(...)
- {
- // Ignore errors
- }
-
- // Delete object
- delete mpSocket;
- mpSocket = 0;
+ // Be nice about closing the socket
+ mapNice.reset();
+ mapSocket.reset();
+ }
+ catch(...)
+ {
+ // Ignore errors
}
// Delete any pending list
@@ -307,9 +306,9 @@ void BackupClientContext::CloseAnyOpenConnection()
// --------------------------------------------------------------------------
int BackupClientContext::GetTimeout() const
{
- if(mpConnection)
+ if(mapConnection.get())
{
- return mpConnection->GetTimeout();
+ return mapConnection->GetTimeout();
}
return (15*60*1000);
@@ -419,20 +418,20 @@ bool BackupClientContext::FindFilename(int64_t ObjectID, int64_t ContainingDirec
// Request filenames from the server, in a "safe" manner to ignore errors properly
{
- BackupProtocolClientGetObjectName send(ObjectID, ContainingDirectory);
+ BackupProtocolGetObjectName send(ObjectID, ContainingDirectory);
connection.Send(send);
}
- std::auto_ptr<BackupProtocolObjectCl> preply(connection.Receive());
+ std::auto_ptr<BackupProtocolMessage> preply(connection.Receive());
// Is it of the right type?
- if(preply->GetType() != BackupProtocolClientObjectName::TypeID)
+ if(preply->GetType() != BackupProtocolObjectName::TypeID)
{
// Was an error or something
return false;
}
// Cast to expected type.
- BackupProtocolClientObjectName *names = (BackupProtocolClientObjectName *)(preply.get());
+ BackupProtocolObjectName *names = (BackupProtocolObjectName *)(preply.get());
// Anything found?
int32_t numElements = names->GetNumNameElements();
@@ -482,10 +481,10 @@ bool BackupClientContext::FindFilename(int64_t ObjectID, int64_t ContainingDirec
}
// Is it a directory?
- rIsDirectoryOut = ((names->GetFlags() & BackupProtocolClientListDirectory::Flags_Dir) == BackupProtocolClientListDirectory::Flags_Dir);
+ rIsDirectoryOut = ((names->GetFlags() & BackupProtocolListDirectory::Flags_Dir) == BackupProtocolListDirectory::Flags_Dir);
// Is it the current version?
- rIsCurrentVersionOut = ((names->GetFlags() & (BackupProtocolClientListDirectory::Flags_OldVersion | BackupProtocolClientListDirectory::Flags_Deleted)) == 0);
+ rIsCurrentVersionOut = ((names->GetFlags() & (BackupProtocolListDirectory::Flags_OldVersion | BackupProtocolListDirectory::Flags_Deleted)) == 0);
// And other information which may be required
if(pModTimeOnServer) *pModTimeOnServer = names->GetModificationTime();
@@ -509,7 +508,7 @@ void BackupClientContext::SetKeepAliveTime(int iSeconds)
{
mKeepAliveTime = iSeconds < 0 ? 0 : iSeconds;
BOX_TRACE("Set keep-alive time to " << mKeepAliveTime << " seconds");
- mKeepAliveTimer = Timer(mKeepAliveTime, "KeepAliveTime");
+ mKeepAliveTimer.Reset(mKeepAliveTime * MILLI_SEC_IN_SEC);
}
// --------------------------------------------------------------------------
@@ -551,7 +550,7 @@ void BackupClientContext::UnManageDiffProcess()
// --------------------------------------------------------------------------
void BackupClientContext::DoKeepAlive()
{
- if (!mpConnection)
+ if (!mapConnection.get())
{
return;
}
@@ -567,9 +566,9 @@ void BackupClientContext::DoKeepAlive()
}
BOX_TRACE("KeepAliveTime reached, sending keep-alive message");
- mpConnection->QueryGetIsAlive();
+ mapConnection->QueryGetIsAlive();
- mKeepAliveTimer = Timer(mKeepAliveTime, "KeepAliveTime");
+ mKeepAliveTimer.Reset(mKeepAliveTime * MILLI_SEC_IN_SEC);
}
int BackupClientContext::GetMaximumDiffingTime()
diff --git a/bin/bbackupd/BackupClientContext.h b/bin/bbackupd/BackupClientContext.h
index 404d2d77..1fcc6ede 100644
--- a/bin/bbackupd/BackupClientContext.h
+++ b/bin/bbackupd/BackupClientContext.h
@@ -16,6 +16,7 @@
#include "BackupDaemonInterface.h"
#include "BackupStoreFile.h"
#include "ExcludeList.h"
+#include "TcpNice.h"
#include "Timer.h"
class TLSContext;
@@ -49,7 +50,8 @@ public:
bool ExtendedLogging,
bool ExtendedLogToFile,
std::string ExtendedLogFile,
- ProgressNotifier &rProgressNotifier
+ ProgressNotifier &rProgressNotifier,
+ bool TcpNiceMode
);
virtual ~BackupClientContext();
private:
@@ -207,6 +209,16 @@ public:
{
return mrProgressNotifier;
}
+
+ void SetNiceMode(bool enabled)
+ {
+ if(mTcpNiceMode)
+ {
+ mapNice->SetEnabled(enabled);
+ }
+ }
+
+ bool mExperimentalSnapshotMode;
private:
LocationResolver &mrResolver;
@@ -214,8 +226,9 @@ private:
std::string mHostname;
int mPort;
uint32_t mAccountNumber;
- SocketStreamTLS *mpSocket;
- BackupProtocolClient *mpConnection;
+ std::auto_ptr<SocketStream> mapSocket;
+ std::auto_ptr<NiceSocketStream> mapNice;
+ std::auto_ptr<BackupProtocolClient> mapConnection;
bool mExtendedLogging;
bool mExtendedLogToFile;
std::string mExtendedLogFile;
@@ -232,6 +245,7 @@ private:
int mKeepAliveTime;
int mMaximumDiffingTime;
ProgressNotifier &mrProgressNotifier;
+ bool mTcpNiceMode;
};
#endif // BACKUPCLIENTCONTEXT__H
diff --git a/bin/bbackupd/BackupClientDeleteList.cpp b/bin/bbackupd/BackupClientDeleteList.cpp
index b9b5b53e..c0414b78 100644
--- a/bin/bbackupd/BackupClientDeleteList.cpp
+++ b/bin/bbackupd/BackupClientDeleteList.cpp
@@ -13,7 +13,7 @@
#include "BackupClientDeleteList.h"
#include "BackupClientContext.h"
-#include "autogen_BackupProtocolClient.h"
+#include "autogen_BackupProtocol.h"
#include "MemLeakFindOn.h"
diff --git a/bin/bbackupd/BackupClientDirectoryRecord.cpp b/bin/bbackupd/BackupClientDirectoryRecord.cpp
index 84c17dab..90caf2e7 100644
--- a/bin/bbackupd/BackupClientDirectoryRecord.cpp
+++ b/bin/bbackupd/BackupClientDirectoryRecord.cpp
@@ -17,21 +17,26 @@
#include <errno.h>
#include <string.h>
-#include "BackupClientDirectoryRecord.h"
-#include "autogen_BackupProtocolClient.h"
+#include "autogen_BackupProtocol.h"
+#include "autogen_CipherException.h"
+#include "autogen_ClientException.h"
+#include "Archive.h"
#include "BackupClientContext.h"
-#include "IOStream.h"
-#include "MemBlockStream.h"
-#include "CommonException.h"
-#include "CollectInBufferStream.h"
-#include "BackupStoreFile.h"
+#include "BackupClientDirectoryRecord.h"
#include "BackupClientInodeToIDMap.h"
-#include "FileModificationTime.h"
#include "BackupDaemon.h"
#include "BackupStoreException.h"
-#include "Archive.h"
-#include "PathUtils.h"
+#include "BackupStoreFile.h"
+#include "BackupStoreFileEncodeStream.h"
+#include "BufferedStream.h"
+#include "CommonException.h"
+#include "CollectInBufferStream.h"
+#include "FileModificationTime.h"
+#include "IOStream.h"
#include "Logging.h"
+#include "MemBlockStream.h"
+#include "PathUtils.h"
+#include "RateLimitingStream.h"
#include "ReadLoggingStream.h"
#include "MemLeakFindOn.h"
@@ -51,6 +56,7 @@ BackupClientDirectoryRecord::BackupClientDirectoryRecord(int64_t ObjectID, const
mSubDirName(rSubDirName),
mInitialSyncDone(false),
mSyncDone(false),
+ mSuppressMultipleLinksWarning(false),
mpPendingEntries(0)
{
::memset(mStateChecksum, 0, sizeof(mStateChecksum));
@@ -98,6 +104,32 @@ void BackupClientDirectoryRecord::DeleteSubDirectories()
mSubDirectories.clear();
}
+std::string BackupClientDirectoryRecord::ConvertVssPathToRealPath(
+ const std::string &rVssPath,
+ const Location& rBackupLocation)
+{
+#ifdef ENABLE_VSS
+ BOX_TRACE("VSS: ConvertVssPathToRealPath: mIsSnapshotCreated = " <<
+ rBackupLocation.mIsSnapshotCreated);
+ BOX_TRACE("VSS: ConvertVssPathToRealPath: File/Directory Path = " <<
+ rVssPath.substr(0, rBackupLocation.mSnapshotPath.length()));
+ BOX_TRACE("VSS: ConvertVssPathToRealPath: Snapshot Path = " <<
+ rBackupLocation.mSnapshotPath);
+ if (rBackupLocation.mIsSnapshotCreated &&
+ rVssPath.substr(0, rBackupLocation.mSnapshotPath.length()) ==
+ rBackupLocation.mSnapshotPath)
+ {
+ std::string convertedPath = rBackupLocation.mPath +
+ rVssPath.substr(rBackupLocation.mSnapshotPath.length());
+ BOX_TRACE("VSS: ConvertVssPathToRealPath: Converted Path = " <<
+ convertedPath);
+ return convertedPath;
+ }
+#endif
+
+ return rVssPath;
+}
+
// --------------------------------------------------------------------------
//
// Function
@@ -115,6 +147,7 @@ void BackupClientDirectoryRecord::SyncDirectory(
int64_t ContainingDirectoryID,
const std::string &rLocalPath,
const std::string &rRemotePath,
+ const Location& rBackupLocation,
bool ThisDirHasJustBeenCreated)
{
BackupClientContext& rContext(rParams.mrContext);
@@ -160,10 +193,16 @@ void BackupClientDirectoryRecord::SyncDirectory(
// just ignore this error. In a future scan, this
// deletion will be noticed, deleted from server,
// and this object deleted.
- rNotifier.NotifyDirStatFailed(this, rLocalPath,
+ rNotifier.NotifyDirStatFailed(this,
+ ConvertVssPathToRealPath(rLocalPath, rBackupLocation),
strerror(errno));
return;
}
+
+ BOX_TRACE("Stat dir '" << rLocalPath << "' "
+ "found device/inode " <<
+ dest_st.st_dev << "/" << dest_st.st_ino);
+
// Store inode number in map so directories are tracked
// in case they're renamed
{
@@ -204,12 +243,12 @@ void BackupClientDirectoryRecord::SyncDirectory(
{
// Report the error (logs and
// eventual email to administrator)
- rNotifier.NotifyFileStatFailed(this, rLocalPath,
+ rNotifier.NotifyFileStatFailed(this,
+ ConvertVssPathToRealPath(rLocalPath, rBackupLocation),
strerror(errno));
// FIXME move to NotifyFileStatFailed()
- SetErrorWhenReadingFilesystemObject(rParams,
- rLocalPath.c_str());
+ SetErrorWhenReadingFilesystemObject(rParams, rLocalPath);
// This shouldn't happen, so we'd better not continue
THROW_EXCEPTION(CommonException, OSFileError)
@@ -221,7 +260,9 @@ void BackupClientDirectoryRecord::SyncDirectory(
DIR *dirHandle = 0;
try
{
- rNotifier.NotifyScanDirectory(this, rLocalPath);
+ std::string nonVssDirPath = ConvertVssPathToRealPath(rLocalPath,
+ rBackupLocation);
+ rNotifier.NotifyScanDirectory(this, nonVssDirPath);
dirHandle = ::opendir(rLocalPath.c_str());
if(dirHandle == 0)
@@ -231,18 +272,20 @@ void BackupClientDirectoryRecord::SyncDirectory(
if (errno == EACCES)
{
rNotifier.NotifyDirListFailed(this,
- rLocalPath, "Access denied");
+ nonVssDirPath,
+ "Access denied");
}
else
{
rNotifier.NotifyDirListFailed(this,
- rLocalPath, strerror(errno));
+ nonVssDirPath,
+ strerror(errno));
}
// Report the error (logs and eventual email
// to administrator)
SetErrorWhenReadingFilesystemObject(rParams,
- rLocalPath.c_str());
+ nonVssDirPath);
// Ignore this directory for now.
return;
}
@@ -279,6 +322,8 @@ void BackupClientDirectoryRecord::SyncDirectory(
// Stat file to get info
filename = MakeFullPath(rLocalPath, en->d_name);
+ std::string realFileName = ConvertVssPathToRealPath(filename,
+ rBackupLocation);
#ifdef WIN32
// Don't stat the file just yet, to ensure
@@ -289,9 +334,18 @@ void BackupClientDirectoryRecord::SyncDirectory(
// Our emulated readdir() abuses en->d_type,
// which would normally contain DT_REG,
// DT_DIR, etc, but we only use it here and
- // prefer S_IFREG, S_IFDIR...
- int type = en->d_type;
- #else
+ // prefer to have the full file attributes.
+ int type;
+ if (en->d_type & FILE_ATTRIBUTE_DIRECTORY)
+ {
+ type = S_IFDIR;
+ }
+ else
+ {
+ type = S_IFREG;
+ }
+
+ #else // !WIN32
if(EMU_LSTAT(filename.c_str(), &file_st) != 0)
{
if(!(rParams.mrContext.ExcludeDir(
@@ -306,15 +360,52 @@ void BackupClientDirectoryRecord::SyncDirectory(
// FIXME move to
// NotifyFileStatFailed()
- SetErrorWhenReadingFilesystemObject(
- rParams, filename.c_str());
+ SetErrorWhenReadingFilesystemObject(rParams, filename);
}
// Ignore this entry for now.
continue;
}
- if(file_st.st_dev != dest_st.st_dev)
+ int type = file_st.st_mode & S_IFMT;
+
+ // ecryptfs reports nlink > 1 for directories
+ // with contents, but no filesystem supports
+ // hardlinking directories? so we can ignore
+ // this if the entry is a directory.
+ if(file_st.st_nlink != 1 && type == S_IFDIR)
+ {
+ BOX_INFO("Ignoring apparent hard link "
+ "count on directory: " <<
+ filename << ", nlink=" <<
+ file_st.st_nlink);
+ }
+ else if(file_st.st_nlink > 1)
+ {
+ if(!mSuppressMultipleLinksWarning)
+ {
+ BOX_WARNING("File is hard linked, this may "
+ "cause rename tracking to fail and "
+ "move files incorrectly in your "
+ "backup! " << filename <<
+ ", nlink=" << file_st.st_nlink <<
+ " (suppressing further warnings)");
+ mSuppressMultipleLinksWarning = true;
+ }
+ SetErrorWhenReadingFilesystemObject(rParams, filename);
+ }
+
+ BOX_TRACE("Stat entry '" << filename << "' "
+ "found device/inode " <<
+ file_st.st_dev << "/" <<
+ file_st.st_ino);
+
+ /* Workaround for apparent btrfs bug, where
+ symlinks appear to be on a different filesystem
+ than their containing directory, thanks to
+ Toke Hoiland-Jorgensen */
+ if(type == S_IFDIR &&
+ file_st.st_dev != dest_st.st_dev)
{
if(!(rParams.mrContext.ExcludeDir(
filename)))
@@ -324,8 +415,6 @@ void BackupClientDirectoryRecord::SyncDirectory(
}
continue;
}
-
- int type = file_st.st_mode & S_IFMT;
#endif
if(type == S_IFREG || type == S_IFLNK)
@@ -333,12 +422,9 @@ void BackupClientDirectoryRecord::SyncDirectory(
// File or symbolic link
// Exclude it?
- if(rParams.mrContext.ExcludeFile(filename))
+ if(rParams.mrContext.ExcludeFile(realFileName))
{
- rNotifier.NotifyFileExcluded(
- this,
- filename);
-
+ rNotifier.NotifyFileExcluded(this, realFileName);
// Next item!
continue;
}
@@ -351,38 +437,50 @@ void BackupClientDirectoryRecord::SyncDirectory(
// Directory
// Exclude it?
- if(rParams.mrContext.ExcludeDir(filename))
+ if(rParams.mrContext.ExcludeDir(realFileName))
{
- rNotifier.NotifyDirExcluded(
- this,
- filename);
+ rNotifier.NotifyDirExcluded(this, realFileName);
// Next item!
continue;
}
+ #ifdef WIN32
+ // exclude reparse points, as Application Data points to the
+ // parent directory under Vista and later, and causes an
+ // infinite loop:
+ // http://social.msdn.microsoft.com/forums/en-US/windowscompatibility/thread/05d14368-25dd-41c8-bdba-5590bf762a68/
+ if (en->d_type & FILE_ATTRIBUTE_REPARSE_POINT)
+ {
+ rNotifier.NotifyMountPointSkipped(this, realFileName);
+ continue;
+ }
+ #endif
+
// Store on list
dirs.push_back(std::string(en->d_name));
}
- else
+ else // not a file or directory, what is it?
{
- if (type == S_IFSOCK || type == S_IFIFO)
+ if (type == S_IFSOCK
+# ifndef WIN32
+ || type == S_IFIFO
+# endif
+ )
{
// removed notification for these types
// see Debian bug 479145, no objections
}
- else if(rParams.mrContext.ExcludeFile(filename))
+ else if(rParams.mrContext.ExcludeFile(realFileName))
{
- rNotifier.NotifyFileExcluded(
- this,
- filename);
+ rNotifier.NotifyFileExcluded(this, realFileName);
}
else
{
- rNotifier.NotifyUnsupportedFileType(
- this, filename);
- SetErrorWhenReadingFilesystemObject(
- rParams, filename.c_str());
+ rNotifier.NotifyUnsupportedFileType(this,
+ realFileName);
+ SetErrorWhenReadingFilesystemObject(rParams,
+ realFileName);
}
continue;
@@ -397,13 +495,12 @@ void BackupClientDirectoryRecord::SyncDirectory(
if(emu_stat(filename.c_str(), &file_st) != 0)
{
rNotifier.NotifyFileStatFailed(this,
- filename,
+ ConvertVssPathToRealPath(filename, rBackupLocation),
strerror(errno));
// Report the error (logs and
// eventual email to administrator)
- SetErrorWhenReadingFilesystemObject(
- rParams, filename.c_str());
+ SetErrorWhenReadingFilesystemObject(rParams, filename);
// Ignore this entry for now.
continue;
@@ -412,7 +509,7 @@ void BackupClientDirectoryRecord::SyncDirectory(
if(file_st.st_dev != link_st.st_dev)
{
rNotifier.NotifyMountPointSkipped(this,
- filename);
+ ConvertVssPathToRealPath(filename, rBackupLocation));
continue;
}
#endif
@@ -432,8 +529,8 @@ void BackupClientDirectoryRecord::SyncDirectory(
// Log that this has happened
if(!rParams.mHaveLoggedWarningAboutFutureFileTimes)
{
- rNotifier.NotifyFileModifiedInFuture(
- this, filename);
+ rNotifier.NotifyFileModifiedInFuture(this,
+ ConvertVssPathToRealPath(filename, rBackupLocation));
rParams.mHaveLoggedWarningAboutFutureFileTimes = true;
}
}
@@ -507,7 +604,7 @@ void BackupClientDirectoryRecord::SyncDirectory(
// Do the directory reading
bool updateCompleteSuccess = UpdateItems(rParams, rLocalPath,
- rRemotePath, pdirOnStore, entriesLeftOver, files, dirs);
+ rRemotePath, rBackupLocation, pdirOnStore, entriesLeftOver, files, dirs);
// LAST THING! (think exception safety)
// Store the new checksum -- don't fetch things unnecessarily in the future
@@ -564,10 +661,11 @@ BackupStoreDirectory *BackupClientDirectoryRecord::FetchDirectoryListing(BackupC
BackupProtocolClient &connection(rParams.mrContext.GetConnection());
// Query the directory
- std::auto_ptr<BackupProtocolClientSuccess> dirreply(connection.QueryListDirectory(
+ std::auto_ptr<BackupProtocolSuccess> dirreply(connection.QueryListDirectory(
mObjectID,
- BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, // both files and directories
- BackupProtocolClientListDirectory::Flags_Deleted | BackupProtocolClientListDirectory::Flags_OldVersion, // exclude old/deleted stuff
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, // both files and directories
+ BackupProtocolListDirectory::Flags_Deleted |
+ BackupProtocolListDirectory::Flags_OldVersion, // exclude old/deleted stuff
true /* want attributes */));
// Retrieve the directory from the stream following
@@ -630,11 +728,38 @@ void BackupClientDirectoryRecord::UpdateAttributes(BackupClientDirectoryRecord::
BackupProtocolClient &connection(rParams.mrContext.GetConnection());
// Exception thrown if this doesn't work
- MemBlockStream attrStream(attr);
+ std::auto_ptr<IOStream> attrStream(new MemBlockStream(attr));
connection.QueryChangeDirAttributes(mObjectID, attrModTime, attrStream);
}
}
+std::string BackupClientDirectoryRecord::DecryptFilename(
+ BackupStoreDirectory::Entry *en,
+ const std::string& rRemoteDirectoryPath)
+{
+ BackupStoreFilenameClear fn(en->GetName());
+ return DecryptFilename(fn, en->GetObjectID(), rRemoteDirectoryPath);
+}
+
+std::string BackupClientDirectoryRecord::DecryptFilename(
+ BackupStoreFilenameClear fn, int64_t filenameObjectID,
+ const std::string& rRemoteDirectoryPath)
+{
+ std::string filenameClear;
+ try
+ {
+ filenameClear = fn.GetClearFilename();
+ }
+ catch(BoxException &e)
+ {
+ BOX_ERROR("Failed to decrypt filename for object " <<
+ BOX_FORMAT_OBJECTID(filenameObjectID) << " in "
+ "directory " << BOX_FORMAT_OBJECTID(mObjectID) <<
+ " (" << rRemoteDirectoryPath << ")");
+ throw;
+ }
+ return filenameClear;
+}
// --------------------------------------------------------------------------
//
@@ -649,6 +774,7 @@ bool BackupClientDirectoryRecord::UpdateItems(
BackupClientDirectoryRecord::SyncParams &rParams,
const std::string &rLocalPath,
const std::string &rRemotePath,
+ const Location& rBackupLocation,
BackupStoreDirectory *pDirOnStore,
std::vector<BackupStoreDirectory::Entry *> &rEntriesLeftOver,
std::vector<std::string> &rFiles,
@@ -673,7 +799,19 @@ bool BackupClientDirectoryRecord::UpdateItems(
BackupStoreDirectory::Entry *en = 0;
while((en = i.Next()) != 0)
{
- decryptedEntries[BackupStoreFilenameClear(en->GetName()).GetClearFilename()] = en;
+ std::string filenameClear;
+ try
+ {
+ filenameClear = DecryptFilename(en,
+ rRemotePath);
+ decryptedEntries[filenameClear] = en;
+ }
+ catch (CipherException &e)
+ {
+ BOX_ERROR("Failed to decrypt a filename, "
+ "pretending that the file doesn't "
+ "exist");
+ }
}
}
@@ -686,26 +824,26 @@ bool BackupClientDirectoryRecord::UpdateItems(
// Filename of this file
std::string filename(MakeFullPath(rLocalPath, *f));
+ std::string nonVssFilePath = ConvertVssPathToRealPath(filename,
+ rBackupLocation);
// Get relevant info about file
box_time_t modTime = 0;
uint64_t attributesHash = 0;
int64_t fileSize = 0;
InodeRefType inodeNum = 0;
- bool hasMultipleHardLinks = true;
// BLOCK
{
// Stat the file
EMU_STRUCT_STAT st;
if(EMU_LSTAT(filename.c_str(), &st) != 0)
{
- rNotifier.NotifyFileStatFailed(this,
- filename, strerror(errno));
+ rNotifier.NotifyFileStatFailed(this, nonVssFilePath,
+ strerror(errno));
// Report the error (logs and
// eventual email to administrator)
- SetErrorWhenReadingFilesystemObject(rParams,
- filename.c_str());
+ SetErrorWhenReadingFilesystemObject(rParams, nonVssFilePath);
// Ignore this entry for now.
continue;
@@ -715,7 +853,6 @@ bool BackupClientDirectoryRecord::UpdateItems(
modTime = FileModificationTime(st);
fileSize = st.st_size;
inodeNum = st.st_ino;
- hasMultipleHardLinks = (st.st_nlink > 1);
attributesHash = BackupClientFileAttributes::GenerateAttributeHash(st, filename, *f);
}
@@ -734,7 +871,7 @@ bool BackupClientDirectoryRecord::UpdateItems(
}
// Check that the entry which might have been found is in fact a file
- if((en != 0) && ((en->GetFlags() & BackupStoreDirectory::Entry::Flags_File) == 0))
+ if((en != 0) && !(en->IsFile()))
{
// Directory exists in the place of this file -- sort it out
RemoveDirectoryInPlaceOfFile(rParams, pDirOnStore,
@@ -760,7 +897,9 @@ bool BackupClientDirectoryRecord::UpdateItems(
bool isCurrentVersion = false;
box_time_t srvModTime = 0, srvAttributesHash = 0;
BackupStoreFilenameClear oldLeafname;
- if(rContext.FindFilename(renameObjectID, renameInDirectory, localPotentialOldName, isDir, isCurrentVersion, &srvModTime, &srvAttributesHash, &oldLeafname))
+ if(rContext.FindFilename(renameObjectID, renameInDirectory,
+ localPotentialOldName, isDir, isCurrentVersion,
+ &srvModTime, &srvAttributesHash, &oldLeafname))
{
// Only interested if it's a file and the latest version
if(!isDir && isCurrentVersion)
@@ -780,8 +919,11 @@ bool BackupClientDirectoryRecord::UpdateItems(
if(!rContext.StorageLimitExceeded())
{
// Rename the existing files (ie include old versions) on the server
- connection.QueryMoveObject(renameObjectID, renameInDirectory, mObjectID /* move to this directory */,
- BackupProtocolClientMoveObject::Flags_MoveAllWithSameName | BackupProtocolClientMoveObject::Flags_AllowMoveOverDeletedObject,
+ connection.QueryMoveObject(renameObjectID,
+ renameInDirectory,
+ mObjectID /* move to this directory */,
+ BackupProtocolMoveObject::Flags_MoveAllWithSameName |
+ BackupProtocolMoveObject::Flags_AllowMoveOverDeletedObject,
storeFilename);
// Stop the attempt to delete the file in the original location
@@ -790,8 +932,11 @@ bool BackupClientDirectoryRecord::UpdateItems(
// Create new entry in the directory for it
// -- will be near enough what's actually on the server for the rest to work.
- en = pDirOnStore->AddEntry(storeFilename, srvModTime, renameObjectID, 0 /* size in blocks unknown, but not needed */,
- BackupStoreDirectory::Entry::Flags_File, srvAttributesHash);
+ en = pDirOnStore->AddEntry(storeFilename,
+ srvModTime, renameObjectID,
+ 0 /* size in blocks unknown, but not needed */,
+ BackupStoreDirectory::Entry::Flags_File,
+ srvAttributesHash);
// Store the object ID for the inode lookup map later
latestObjectID = renameObjectID;
@@ -833,6 +978,7 @@ bool BackupClientDirectoryRecord::UpdateItems(
// and if we know about it from a directory listing, that it hasn't got the same upload time as on the store
bool doUpload = false;
+ std::string decisionReason = "unknown";
// Only upload a file if the mod time locally is
// different to that on the server.
@@ -848,16 +994,12 @@ bool BackupClientDirectoryRecord::UpdateItems(
if (pDirOnStore != 0 && en == 0)
{
doUpload = true;
- BOX_TRACE("Upload decision: " <<
- filename << ": will upload "
- "(not on server)");
+ decisionReason = "not on server";
}
else if (modTime >= rParams.mSyncPeriodStart)
{
doUpload = true;
- BOX_TRACE("Upload decision: " <<
- filename << ": will upload "
- "(modified since last sync)");
+ decisionReason = "modified since last sync";
}
}
@@ -874,9 +1016,7 @@ bool BackupClientDirectoryRecord::UpdateItems(
> rParams.mMaxUploadWait)
{
doUpload = true;
- BOX_TRACE("Upload decision: " <<
- filename << ": will upload "
- "(continually modified)");
+ decisionReason = "continually modified";
}
// Then make sure that if files are added with a
@@ -892,9 +1032,7 @@ bool BackupClientDirectoryRecord::UpdateItems(
en->GetModificationTime() != modTime)
{
doUpload = true;
- BOX_TRACE("Upload decision: " <<
- filename << ": will upload "
- "(mod time changed)");
+ decisionReason = "mod time changed";
}
// And just to catch really badly off clocks in
@@ -905,17 +1043,14 @@ bool BackupClientDirectoryRecord::UpdateItems(
rParams.mUploadAfterThisTimeInTheFuture)
{
doUpload = true;
- BOX_TRACE("Upload decision: " <<
- filename << ": will upload "
- "(mod time in the future)");
+ decisionReason = "mod time in the future";
}
}
if (en != 0 && en->GetModificationTime() == modTime)
{
- BOX_TRACE("Upload decision: " <<
- filename << ": will not upload "
- "(not modified since last upload)");
+ doUpload = false;
+ decisionReason = "not modified since last upload";
}
else if (!doUpload)
{
@@ -924,22 +1059,26 @@ bool BackupClientDirectoryRecord::UpdateItems(
box_time_t now = GetCurrentBoxTime();
int age = BoxTimeToSeconds(now -
modTime);
- BOX_TRACE("Upload decision: " <<
- filename << ": will not upload "
- "(modified too recently: "
- "only " << age << " seconds ago)");
+ std::ostringstream s;
+ s << "modified too recently: "
+ "only " << age << " seconds ago";
+ decisionReason = s.str();
}
else
{
- BOX_TRACE("Upload decision: " <<
- filename << ": will not upload "
- "(mod time is " << modTime <<
+ std::ostringstream s;
+ s << "mod time is " << modTime <<
" which is outside sync window, "
<< rParams.mSyncPeriodStart << " to "
- << rParams.mSyncPeriodEnd << ")");
+ << rParams.mSyncPeriodEnd;
+ decisionReason = s.str();
}
}
+ BOX_TRACE("Upload decision: " << nonVssFilePath << ": " <<
+ (doUpload ? "will upload" : "will not upload") <<
+ " (" << decisionReason << ")");
+
bool fileSynced = true;
if (doUpload)
@@ -968,7 +1107,9 @@ bool BackupClientDirectoryRecord::UpdateItems(
try
{
latestObjectID = UploadFile(rParams,
- filename, storeFilename,
+ filename,
+ nonVssFilePath,
+ storeFilename,
fileSize, modTime,
attributesHash,
noPreviousVersionOnServer);
@@ -992,7 +1133,7 @@ bool BackupClientDirectoryRecord::UpdateItems(
// retries would probably just cause
// more problems.
rNotifier.NotifyFileUploadException(
- this, filename, e);
+ this, nonVssFilePath, e);
throw;
}
catch(BoxException &e)
@@ -1009,9 +1150,10 @@ bool BackupClientDirectoryRecord::UpdateItems(
// code false, to show error in directory
allUpdatedSuccessfully = false;
// Log it.
- SetErrorWhenReadingFilesystemObject(rParams, filename.c_str());
- rNotifier.NotifyFileUploadException(
- this, filename, e);
+ SetErrorWhenReadingFilesystemObject(rParams,
+ nonVssFilePath);
+ rNotifier.NotifyFileUploadException(this,
+ nonVssFilePath, e);
}
// Update structures if the file was uploaded
@@ -1029,8 +1171,7 @@ bool BackupClientDirectoryRecord::UpdateItems(
}
else
{
- rNotifier.NotifyFileSkippedServerFull(this,
- filename);
+ rNotifier.NotifyFileSkippedServerFull(this, nonVssFilePath);
}
}
else if(en != 0 && en->GetAttributesHash() != attributesHash)
@@ -1050,22 +1191,23 @@ bool BackupClientDirectoryRecord::UpdateItems(
{
try
{
- rNotifier.NotifyFileUploadingAttributes(
- this, filename);
+ rNotifier.NotifyFileUploadingAttributes(this,
+ nonVssFilePath);
// Update store
BackupClientFileAttributes attr;
- attr.ReadAttributes(filename.c_str(), false /* put mod times in the attributes, please */);
- MemBlockStream attrStream(attr);
+ attr.ReadAttributes(filename,
+ false /* put mod times in the attributes, please */);
+ std::auto_ptr<IOStream> attrStream(
+ new MemBlockStream(attr));
connection.QuerySetReplacementFileAttributes(mObjectID, attributesHash, storeFilename, attrStream);
fileSynced = true;
}
catch (BoxException &e)
{
- BOX_ERROR("Failed to read or store "
- "file attributes for '" <<
- filename << "', will try "
- "again later");
+ BOX_ERROR("Failed to read or store file attributes "
+ "for '" << nonVssFilePath << "', will try again "
+ "later");
}
}
}
@@ -1109,7 +1251,7 @@ bool BackupClientDirectoryRecord::UpdateItems(
{
// Use this one
BOX_TRACE("Storing uploaded file ID " <<
- inodeNum << " (" << filename << ") "
+ inodeNum << " (" << nonVssFilePath << ") "
"in ID map as object " <<
latestObjectID << " with parent " <<
mObjectID);
@@ -1126,7 +1268,12 @@ bool BackupClientDirectoryRecord::UpdateItems(
// Found
if (dirid != mObjectID)
{
- BOX_WARNING("Found conflicting parent ID for file ID " << inodeNum << " (" << filename << "): expected " << mObjectID << " but found " << dirid << " (same directory used in two different locations?)");
+ BOX_WARNING("Found conflicting parent ID for "
+ "file ID " << inodeNum << " (" <<
+ nonVssFilePath << "): expected " <<
+ mObjectID << " but found " << dirid <<
+ " (same directory used in two different "
+ "locations?)");
}
ASSERT(dirid == mObjectID);
@@ -1136,11 +1283,9 @@ bool BackupClientDirectoryRecord::UpdateItems(
// into it. However, in a long running process this may happen occasionally and
// not indicate anything wrong.
// Run the release version for real life use, where this check is not made.
- BOX_TRACE("Storing found file ID " <<
- inodeNum << " (" << filename <<
- ") in ID map as object " <<
- objid << " with parent " <<
- mObjectID);
+ BOX_TRACE("Storing found file ID " << inodeNum <<
+ " (" << nonVssFilePath << ") in ID map as "
+ "object " << objid << " with parent " << mObjectID);
idMap.AddToMap(inodeNum, objid,
mObjectID /* containing directory */);
}
@@ -1149,7 +1294,7 @@ bool BackupClientDirectoryRecord::UpdateItems(
if (fileSynced)
{
- rNotifier.NotifyFileSynchronised(this, filename,
+ rNotifier.NotifyFileSynchronised(this, nonVssFilePath,
fileSize);
}
}
@@ -1175,6 +1320,8 @@ bool BackupClientDirectoryRecord::UpdateItems(
// Get the local filename
std::string dirname(MakeFullPath(rLocalPath, *d));
+ std::string nonVssDirPath = ConvertVssPathToRealPath(dirname,
+ rBackupLocation);
// See if it's in the listing (if we have one)
BackupStoreFilenameClear storeFilename(*d);
@@ -1189,14 +1336,17 @@ bool BackupClientDirectoryRecord::UpdateItems(
}
// Check that the entry which might have been found is in fact a directory
- if((en != 0) && ((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) == 0))
+ if((en != 0) && !(en->IsDir()))
{
// Entry exists, but is not a directory. Bad.
// Get rid of it.
BackupProtocolClient &connection(rContext.GetConnection());
connection.QueryDeleteFile(mObjectID /* in directory */, storeFilename);
+
+ std::string filenameClear = DecryptFilename(en,
+ rRemotePath);
rNotifier.NotifyFileDeleted(en->GetObjectID(),
- storeFilename.GetClearFilename());
+ filenameClear);
// Nothing found
en = 0;
@@ -1266,7 +1416,7 @@ bool BackupClientDirectoryRecord::UpdateItems(
BOX_WARNING("Failed to read attributes "
"of directory, cannot check "
"for rename, assuming new: '"
- << dirname << "'");
+ << nonVssDirPath << "'");
failedToReadAttributes = true;
}
@@ -1284,7 +1434,9 @@ bool BackupClientDirectoryRecord::UpdateItems(
std::string localPotentialOldName;
bool isDir = false;
bool isCurrentVersion = false;
- if(rContext.FindFilename(renameObjectID, renameInDirectory, localPotentialOldName, isDir, isCurrentVersion))
+ if(rContext.FindFilename(renameObjectID,
+ renameInDirectory, localPotentialOldName,
+ isDir, isCurrentVersion))
{
// Only interested if it's a directory
if(isDir && isCurrentVersion)
@@ -1309,13 +1461,16 @@ bool BackupClientDirectoryRecord::UpdateItems(
// in the else if(...) above will be correct.
// Build attribute stream for sending
- MemBlockStream attrStream(attr);
+ std::auto_ptr<IOStream> attrStream(new MemBlockStream(attr));
if(renameDir)
{
// Rename the existing directory on the server
- connection.QueryMoveObject(renameObjectID, renameInDirectory, mObjectID /* move to this directory */,
- BackupProtocolClientMoveObject::Flags_MoveAllWithSameName | BackupProtocolClientMoveObject::Flags_AllowMoveOverDeletedObject,
+ connection.QueryMoveObject(renameObjectID,
+ renameInDirectory,
+ mObjectID /* move to this directory */,
+ BackupProtocolMoveObject::Flags_MoveAllWithSameName |
+ BackupProtocolMoveObject::Flags_AllowMoveOverDeletedObject,
storeFilename);
// Put the latest attributes on it
@@ -1332,12 +1487,22 @@ bool BackupClientDirectoryRecord::UpdateItems(
else
{
// Create a new directory
- std::auto_ptr<BackupProtocolClientSuccess> dirCreate(connection.QueryCreateDirectory(
- mObjectID, attrModTime, storeFilename, attrStream));
+ std::auto_ptr<BackupProtocolSuccess> dirCreate(
+ connection.QueryCreateDirectory(
+ mObjectID, attrModTime,
+ storeFilename, attrStream));
subDirObjectID = dirCreate->GetObjectID();
// Flag as having done this for optimisation later
haveJustCreatedDirOnServer = true;
+
+ std::string filenameClear =
+ DecryptFilename(storeFilename,
+ subDirObjectID,
+ rRemotePath);
+ rNotifier.NotifyDirectoryCreated(
+ subDirObjectID, filenameClear,
+ nonVssDirPath);
}
}
@@ -1365,8 +1530,8 @@ bool BackupClientDirectoryRecord::UpdateItems(
if(psubDirRecord)
{
// Sync this sub directory too
- psubDirRecord->SyncDirectory(rParams, mObjectID,
- dirname, rRemotePath + "/" + *d,
+ psubDirRecord->SyncDirectory(rParams, mObjectID, dirname,
+ rRemotePath + "/" + *d, rBackupLocation,
haveJustCreatedDirOnServer);
}
@@ -1397,10 +1562,26 @@ bool BackupClientDirectoryRecord::UpdateItems(
// If there's an error during the process, it doesn't matter if things
// aren't actually deleted, as the whole state will be reset anyway.
BackupClientDeleteList &rdel(rContext.GetDeleteList());
+ std::string filenameClear;
+ bool isCorruptFilename = false;
+
+ try
+ {
+ filenameClear = DecryptFilename(en,
+ rRemotePath);
+ }
+ catch (CipherException &e)
+ {
+ BOX_ERROR("Failed to decrypt a filename, "
+ "scheduling that file for deletion");
+ filenameClear = "<corrupt filename>";
+ isCorruptFilename = true;
+ }
- BackupStoreFilenameClear clear(en->GetName());
std::string localName = MakeFullPath(rLocalPath,
- clear.GetClearFilename());
+ filenameClear);
+ std::string nonVssLocalName = ConvertVssPathToRealPath(localName,
+ rBackupLocation);
// Delete this entry -- file or directory?
if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_File) != 0)
@@ -1418,20 +1599,17 @@ bool BackupClientDirectoryRecord::UpdateItems(
// If there's a directory record for it in
// the sub directory map, delete it now
BackupStoreFilenameClear dirname(en->GetName());
- std::map<std::string, BackupClientDirectoryRecord *>::iterator e(mSubDirectories.find(dirname.GetClearFilename()));
- if(e != mSubDirectories.end())
+ std::map<std::string, BackupClientDirectoryRecord *>::iterator
+ e(mSubDirectories.find(filenameClear));
+ if(e != mSubDirectories.end() && !isCorruptFilename)
{
// Carefully delete the entry from the map
BackupClientDirectoryRecord *rec = e->second;
mSubDirectories.erase(e);
delete rec;
- std::string name = MakeFullPath(
- rLocalPath,
- dirname.GetClearFilename());
-
- BOX_TRACE("Deleted directory record "
- "for " << name);
+ BOX_TRACE("Deleted directory record for " <<
+ nonVssLocalName);
}
}
}
@@ -1498,6 +1676,7 @@ void BackupClientDirectoryRecord::RemoveDirectoryInPlaceOfFile(
int64_t BackupClientDirectoryRecord::UploadFile(
BackupClientDirectoryRecord::SyncParams &rParams,
const std::string &rFilename,
+ const std::string &rNonVssFilePath,
const BackupStoreFilename &rStoreFilename,
int64_t FileSize,
box_time_t ModificationTime,
@@ -1512,11 +1691,14 @@ int64_t BackupClientDirectoryRecord::UploadFile(
// Info
int64_t objID = 0;
- bool doNormalUpload = true;
+ int64_t uploadedSize = -1;
// Use a try block to catch store full errors
try
{
+ std::auto_ptr<BackupStoreFileEncodeStream> apStreamToUpload;
+ int64_t diffFromID = 0;
+
// Might an old version be on the server, and is the file
// size over the diffing threshold?
if(!NoPreviousVersionOnServer &&
@@ -1524,14 +1706,14 @@ int64_t BackupClientDirectoryRecord::UploadFile(
{
// YES -- try to do diff, if possible
// First, query the server to see if there's an old version available
- std::auto_ptr<BackupProtocolClientSuccess> getBlockIndex(connection.QueryGetBlockIndexByName(mObjectID, rStoreFilename));
- int64_t diffFromID = getBlockIndex->GetObjectID();
+ std::auto_ptr<BackupProtocolSuccess> getBlockIndex(connection.QueryGetBlockIndexByName(mObjectID, rStoreFilename));
+ diffFromID = getBlockIndex->GetObjectID();
if(diffFromID != 0)
{
// Found an old version
- rNotifier.NotifyFileUploadingPatch(this,
- rFilename);
+ rNotifier.NotifyFileUploadingPatch(this,
+ rNonVssFilePath);
// Get the index
std::auto_ptr<IOStream> blockIndexStream(connection.ReceiveStream());
@@ -1543,55 +1725,68 @@ int64_t BackupClientDirectoryRecord::UploadFile(
rContext.ManageDiffProcess();
bool isCompletelyDifferent = false;
- std::auto_ptr<IOStream> patchStream(
- BackupStoreFile::EncodeFileDiff(
- rFilename.c_str(),
- mObjectID, /* containing directory */
- rStoreFilename, diffFromID, *blockIndexStream,
- connection.GetTimeout(),
- &rContext, // DiffTimer implementation
- 0 /* not interested in the modification time */,
- &isCompletelyDifferent));
-
- rContext.UnManageDiffProcess();
- //
- // Upload the patch to the store
- //
- std::auto_ptr<BackupProtocolClientSuccess> stored(connection.QueryStoreFile(mObjectID, ModificationTime,
- AttributesHash, isCompletelyDifferent?(0):(diffFromID), rStoreFilename, *patchStream));
-
- // Get object ID from the result
- objID = stored->GetObjectID();
+ apStreamToUpload = BackupStoreFile::EncodeFileDiff(
+ rFilename,
+ mObjectID, /* containing directory */
+ rStoreFilename, diffFromID, *blockIndexStream,
+ connection.GetTimeout(),
+ &rContext, // DiffTimer implementation
+ 0 /* not interested in the modification time */,
+ &isCompletelyDifferent);
- // Don't attempt to upload it again!
- doNormalUpload = false;
+ if(isCompletelyDifferent)
+ {
+ diffFromID = 0;
+ }
+
+ rContext.UnManageDiffProcess();
}
}
- if(doNormalUpload)
+ if(!apStreamToUpload.get()) // No patch upload, so do a normal upload
{
// below threshold or nothing to diff from, so upload whole
- rNotifier.NotifyFileUploading(this, rFilename);
+ rNotifier.NotifyFileUploading(this, rNonVssFilePath);
// Prepare to upload, getting a stream which will encode the file as we go along
- std::auto_ptr<IOStream> upload(
- BackupStoreFile::EncodeFile(rFilename.c_str(),
- mObjectID, rStoreFilename, NULL,
- &rParams,
- &(rParams.mrRunStatusProvider)));
-
- // Send to store
- std::auto_ptr<BackupProtocolClientSuccess> stored(
- connection.QueryStoreFile(
- mObjectID, ModificationTime,
- AttributesHash,
- 0 /* no diff from file ID */,
- rStoreFilename, *upload));
-
- // Get object ID from the result
- objID = stored->GetObjectID();
+ apStreamToUpload = BackupStoreFile::EncodeFile(
+ rFilename, mObjectID, /* containing directory */
+ rStoreFilename, NULL, &rParams,
+ &(rParams.mrRunStatusProvider));
+ }
+
+ rContext.SetNiceMode(true);
+ std::auto_ptr<IOStream> apWrappedStream;
+
+ if(rParams.mMaxUploadRate > 0)
+ {
+ apWrappedStream.reset(new RateLimitingStream(
+ *apStreamToUpload, rParams.mMaxUploadRate));
+ }
+ else
+ {
+ // Wrap the stream in *something*, so that
+ // QueryStoreFile() doesn't delete the original
+ // stream (upload object) and we can retrieve
+ // the byte counter.
+ apWrappedStream.reset(new BufferedStream(
+ *apStreamToUpload));
}
+
+ // Send to store
+ std::auto_ptr<BackupProtocolSuccess> stored(
+ connection.QueryStoreFile(
+ mObjectID, ModificationTime,
+ AttributesHash,
+ diffFromID,
+ rStoreFilename, apWrappedStream));
+
+ rContext.SetNiceMode(false);
+
+ // Get object ID from the result
+ objID = stored->GetObjectID();
+ uploadedSize = apStreamToUpload->GetTotalBytesSent();
}
catch(BoxException &e)
{
@@ -1605,8 +1800,8 @@ int64_t BackupClientDirectoryRecord::UploadFile(
int type, subtype;
if(connection.GetLastError(type, subtype))
{
- if(type == BackupProtocolClientError::ErrorType
- && subtype == BackupProtocolClientError::Err_StorageLimitExceeded)
+ if(type == BackupProtocolError::ErrorType
+ && subtype == BackupProtocolError::Err_StorageLimitExceeded)
{
// The hard limit was exceeded on the server, notify!
rParams.mrSysadminNotifier.NotifySysadmin(
@@ -1617,7 +1812,7 @@ int64_t BackupClientDirectoryRecord::UploadFile(
return 0;
}
rNotifier.NotifyFileUploadServerError(this,
- rFilename, type, subtype);
+ rNonVssFilePath, type, subtype);
}
}
@@ -1625,7 +1820,8 @@ int64_t BackupClientDirectoryRecord::UploadFile(
throw;
}
- rNotifier.NotifyFileUploaded(this, rFilename, FileSize);
+ rNotifier.NotifyFileUploaded(this, rNonVssFilePath, FileSize,
+ uploadedSize);
// Return the new object ID of this file
return objID;
@@ -1641,7 +1837,9 @@ int64_t BackupClientDirectoryRecord::UploadFile(
// Created: 29/3/04
//
// --------------------------------------------------------------------------
-void BackupClientDirectoryRecord::SetErrorWhenReadingFilesystemObject(BackupClientDirectoryRecord::SyncParams &rParams, const char *Filename)
+void BackupClientDirectoryRecord::SetErrorWhenReadingFilesystemObject(
+ BackupClientDirectoryRecord::SyncParams &rParams,
+ const std::string& rFilename)
{
// Zero hash, so it gets synced properly next time round.
::memset(mStateChecksum, 0, sizeof(mStateChecksum));
@@ -1671,19 +1869,20 @@ BackupClientDirectoryRecord::SyncParams::SyncParams(
SysadminNotifier &rSysadminNotifier,
ProgressNotifier &rProgressNotifier,
BackupClientContext &rContext)
- : mSyncPeriodStart(0),
- mSyncPeriodEnd(0),
- mMaxUploadWait(0),
- mMaxFileTimeInFuture(99999999999999999LL),
- mFileTrackingSizeThreshold(16*1024),
- mDiffingUploadSizeThreshold(16*1024),
- mrRunStatusProvider(rRunStatusProvider),
- mrSysadminNotifier(rSysadminNotifier),
- mrProgressNotifier(rProgressNotifier),
- mrContext(rContext),
- mReadErrorsOnFilesystemObjects(false),
- mUploadAfterThisTimeInTheFuture(99999999999999999LL),
- mHaveLoggedWarningAboutFutureFileTimes(false)
+: mSyncPeriodStart(0),
+ mSyncPeriodEnd(0),
+ mMaxUploadWait(0),
+ mMaxFileTimeInFuture(99999999999999999LL),
+ mFileTrackingSizeThreshold(16*1024),
+ mDiffingUploadSizeThreshold(16*1024),
+ mrRunStatusProvider(rRunStatusProvider),
+ mrSysadminNotifier(rSysadminNotifier),
+ mrProgressNotifier(rProgressNotifier),
+ mrContext(rContext),
+ mReadErrorsOnFilesystemObjects(false),
+ mMaxUploadRate(0),
+ mUploadAfterThisTimeInTheFuture(99999999999999999LL),
+ mHaveLoggedWarningAboutFutureFileTimes(false)
{
}
@@ -1874,3 +2073,220 @@ void BackupClientDirectoryRecord::Serialize(Archive & rArchive) const
pSubItem->Serialize(rArchive);
}
}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Location::Location()
+// Purpose: Constructor
+// Created: 11/11/03
+//
+// --------------------------------------------------------------------------
+Location::Location()
+ : mIDMapIndex(0),
+ mpExcludeFiles(0),
+ mpExcludeDirs(0)
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Location::~Location()
+// Purpose: Destructor
+// Created: 11/11/03
+//
+// --------------------------------------------------------------------------
+Location::~Location()
+{
+ // Clean up exclude locations
+ if(mpExcludeDirs != 0)
+ {
+ delete mpExcludeDirs;
+ mpExcludeDirs = 0;
+ }
+ if(mpExcludeFiles != 0)
+ {
+ delete mpExcludeFiles;
+ mpExcludeFiles = 0;
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Location::Serialize(Archive & rArchive)
+// Purpose: Serializes this object instance into a stream of bytes,
+// using an Archive abstraction.
+//
+// Created: 2005/04/11
+//
+// --------------------------------------------------------------------------
+void Location::Serialize(Archive & rArchive) const
+{
+ //
+ //
+ //
+ rArchive.Write(mName);
+ rArchive.Write(mPath);
+ rArchive.Write(mIDMapIndex);
+
+ //
+ //
+ //
+ if(mpDirectoryRecord.get() == NULL)
+ {
+ int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_NOOP;
+ rArchive.Write(aMagicMarker);
+ }
+ else
+ {
+ int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_RECURSE; // be explicit about whether recursion follows
+ rArchive.Write(aMagicMarker);
+
+ mpDirectoryRecord->Serialize(rArchive);
+ }
+
+ //
+ //
+ //
+ if(!mpExcludeFiles)
+ {
+ int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_NOOP;
+ rArchive.Write(aMagicMarker);
+ }
+ else
+ {
+ int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_RECURSE; // be explicit about whether recursion follows
+ rArchive.Write(aMagicMarker);
+
+ mpExcludeFiles->Serialize(rArchive);
+ }
+
+ //
+ //
+ //
+ if(!mpExcludeDirs)
+ {
+ int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_NOOP;
+ rArchive.Write(aMagicMarker);
+ }
+ else
+ {
+ int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_RECURSE; // be explicit about whether recursion follows
+ rArchive.Write(aMagicMarker);
+
+ mpExcludeDirs->Serialize(rArchive);
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Location::Deserialize(Archive & rArchive)
+// Purpose: Deserializes this object instance from a stream of bytes, using an Archive abstraction.
+//
+// Created: 2005/04/11
+//
+// --------------------------------------------------------------------------
+void Location::Deserialize(Archive &rArchive)
+{
+ //
+ //
+ //
+ mpDirectoryRecord.reset(NULL);
+ if(mpExcludeFiles)
+ {
+ delete mpExcludeFiles;
+ mpExcludeFiles = NULL;
+ }
+ if(mpExcludeDirs)
+ {
+ delete mpExcludeDirs;
+ mpExcludeDirs = NULL;
+ }
+
+ //
+ //
+ //
+ rArchive.Read(mName);
+ rArchive.Read(mPath);
+ rArchive.Read(mIDMapIndex);
+
+ //
+ //
+ //
+ int64_t aMagicMarker = 0;
+ rArchive.Read(aMagicMarker);
+
+ if(aMagicMarker == ARCHIVE_MAGIC_VALUE_NOOP)
+ {
+ // NOOP
+ }
+ else if(aMagicMarker == ARCHIVE_MAGIC_VALUE_RECURSE)
+ {
+ BackupClientDirectoryRecord *pSubRecord = new BackupClientDirectoryRecord(0, "");
+ if(!pSubRecord)
+ {
+ throw std::bad_alloc();
+ }
+
+ mpDirectoryRecord.reset(pSubRecord);
+ mpDirectoryRecord->Deserialize(rArchive);
+ }
+ else
+ {
+ // there is something going on here
+ THROW_EXCEPTION(ClientException, CorruptStoreObjectInfoFile);
+ }
+
+ //
+ //
+ //
+ rArchive.Read(aMagicMarker);
+
+ if(aMagicMarker == ARCHIVE_MAGIC_VALUE_NOOP)
+ {
+ // NOOP
+ }
+ else if(aMagicMarker == ARCHIVE_MAGIC_VALUE_RECURSE)
+ {
+ mpExcludeFiles = new ExcludeList;
+ if(!mpExcludeFiles)
+ {
+ throw std::bad_alloc();
+ }
+
+ mpExcludeFiles->Deserialize(rArchive);
+ }
+ else
+ {
+ // there is something going on here
+ THROW_EXCEPTION(ClientException, CorruptStoreObjectInfoFile);
+ }
+
+ //
+ //
+ //
+ rArchive.Read(aMagicMarker);
+
+ if(aMagicMarker == ARCHIVE_MAGIC_VALUE_NOOP)
+ {
+ // NOOP
+ }
+ else if(aMagicMarker == ARCHIVE_MAGIC_VALUE_RECURSE)
+ {
+ mpExcludeDirs = new ExcludeList;
+ if(!mpExcludeDirs)
+ {
+ throw std::bad_alloc();
+ }
+
+ mpExcludeDirs->Deserialize(rArchive);
+ }
+ else
+ {
+ // there is something going on here
+ THROW_EXCEPTION(ClientException, CorruptStoreObjectInfoFile);
+ }
+}
diff --git a/bin/bbackupd/BackupClientDirectoryRecord.h b/bin/bbackupd/BackupClientDirectoryRecord.h
index fce3fc04..fb9d7fc8 100644
--- a/bin/bbackupd/BackupClientDirectoryRecord.h
+++ b/bin/bbackupd/BackupClientDirectoryRecord.h
@@ -12,6 +12,7 @@
#include <string>
#include <map>
+#include <memory>
#include "BackupClientFileAttributes.h"
#include "BackupDaemonInterface.h"
@@ -21,9 +22,18 @@
#include "ReadLoggingStream.h"
#include "RunStatusProvider.h"
+#ifdef ENABLE_VSS
+# include <comdef.h>
+# include <Vss.h>
+# include <VsWriter.h>
+# include <VsBackup.h>
+#endif
+
class Archive;
class BackupClientContext;
class BackupDaemon;
+class ExcludeList;
+class Location;
// --------------------------------------------------------------------------
//
@@ -86,6 +96,7 @@ public:
ProgressNotifier &mrProgressNotifier;
BackupClientContext &mrContext;
bool mReadErrorsOnFilesystemObjects;
+ int64_t mMaxUploadRate;
// Member variables modified by syncing process
box_time_t mUploadAfterThisTimeInTheFuture;
@@ -124,8 +135,14 @@ public:
int64_t ContainingDirectoryID,
const std::string &rLocalPath,
const std::string &rRemotePath,
+ const Location& rBackupLocation,
bool ThisDirHasJustBeenCreated = false);
+ std::string ConvertVssPathToRealPath(const std::string &rVssPath,
+ const Location& rBackupLocation);
+
+ int64_t GetObjectID() const { return mObjectID; }
+
private:
void DeleteSubDirectories();
BackupStoreDirectory *FetchDirectoryListing(SyncParams &rParams);
@@ -134,27 +151,34 @@ private:
const std::string &rLocalPath);
bool UpdateItems(SyncParams &rParams, const std::string &rLocalPath,
const std::string &rRemotePath,
+ const Location& rBackupLocation,
BackupStoreDirectory *pDirOnStore,
std::vector<BackupStoreDirectory::Entry *> &rEntriesLeftOver,
std::vector<std::string> &rFiles,
const std::vector<std::string> &rDirs);
int64_t UploadFile(SyncParams &rParams,
const std::string &rFilename,
+ const std::string &rNonVssFilePath,
const BackupStoreFilename &rStoreFilename,
int64_t FileSize, box_time_t ModificationTime,
box_time_t AttributesHash, bool NoPreviousVersionOnServer);
void SetErrorWhenReadingFilesystemObject(SyncParams &rParams,
- const char *Filename);
+ const std::string& rFilename);
void RemoveDirectoryInPlaceOfFile(SyncParams &rParams,
BackupStoreDirectory* pDirOnStore,
BackupStoreDirectory::Entry* pEntry,
const std::string &rFilename);
+ std::string DecryptFilename(BackupStoreDirectory::Entry *en,
+ const std::string& rRemoteDirectoryPath);
+ std::string DecryptFilename(BackupStoreFilenameClear fn,
+ int64_t filenameObjectID,
+ const std::string& rRemoteDirectoryPath);
-private:
int64_t mObjectID;
std::string mSubDirName;
bool mInitialSyncDone;
bool mSyncDone;
+ bool mSuppressMultipleLinksWarning;
// Checksum of directory contents and attributes, used to detect changes
uint8_t mStateChecksum[MD5Digest::DigestLength];
@@ -166,6 +190,32 @@ private:
// waste a lot of memory because of STL allocation policies.
};
+class Location
+{
+public:
+ Location();
+ ~Location();
+
+ void Deserialize(Archive & rArchive);
+ void Serialize(Archive & rArchive) const;
+private:
+ Location(const Location &); // copy not allowed
+ Location &operator=(const Location &);
+public:
+ std::string mName;
+ std::string mPath;
+ std::auto_ptr<BackupClientDirectoryRecord> mpDirectoryRecord;
+ int mIDMapIndex;
+ ExcludeList *mpExcludeFiles;
+ ExcludeList *mpExcludeDirs;
+
+#ifdef ENABLE_VSS
+ bool mIsSnapshotCreated;
+ VSS_ID mSnapshotVolumeId;
+ std::string mSnapshotPath;
+#endif
+};
+
#endif // BACKUPCLIENTDIRECTORYRECORD__H
diff --git a/bin/bbackupd/BackupClientInodeToIDMap.cpp b/bin/bbackupd/BackupClientInodeToIDMap.cpp
index b9f56c5a..8240d62c 100644
--- a/bin/bbackupd/BackupClientInodeToIDMap.cpp
+++ b/bin/bbackupd/BackupClientInodeToIDMap.cpp
@@ -9,32 +9,53 @@
#include "Box.h"
-#ifdef HAVE_DB
- // Include db headers and other OS files if they're needed for the disc implementation
- #include <sys/types.h>
- #include <fcntl.h>
- #include <limits.h>
- #include <db.h>
- #include <sys/stat.h>
-#endif
+#include <stdlib.h>
+#include <depot.h>
#define BACKIPCLIENTINODETOIDMAP_IMPLEMENTATION
#include "BackupClientInodeToIDMap.h"
+#undef BACKIPCLIENTINODETOIDMAP_IMPLEMENTATION
#include "BackupStoreException.h"
-
#include "MemLeakFindOn.h"
-// What type of Berkeley DB shall we use?
-#define TABLE_DATABASE_TYPE DB_HASH
-
typedef struct
{
int64_t mObjectID;
int64_t mInDirectory;
} IDBRecord;
+#define BOX_DBM_MESSAGE(stuff) stuff << " (qdbm): " << dperrmsg(dpecode)
+
+#define BOX_LOG_DBM_ERROR(stuff) \
+ BOX_ERROR(BOX_DBM_MESSAGE(stuff))
+
+#define THROW_DBM_ERROR(message, filename, exception, subtype) \
+ BOX_LOG_DBM_ERROR(message << ": " << filename); \
+ THROW_EXCEPTION_MESSAGE(exception, subtype, \
+ BOX_DBM_MESSAGE(message << ": " << filename));
+
+#define ASSERT_DBM_OK(operation, message, filename, exception, subtype) \
+ if(!(operation)) \
+ { \
+ THROW_DBM_ERROR(message, filename, exception, subtype); \
+ }
+
+#define ASSERT_DBM_OPEN() \
+ if(mpDepot == 0) \
+ { \
+ THROW_EXCEPTION_MESSAGE(BackupStoreException, InodeMapNotOpen, \
+ "Inode database not open"); \
+ }
+
+#define ASSERT_DBM_CLOSED() \
+ if(mpDepot != 0) \
+ { \
+ THROW_EXCEPTION_MESSAGE(CommonException, Internal, \
+ "Inode database already open: " << mFilename); \
+ }
+
// --------------------------------------------------------------------------
//
// Function
@@ -44,11 +65,9 @@ typedef struct
//
// --------------------------------------------------------------------------
BackupClientInodeToIDMap::BackupClientInodeToIDMap()
-#ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
: mReadOnly(true),
mEmpty(false),
- dbp(0)
-#endif
+ mpDepot(0)
{
}
@@ -62,19 +81,12 @@ BackupClientInodeToIDMap::BackupClientInodeToIDMap()
// --------------------------------------------------------------------------
BackupClientInodeToIDMap::~BackupClientInodeToIDMap()
{
-#ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
- if(dbp != 0)
+ if(mpDepot != 0)
{
-#if BDB_VERSION_MAJOR >= 3
- dbp->close(0);
-#else
- dbp->close(dbp);
-#endif
+ Close();
}
-#endif
}
-
// --------------------------------------------------------------------------
//
// Function
@@ -83,56 +95,59 @@ BackupClientInodeToIDMap::~BackupClientInodeToIDMap()
// Created: 20/11/03
//
// --------------------------------------------------------------------------
-void BackupClientInodeToIDMap::Open(const char *Filename, bool ReadOnly, bool CreateNew)
+void BackupClientInodeToIDMap::Open(const char *Filename, bool ReadOnly,
+ bool CreateNew)
{
-#ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
+ mFilename = Filename;
+
// Correct arguments?
ASSERT(!(CreateNew && ReadOnly));
// Correct usage?
- ASSERT(dbp == 0);
+ ASSERT_DBM_CLOSED();
ASSERT(!mEmpty);
// Open the database file
-#if BDB_VERSION_MAJOR >= 3
- dbp = new Db(0,0);
- dbp->set_pagesize(1024); /* Page size: 1K. */
- dbp->set_cachesize(0, 32 * 1024, 0);
- dbp->open(NULL, Filename, NULL, DB_HASH, DB_CREATE, 0664);
-#else
- dbp = dbopen(Filename, (CreateNew?O_CREAT:0) | (ReadOnly?O_RDONLY:O_RDWR), S_IRUSR | S_IWUSR | S_IRGRP, TABLE_DATABASE_TYPE, NULL);
-#endif
- if(dbp == NULL)
+ int mode = ReadOnly ? DP_OREADER : DP_OWRITER;
+ if(CreateNew)
{
- THROW_EXCEPTION(BackupStoreException, BerkelyDBFailure);
+ mode |= DP_OCREAT;
+ }
+
+ mpDepot = dpopen(Filename, mode, 0);
+
+ if(!mpDepot)
+ {
+ BOX_WARNING(BOX_DBM_MESSAGE("Failed to open inode "
+ "database: " << mFilename));
+ THROW_EXCEPTION_MESSAGE(BackupStoreException, BerkelyDBFailure,
+ BOX_DBM_MESSAGE("Failed to open inode database: " <<
+ mFilename));
}
// Read only flag
mReadOnly = ReadOnly;
-#endif
}
// --------------------------------------------------------------------------
//
// Function
// Name: BackupClientInodeToIDMap::OpenEmpty()
-// Purpose: 'Open' this map. Not associated with a disc file. Useful for when a map
-// is required, but is against an empty file on disc which shouldn't be created.
-// Implies read only.
+// Purpose: 'Open' this map. Not associated with a disc file.
+// Useful for when a map is required, but is against
+// an empty file on disc which shouldn't be created.
+// Implies read only.
// Created: 20/11/03
//
// --------------------------------------------------------------------------
void BackupClientInodeToIDMap::OpenEmpty()
{
-#ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
- ASSERT(dbp == 0);
+ ASSERT_DBM_CLOSED();
+ ASSERT(mpDepot == 0);
mEmpty = true;
mReadOnly = true;
-#endif
}
-
-
// --------------------------------------------------------------------------
//
// Function
@@ -143,75 +158,46 @@ void BackupClientInodeToIDMap::OpenEmpty()
// --------------------------------------------------------------------------
void BackupClientInodeToIDMap::Close()
{
-#ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
- if(dbp != 0)
- {
-#if BDB_VERSION_MAJOR >= 3
- if(dbp->close(0) != 0)
-#else
- if(dbp->close(dbp) != 0)
-#endif
- {
- THROW_EXCEPTION(BackupStoreException, BerkelyDBFailure);
- }
- dbp = 0;
- }
-#endif
+ ASSERT_DBM_OPEN();
+ ASSERT_DBM_OK(dpclose(mpDepot), "Failed to close inode database",
+ mFilename, BackupStoreException, BerkelyDBFailure);
+ mpDepot = 0;
}
-
// --------------------------------------------------------------------------
//
// Function
-// Name: BackupClientInodeToIDMap::AddToMap(InodeRefType, int64_t, int64_t)
-// Purpose: Adds an entry to the map. Overwrites any existing entry.
+// Name: BackupClientInodeToIDMap::AddToMap(InodeRefType,
+// int64_t, int64_t)
+// Purpose: Adds an entry to the map. Overwrites any existing
+// entry.
// Created: 11/11/03
//
// --------------------------------------------------------------------------
-void BackupClientInodeToIDMap::AddToMap(InodeRefType InodeRef, int64_t ObjectID, int64_t InDirectory)
+void BackupClientInodeToIDMap::AddToMap(InodeRefType InodeRef, int64_t ObjectID,
+ int64_t InDirectory)
{
-#ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
- mMap[InodeRef] = std::pair<int64_t, int64_t>(ObjectID, InDirectory);
-#else
if(mReadOnly)
{
THROW_EXCEPTION(BackupStoreException, InodeMapIsReadOnly);
}
- if(dbp == 0)
+ if(mpDepot == 0)
{
THROW_EXCEPTION(BackupStoreException, InodeMapNotOpen);
}
+ ASSERT_DBM_OPEN();
+
// Setup structures
IDBRecord rec;
rec.mObjectID = ObjectID;
rec.mInDirectory = InDirectory;
-#if BDB_VERSION_MAJOR >= 3
- Dbt key(&InodeRef, sizeof(InodeRef));
- Dbt data(&rec, sizeof(rec));
-
- if (dbp->put(0, &key, &data, 0) != 0) {
- THROW_EXCEPTION(BackupStoreException, BerkelyDBFailure);
- }
-#else
-
- DBT key;
- key.data = &InodeRef;
- key.size = sizeof(InodeRef);
-
- DBT data;
- data.data = &rec;
- data.size = sizeof(rec);
-
- // Add to map (or replace existing entry)
- if(dbp->put(dbp, &key, &data, 0) != 0)
- {
- THROW_EXCEPTION(BackupStoreException, BerkelyDBFailure);
- }
-#endif
-#endif
+ ASSERT_DBM_OK(dpput(mpDepot, (const char *)&InodeRef, sizeof(InodeRef),
+ (const char *)&rec, sizeof(rec), DP_DOVER),
+ "Failed to add record to inode database", mFilename,
+ BackupStoreException, BerkelyDBFailure);
}
// --------------------------------------------------------------------------
@@ -228,100 +214,32 @@ void BackupClientInodeToIDMap::AddToMap(InodeRefType InodeRef, int64_t ObjectID,
bool BackupClientInodeToIDMap::Lookup(InodeRefType InodeRef,
int64_t &rObjectIDOut, int64_t &rInDirectoryOut) const
{
-#ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
- std::map<InodeRefType, std::pair<int64_t, int64_t> >::const_iterator i(mMap.find(InodeRef));
-
- // Found?
- if(i == mMap.end())
- {
- return false;
- }
-
- // Yes. Return the details
- rObjectIDOut = i->second.first;
- rInDirectoryOut = i->second.second;
- return true;
-#else
if(mEmpty)
{
// Map is empty
return false;
}
- if(dbp == 0)
+ if(mpDepot == 0)
{
THROW_EXCEPTION(BackupStoreException, InodeMapNotOpen);
}
-
-#if BDB_VERSION_MAJOR >= 3
- Dbt key(&InodeRef, sizeof(InodeRef));
- Dbt data(0, 0);
- switch(dbp->get(NULL, &key, &data, 0))
-#else
- DBT key;
- key.data = &InodeRef;
- key.size = sizeof(InodeRef);
- DBT data;
- data.data = 0;
- data.size = 0;
+ ASSERT_DBM_OPEN();
- switch(dbp->get(dbp, &key, &data, 0))
-#endif
+ IDBRecord rec;
+ if(dpgetwb(mpDepot, (const char *)&InodeRef, sizeof(InodeRef),
+ 0, sizeof(IDBRecord), (char *)&rec) == -1)
{
- case 1: // key not in file
+ // key not in file
return false;
-
- case -1: // error
- default: // not specified in docs
- THROW_EXCEPTION(BackupStoreException, BerkelyDBFailure);
- return false;
-
- case 0: // success, found it
- break;
}
-
- // Check for sensible return
-#if BDB_VERSION_MAJOR >= 3
- if(key.get_data() == 0 || data.get_size() != sizeof(IDBRecord))
- {
- // Assert in debug version
- ASSERT(key.get_data() == 0 || data.get_size() != sizeof(IDBRecord));
- // Invalid entries mean it wasn't found
- return false;
- }
-
- // Data alignment isn't guaranteed to be on a suitable boundary
- IDBRecord rec;
-
- ::memcpy(&rec, data.get_data(), sizeof(rec));
-#else
- if(key.data == 0 || data.size != sizeof(IDBRecord))
- {
- // Assert in debug version
- ASSERT(key.data == 0 || data.size != sizeof(IDBRecord));
-
- // Invalid entries mean it wasn't found
- return false;
- }
-
- // Data alignment isn't guaranteed to be on a suitable boundary
- IDBRecord rec;
-
- ::memcpy(&rec, data.data, sizeof(rec));
-#endif
-
// Return data
rObjectIDOut = rec.mObjectID;
rInDirectoryOut = rec.mInDirectory;
- // Don't have to worry about freeing the returned data
-
// Found
return true;
-#endif
}
-
-
diff --git a/bin/bbackupd/BackupClientInodeToIDMap.h b/bin/bbackupd/BackupClientInodeToIDMap.h
index 1dfef702..fbe45114 100644
--- a/bin/bbackupd/BackupClientInodeToIDMap.h
+++ b/bin/bbackupd/BackupClientInodeToIDMap.h
@@ -8,25 +8,16 @@
// --------------------------------------------------------------------------
#ifndef BACKUPCLIENTINODETOIDMAP_H
-#define BACKUPCLIENTINODETOIDMAP__H
+#define BACKUPCLIENTINODETOIDMAP_H
#include <sys/types.h>
#include <map>
#include <utility>
-// Use in memory implementation if there isn't access to the Berkely DB on this platform
-#ifndef HAVE_DB
- #define BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
-#endif
-
// avoid having to include the DB files when not necessary
#ifndef BACKIPCLIENTINODETOIDMAP_IMPLEMENTATION
-#ifdef BERKELY_V4
- class Db;
-#else
- class DB;
-#endif
+ class DEPOT;
#endif
// --------------------------------------------------------------------------
@@ -55,19 +46,12 @@ public:
void Close();
private:
-#ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
- std::map<InodeRefType, std::pair<int64_t, int64_t> > mMap;
-#else
bool mReadOnly;
bool mEmpty;
-#ifdef BERKELY_V4
- Db *dbp; // c++ style implimentation
-#else
- DB *dbp; // C style interface, use notation from documentation
-#endif // BERKELY_V4
-#endif // BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
+ std::string mFilename;
+ DEPOT *mpDepot;
};
-#endif // BACKUPCLIENTINODETOIDMAP__H
+#endif // BACKUPCLIENTINODETOIDMAP_H
diff --git a/bin/bbackupd/BackupDaemon.cpp b/bin/bbackupd/BackupDaemon.cpp
index b6f90cad..39bb98e3 100644
--- a/bin/bbackupd/BackupDaemon.cpp
+++ b/bin/bbackupd/BackupDaemon.cpp
@@ -40,6 +40,8 @@
#endif
#include <iostream>
+#include <set>
+#include <sstream>
#include "Configuration.h"
#include "IOStream.h"
@@ -49,7 +51,7 @@
#include "SSLLib.h"
-#include "autogen_BackupProtocolClient.h"
+#include "autogen_BackupProtocol.h"
#include "autogen_ClientException.h"
#include "autogen_ConversionException.h"
#include "Archive.h"
@@ -82,6 +84,85 @@
#include "Win32BackupService.h"
extern Win32BackupService* gpDaemonService;
+
+# ifdef ENABLE_VSS
+# include <comdef.h>
+# include <Vss.h>
+# include <VsWriter.h>
+# include <VsBackup.h>
+
+ // http://www.flounder.com/cstring.htm
+ std::string GetMsgForHresult(HRESULT hr)
+ {
+ std::ostringstream buf;
+
+ if(hr == VSS_S_ASYNC_CANCELLED)
+ {
+ buf << "VSS async operation cancelled";
+ }
+ else if(hr == VSS_S_ASYNC_FINISHED)
+ {
+ buf << "VSS async operation finished";
+ }
+ else if(hr == VSS_S_ASYNC_PENDING)
+ {
+ buf << "VSS async operation pending";
+ }
+ else
+ {
+ buf << _com_error(hr).ErrorMessage();
+ }
+
+ buf << " (" << BOX_FORMAT_HEX32(hr) << ")";
+ return buf.str();
+ }
+
+ std::string WideStringToString(WCHAR *buf)
+ {
+ if (buf == NULL)
+ {
+ return "(null)";
+ }
+
+ char* pStr = ConvertFromWideString(buf, CP_UTF8);
+
+ if(pStr == NULL)
+ {
+ return "(conversion failed)";
+ }
+
+ std::string result(pStr);
+ free(pStr);
+ return result;
+ }
+
+ std::string GuidToString(GUID guid)
+ {
+ wchar_t buf[64];
+ StringFromGUID2(guid, buf, sizeof(buf));
+ return WideStringToString(buf);
+ }
+
+ std::string BstrToString(const BSTR arg)
+ {
+ if(arg == NULL)
+ {
+ return std::string("(null)");
+ }
+ else
+ {
+ // Extract the *long* before where the arg points to
+ long len = ((long *)arg)[-1] / 2;
+ std::wstring wstr((WCHAR *)arg, len);
+ std::string str;
+ if(!ConvertFromWideString(wstr, &str, CP_UTF8))
+ {
+ throw std::exception("string conversion failed");
+ }
+ return str;
+ }
+ }
+# endif
#endif
#include "MemLeakFindOn.h"
@@ -114,6 +195,9 @@ BackupDaemon::BackupDaemon()
mUpdateStoreInterval(0),
mDeleteStoreObjectInfoFile(false),
mDoSyncForcedByPreviousSyncError(false),
+ mNumFilesUploaded(-1),
+ mNumDirsCreated(-1),
+ mMaxBandwidthFromSyncAllowScript(0),
mLogAllFileAccess(false),
mpProgressNotifier(this),
mpLocationResolver(this),
@@ -125,6 +209,9 @@ BackupDaemon::BackupDaemon()
mRunAsService(false),
mServiceName("bbackupd")
#endif
+#ifdef ENABLE_VSS
+ , mpVssBackupComponents(NULL)
+#endif
{
// Only ever one instance of a daemon
SSLLib::Initialise();
@@ -239,7 +326,7 @@ void BackupDaemon::SetupInInitialProcess()
void BackupDaemon::DeleteAllLocations()
{
// Run through, and delete everything
- for(std::vector<Location *>::iterator i = mLocations.begin();
+ for(Locations::iterator i = mLocations.begin();
i != mLocations.end(); ++i)
{
delete *i;
@@ -311,6 +398,16 @@ int BackupDaemon::Main(const std::string &rConfigFileName)
return RemoveService(mServiceName);
}
+#ifdef ENABLE_VSS
+ HRESULT result = CoInitialize(NULL);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to initialize COM: " <<
+ GetMsgForHresult(result));
+ return 1;
+ }
+#endif
+
int returnCode;
if (mRunAsService)
@@ -416,7 +513,7 @@ void BackupDaemon::InitCrypto()
keyFile.c_str(), caFile.c_str());
// Set up the keys for various things
- BackupClientCryptoKeys_Setup(conf.GetKeyValue("KeysFile").c_str());
+ BackupClientCryptoKeys_Setup(conf.GetKeyValue("KeysFile"));
}
// --------------------------------------------------------------------------
@@ -573,15 +670,14 @@ void BackupDaemon::Run2()
void BackupDaemon::RunSyncNowWithExceptionHandling()
{
- OnBackupStart();
-
- // Do sync
bool errorOccurred = false;
int errorCode = 0, errorSubCode = 0;
const char* errorString = "unknown";
try
{
+ OnBackupStart();
+ // Do sync
RunSyncNow();
}
catch(BoxException &e)
@@ -607,6 +703,7 @@ void BackupDaemon::RunSyncNowWithExceptionHandling()
// do not retry immediately without a good reason
mDoSyncForcedByPreviousSyncError = false;
+ // Notify system administrator about the final state of the backup
if(errorOccurred)
{
// Is it a berkely db failure?
@@ -624,12 +721,7 @@ void BackupDaemon::RunSyncNowWithExceptionHandling()
DeleteCorruptBerkelyDbFiles();
}
- // Clear state data
- // Go back to beginning of time
- mLastSyncTime = 0;
- mClientStoreMarker = BackupClientContext::ClientStoreMarker_NotKnown; // no store marker, so download everything
- DeleteAllLocations();
- DeleteAllIDMaps();
+ ResetCachedState();
// Handle restart?
if(StopRun())
@@ -668,16 +760,19 @@ void BackupDaemon::RunSyncNowWithExceptionHandling()
SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY);
}
}
- // Notify system administrator about the final state of the backup
- else if(mReadErrorsOnFilesystemObjects)
+
+ if(mReadErrorsOnFilesystemObjects)
{
NotifySysadmin(SysadminNotifier::ReadError);
}
- else if(mStorageLimitExceeded)
+
+ if(mStorageLimitExceeded)
{
NotifySysadmin(SysadminNotifier::StoreFull);
}
- else
+
+ if (!errorOccurred && !mReadErrorsOnFilesystemObjects &&
+ !mStorageLimitExceeded)
{
NotifySysadmin(SysadminNotifier::BackupOK);
}
@@ -689,6 +784,16 @@ void BackupDaemon::RunSyncNowWithExceptionHandling()
OnBackupFinish();
}
+void BackupDaemon::ResetCachedState()
+{
+ // Clear state data
+ // Go back to beginning of time
+ mLastSyncTime = 0;
+ mClientStoreMarker = BackupClientContext::ClientStoreMarker_NotKnown; // no store marker, so download everything
+ DeleteAllLocations();
+ DeleteAllIDMaps();
+}
+
void BackupDaemon::RunSyncNow()
{
// Delete the serialised store object file,
@@ -746,7 +851,9 @@ void BackupDaemon::RunSyncNow()
conf.GetKeyValueUint32("AccountNumber"),
conf.GetKeyValueBool("ExtendedLogging"),
conf.KeyExists("ExtendedLogFile"),
- extendedLogFile, *mpProgressNotifier
+ extendedLogFile,
+ *mpProgressNotifier,
+ conf.GetKeyValueBool("TcpNice")
);
// The minimum age a file needs to be before it will be
@@ -830,6 +937,19 @@ void BackupDaemon::RunSyncNow()
conf.GetKeyValueInt("DiffingUploadSizeThreshold");
params.mMaxFileTimeInFuture =
SecondsToBoxTime(conf.GetKeyValueInt("MaxFileTimeInFuture"));
+ mNumFilesUploaded = 0;
+ mNumDirsCreated = 0;
+
+ if(conf.KeyExists("MaxUploadRate"))
+ {
+ params.mMaxUploadRate = conf.GetKeyValueInt("MaxUploadRate");
+ }
+
+ if(mMaxBandwidthFromSyncAllowScript != 0)
+ {
+ params.mMaxUploadRate = mMaxBandwidthFromSyncAllowScript;
+ }
+
mDeleteRedundantLocationsAfter =
conf.GetKeyValueInt("DeleteRedundantLocationsAfter");
mStorageLimitExceeded = false;
@@ -858,7 +978,6 @@ void BackupDaemon::RunSyncNow()
// Set up the locations, if necessary --
// need to do it here so we have a
// (potential) connection to use
- if(mLocations.empty())
{
const Configuration &locations(
conf.GetSubConfiguration(
@@ -876,9 +995,13 @@ void BackupDaemon::RunSyncNow()
// Delete any unused directories?
DeleteUnusedRootDirEntries(clientContext);
+
+#ifdef ENABLE_VSS
+ CreateVssBackupComponents();
+#endif
// Go through the records, syncing them
- for(std::vector<Location *>::const_iterator
+ for(Locations::const_iterator
i(mLocations.begin());
i != mLocations.end(); ++i)
{
@@ -894,10 +1017,17 @@ void BackupDaemon::RunSyncNow()
(*i)->mpExcludeDirs);
// Sync the directory
- (*i)->mpDirectoryRecord->SyncDirectory(
- params,
- BackupProtocolClientListDirectory::RootDirectory,
- (*i)->mPath, std::string("/") + (*i)->mName);
+ std::string locationPath = (*i)->mPath;
+#ifdef ENABLE_VSS
+ if((*i)->mIsSnapshotCreated)
+ {
+ locationPath = (*i)->mSnapshotPath;
+ }
+#endif
+
+ (*i)->mpDirectoryRecord->SyncDirectory(params,
+ BackupProtocolListDirectory::RootDirectory,
+ locationPath, std::string("/") + (*i)->mName, **i);
// Unset exclude lists (just in case)
clientContext.SetExcludeLists(0, 0);
@@ -910,11 +1040,15 @@ void BackupDaemon::RunSyncNow()
// Close any open connection
clientContext.CloseAnyOpenConnection();
-
+
+#ifdef ENABLE_VSS
+ CleanupVssBackupComponents();
+#endif
+
// Get the new store marker
mClientStoreMarker = clientContext.GetClientStoreMarker();
mStorageLimitExceeded = clientContext.StorageLimitExceeded();
- mReadErrorsOnFilesystemObjects =
+ mReadErrorsOnFilesystemObjects |=
params.mReadErrorsOnFilesystemObjects;
if(!mStorageLimitExceeded)
@@ -948,6 +1082,562 @@ void BackupDaemon::RunSyncNow()
// --------------------------------------------------------------------------------------------
}
+#ifdef ENABLE_VSS
+bool BackupDaemon::WaitForAsync(IVssAsync *pAsync,
+ const std::string& description)
+{
+ BOX_INFO("VSS: waiting for " << description << " to complete");
+ HRESULT result;
+
+ do
+ {
+ result = pAsync->Wait(1000);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to wait for " << description <<
+ " to complete: " << GetMsgForHresult(result));
+ break;
+ }
+
+ HRESULT result2;
+ result = pAsync->QueryStatus(&result2, NULL);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to query " << description <<
+ " status: " << GetMsgForHresult(result));
+ break;
+ }
+
+ result = result2;
+ BOX_INFO("VSS: " << description << " status: " <<
+ GetMsgForHresult(result));
+ }
+ while(result == VSS_S_ASYNC_PENDING);
+
+ pAsync->Release();
+
+ return (result == VSS_S_ASYNC_FINISHED);
+}
+
+#define CALL_MEMBER_FN(object, method) ((object).*(method))
+
+bool BackupDaemon::CallAndWaitForAsync(AsyncMethod method,
+ const std::string& description)
+{
+ IVssAsync *pAsync;
+ HRESULT result = CALL_MEMBER_FN(*mpVssBackupComponents, method)(&pAsync);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: " << description << " failed: " <<
+ GetMsgForHresult(result));
+ return false;
+ }
+
+ return WaitForAsync(pAsync, description);
+}
+
+void FreeSnapshotProp(VSS_SNAPSHOT_PROP *pSnap)
+{
+ CoTaskMemFree(pSnap->m_pwszSnapshotDeviceObject);
+ CoTaskMemFree(pSnap->m_pwszOriginalVolumeName);
+ CoTaskMemFree(pSnap->m_pwszOriginatingMachine);
+ CoTaskMemFree(pSnap->m_pwszServiceMachine);
+ CoTaskMemFree(pSnap->m_pwszExposedName);
+ CoTaskMemFree(pSnap->m_pwszExposedPath);
+}
+
+void BackupDaemon::CreateVssBackupComponents()
+{
+ std::map<char, VSS_ID> volumesIncluded;
+
+ HRESULT result = ::CreateVssBackupComponents(&mpVssBackupComponents);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to create backup components: " <<
+ GetMsgForHresult(result));
+ return;
+ }
+
+ result = mpVssBackupComponents->InitializeForBackup(NULL);
+ if(result != S_OK)
+ {
+ std::string message = GetMsgForHresult(result);
+
+ if (result == VSS_E_UNEXPECTED)
+ {
+ message = "Check the Application Log for details, and ensure "
+ "that the Volume Shadow Copy, COM+ System Application, "
+ "and Distributed Transaction Coordinator services "
+ "are running";
+ }
+
+ BOX_ERROR("VSS: Failed to initialize for backup: " << message);
+ return;
+ }
+
+ result = mpVssBackupComponents->SetContext(VSS_CTX_BACKUP);
+ if(result == E_NOTIMPL)
+ {
+ BOX_INFO("VSS: Failed to set context to VSS_CTX_BACKUP: "
+ "not implemented, probably Windows XP, ignored.");
+ }
+ else if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to set context to VSS_CTX_BACKUP: " <<
+ GetMsgForHresult(result));
+ return;
+ }
+
+ result = mpVssBackupComponents->SetBackupState(
+ false, /* no components for now */
+ true, /* might as well ask for a bootable backup */
+ VSS_BT_FULL,
+ false /* what is Partial File Support? */);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to set backup state: " <<
+ GetMsgForHresult(result));
+ return;
+ }
+
+ if(!CallAndWaitForAsync(&IVssBackupComponents::GatherWriterMetadata,
+ "GatherWriterMetadata()"))
+ {
+ goto CreateVssBackupComponents_cleanup_WriterMetadata;
+ }
+
+ UINT writerCount;
+ result = mpVssBackupComponents->GetWriterMetadataCount(&writerCount);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to get writer count: " <<
+ GetMsgForHresult(result));
+ goto CreateVssBackupComponents_cleanup_WriterMetadata;
+ }
+
+ for(UINT iWriter = 0; iWriter < writerCount; iWriter++)
+ {
+ BOX_INFO("VSS: Getting metadata from writer " << iWriter);
+ VSS_ID writerInstance;
+ IVssExamineWriterMetadata* pMetadata;
+ result = mpVssBackupComponents->GetWriterMetadata(iWriter,
+ &writerInstance, &pMetadata);
+ if(result != S_OK)
+ {
+ BOX_ERROR("Failed to get VSS metadata from writer " << iWriter <<
+ ": " << GetMsgForHresult(result));
+ continue;
+ }
+
+ UINT includeFiles, excludeFiles, numComponents;
+ result = pMetadata->GetFileCounts(&includeFiles, &excludeFiles,
+ &numComponents);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to get metadata file counts from "
+ "writer " << iWriter << ": " <<
+ GetMsgForHresult(result));
+ pMetadata->Release();
+ continue;
+ }
+
+ for(UINT iComponent = 0; iComponent < numComponents; iComponent++)
+ {
+ IVssWMComponent* pComponent;
+ result = pMetadata->GetComponent(iComponent, &pComponent);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to get metadata component " <<
+ iComponent << " from writer " << iWriter << ": " <<
+ GetMsgForHresult(result));
+ continue;
+ }
+
+ PVSSCOMPONENTINFO pComponentInfo;
+ result = pComponent->GetComponentInfo(&pComponentInfo);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to get metadata component " <<
+ iComponent << " info from writer " << iWriter << ": " <<
+ GetMsgForHresult(result));
+ pComponent->Release();
+ continue;
+ }
+
+ BOX_TRACE("VSS: writer " << iWriter << " component " <<
+ iComponent << " info:");
+ switch(pComponentInfo->type)
+ {
+ case VSS_CT_UNDEFINED: BOX_TRACE("VSS: type: undefined"); break;
+ case VSS_CT_DATABASE: BOX_TRACE("VSS: type: database"); break;
+ case VSS_CT_FILEGROUP: BOX_TRACE("VSS: type: filegroup"); break;
+ default:
+ BOX_WARNING("VSS: type: unknown (" << pComponentInfo->type << ")");
+ }
+
+ BOX_TRACE("VSS: logical path: " <<
+ BstrToString(pComponentInfo->bstrLogicalPath));
+ BOX_TRACE("VSS: component name: " <<
+ BstrToString(pComponentInfo->bstrComponentName));
+ BOX_TRACE("VSS: caption: " <<
+ BstrToString(pComponentInfo->bstrCaption));
+ BOX_TRACE("VSS: restore metadata: " <<
+ pComponentInfo->bRestoreMetadata);
+ BOX_TRACE("VSS: notify on complete: " <<
+ pComponentInfo->bRestoreMetadata);
+ BOX_TRACE("VSS: selectable: " <<
+ pComponentInfo->bSelectable);
+ BOX_TRACE("VSS: selectable for restore: " <<
+ pComponentInfo->bSelectableForRestore);
+ BOX_TRACE("VSS: component flags: " <<
+ BOX_FORMAT_HEX32(pComponentInfo->dwComponentFlags));
+ BOX_TRACE("VSS: file count: " <<
+ pComponentInfo->cFileCount);
+ BOX_TRACE("VSS: databases: " <<
+ pComponentInfo->cDatabases);
+ BOX_TRACE("VSS: log files: " <<
+ pComponentInfo->cLogFiles);
+ BOX_TRACE("VSS: dependencies: " <<
+ pComponentInfo->cDependencies);
+
+ pComponent->FreeComponentInfo(pComponentInfo);
+ pComponent->Release();
+ }
+
+ pMetadata->Release();
+ }
+
+ VSS_ID snapshotSetId;
+ result = mpVssBackupComponents->StartSnapshotSet(&snapshotSetId);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to start snapshot set: " <<
+ GetMsgForHresult(result));
+ goto CreateVssBackupComponents_cleanup_WriterMetadata;
+ }
+
+ // Add all volumes included as backup locations to the snapshot set
+ for(Locations::iterator
+ iLocation = mLocations.begin();
+ iLocation != mLocations.end();
+ iLocation++)
+ {
+ Location& rLocation(**iLocation);
+ std::string path = rLocation.mPath;
+ // convert to absolute and remove Unicode prefix
+ path = ConvertPathToAbsoluteUnicode(path.c_str()).substr(4);
+
+ if(path.length() >= 3 && path[1] == ':' && path[2] == '\\')
+ {
+ std::string volumeRoot = path.substr(0, 3);
+
+ std::map<char, VSS_ID>::iterator i =
+ volumesIncluded.find(path[0]);
+
+ if(i == volumesIncluded.end())
+ {
+ std::wstring volumeRootWide;
+ volumeRootWide.push_back((WCHAR) path[0]);
+ volumeRootWide.push_back((WCHAR) ':');
+ volumeRootWide.push_back((WCHAR) '\\');
+ VSS_ID newVolumeId;
+ result = mpVssBackupComponents->AddToSnapshotSet(
+ (VSS_PWSZ)(volumeRootWide.c_str()), GUID_NULL,
+ &newVolumeId);
+ if(result == S_OK)
+ {
+ BOX_TRACE("VSS: Added volume " << volumeRoot <<
+ " for backup location " << path <<
+ " to snapshot set");
+ volumesIncluded[path[0]] = newVolumeId;
+ rLocation.mSnapshotVolumeId = newVolumeId;
+ rLocation.mIsSnapshotCreated = true;
+
+ // If the snapshot path starts with the volume root
+ // (drive letter), because the path is absolute (as
+ // it should be), then remove it so that the
+ // resulting snapshot path can be appended to the
+ // snapshot device object to make a real path,
+ // without a spurious drive letter in it.
+
+ if (path.substr(0, volumeRoot.length()) == volumeRoot)
+ {
+ path = path.substr(volumeRoot.length());
+ }
+
+ rLocation.mSnapshotPath = path;
+ }
+ else
+ {
+ BOX_ERROR("VSS: Failed to add volume " <<
+ volumeRoot << " to snapshot set: " <<
+ GetMsgForHresult(result));
+ goto CreateVssBackupComponents_cleanup_WriterMetadata;
+ }
+ }
+ else
+ {
+ BOX_TRACE("VSS: Skipping already included volume " <<
+ volumeRoot << " for backup location " << path);
+ rLocation.mSnapshotVolumeId = i->second;
+ rLocation.mIsSnapshotCreated = true;
+ }
+ }
+ else
+ {
+ BOX_WARNING("VSS: Skipping backup location " << path <<
+ " which does not start with a volume specification");
+ }
+ }
+
+ if(!CallAndWaitForAsync(&IVssBackupComponents::PrepareForBackup,
+ "PrepareForBackup()"))
+ {
+ goto CreateVssBackupComponents_cleanup_WriterMetadata;
+ }
+
+ if(!CallAndWaitForAsync(&IVssBackupComponents::DoSnapshotSet,
+ "DoSnapshotSet()"))
+ {
+ goto CreateVssBackupComponents_cleanup_WriterMetadata;
+ }
+
+ if(!CallAndWaitForAsync(&IVssBackupComponents::GatherWriterStatus,
+ "GatherWriterStatus()"))
+ {
+ goto CreateVssBackupComponents_cleanup_WriterStatus;
+ }
+
+ result = mpVssBackupComponents->GetWriterStatusCount(&writerCount);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to get writer status count: " <<
+ GetMsgForHresult(result));
+ goto CreateVssBackupComponents_cleanup_WriterStatus;
+ }
+
+ for(UINT iWriter = 0; iWriter < writerCount; iWriter++)
+ {
+ VSS_ID instance, writer;
+ BSTR writerNameBstr;
+ VSS_WRITER_STATE writerState;
+ HRESULT writerResult;
+
+ result = mpVssBackupComponents->GetWriterStatus(iWriter,
+ &instance, &writer, &writerNameBstr, &writerState,
+ &writerResult);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to query writer " << iWriter <<
+ " status: " << GetMsgForHresult(result));
+ goto CreateVssBackupComponents_cleanup_WriterStatus;
+ }
+
+ std::string writerName = BstrToString(writerNameBstr);
+ ::SysFreeString(writerNameBstr);
+
+ if(writerResult != S_OK)
+ {
+ BOX_ERROR("VSS: Writer " << iWriter << " (" <<
+ writerName << ") failed: " <<
+ GetMsgForHresult(writerResult));
+ continue;
+ }
+
+ std::string stateName;
+
+ switch(writerState)
+ {
+#define WRITER_STATE(code) \
+ case code: stateName = #code; break;
+ WRITER_STATE(VSS_WS_UNKNOWN);
+ WRITER_STATE(VSS_WS_STABLE);
+ WRITER_STATE(VSS_WS_WAITING_FOR_FREEZE);
+ WRITER_STATE(VSS_WS_WAITING_FOR_THAW);
+ WRITER_STATE(VSS_WS_WAITING_FOR_POST_SNAPSHOT);
+ WRITER_STATE(VSS_WS_WAITING_FOR_BACKUP_COMPLETE);
+ WRITER_STATE(VSS_WS_FAILED_AT_IDENTIFY);
+ WRITER_STATE(VSS_WS_FAILED_AT_PREPARE_BACKUP);
+ WRITER_STATE(VSS_WS_FAILED_AT_PREPARE_SNAPSHOT);
+ WRITER_STATE(VSS_WS_FAILED_AT_FREEZE);
+ WRITER_STATE(VSS_WS_FAILED_AT_THAW);
+ WRITER_STATE(VSS_WS_FAILED_AT_POST_SNAPSHOT);
+ WRITER_STATE(VSS_WS_FAILED_AT_BACKUP_COMPLETE);
+ WRITER_STATE(VSS_WS_FAILED_AT_PRE_RESTORE);
+ WRITER_STATE(VSS_WS_FAILED_AT_POST_RESTORE);
+ WRITER_STATE(VSS_WS_FAILED_AT_BACKUPSHUTDOWN);
+#undef WRITER_STATE
+ default:
+ std::ostringstream o;
+ o << "unknown (" << writerState << ")";
+ stateName = o.str();
+ }
+
+ BOX_TRACE("VSS: Writer " << iWriter << " (" <<
+ writerName << ") is in state " << stateName);
+ }
+
+ // lookup new snapshot volume for each location that has a snapshot
+ for(Locations::iterator
+ iLocation = mLocations.begin();
+ iLocation != mLocations.end();
+ iLocation++)
+ {
+ Location& rLocation(**iLocation);
+ if(rLocation.mIsSnapshotCreated)
+ {
+ VSS_SNAPSHOT_PROP prop;
+ result = mpVssBackupComponents->GetSnapshotProperties(
+ rLocation.mSnapshotVolumeId, &prop);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to get snapshot properties "
+ "for volume " << GuidToString(rLocation.mSnapshotVolumeId) <<
+ " for location " << rLocation.mPath << ": " <<
+ GetMsgForHresult(result));
+ rLocation.mIsSnapshotCreated = false;
+ continue;
+ }
+
+ rLocation.mSnapshotPath =
+ WideStringToString(prop.m_pwszSnapshotDeviceObject) +
+ DIRECTORY_SEPARATOR + rLocation.mSnapshotPath;
+ FreeSnapshotProp(&prop);
+
+ BOX_INFO("VSS: Location " << rLocation.mPath << " using "
+ "snapshot path " << rLocation.mSnapshotPath);
+ }
+ }
+
+ IVssEnumObject *pEnum;
+ result = mpVssBackupComponents->Query(GUID_NULL, VSS_OBJECT_NONE,
+ VSS_OBJECT_SNAPSHOT, &pEnum);
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to query snapshot list: " <<
+ GetMsgForHresult(result));
+ goto CreateVssBackupComponents_cleanup_WriterStatus;
+ }
+
+ while(result == S_OK)
+ {
+ VSS_OBJECT_PROP rgelt;
+ ULONG count;
+ result = pEnum->Next(1, &rgelt, &count);
+
+ if(result == S_FALSE)
+ {
+ // end of list, break out of the loop
+ break;
+ }
+ else if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to enumerate snapshot: " <<
+ GetMsgForHresult(result));
+ }
+ else if(count != 1)
+ {
+ BOX_ERROR("VSS: Failed to enumerate snapshot: " <<
+ "Next() returned " << count << " objects instead of 1");
+ }
+ else if(rgelt.Type != VSS_OBJECT_SNAPSHOT)
+ {
+ BOX_ERROR("VSS: Failed to enumerate snapshot: " <<
+ "Next() returned a type " << rgelt.Type << " object "
+ "instead of VSS_OBJECT_SNAPSHOT");
+ }
+ else
+ {
+ VSS_SNAPSHOT_PROP *pSnap = &rgelt.Obj.Snap;
+ BOX_TRACE("VSS: Snapshot ID: " <<
+ GuidToString(pSnap->m_SnapshotId));
+ BOX_TRACE("VSS: Snapshot set ID: " <<
+ GuidToString(pSnap->m_SnapshotSetId));
+ BOX_TRACE("VSS: Number of volumes: " <<
+ pSnap->m_lSnapshotsCount);
+ BOX_TRACE("VSS: Snapshot device object: " <<
+ WideStringToString(pSnap->m_pwszSnapshotDeviceObject));
+ BOX_TRACE("VSS: Original volume name: " <<
+ WideStringToString(pSnap->m_pwszOriginalVolumeName));
+ BOX_TRACE("VSS: Originating machine: " <<
+ WideStringToString(pSnap->m_pwszOriginatingMachine));
+ BOX_TRACE("VSS: Service machine: " <<
+ WideStringToString(pSnap->m_pwszServiceMachine));
+ BOX_TRACE("VSS: Exposed name: " <<
+ WideStringToString(pSnap->m_pwszExposedName));
+ BOX_TRACE("VSS: Exposed path: " <<
+ WideStringToString(pSnap->m_pwszExposedPath));
+ BOX_TRACE("VSS: Provider ID: " <<
+ GuidToString(pSnap->m_ProviderId));
+ BOX_TRACE("VSS: Snapshot attributes: " <<
+ BOX_FORMAT_HEX32(pSnap->m_lSnapshotAttributes));
+ BOX_TRACE("VSS: Snapshot creation time: " <<
+ BOX_FORMAT_HEX32(pSnap->m_tsCreationTimestamp));
+
+ std::string status;
+ switch(pSnap->m_eStatus)
+ {
+ case VSS_SS_UNKNOWN: status = "Unknown (error)"; break;
+ case VSS_SS_PREPARING: status = "Preparing"; break;
+ case VSS_SS_PROCESSING_PREPARE: status = "Preparing (processing)"; break;
+ case VSS_SS_PREPARED: status = "Prepared"; break;
+ case VSS_SS_PROCESSING_PRECOMMIT: status = "Precommitting"; break;
+ case VSS_SS_PRECOMMITTED: status = "Precommitted"; break;
+ case VSS_SS_PROCESSING_COMMIT: status = "Commiting"; break;
+ case VSS_SS_COMMITTED: status = "Committed"; break;
+ case VSS_SS_PROCESSING_POSTCOMMIT: status = "Postcommitting"; break;
+ case VSS_SS_PROCESSING_PREFINALCOMMIT: status = "Pre final committing"; break;
+ case VSS_SS_PREFINALCOMMITTED: status = "Pre final committed"; break;
+ case VSS_SS_PROCESSING_POSTFINALCOMMIT: status = "Post final committing"; break;
+ case VSS_SS_CREATED: status = "Created"; break;
+ case VSS_SS_ABORTED: status = "Aborted"; break;
+ case VSS_SS_DELETED: status = "Deleted"; break;
+ case VSS_SS_POSTCOMMITTED: status = "Postcommitted"; break;
+ default:
+ std::ostringstream buf;
+ buf << "Unknown code: " << pSnap->m_eStatus;
+ status = buf.str();
+ }
+
+ BOX_TRACE("VSS: Snapshot status: " << status);
+ FreeSnapshotProp(pSnap);
+ }
+ }
+
+ pEnum->Release();
+
+CreateVssBackupComponents_cleanup_WriterStatus:
+ result = mpVssBackupComponents->FreeWriterStatus();
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to free writer status: " <<
+ GetMsgForHresult(result));
+ }
+
+CreateVssBackupComponents_cleanup_WriterMetadata:
+ result = mpVssBackupComponents->FreeWriterMetadata();
+ if(result != S_OK)
+ {
+ BOX_ERROR("VSS: Failed to free writer metadata: " <<
+ GetMsgForHresult(result));
+ }
+}
+
+void BackupDaemon::CleanupVssBackupComponents()
+{
+ if(mpVssBackupComponents == NULL)
+ {
+ return;
+ }
+
+ CallAndWaitForAsync(&IVssBackupComponents::BackupComplete,
+ "BackupComplete()");
+
+ mpVssBackupComponents->Release();
+ mpVssBackupComponents = NULL;
+}
+#endif
+
void BackupDaemon::OnBackupStart()
{
// Touch a file to record times in filesystem
@@ -969,28 +1659,37 @@ void BackupDaemon::OnBackupStart()
void BackupDaemon::OnBackupFinish()
{
- // Log
- BOX_NOTICE("Finished scan of local files");
-
- // Log the stats
- BOX_NOTICE("File statistics: total file size uploaded "
- << BackupStoreFile::msStats.mBytesInEncodedFiles
- << ", bytes already on server "
- << BackupStoreFile::msStats.mBytesAlreadyOnServer
- << ", encoded size "
- << BackupStoreFile::msStats.mTotalFileStreamSize);
-
- // Reset statistics again
- BackupStoreFile::ResetStats();
+ try
+ {
+ // Log
+ BOX_NOTICE("Finished scan of local files");
- // Notify administrator
- NotifySysadmin(SysadminNotifier::BackupFinish);
+ // Log the stats
+ BOX_NOTICE("File statistics: total file size uploaded "
+ << BackupStoreFile::msStats.mBytesInEncodedFiles
+ << ", bytes already on server "
+ << BackupStoreFile::msStats.mBytesAlreadyOnServer
+ << ", encoded size "
+ << BackupStoreFile::msStats.mTotalFileStreamSize
+ << ", " << mNumFilesUploaded << " files uploaded, "
+ << mNumDirsCreated << " dirs created");
- // Tell anything connected to the command socket
- SendSyncStartOrFinish(false /* finish */);
+ // Reset statistics again
+ BackupStoreFile::ResetStats();
- // Touch a file to record times in filesystem
- TouchFileInWorkingDir("last_sync_finish");
+ // Notify administrator
+ NotifySysadmin(SysadminNotifier::BackupFinish);
+
+ // Tell anything connected to the command socket
+ SendSyncStartOrFinish(false /* finish */);
+
+ // Touch a file to record times in filesystem
+ TouchFileInWorkingDir("last_sync_finish");
+ }
+ catch (std::exception &e)
+ {
+ BOX_ERROR("Failed to perform backup finish actions: " << e.what());
+ }
}
// --------------------------------------------------------------------------
@@ -1032,33 +1731,14 @@ int BackupDaemon::UseScriptToSeeIfSyncAllowed()
std::string line;
if(getLine.GetLine(line, true, 30000)) // 30 seconds should be enough
{
- // Got a string, interpret
- if(line == "now")
- {
- // Script says do it now. Obey.
- waitInSeconds = -1;
- }
- else
- {
- try
- {
- // How many seconds to wait?
- waitInSeconds = BoxConvert::Convert<int32_t, const std::string&>(line);
- }
- catch(ConversionException &e)
- {
- BOX_ERROR("Invalid output from "
- "SyncAllowScript: '" <<
- line << "' (" << script << ")");
- throw;
- }
-
- BOX_NOTICE("Delaying sync by " << waitInSeconds
- << " seconds due to SyncAllowScript "
- << "(" << script << ")");
- }
+ waitInSeconds = BackupDaemon::ParseSyncAllowScriptOutput(script, line);
+ }
+ else
+ {
+ BOX_ERROR("SyncAllowScript output nothing within "
+ "30 seconds, waiting 5 minutes to try again"
+ " (" << script << ")");
}
-
}
catch(std::exception &e)
{
@@ -1083,6 +1763,83 @@ int BackupDaemon::UseScriptToSeeIfSyncAllowed()
return waitInSeconds;
}
+int BackupDaemon::ParseSyncAllowScriptOutput(const std::string& script,
+ const std::string& output)
+{
+ int waitInSeconds = (60*5);
+ std::istringstream iss(output);
+
+ std::string delay;
+ iss >> delay;
+
+ if(delay == "")
+ {
+ BOX_ERROR("SyncAllowScript output an empty line");
+ return waitInSeconds;
+ }
+
+ // Got a string, interpret
+ if(delay == "now")
+ {
+ // Script says do it now. Obey.
+ waitInSeconds = -1;
+
+ BOX_NOTICE("SyncAllowScript requested a backup now "
+ << "(" << script << ")");
+ }
+ else
+ {
+ try
+ {
+ // How many seconds to wait?
+ waitInSeconds = BoxConvert::Convert<int32_t, const std::string&>(delay);
+ }
+ catch(ConversionException &e)
+ {
+ BOX_ERROR("SyncAllowScript output an invalid "
+ "number: '" << output << "' (" <<
+ script << ")");
+ throw;
+ }
+
+ BOX_NOTICE("SyncAllowScript requested a delay of " <<
+ waitInSeconds << " seconds due to SyncAllowScript "
+ << "(" << script << ")");
+ }
+
+ if(iss.eof())
+ {
+ // No bandwidth limit requested
+ mMaxBandwidthFromSyncAllowScript = 0;
+ BOX_NOTICE("SyncAllowScript did not set a maximum bandwidth "
+ "(" << script << ")");
+ }
+ else
+ {
+ std::string maxBandwidth;
+ iss >> maxBandwidth;
+
+ try
+ {
+ // How many seconds to wait?
+ mMaxBandwidthFromSyncAllowScript =
+ BoxConvert::Convert<int32_t, const std::string&>(maxBandwidth);
+ }
+ catch(ConversionException &e)
+ {
+ BOX_ERROR("Invalid maximum bandwidth from "
+ "SyncAllowScript: '" <<
+ output << "' (" << script << ")");
+ throw;
+ }
+
+ BOX_NOTICE("SyncAllowScript set maximum bandwidth to " <<
+ mMaxBandwidthFromSyncAllowScript << " kB/s (" <<
+ script << ")");
+ }
+
+ return waitInSeconds;
+}
// --------------------------------------------------------------------------
@@ -1418,33 +2175,20 @@ void BackupDaemon::SendSyncStartOrFinish(bool SendStart)
// --------------------------------------------------------------------------
void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Configuration &rLocationsConf)
{
- if(!mLocations.empty())
- {
- // Looks correctly set up
- return;
- }
-
- // Make sure that if a directory is reinstated, then it doesn't get deleted
- mDeleteUnusedRootDirEntriesAfter = 0;
- mUnusedRootDirEntries.clear();
-
- // Just a check to make sure it's right.
- DeleteAllLocations();
-
// Going to need a copy of the root directory. Get a connection,
// and fetch it.
- BackupProtocolClient &connection(rClientContext.GetConnection());
+ BackupProtocolCallable& connection(rClientContext.GetConnection());
// Ask server for a list of everything in the root directory,
// which is a directory itself
- std::auto_ptr<BackupProtocolClientSuccess> dirreply(
+ std::auto_ptr<BackupProtocolSuccess> dirreply(
connection.QueryListDirectory(
- BackupProtocolClientListDirectory::RootDirectory,
+ BackupProtocolListDirectory::RootDirectory,
// only directories
- BackupProtocolClientListDirectory::Flags_Dir,
+ BackupProtocolListDirectory::Flags_Dir,
// exclude old/deleted stuff
- BackupProtocolClientListDirectory::Flags_Deleted |
- BackupProtocolClientListDirectory::Flags_OldVersion,
+ BackupProtocolListDirectory::Flags_Deleted |
+ BackupProtocolListDirectory::Flags_OldVersion,
false /* no attributes */));
// Retrieve the directory from the stream following
@@ -1537,35 +2281,68 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con
// making sure there's a directory created for it.
std::vector<std::string> locNames =
rLocationsConf.GetSubConfigurationNames();
-
+
+ // We only want completely configured locations to be in the list
+ // when this function exits, so move them all to a temporary list.
+ // Entries matching a properly configured location will be moved
+ // back to mLocations. Anything left in this list after the loop
+ // finishes will be deleted.
+ Locations tmpLocations = mLocations;
+ mLocations.clear();
+
+ // The ID map list will be repopulated automatically by this loop
+ mIDMapMounts.clear();
+
for(std::vector<std::string>::iterator
pLocName = locNames.begin();
pLocName != locNames.end();
pLocName++)
{
+ Location* pLoc = NULL;
+
+ // Try to find and reuse an existing Location object
+ for(Locations::const_iterator
+ i = tmpLocations.begin();
+ i != tmpLocations.end(); i++)
+ {
+ if ((*i)->mName == *pLocName)
+ {
+ BOX_TRACE("Location already configured: " << *pLocName);
+ pLoc = *i;
+ break;
+ }
+ }
+
const Configuration& rConfig(
rLocationsConf.GetSubConfiguration(*pLocName));
- BOX_TRACE("new location: " << *pLocName);
-
- // Create a record for it
- std::auto_ptr<Location> apLoc(new Location);
+ std::auto_ptr<Location> apLoc;
try
{
- // Setup names in the location record
- apLoc->mName = *pLocName;
- apLoc->mPath = rConfig.GetKeyValue("Path");
-
- // Read the exclude lists from the Configuration
- apLoc->mpExcludeFiles = BackupClientMakeExcludeList_Files(rConfig);
- apLoc->mpExcludeDirs = BackupClientMakeExcludeList_Dirs(rConfig);
+ if(pLoc == NULL)
+ {
+ // Create a record for it
+ BOX_TRACE("New location: " << *pLocName);
+ pLoc = new Location;
+
+ // ensure deletion if setup fails
+ apLoc.reset(pLoc);
+
+ // Setup names in the location record
+ pLoc->mName = *pLocName;
+ pLoc->mPath = rConfig.GetKeyValue("Path");
+
+ // Read the exclude lists from the Configuration
+ pLoc->mpExcludeFiles = BackupClientMakeExcludeList_Files(rConfig);
+ pLoc->mpExcludeDirs = BackupClientMakeExcludeList_Dirs(rConfig);
+ }
// Does this exist on the server?
// Remove from dir object early, so that if we fail
// to stat the local directory, we still don't
// consider to remote one for deletion.
BackupStoreDirectory::Iterator iter(dir);
- BackupStoreFilenameClear dirname(apLoc->mName); // generate the filename
+ BackupStoreFilenameClear dirname(pLoc->mName); // generate the filename
BackupStoreDirectory::Entry *en = iter.FindMatchingClearName(dirname);
int64_t oid = 0;
if(en != 0)
@@ -1585,17 +2362,17 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con
// BSD style statfs -- includes mount point, which is nice.
#ifdef HAVE_STRUCT_STATVFS_F_MNTONNAME
struct statvfs s;
- if(::statvfs(apLoc->mPath.c_str(), &s) != 0)
+ if(::statvfs(pLoc->mPath.c_str(), &s) != 0)
#else // HAVE_STRUCT_STATVFS_F_MNTONNAME
struct statfs s;
- if(::statfs(apLoc->mPath.c_str(), &s) != 0)
+ if(::statfs(pLoc->mPath.c_str(), &s) != 0)
#endif // HAVE_STRUCT_STATVFS_F_MNTONNAME
{
- BOX_LOG_SYS_WARNING("Failed to stat location "
- "path '" << apLoc->mPath <<
- "', skipping location '" <<
- apLoc->mName << "'");
- continue;
+ THROW_SYS_ERROR("Failed to stat path "
+ "'" << pLoc->mPath << "' "
+ "for location "
+ "'" << pLoc->mName << "'",
+ CommonException, OSFileError);
}
// Where the filesystem is mounted
@@ -1604,10 +2381,10 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con
#else // !HAVE_STRUCT_STATFS_F_MNTONNAME && !WIN32
// Warn in logs if the directory isn't absolute
- if(apLoc->mPath[0] != '/')
+ if(pLoc->mPath[0] != '/')
{
BOX_WARNING("Location path '"
- << apLoc->mPath
+ << pLoc->mPath
<< "' is not absolute");
}
// Go through the mount points found, and find a suitable one
@@ -1622,7 +2399,7 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con
// If it matches, the file belongs in that mount point
// (sorting order ensures this)
BOX_TRACE("checking against mount point " << *i);
- if(::strncmp(i->c_str(), apLoc->mPath.c_str(), i->size()) == 0)
+ if(::strncmp(i->c_str(), pLoc->mPath.c_str(), i->size()) == 0)
{
// Match
mountName = *i;
@@ -1630,7 +2407,7 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con
}
}
BOX_TRACE("mount point chosen for "
- << apLoc->mPath << " is "
+ << pLoc->mPath << " is "
<< mountName);
}
@@ -1641,12 +2418,12 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con
if(f != mounts.end())
{
// Yes -- store the index
- apLoc->mIDMapIndex = f->second;
+ pLoc->mIDMapIndex = f->second;
}
else
{
// No -- new index
- apLoc->mIDMapIndex = numIDMaps;
+ pLoc->mIDMapIndex = numIDMaps;
mounts[mountName] = numIDMaps;
// Store the mount name
@@ -1666,7 +2443,7 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con
BackupClientFileAttributes attr;
try
{
- attr.ReadAttributes(apLoc->mPath.c_str(),
+ attr.ReadAttributes(pLoc->mPath.c_str(),
true /* directories have zero mod times */,
0 /* not interested in mod time */,
&attrModTime /* get the attribute modification time */);
@@ -1674,19 +2451,20 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con
catch (BoxException &e)
{
BOX_ERROR("Failed to get attributes "
- "for path '" << apLoc->mPath
+ "for path '" << pLoc->mPath
<< "', skipping location '" <<
- apLoc->mName << "'");
- continue;
+ pLoc->mName << "'");
+ throw;
}
// Execute create directory command
try
{
- MemBlockStream attrStream(attr);
- std::auto_ptr<BackupProtocolClientSuccess>
+ std::auto_ptr<IOStream> attrStream(
+ new MemBlockStream(attr));
+ std::auto_ptr<BackupProtocolSuccess>
dirCreate(connection.QueryCreateDirectory(
- BackupProtocolClientListDirectory::RootDirectory,
+ BackupProtocolListDirectory::RootDirectory,
attrModTime, dirname, attrStream));
// Object ID for later creation
@@ -1695,40 +2473,64 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con
catch (BoxException &e)
{
BOX_ERROR("Failed to create remote "
- "directory '/" << apLoc->mName <<
+ "directory '/" << pLoc->mName <<
"', skipping location '" <<
- apLoc->mName << "'");
- continue;
+ pLoc->mName << "'");
+ throw;
}
}
// Create and store the directory object for the root of this location
ASSERT(oid != 0);
- BackupClientDirectoryRecord *precord =
- new BackupClientDirectoryRecord(oid, *pLocName);
- apLoc->mpDirectoryRecord.reset(precord);
+ if(pLoc->mpDirectoryRecord.get() == NULL)
+ {
+ BackupClientDirectoryRecord *precord =
+ new BackupClientDirectoryRecord(oid, *pLocName);
+ pLoc->mpDirectoryRecord.reset(precord);
+ }
+ // Remove it from the temporary list to avoid deletion
+ tmpLocations.remove(pLoc);
+
// Push it back on the vector of locations
- mLocations.push_back(apLoc.release());
+ mLocations.push_back(pLoc);
+
+ if(apLoc.get() != NULL)
+ {
+ // Don't delete it now!
+ apLoc.release();
+ }
}
catch (std::exception &e)
{
BOX_ERROR("Failed to configure location '"
- << apLoc->mName << "' path '"
- << apLoc->mPath << "': " << e.what() <<
+ << pLoc->mName << "' path '"
+ << pLoc->mPath << "': " << e.what() <<
": please check for previous errors");
- throw;
+ mReadErrorsOnFilesystemObjects = true;
}
catch(...)
{
BOX_ERROR("Failed to configure location '"
- << apLoc->mName << "' path '"
- << apLoc->mPath << "': please check for "
+ << pLoc->mName << "' path '"
+ << pLoc->mPath << "': please check for "
"previous errors");
- throw;
+ mReadErrorsOnFilesystemObjects = true;
}
}
+
+ // Now remove any leftovers
+ for(BackupDaemon::Locations::iterator
+ i = tmpLocations.begin();
+ i != tmpLocations.end(); i++)
+ {
+ BOX_INFO("Removing obsolete location from memory: " <<
+ (*i)->mName);
+ delete *i;
+ }
+
+ tmpLocations.clear();
// Any entries in the root directory which need deleting?
if(dir.GetNumberOfEntries() > 0 &&
@@ -1791,31 +2593,11 @@ void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Con
// --------------------------------------------------------------------------
void BackupDaemon::SetupIDMapsForSync()
{
- // Need to do different things depending on whether it's an
- // in memory implementation, or whether it's all stored on disc.
-
-#ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
-
- // Make sure we have some blank, empty ID maps
- DeleteIDMapVector(mNewIDMaps);
- FillIDMapVector(mNewIDMaps, true /* new maps */);
-
- // Then make sure that the current maps have objects,
- // even if they are empty (for the very first run)
- if(mCurrentIDMaps.empty())
- {
- FillIDMapVector(mCurrentIDMaps, false /* current maps */);
- }
-
-#else
-
// Make sure we have some blank, empty ID maps
DeleteIDMapVector(mNewIDMaps);
FillIDMapVector(mNewIDMaps, true /* new maps */);
DeleteIDMapVector(mCurrentIDMaps);
FillIDMapVector(mCurrentIDMaps, false /* new maps */);
-
-#endif
}
@@ -1848,6 +2630,24 @@ void BackupDaemon::FillIDMapVector(std::vector<BackupClientInodeToIDMap *> &rVec
filename += ".n";
}
+ // The new map file should not exist yet. If there's
+ // one left over from a previous failed run, it's not
+ // useful to us because we never read from it and will
+ // overwrite the entries of all files that still
+ // exist, so we should just delete it and start afresh.
+ if(NewMaps && FileExists(filename.c_str()))
+ {
+ BOX_NOTICE("Found an incomplete ID map "
+ "database, deleting it to start "
+ "afresh: " << filename);
+ if(unlink(filename.c_str()) != 0)
+ {
+ BOX_LOG_NATIVE_ERROR(BOX_FILE_MESSAGE(
+ filename, "Failed to delete "
+ "incomplete ID map database"));
+ }
+ }
+
// If it's not a new map, it may not exist in which case an empty map should be created
if(!NewMaps && !FileExists(filename.c_str()))
{
@@ -1942,21 +2742,6 @@ void BackupDaemon::MakeMapBaseName(unsigned int MountNumber, std::string &rNameO
// --------------------------------------------------------------------------
void BackupDaemon::CommitIDMapsAfterSync()
{
- // Need to do different things depending on whether it's an in memory implementation,
- // or whether it's all stored on disc.
-
-#ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION
- // Remove the current ID maps
- DeleteIDMapVector(mCurrentIDMaps);
-
- // Copy the (pointers to) "new" maps over to be the new "current" maps
- mCurrentIDMaps = mNewIDMaps;
-
- // Clear the new ID maps vector (not delete them!)
- mNewIDMaps.clear();
-
-#else
-
// Get rid of the maps in memory (leaving them on disc of course)
DeleteIDMapVector(mCurrentIDMaps);
DeleteIDMapVector(mNewIDMaps);
@@ -1980,8 +2765,6 @@ void BackupDaemon::CommitIDMapsAfterSync()
THROW_EXCEPTION(CommonException, OSFileError)
}
}
-
-#endif
}
@@ -2003,7 +2786,6 @@ void BackupDaemon::DeleteIDMapVector(std::vector<BackupClientInodeToIDMap *> &rV
rVector.pop_back();
// Close and delete
- toDel->Close();
delete toDel;
}
ASSERT(rVector.size() == 0);
@@ -2022,7 +2804,7 @@ void BackupDaemon::DeleteIDMapVector(std::vector<BackupClientInodeToIDMap *> &rV
bool BackupDaemon::FindLocationPathName(const std::string &rLocationName, std::string &rPathOut) const
{
// Search for the location
- for(std::vector<Location *>::const_iterator i(mLocations.begin()); i != mLocations.end(); ++i)
+ for(Locations::const_iterator i(mLocations.begin()); i != mLocations.end(); ++i)
{
if((*i)->mName == rLocationName)
{
@@ -2120,7 +2902,16 @@ void BackupDaemon::TouchFileInWorkingDir(const char *Filename)
fn += Filename;
// Open and close it to update the timestamp
- FileStream touch(fn.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ try
+ {
+ FileStream touch(fn, O_WRONLY | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR);
+ }
+ catch (std::exception &e)
+ {
+ BOX_ERROR("Failed to write to timestamp file: " << fn << ": " <<
+ e.what());
+ }
}
@@ -2259,7 +3050,7 @@ void BackupDaemon::DeleteUnusedRootDirEntries(BackupClientContext &rContext)
// Entries to delete, and it's the right time to do so...
BOX_NOTICE("Deleting unused locations from store root...");
- BackupProtocolClient &connection(rContext.GetConnection());
+ BackupProtocolCallable &connection(rContext.GetConnection());
for(std::vector<std::pair<int64_t,std::string> >::iterator
i(mUnusedRootDirEntries.begin());
i != mUnusedRootDirEntries.end(); ++i)
@@ -2290,222 +3081,6 @@ typedef struct
// --------------------------------------------------------------------------
//
// Function
-// Name: BackupDaemon::Location::Location()
-// Purpose: Constructor
-// Created: 11/11/03
-//
-// --------------------------------------------------------------------------
-BackupDaemon::Location::Location()
- : mIDMapIndex(0),
- mpExcludeFiles(0),
- mpExcludeDirs(0)
-{
-}
-
-// --------------------------------------------------------------------------
-//
-// Function
-// Name: BackupDaemon::Location::~Location()
-// Purpose: Destructor
-// Created: 11/11/03
-//
-// --------------------------------------------------------------------------
-BackupDaemon::Location::~Location()
-{
- // Clean up exclude locations
- if(mpExcludeDirs != 0)
- {
- delete mpExcludeDirs;
- mpExcludeDirs = 0;
- }
- if(mpExcludeFiles != 0)
- {
- delete mpExcludeFiles;
- mpExcludeFiles = 0;
- }
-}
-
-// --------------------------------------------------------------------------
-//
-// Function
-// Name: BackupDaemon::Location::Deserialize(Archive & rArchive)
-// Purpose: Deserializes this object instance from a stream of bytes, using an Archive abstraction.
-//
-// Created: 2005/04/11
-//
-// --------------------------------------------------------------------------
-void BackupDaemon::Location::Deserialize(Archive &rArchive)
-{
- //
- //
- //
- mpDirectoryRecord.reset(NULL);
- if(mpExcludeFiles)
- {
- delete mpExcludeFiles;
- mpExcludeFiles = NULL;
- }
- if(mpExcludeDirs)
- {
- delete mpExcludeDirs;
- mpExcludeDirs = NULL;
- }
-
- //
- //
- //
- rArchive.Read(mName);
- rArchive.Read(mPath);
- rArchive.Read(mIDMapIndex);
-
- //
- //
- //
- int64_t aMagicMarker = 0;
- rArchive.Read(aMagicMarker);
-
- if(aMagicMarker == ARCHIVE_MAGIC_VALUE_NOOP)
- {
- // NOOP
- }
- else if(aMagicMarker == ARCHIVE_MAGIC_VALUE_RECURSE)
- {
- BackupClientDirectoryRecord *pSubRecord = new BackupClientDirectoryRecord(0, "");
- if(!pSubRecord)
- {
- throw std::bad_alloc();
- }
-
- mpDirectoryRecord.reset(pSubRecord);
- mpDirectoryRecord->Deserialize(rArchive);
- }
- else
- {
- // there is something going on here
- THROW_EXCEPTION(ClientException, CorruptStoreObjectInfoFile);
- }
-
- //
- //
- //
- rArchive.Read(aMagicMarker);
-
- if(aMagicMarker == ARCHIVE_MAGIC_VALUE_NOOP)
- {
- // NOOP
- }
- else if(aMagicMarker == ARCHIVE_MAGIC_VALUE_RECURSE)
- {
- mpExcludeFiles = new ExcludeList;
- if(!mpExcludeFiles)
- {
- throw std::bad_alloc();
- }
-
- mpExcludeFiles->Deserialize(rArchive);
- }
- else
- {
- // there is something going on here
- THROW_EXCEPTION(ClientException, CorruptStoreObjectInfoFile);
- }
-
- //
- //
- //
- rArchive.Read(aMagicMarker);
-
- if(aMagicMarker == ARCHIVE_MAGIC_VALUE_NOOP)
- {
- // NOOP
- }
- else if(aMagicMarker == ARCHIVE_MAGIC_VALUE_RECURSE)
- {
- mpExcludeDirs = new ExcludeList;
- if(!mpExcludeDirs)
- {
- throw std::bad_alloc();
- }
-
- mpExcludeDirs->Deserialize(rArchive);
- }
- else
- {
- // there is something going on here
- THROW_EXCEPTION(ClientException, CorruptStoreObjectInfoFile);
- }
-}
-
-// --------------------------------------------------------------------------
-//
-// Function
-// Name: BackupDaemon::Location::Serialize(Archive & rArchive)
-// Purpose: Serializes this object instance into a stream of bytes, using an Archive abstraction.
-//
-// Created: 2005/04/11
-//
-// --------------------------------------------------------------------------
-void BackupDaemon::Location::Serialize(Archive & rArchive) const
-{
- //
- //
- //
- rArchive.Write(mName);
- rArchive.Write(mPath);
- rArchive.Write(mIDMapIndex);
-
- //
- //
- //
- if(mpDirectoryRecord.get() == NULL)
- {
- int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_NOOP;
- rArchive.Write(aMagicMarker);
- }
- else
- {
- int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_RECURSE; // be explicit about whether recursion follows
- rArchive.Write(aMagicMarker);
-
- mpDirectoryRecord->Serialize(rArchive);
- }
-
- //
- //
- //
- if(!mpExcludeFiles)
- {
- int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_NOOP;
- rArchive.Write(aMagicMarker);
- }
- else
- {
- int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_RECURSE; // be explicit about whether recursion follows
- rArchive.Write(aMagicMarker);
-
- mpExcludeFiles->Serialize(rArchive);
- }
-
- //
- //
- //
- if(!mpExcludeDirs)
- {
- int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_NOOP;
- rArchive.Write(aMagicMarker);
- }
- else
- {
- int64_t aMagicMarker = ARCHIVE_MAGIC_VALUE_RECURSE; // be explicit about whether recursion follows
- rArchive.Write(aMagicMarker);
-
- mpExcludeDirs->Serialize(rArchive);
- }
-}
-
-// --------------------------------------------------------------------------
-//
-// Function
// Name: BackupDaemon::CommandSocketInfo::CommandSocketInfo()
// Purpose: Constructor
// Created: 18/2/04
@@ -2591,10 +3166,11 @@ bool BackupDaemon::SerializeStoreObjectInfo(box_time_t theLastSyncTime,
int64_t iCount = mLocations.size();
anArchive.Write(iCount);
- for(int v = 0; v < iCount; v++)
+ for(Locations::const_iterator i = mLocations.begin();
+ i != mLocations.end(); i++)
{
- ASSERT(mLocations[v]);
- mLocations[v]->Serialize(anArchive);
+ ASSERT(*i);
+ (*i)->Serialize(anArchive);
}
//
diff --git a/bin/bbackupd/BackupDaemon.h b/bin/bbackupd/BackupDaemon.h
index b41c6508..1d3c991e 100644
--- a/bin/bbackupd/BackupDaemon.h
+++ b/bin/bbackupd/BackupDaemon.h
@@ -24,13 +24,20 @@
#include "SocketStream.h"
#include "TLSContext.h"
-#include "autogen_BackupProtocolClient.h"
+#include "autogen_BackupProtocol.h"
#ifdef WIN32
#include "WinNamedPipeListener.h"
#include "WinNamedPipeStream.h"
#endif
+#ifdef ENABLE_VSS
+# include <comdef.h>
+# include <Vss.h>
+# include <VsWriter.h>
+# include <VsBackup.h>
+#endif
+
class BackupClientDirectoryRecord;
class BackupClientContext;
class Configuration;
@@ -110,6 +117,7 @@ public:
void InitCrypto();
void RunSyncNowWithExceptionHandling();
void RunSyncNow();
+ void ResetCachedState();
void OnBackupStart();
void OnBackupFinish();
// TouchFileInWorkingDir is only here for use by Boxi.
@@ -150,33 +158,15 @@ private:
int UseScriptToSeeIfSyncAllowed();
public:
- class Location
- {
- public:
- Location();
- ~Location();
-
- void Deserialize(Archive & rArchive);
- void Serialize(Archive & rArchive) const;
- private:
- Location(const Location &); // copy not allowed
- Location &operator=(const Location &);
- public:
- std::string mName;
- std::string mPath;
- std::auto_ptr<BackupClientDirectoryRecord> mpDirectoryRecord;
- int mIDMapIndex;
- ExcludeList *mpExcludeFiles;
- ExcludeList *mpExcludeDirs;
- };
-
- typedef const std::vector<Location *> Locations;
+ int ParseSyncAllowScriptOutput(const std::string& script,
+ const std::string& output);
+ typedef std::list<Location *> Locations;
Locations GetLocations() { return mLocations; }
private:
int mState; // what the daemon is currently doing
- std::vector<Location *> mLocations;
+ Locations mLocations;
std::vector<std::string> mIDMapMounts;
std::vector<BackupClientInodeToIDMap *> mCurrentIDMaps;
@@ -222,8 +212,11 @@ private:
TLSContext mTlsContext;
bool mDeleteStoreObjectInfoFile;
bool mDoSyncForcedByPreviousSyncError;
+ int64_t mNumFilesUploaded, mNumDirsCreated;
+ int mMaxBandwidthFromSyncAllowScript;
public:
+ int GetMaxBandwidthFromSyncAllowScript() { return mMaxBandwidthFromSyncAllowScript; }
bool StopRun() { return this->Daemon::StopRun(); }
bool StorageLimitExceeded() { return mStorageLimitExceeded; }
@@ -368,7 +361,7 @@ public:
int type, int subtype)
{
std::ostringstream msgs;
- if (type != BackupProtocolClientError::ErrorType)
+ if (type != BackupProtocolError::ErrorType)
{
msgs << "unknown error type " << type;
}
@@ -376,46 +369,46 @@ public:
{
switch(subtype)
{
- case BackupProtocolClientError::Err_WrongVersion:
+ case BackupProtocolError::Err_WrongVersion:
msgs << "WrongVersion";
break;
- case BackupProtocolClientError::Err_NotInRightProtocolPhase:
+ case BackupProtocolError::Err_NotInRightProtocolPhase:
msgs << "NotInRightProtocolPhase";
break;
- case BackupProtocolClientError::Err_BadLogin:
+ case BackupProtocolError::Err_BadLogin:
msgs << "BadLogin";
break;
- case BackupProtocolClientError::Err_CannotLockStoreForWriting:
+ case BackupProtocolError::Err_CannotLockStoreForWriting:
msgs << "CannotLockStoreForWriting";
break;
- case BackupProtocolClientError::Err_SessionReadOnly:
+ case BackupProtocolError::Err_SessionReadOnly:
msgs << "SessionReadOnly";
break;
- case BackupProtocolClientError::Err_FileDoesNotVerify:
+ case BackupProtocolError::Err_FileDoesNotVerify:
msgs << "FileDoesNotVerify";
break;
- case BackupProtocolClientError::Err_DoesNotExist:
+ case BackupProtocolError::Err_DoesNotExist:
msgs << "DoesNotExist";
break;
- case BackupProtocolClientError::Err_DirectoryAlreadyExists:
+ case BackupProtocolError::Err_DirectoryAlreadyExists:
msgs << "DirectoryAlreadyExists";
break;
- case BackupProtocolClientError::Err_CannotDeleteRoot:
+ case BackupProtocolError::Err_CannotDeleteRoot:
msgs << "CannotDeleteRoot";
break;
- case BackupProtocolClientError::Err_TargetNameExists:
+ case BackupProtocolError::Err_TargetNameExists:
msgs << "TargetNameExists";
break;
- case BackupProtocolClientError::Err_StorageLimitExceeded:
+ case BackupProtocolError::Err_StorageLimitExceeded:
msgs << "StorageLimitExceeded";
break;
- case BackupProtocolClientError::Err_DiffFromFileDoesNotExist:
+ case BackupProtocolError::Err_DiffFromFileDoesNotExist:
msgs << "DiffFromFileDoesNotExist";
break;
- case BackupProtocolClientError::Err_DoesNotExistInDirectory:
+ case BackupProtocolError::Err_DoesNotExistInDirectory:
msgs << "DoesNotExistInDirectory";
break;
- case BackupProtocolClientError::Err_PatchConsistencyError:
+ case BackupProtocolError::Err_PatchConsistencyError:
msgs << "PatchConsistencyError";
break;
default:
@@ -457,12 +450,15 @@ public:
virtual void NotifyFileUploaded(
const BackupClientDirectoryRecord* pDirRecord,
const std::string& rLocalPath,
- int64_t FileSize)
+ int64_t FileSize, int64_t UploadedSize)
{
if (mLogAllFileAccess)
{
- BOX_NOTICE("Uploaded file: " << rLocalPath);
- }
+ BOX_NOTICE("Uploaded file: " << rLocalPath << ", "
+ "total size = " << FileSize << ", "
+ "uploaded size = " << UploadedSize);
+ }
+ mNumFilesUploaded++;
}
virtual void NotifyFileSynchronised(
const BackupClientDirectoryRecord* pDirRecord,
@@ -474,6 +470,19 @@ public:
BOX_INFO("Synchronised file: " << rLocalPath);
}
}
+ virtual void NotifyDirectoryCreated(
+ int64_t ObjectID,
+ const std::string& rLocalPath,
+ const std::string& rRemotePath)
+ {
+ if (mLogAllFileAccess)
+ {
+ BOX_NOTICE("Created directory: " << rRemotePath <<
+ " (ID " << BOX_FORMAT_OBJECTID(ObjectID) <<
+ ")");
+ }
+ mNumDirsCreated++;
+ }
virtual void NotifyDirectoryDeleted(
int64_t ObjectID,
const std::string& rRemotePath)
@@ -520,6 +529,16 @@ public:
bool mInstallService, mRemoveService, mRunAsService;
std::string mServiceName;
#endif
+
+#ifdef ENABLE_VSS
+ IVssBackupComponents* mpVssBackupComponents;
+ void CreateVssBackupComponents();
+ bool WaitForAsync(IVssAsync *pAsync, const std::string& description);
+ typedef HRESULT (__stdcall IVssBackupComponents::*AsyncMethod)(IVssAsync**);
+ bool CallAndWaitForAsync(AsyncMethod method,
+ const std::string& description);
+ void CleanupVssBackupComponents();
+#endif
};
#endif // BACKUPDAEMON__H
diff --git a/bin/bbackupd/BackupDaemonInterface.h b/bin/bbackupd/BackupDaemonInterface.h
index 2a2d8d4b..a847b264 100644
--- a/bin/bbackupd/BackupDaemonInterface.h
+++ b/bin/bbackupd/BackupDaemonInterface.h
@@ -129,11 +129,15 @@ class ProgressNotifier
virtual void NotifyFileUploaded(
const BackupClientDirectoryRecord* pDirRecord,
const std::string& rLocalPath,
- int64_t FileSize) = 0;
+ int64_t FileSize, int64_t UploadedSize) = 0;
virtual void NotifyFileSynchronised(
const BackupClientDirectoryRecord* pDirRecord,
const std::string& rLocalPath,
int64_t FileSize) = 0;
+ virtual void NotifyDirectoryCreated(
+ int64_t ObjectID,
+ const std::string& rLocalPath,
+ const std::string& rRemotePath) = 0;
virtual void NotifyDirectoryDeleted(
int64_t ObjectID,
const std::string& rRemotePath) = 0;
diff --git a/bin/bbackupd/bbackupd-config.in b/bin/bbackupd/bbackupd-config.in
index 98dc8b6e..1fc224c2 100755
--- a/bin/bbackupd/bbackupd-config.in
+++ b/bin/bbackupd/bbackupd-config.in
@@ -227,7 +227,7 @@ SUBJECT="BACKUP PROBLEM on host $hostname"
SENDTO="$current_username"
if [ "\$1" = "" ]; then
- echo "Usage: \$0 <store-full|read-error|backup-error|backup-start|backup-finish>" >&2
+ echo "Usage: \$0 <store-full|read-error|backup-ok|backup-error|backup-start|backup-finish>" >&2
exit 2
elif [ "\$1" = store-full ]; then
$sendmail \$SENDTO <<EOM
@@ -262,7 +262,7 @@ these errors, and take appropriate action.
Other files are being backed up.
EOM
-elif [ "\$1" = backup-start -o "\$1" = backup-finish ]; then
+elif [ "\$1" = backup-start -o "\$1" = backup-finish -o "\$1" = backup-ok ]; then
# do nothing by default
true
else
diff --git a/bin/bbackupd/bbackupd.cpp b/bin/bbackupd/bbackupd.cpp
index d334a2df..bb64f745 100644
--- a/bin/bbackupd/bbackupd.cpp
+++ b/bin/bbackupd/bbackupd.cpp
@@ -41,12 +41,13 @@ int main(int argc, const char *argv[])
ExitCode = gpDaemonService->Daemon::Main(
BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE,
argc, argv);
- delete gpDaemonService;
+ delete gpDaemonService;
#else // !WIN32
BackupDaemon daemon;
- ExitCode = daemon.Main(BOX_FILE_BBACKUPD_DEFAULT_CONFIG, argc, argv);
+ ExitCode = daemon.Main(BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE,
+ argc, argv);
#endif // WIN32
diff --git a/bin/bbackupd/win32/installer.iss b/bin/bbackupd/win32/installer.iss
index 20e3addb..e69de29b 100644
--- a/bin/bbackupd/win32/installer.iss
+++ b/bin/bbackupd/win32/installer.iss
@@ -1,51 +0,0 @@
-; Script to generate output file for Box Backup client for the Windows Platform
-;
-; Very important - this is the release process
-;
-; 1/ Upgrade BOX_VERSION in the file emu.h to the current version for example 0.09eWin32 - then perform a full rebuild
-;
-; 2/ Upgrade the AppVerName below to reflect the version
-;
-; 3/ Generate the output file, then rename it to the relevent filename to reflect the version
-
-[Setup]
-AppName=Box Backup
-AppVerName=BoxWin32 0.09h
-AppPublisher=Fluffy & Omniis
-AppPublisherURL=http://www.omniis.com
-AppSupportURL=http://www.omniis.com
-AppUpdatesURL=http://www.omniis.com
-DefaultDirName={pf}\Box Backup
-DefaultGroupName=Box Backup
-Compression=lzma
-SolidCompression=yes
-PrivilegesRequired=admin
-
-[Files]
-Source: "..\..\Release\bbackupd.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace
-Source: "..\..\Release\bbackupctl.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace
-Source: "..\..\Release\bbackupquery.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace
-Source: "..\..\ExceptionCodes.txt"; DestDir: "{app}"; Flags: ignoreversion restartreplace
-Source: "icon.ico"; DestDir: "{app}\"; Flags: ignoreversion restartreplace
-Source: "msvcr71.dll"; DestDir: "{app}\"; Flags: restartreplace
-Source: "bbackupd.conf"; DestDir: "{app}"; Flags: confirmoverwrite
-Source: "..\..\..\zlib\zlib1.dll"; DestDir: "{app}"; Flags: ignoreversion restartreplace
-Source: "..\..\..\openssl\bin\libeay32.dll"; DestDir: "{app}"; Flags: ignoreversion restartreplace
-Source: "..\..\..\openssl\bin\ssleay32.dll"; DestDir: "{app}"; Flags: ignoreversion restartreplace
-Source: "ReadMe.txt"; DestDir: "{app}"; Flags: ignoreversion restartreplace
-
-; NOTE: Don't use "Flags: ignoreversion" on any shared system files
-
-[Icons]
-Name: "{group}\Box Backup Query"; Filename: "{app}\bbackupquery.exe"; IconFilename: "{app}\icon.ico" ;Parameters: "-c bbackupd.conf"; WorkingDir: "{app}"
-Name: "{group}\Service\Install Service"; Filename: "{app}\bbackupd.exe"; IconFilename: "{app}\icon.ico" ;Parameters: "-i"; WorkingDir: "{app}"
-Name: "{group}\Service\Remove Service"; Filename: "{app}\bbackupd.exe"; IconFilename: "{app}\icon.ico" ;Parameters: "-r"; WorkingDir: "{app}"
-Name: "{group}\Initiate Backup Now"; Filename: "{app}\bbackupctl.exe"; IconFilename: "{app}\icon.ico" ;Parameters: "-c bbackupd.conf sync"; WorkingDir: "{app}"
-
-[Dirs]
-Name: "{app}\bbackupd"
-
-[Run]
-Filename: "{app}\bbackupd.exe"; Description: "Install Boxbackup as service"; Parameters: "-i"; Flags: postinstall
-Filename: "{app}\Readme.txt"; Description: "View upgrade notes"; Flags: postinstall shellexec skipifsilent
-
diff --git a/bin/bbackupquery/BackupQueries.cpp b/bin/bbackupquery/BackupQueries.cpp
index 60724800..b8b9525b 100644
--- a/bin/bbackupquery/BackupQueries.cpp
+++ b/bin/bbackupquery/BackupQueries.cpp
@@ -48,9 +48,9 @@
#include "Logging.h"
#include "PathUtils.h"
#include "SelfFlushingStream.h"
-#include "TemporaryDirectory.h"
#include "Utils.h"
-#include "autogen_BackupProtocolClient.h"
+#include "autogen_BackupProtocol.h"
+#include "autogen_CipherException.h"
#include "MemLeakFindOn.h"
@@ -100,12 +100,6 @@ BackupQueries::~BackupQueries()
{
}
-typedef struct
-{
- const char* name;
- const char* opts;
-} QueryCommandSpecification;
-
// --------------------------------------------------------------------------
//
// Function
@@ -114,173 +108,46 @@ typedef struct
// Created: 2003/10/10
//
// --------------------------------------------------------------------------
-void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine)
+void BackupQueries::DoCommand(ParsedCommand& rCommand)
{
- // is the command a shell command?
- if(Command[0] == 's' && Command[1] == 'h' && Command[2] == ' ' && Command[3] != '\0')
+ // Check...
+
+ if(rCommand.mFailed)
{
- // Yes, run shell command
- int result = ::system(Command + 3);
- if(result != 0)
- {
- BOX_WARNING("System command returned error code " <<
- result);
- SetReturnCode(ReturnCode::Command_Error);
- }
+ BOX_ERROR("Parse failed");
return;
}
- // split command into components
- std::vector<std::string> cmdElements;
- std::string options;
+ if(rCommand.mCmdElements.size() < 1)
{
- const char *c = Command;
- bool inQuoted = false;
- bool inOptions = false;
-
- std::string s;
- while(*c != 0)
- {
- // Terminating char?
- if(*c == ((inQuoted)?'"':' '))
- {
- if(!s.empty()) cmdElements.push_back(s);
- s.resize(0);
- inQuoted = false;
- inOptions = false;
- }
- else
- {
- // No. Start of quoted parameter?
- if(s.empty() && *c == '"')
- {
- inQuoted = true;
- }
- // Start of options?
- else if(s.empty() && *c == '-')
- {
- inOptions = true;
- }
- else
- {
- if(inOptions)
- {
- // Option char
- options += *c;
- }
- else
- {
- // Normal string char
- s += *c;
- }
- }
- }
-
- ++c;
- }
- if(!s.empty()) cmdElements.push_back(s);
+ // blank command
+ return;
}
-
- #ifdef WIN32
- if (isFromCommandLine)
+
+ if(rCommand.pSpec->type == Command_sh &&
+ rCommand.mCmdElements.size() == 2)
{
- for (std::vector<std::string>::iterator
- i = cmdElements.begin();
- i != cmdElements.end(); i++)
+ // Yes, run shell command
+ int result = ::system(rCommand.mCmdElements[1].c_str());
+ if(result != 0)
{
- std::string converted;
- if (!ConvertEncoding(*i, CP_ACP, converted,
- GetConsoleCP()))
- {
- BOX_ERROR("Failed to convert encoding");
- return;
- }
- *i = converted;
+ BOX_WARNING("System command returned error code " <<
+ result);
+ SetReturnCode(ReturnCode::Command_Error);
}
- }
- #endif
-
- // Check...
- if(cmdElements.size() < 1)
- {
- // blank command
return;
}
-
- // Data about commands
- static QueryCommandSpecification commands[] =
- {
- { "quit", "" },
- { "exit", "" },
- { "list", "rodIFtTash", },
- { "pwd", "" },
- { "cd", "od" },
- { "lcd", "" },
- { "sh", "" },
- { "getobject", "" },
- { "get", "i" },
- { "compare", "alcqAEQ" },
- { "restore", "drif" },
- { "help", "" },
- { "usage", "m" },
- { "undelete", "i" },
- { "delete", "i" },
- { NULL, NULL }
- };
-
- typedef enum
- {
- Command_Quit = 0,
- Command_Exit,
- Command_List,
- Command_pwd,
- Command_cd,
- Command_lcd,
- Command_sh,
- Command_GetObject,
- Command_Get,
- Command_Compare,
- Command_Restore,
- Command_Help,
- Command_Usage,
- Command_Undelete,
- Command_Delete,
- }
- CommandType;
-
- static const char *alias[] = {"ls", 0};
- static const int aliasIs[] = {Command_List, 0};
-
- // Work out which command it is...
- int cmd = 0;
- while(commands[cmd].name != 0 && ::strcmp(cmdElements[0].c_str(), commands[cmd].name) != 0)
- {
- cmd++;
- }
- if(commands[cmd].name == 0)
+
+ if(rCommand.pSpec->type == Command_Unknown)
{
- // Check for aliases
- int a;
- for(a = 0; alias[a] != 0; ++a)
- {
- if(::strcmp(cmdElements[0].c_str(), alias[a]) == 0)
- {
- // Found an alias
- cmd = aliasIs[a];
- break;
- }
- }
-
// No such command
- if(alias[a] == 0)
- {
- BOX_ERROR("Unrecognised command: " << Command);
- return;
- }
+ BOX_ERROR("Unrecognised command: " << rCommand.mCmdElements[0]);
+ return;
}
// Arguments
- std::vector<std::string> args(cmdElements.begin() + 1, cmdElements.end());
+ std::vector<std::string> args(rCommand.mCmdElements.begin() + 1,
+ rCommand.mCmdElements.end());
// Set up options
bool opts[256];
@@ -288,14 +155,14 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine)
// BLOCK
{
// options
- const char *c = options.c_str();
+ const char *c = rCommand.mOptions.c_str();
while(*c != 0)
{
// Valid option?
- if(::strchr(commands[cmd].opts, *c) == NULL)
+ if(::strchr(rCommand.pSpec->opts, *c) == NULL)
{
BOX_ERROR("Invalid option '" << *c << "' for "
- "command " << commands[cmd].name);
+ "command " << rCommand.pSpec->name);
return;
}
opts[(int)*c] = true;
@@ -303,17 +170,16 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine)
}
}
- if(cmd != Command_Quit && cmd != Command_Exit)
+ if(rCommand.pSpec->type != Command_Quit)
{
// If not a quit command, set the return code to zero
SetReturnCode(ReturnCode::Command_OK);
}
// Handle command
- switch(cmd)
+ switch(rCommand.pSpec->type)
{
case Command_Quit:
- case Command_Exit:
mQuitNow = true;
break;
@@ -375,7 +241,7 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine)
break;
default:
- BOX_ERROR("Unknown command: " << Command);
+ BOX_ERROR("Unknown command: " << rCommand.mCmdElements[0]);
break;
}
}
@@ -392,8 +258,6 @@ void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine)
void BackupQueries::CommandList(const std::vector<std::string> &args, const bool *opts)
{
#define LIST_OPTION_RECURSIVE 'r'
- #define LIST_OPTION_ALLOWOLD 'o'
- #define LIST_OPTION_ALLOWDELETED 'd'
#define LIST_OPTION_NOOBJECTID 'I'
#define LIST_OPTION_NOFLAGS 'F'
#define LIST_OPTION_TIMES_LOCAL 't'
@@ -492,22 +356,28 @@ static std::string GetTimeString(BackupStoreDirectory::Entry& en,
// Created: 2003/10/10
//
// --------------------------------------------------------------------------
-void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool *opts, bool FirstLevel)
+void BackupQueries::List(int64_t DirID, const std::string &rListRoot,
+ const bool *opts, bool FirstLevel, std::ostream* pOut)
{
+#ifdef WIN32
+ DWORD n_chars;
+ HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
+#endif
+
// Generate exclude flags
- int16_t excludeFlags = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING;
- if(!opts[LIST_OPTION_ALLOWOLD]) excludeFlags |= BackupProtocolClientListDirectory::Flags_OldVersion;
- if(!opts[LIST_OPTION_ALLOWDELETED]) excludeFlags |= BackupProtocolClientListDirectory::Flags_Deleted;
+ int16_t excludeFlags = BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING;
+ if(!opts[LIST_OPTION_ALLOWOLD]) excludeFlags |= BackupProtocolListDirectory::Flags_OldVersion;
+ if(!opts[LIST_OPTION_ALLOWDELETED]) excludeFlags |= BackupProtocolListDirectory::Flags_Deleted;
// Do communication
try
{
mrConnection.QueryListDirectory(
- DirID,
- BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
- // both files and directories
- excludeFlags,
- true /* want attributes */);
+ DirID,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ // both files and directories
+ excludeFlags,
+ true /* want attributes */);
}
catch (std::exception &e)
{
@@ -522,7 +392,6 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool
return;
}
-
// Retrieve the directory from the stream following
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream());
@@ -533,6 +402,8 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool
BackupStoreDirectory::Entry *en = 0;
while((en = i.Next()) != 0)
{
+ std::ostringstream buf;
+
// Display this entry
BackupStoreFilenameClear clear(en->GetName());
@@ -540,11 +411,9 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool
if(!opts[LIST_OPTION_NOOBJECTID])
{
// add object ID to line
-#ifdef _MSC_VER
- printf("%08I64x ", (int64_t)en->GetObjectID());
-#else
- printf("%08llx ", (long long)en->GetObjectID());
-#endif
+ buf << std::hex << std::internal << std::setw(8) <<
+ std::setfill('0') << en->GetObjectID() <<
+ std::dec << " ";
}
// Flags?
@@ -571,44 +440,40 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool
// terminate
*(f++) = ' ';
*(f++) = '\0';
- printf(displayflags);
+ buf << displayflags;
if(en_flags != 0)
{
- printf("[ERROR: Entry has additional flags set] ");
+ buf << "[ERROR: Entry has additional flags set] ";
}
}
if(opts[LIST_OPTION_TIMES_UTC])
{
// Show UTC times...
- printf("%s ", GetTimeString(*en, false,
- opts[LIST_OPTION_TIMES_ATTRIBS]).c_str());
+ buf << GetTimeString(*en, false,
+ opts[LIST_OPTION_TIMES_ATTRIBS]) << " ";
}
if(opts[LIST_OPTION_TIMES_LOCAL])
{
// Show local times...
- printf("%s ", GetTimeString(*en, true,
- opts[LIST_OPTION_TIMES_ATTRIBS]).c_str());
+ buf << GetTimeString(*en, true,
+ opts[LIST_OPTION_TIMES_ATTRIBS]) << " ";
}
if(opts[LIST_OPTION_DISPLAY_HASH])
{
-#ifdef _MSC_VER
- printf("%016I64x ", (int64_t)en->GetAttributesHash());
-#else
- printf("%016llx ", (long long)en->GetAttributesHash());
-#endif
+ buf << std::hex << std::internal << std::setw(16) <<
+ std::setfill('0') << en->GetAttributesHash() <<
+ std::dec;
}
if(opts[LIST_OPTION_SIZEINBLOCKS])
{
-#ifdef _MSC_VER
- printf("%05I64d ", (int64_t)en->GetSizeInBlocks());
-#else
- printf("%05lld ", (long long)en->GetSizeInBlocks());
-#endif
+ buf << std::internal << std::setw(5) <<
+ std::setfill('0') << en->GetSizeInBlocks() <<
+ " ";
}
// add name
@@ -618,30 +483,60 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool
std::string listRootDecoded;
if(!ConvertUtf8ToConsole(rListRoot.c_str(),
listRootDecoded)) return;
- printf("%s/", listRootDecoded.c_str());
+ listRootDecoded += "/";
+ buf << listRootDecoded;
+ WriteConsole(hOut, listRootDecoded.c_str(),
+ strlen(listRootDecoded.c_str()), &n_chars, NULL);
#else
- printf("%s/", rListRoot.c_str());
+ buf << rListRoot << "/";
#endif
}
+ std::string fileName;
+ try
+ {
+ fileName = clear.GetClearFilename();
+ }
+ catch(CipherException &e)
+ {
+ fileName = "<decrypt failed>";
+ }
+
#ifdef WIN32
+ std::string fileNameUtf8 = fileName;
+ if(!ConvertUtf8ToConsole(fileNameUtf8, fileName))
{
- std::string fileName;
- if(!ConvertUtf8ToConsole(
- clear.GetClearFilename().c_str(), fileName))
- return;
- printf("%s", fileName.c_str());
+ fileName = fileNameUtf8 + " [convert encoding failed]";
}
-#else
- printf("%s", clear.GetClearFilename().c_str());
#endif
-
+
+ buf << fileName;
+
if(!en->GetName().IsEncrypted())
{
- printf("[FILENAME NOT ENCRYPTED]");
+ buf << " [FILENAME NOT ENCRYPTED]";
}
- printf("\n");
+ buf << std::endl;
+
+ if(pOut)
+ {
+ (*pOut) << buf.str();
+ }
+ else
+ {
+#ifdef WIN32
+ std::string line = buf.str();
+ if (!WriteConsole(hOut, line.c_str(), line.size(),
+ &n_chars, NULL))
+ {
+ // WriteConsole failed, try standard method
+ std::cout << buf.str();
+ }
+#else
+ std::cout << buf.str();
+#endif
+ }
// Directory?
if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) != 0)
@@ -652,7 +547,9 @@ void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool
std::string subroot(rListRoot);
if(!FirstLevel) subroot += '/';
subroot += clear.GetClearFilename();
- List(en->GetObjectID(), subroot, opts, false /* not the first level to list */);
+ List(en->GetObjectID(), subroot, opts,
+ false /* not the first level to list */,
+ pOut);
}
}
}
@@ -681,7 +578,7 @@ int64_t BackupQueries::FindDirectoryObjectID(const std::string &rDirName,
// Start from current stack, or root, whichever is required
std::vector<std::pair<std::string, int64_t> > stack;
- int64_t dirID = BackupProtocolClientListDirectory::RootDirectory;
+ int64_t dirID = BackupProtocolListDirectory::RootDirectory;
if(rDirName.size() > 0 && rDirName[0] == '/')
{
// Root, do nothing
@@ -697,9 +594,9 @@ int64_t BackupQueries::FindDirectoryObjectID(const std::string &rDirName,
}
// Generate exclude flags
- int16_t excludeFlags = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING;
- if(!AllowOldVersion) excludeFlags |= BackupProtocolClientListDirectory::Flags_OldVersion;
- if(!AllowDeletedDirs) excludeFlags |= BackupProtocolClientListDirectory::Flags_Deleted;
+ int16_t excludeFlags = BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING;
+ if(!AllowOldVersion) excludeFlags |= BackupProtocolListDirectory::Flags_OldVersion;
+ if(!AllowDeletedDirs) excludeFlags |= BackupProtocolListDirectory::Flags_Deleted;
// Read directories
for(unsigned int e = 0; e < dirElements.size(); ++e)
@@ -719,20 +616,20 @@ int64_t BackupQueries::FindDirectoryObjectID(const std::string &rDirName,
stack.pop_back();
// New dir ID
- dirID = (stack.size() > 0)?(stack[stack.size() - 1].second):BackupProtocolClientListDirectory::RootDirectory;
+ dirID = (stack.size() > 0)?(stack[stack.size() - 1].second):BackupProtocolListDirectory::RootDirectory;
}
else
{
// At root anyway
- dirID = BackupProtocolClientListDirectory::RootDirectory;
+ dirID = BackupProtocolListDirectory::RootDirectory;
}
}
else
{
// Not blank element. Read current directory.
- std::auto_ptr<BackupProtocolClientSuccess> dirreply(mrConnection.QueryListDirectory(
+ std::auto_ptr<BackupProtocolSuccess> dirreply(mrConnection.QueryListDirectory(
dirID,
- BackupProtocolClientListDirectory::Flags_Dir, // just directories
+ BackupProtocolListDirectory::Flags_Dir, // just directories
excludeFlags,
true /* want attributes */));
@@ -783,7 +680,7 @@ int64_t BackupQueries::GetCurrentDirectoryID()
// Special case for root
if(mDirStack.size() == 0)
{
- return BackupProtocolClientListDirectory::RootDirectory;
+ return BackupProtocolListDirectory::RootDirectory;
}
// Otherwise, get from the last entry on the stack
@@ -955,7 +852,8 @@ void BackupQueries::CommandGetObject(const std::vector<std::string> &args, const
int64_t id = ::strtoll(args[0].c_str(), 0, 16);
if(id == std::numeric_limits<long long>::min() || id == std::numeric_limits<long long>::max() || id == 0)
{
- BOX_ERROR("Not a valid object ID (specified in hex).");
+ BOX_ERROR("Not a valid object ID (specified in hex): " <<
+ args[0]);
return;
}
@@ -974,8 +872,8 @@ void BackupQueries::CommandGetObject(const std::vector<std::string> &args, const
try
{
// Request object
- std::auto_ptr<BackupProtocolClientSuccess> getobj(mrConnection.QueryGetObject(id));
- if(getobj->GetObjectID() != BackupProtocolClientGetObject::NoObject)
+ std::auto_ptr<BackupProtocolSuccess> getobj(mrConnection.QueryGetObject(id));
+ if(getobj->GetObjectID() != BackupProtocolGetObject::NoObject)
{
// Stream that object out to the file
std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
@@ -1062,7 +960,8 @@ int64_t BackupQueries::FindFileID(const std::string& rNameOrIdString,
fileId == std::numeric_limits<long long>::max() ||
fileId == 0)
{
- BOX_ERROR("Not a valid object ID (specified in hex).");
+ BOX_ERROR("Not a valid object ID (specified in hex): "
+ << rNameOrIdString);
return 0;
}
@@ -1154,19 +1053,19 @@ void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts)
if(opts['i'])
{
// can retrieve anything by ID
- flagsExclude = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING;
+ flagsExclude = BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING;
}
else
{
// only current versions by name
flagsExclude =
- BackupProtocolClientListDirectory::Flags_OldVersion |
- BackupProtocolClientListDirectory::Flags_Deleted;
+ BackupProtocolListDirectory::Flags_OldVersion |
+ BackupProtocolListDirectory::Flags_Deleted;
}
fileId = FindFileID(args[0], opts, &dirId, &localName,
- BackupProtocolClientListDirectory::Flags_File, // just files
+ BackupProtocolListDirectory::Flags_File, // just files
flagsExclude, NULL /* don't care about flags found */);
if (fileId == 0)
@@ -1469,6 +1368,159 @@ void BackupQueries::Compare(const std::string &rStoreDir,
Compare(dirID, storeDirEncoded, localDirEncoded, rParams);
}
+void BackupQueries::CompareOneFile(int64_t DirID,
+ BackupStoreDirectory::Entry *pEntry,
+ const std::string& rLocalPath,
+ const std::string& rStorePath,
+ BoxBackupCompareParams &rParams)
+{
+ int64_t fileId = pEntry->GetObjectID();
+ int64_t fileSize = 0;
+
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(rLocalPath.c_str(), &st) == 0)
+ {
+ fileSize = st.st_size;
+ }
+
+ try
+ {
+ // Files the same flag?
+ bool equal = true;
+
+ // File modified after last sync flag
+ bool modifiedAfterLastSync = false;
+
+ bool hasDifferentAttribs = false;
+
+ bool alreadyReported = false;
+
+ if(rParams.QuickCompare())
+ {
+ // Compare file -- fetch it
+ mrConnection.QueryGetBlockIndexByID(fileId);
+
+ // Stream containing block index
+ std::auto_ptr<IOStream> blockIndexStream(mrConnection.ReceiveStream());
+
+ // Compare
+ equal = BackupStoreFile::CompareFileContentsAgainstBlockIndex(
+ rLocalPath.c_str(), *blockIndexStream,
+ mrConnection.GetTimeout());
+ }
+ else
+ {
+ // Compare file -- fetch it
+ mrConnection.QueryGetFile(DirID, pEntry->GetObjectID());
+
+ // Stream containing encoded file
+ std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
+
+ // Decode it
+ std::auto_ptr<BackupStoreFile::DecodedStream> fileOnServerStream;
+
+ // Got additional attributes?
+ if(pEntry->HasAttributes())
+ {
+ // Use these attributes
+ const StreamableMemBlock &storeAttr(pEntry->GetAttributes());
+ BackupClientFileAttributes attr(storeAttr);
+ fileOnServerStream.reset(
+ BackupStoreFile::DecodeFileStream(
+ *objectStream,
+ mrConnection.GetTimeout(),
+ &attr).release());
+ }
+ else
+ {
+ // Use attributes stored in file
+ fileOnServerStream.reset(BackupStoreFile::DecodeFileStream(*objectStream, mrConnection.GetTimeout()).release());
+ }
+
+ // Should always be something in the auto_ptr, it's how the interface is defined. But be paranoid.
+ if(!fileOnServerStream.get())
+ {
+ THROW_EXCEPTION(BackupStoreException, Internal)
+ }
+
+ // Compare attributes
+ BackupClientFileAttributes localAttr;
+ box_time_t fileModTime = 0;
+ localAttr.ReadAttributes(rLocalPath.c_str(), false /* don't zero mod times */, &fileModTime);
+ modifiedAfterLastSync = (fileModTime > rParams.LatestFileUploadTime());
+ bool ignoreAttrModTime = true;
+
+ #ifdef WIN32
+ // attr mod time is really
+ // creation time, so check it
+ ignoreAttrModTime = false;
+ #endif
+
+ if(!rParams.IgnoreAttributes() &&
+ #ifdef PLATFORM_DISABLE_SYMLINK_ATTRIB_COMPARE
+ !fileOnServerStream->IsSymLink() &&
+ #endif
+ !localAttr.Compare(fileOnServerStream->GetAttributes(),
+ ignoreAttrModTime,
+ fileOnServerStream->IsSymLink() /* ignore modification time if it's a symlink */))
+ {
+ hasDifferentAttribs = true;
+ }
+
+ // Compare contents, if it's a regular file not a link
+ // Remember, we MUST read the entire stream from the server.
+ SelfFlushingStream flushObject(*objectStream);
+
+ if(!fileOnServerStream->IsSymLink())
+ {
+ SelfFlushingStream flushFile(*fileOnServerStream);
+ // Open the local file
+ std::auto_ptr<FileStream> apLocalFile;
+
+ try
+ {
+ apLocalFile.reset(new FileStream(rLocalPath.c_str()));
+ }
+ catch(std::exception &e)
+ {
+ rParams.NotifyLocalFileReadFailed(rLocalPath,
+ rStorePath, fileSize, e);
+ alreadyReported = true;
+ }
+ catch(...)
+ {
+ rParams.NotifyLocalFileReadFailed(rLocalPath,
+ rStorePath, fileSize);
+ alreadyReported = true;
+ }
+
+ if(apLocalFile.get())
+ {
+ equal = apLocalFile->CompareWith(*fileOnServerStream,
+ mrConnection.GetTimeout());
+ }
+ }
+ }
+
+ rParams.NotifyFileCompared(rLocalPath, rStorePath, fileSize,
+ hasDifferentAttribs, !equal, modifiedAfterLastSync,
+ pEntry->HasAttributes());
+ }
+ catch(BoxException &e)
+ {
+ rParams.NotifyDownloadFailed(rLocalPath, rStorePath, fileSize,
+ e);
+ }
+ catch(std::exception &e)
+ {
+ rParams.NotifyDownloadFailed(rLocalPath, rStorePath, fileSize,
+ e);
+ }
+ catch(...)
+ {
+ rParams.NotifyDownloadFailed(rLocalPath, rStorePath, fileSize);
+ }
+}
// --------------------------------------------------------------------------
//
@@ -1503,10 +1555,10 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir,
// Get the directory listing from the store
mrConnection.QueryListDirectory(
DirID,
- BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
// get everything
- BackupProtocolClientListDirectory::Flags_OldVersion |
- BackupProtocolClientListDirectory::Flags_Deleted,
+ BackupProtocolListDirectory::Flags_OldVersion |
+ BackupProtocolListDirectory::Flags_Deleted,
// except for old versions and deleted files
true /* want attributes */);
@@ -1700,124 +1752,8 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir,
}
else
{
- int64_t fileSize = 0;
-
- EMU_STRUCT_STAT st;
- if(EMU_STAT(localPath.c_str(), &st) == 0)
- {
- fileSize = st.st_size;
- }
-
- try
- {
- // Files the same flag?
- bool equal = true;
-
- // File modified after last sync flag
- bool modifiedAfterLastSync = false;
-
- bool hasDifferentAttribs = false;
-
- if(rParams.QuickCompare())
- {
- // Compare file -- fetch it
- mrConnection.QueryGetBlockIndexByID(i->second->GetObjectID());
-
- // Stream containing block index
- std::auto_ptr<IOStream> blockIndexStream(mrConnection.ReceiveStream());
-
- // Compare
- equal = BackupStoreFile::CompareFileContentsAgainstBlockIndex(localPath.c_str(), *blockIndexStream, mrConnection.GetTimeout());
- }
- else
- {
- // Compare file -- fetch it
- mrConnection.QueryGetFile(DirID, i->second->GetObjectID());
-
- // Stream containing encoded file
- std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream());
-
- // Decode it
- std::auto_ptr<BackupStoreFile::DecodedStream> fileOnServerStream;
- // Got additional attributes?
- if(i->second->HasAttributes())
- {
- // Use these attributes
- const StreamableMemBlock &storeAttr(i->second->GetAttributes());
- BackupClientFileAttributes attr(storeAttr);
- fileOnServerStream.reset(BackupStoreFile::DecodeFileStream(*objectStream, mrConnection.GetTimeout(), &attr).release());
- }
- else
- {
- // Use attributes stored in file
- fileOnServerStream.reset(BackupStoreFile::DecodeFileStream(*objectStream, mrConnection.GetTimeout()).release());
- }
-
- // Should always be something in the auto_ptr, it's how the interface is defined. But be paranoid.
- if(!fileOnServerStream.get())
- {
- THROW_EXCEPTION(BackupStoreException, Internal)
- }
-
- // Compare attributes
- BackupClientFileAttributes localAttr;
- box_time_t fileModTime = 0;
- localAttr.ReadAttributes(localPath.c_str(), false /* don't zero mod times */, &fileModTime);
- modifiedAfterLastSync = (fileModTime > rParams.LatestFileUploadTime());
- bool ignoreAttrModTime = true;
-
- #ifdef WIN32
- // attr mod time is really
- // creation time, so check it
- ignoreAttrModTime = false;
- #endif
-
- if(!rParams.IgnoreAttributes() &&
- #ifdef PLATFORM_DISABLE_SYMLINK_ATTRIB_COMPARE
- !fileOnServerStream->IsSymLink() &&
- #endif
- !localAttr.Compare(fileOnServerStream->GetAttributes(),
- ignoreAttrModTime,
- fileOnServerStream->IsSymLink() /* ignore modification time if it's a symlink */))
- {
- hasDifferentAttribs = true;
- }
-
- // Compare contents, if it's a regular file not a link
- // Remember, we MUST read the entire stream from the server.
- SelfFlushingStream flushObject(*objectStream);
-
- if(!fileOnServerStream->IsSymLink())
- {
- SelfFlushingStream flushFile(*fileOnServerStream);
- // Open the local file
- FileStream l(localPath.c_str());
- equal = l.CompareWith(*fileOnServerStream,
- mrConnection.GetTimeout());
- }
- }
-
- rParams.NotifyFileCompared(localPath,
- storePath, fileSize,
- hasDifferentAttribs, !equal,
- modifiedAfterLastSync,
- i->second->HasAttributes());
- }
- catch(BoxException &e)
- {
- rParams.NotifyDownloadFailed(localPath,
- storePath, fileSize, e);
- }
- catch(std::exception &e)
- {
- rParams.NotifyDownloadFailed(localPath,
- storePath, fileSize, e);
- }
- catch(...)
- {
- rParams.NotifyDownloadFailed(localPath,
- storePath, fileSize);
- }
+ CompareOneFile(DirID, i->second, localPath,
+ storePath, rParams);
// Remove from set so that we know it's been compared
localFiles.erase(local);
@@ -1947,9 +1883,10 @@ void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir,
void BackupQueries::CommandRestore(const std::vector<std::string> &args, const bool *opts)
{
// Check arguments
- if(args.size() != 2)
+ if(args.size() < 1 || args.size() > 2)
{
- BOX_ERROR("Incorrect usage. restore [-drif] <remote-name> <local-name>");
+ BOX_ERROR("Incorrect usage. restore [-drif] <remote-name> "
+ "[<local-name>]");
return;
}
@@ -1966,7 +1903,8 @@ void BackupQueries::CommandRestore(const std::vector<std::string> &args, const b
dirID = ::strtoll(args[0].c_str(), 0, 16);
if(dirID == std::numeric_limits<long long>::min() || dirID == std::numeric_limits<long long>::max() || dirID == 0)
{
- BOX_ERROR("Not a valid object ID (specified in hex)");
+ BOX_ERROR("Not a valid object ID (specified in hex): "
+ << args[0]);
return;
}
std::ostringstream oss;
@@ -1994,18 +1932,30 @@ void BackupQueries::CommandRestore(const std::vector<std::string> &args, const b
BOX_ERROR("Directory '" << args[0] << "' not found on server");
return;
}
- if(dirID == BackupProtocolClientListDirectory::RootDirectory)
+
+ if(dirID == BackupProtocolListDirectory::RootDirectory)
{
BOX_ERROR("Cannot restore the root directory -- restore locations individually.");
return;
}
-
-#ifdef WIN32
+
std::string localName;
- if(!ConvertConsoleToUtf8(args[1].c_str(), localName)) return;
-#else
- std::string localName(args[1]);
-#endif
+
+ if(args.size() == 2)
+ {
+ #ifdef WIN32
+ if(!ConvertConsoleToUtf8(args[1].c_str(), localName))
+ {
+ return;
+ }
+ #else
+ localName = args[1];
+ #endif
+ }
+ else
+ {
+ localName = args[0];
+ }
// Go and restore...
int result;
@@ -2082,8 +2032,8 @@ void BackupQueries::CommandRestore(const std::vector<std::string> &args, const b
// These are autogenerated by a script.
-extern char *help_commands[];
-extern char *help_text[];
+extern const char *help_commands[];
+extern const char *help_text[];
// --------------------------------------------------------------------------
@@ -2140,7 +2090,7 @@ void BackupQueries::CommandUsage(const bool *opts)
bool MachineReadable = opts['m'];
// Request full details from the server
- std::auto_ptr<BackupProtocolClientAccountUsage> usage(mrConnection.QueryGetAccountUsage());
+ std::auto_ptr<BackupProtocolAccountUsage> usage(mrConnection.QueryGetAccountUsage());
// Display each entry in turn
int64_t hardLimit = usage->GetBlocksHardLimit();
@@ -2216,9 +2166,9 @@ void BackupQueries::CommandUndelete(const std::vector<std::string> &args, const
fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName,
/* include files and directories */
- BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING,
/* include old and deleted files */
- BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING,
&flagsOut);
if (fileId == 0)
@@ -2231,7 +2181,7 @@ void BackupQueries::CommandUndelete(const std::vector<std::string> &args, const
try
{
// Undelete object
- if(flagsOut & BackupProtocolClientListDirectory::Flags_File)
+ if(flagsOut & BackupProtocolListDirectory::Flags_File)
{
mrConnection.QueryUndeleteFile(parentId, fileId);
}
@@ -2296,10 +2246,10 @@ void BackupQueries::CommandDelete(const std::vector<std::string> &args,
fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName,
/* include files and directories */
- BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING,
/* exclude old and deleted files */
- BackupProtocolClientListDirectory::Flags_OldVersion |
- BackupProtocolClientListDirectory::Flags_Deleted,
+ BackupProtocolListDirectory::Flags_OldVersion |
+ BackupProtocolListDirectory::Flags_Deleted,
&flagsOut);
if (fileId == 0)
@@ -2314,7 +2264,7 @@ void BackupQueries::CommandDelete(const std::vector<std::string> &args,
try
{
// Delete object
- if(flagsOut & BackupProtocolClientListDirectory::Flags_File)
+ if(flagsOut & BackupProtocolListDirectory::Flags_File)
{
mrConnection.QueryDeleteFile(parentId, fn);
}
diff --git a/bin/bbackupquery/BackupQueries.h b/bin/bbackupquery/BackupQueries.h
index 392aa428..62ff231d 100644
--- a/bin/bbackupquery/BackupQueries.h
+++ b/bin/bbackupquery/BackupQueries.h
@@ -10,16 +10,40 @@
#ifndef BACKUPQUERIES__H
#define BACKUPQUERIES__H
-#include <vector>
+#include <iostream>
#include <string>
+#include <vector>
#include "BoxTime.h"
#include "BoxBackupCompareParams.h"
+#include "BackupStoreDirectory.h"
class BackupProtocolClient;
class Configuration;
class ExcludeList;
+typedef enum
+{
+ Command_Unknown = 0,
+ Command_Quit,
+ Command_List,
+ Command_pwd,
+ Command_cd,
+ Command_lcd,
+ Command_sh,
+ Command_GetObject,
+ Command_Get,
+ Command_Compare,
+ Command_Restore,
+ Command_Help,
+ Command_Usage,
+ Command_Undelete,
+ Command_Delete,
+}
+CommandType;
+
+struct QueryCommandSpecification;
+
// --------------------------------------------------------------------------
//
// Class
@@ -38,8 +62,24 @@ public:
private:
BackupQueries(const BackupQueries &);
public:
+ struct ParsedCommand
+ {
+ std::vector<std::string> mCmdElements;
+ std::string mOptions;
+ std::string mCompleteCommand;
+ bool mInOptions, mFailed;
+ QueryCommandSpecification* pSpec;
+ // mArgCount is the same as mCmdElements.size() for a complete
+ // command, but if the command line ends in a space,
+ // e.g. during readline parsing, it can be one greater,
+ // to indicate that we should complete the next item instead.
+ size_t mCompleteArgCount;
+ ParsedCommand(const std::string& Command,
+ bool isFromCommandLine);
+ bool IsEmpty() { return mCmdElements.empty(); }
+ };
- void DoCommand(const char *Command, bool isFromCommandLine);
+ void DoCommand(ParsedCommand& rCommand);
// Ready to stop?
bool Stop() {return mQuitNow;}
@@ -47,9 +87,11 @@ public:
// Return code?
int GetReturnCode() {return mReturnCode;}
-private:
- // Commands
+ void List(int64_t DirID, const std::string &rListRoot, const bool *opts,
+ bool FirstLevel, std::ostream* pOut = NULL);
void CommandList(const std::vector<std::string> &args, const bool *opts);
+
+ // Commands
void CommandChangeDir(const std::vector<std::string> &args, const bool *opts);
void CommandChangeLocalDir(const std::vector<std::string> &args);
void CommandGetObject(const std::vector<std::string> &args, const bool *opts);
@@ -64,11 +106,6 @@ private:
int64_t HardLimit, int32_t BlockSize, bool MachineReadable);
void CommandHelp(const std::vector<std::string> &args);
- // Implementations
- void List(int64_t DirID, const std::string &rListRoot, const bool *opts,
- bool FirstLevel);
-
-public:
class CompareParams : public BoxBackupCompareParams
{
public:
@@ -201,6 +238,24 @@ public:
mUncheckedFiles ++;
}
+ virtual void NotifyLocalFileReadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes,
+ std::exception& rException)
+ {
+ BOX_ERROR("Failed to read local file '" <<
+ ConvertForConsole(rLocalPath) << "': " <<
+ rException.what());
+ mUncheckedFiles ++;
+ }
+
+ virtual void NotifyLocalFileReadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes)
+ {
+ BOX_ERROR("Failed to read local file '" <<
+ ConvertForConsole(rLocalPath));
+ mUncheckedFiles ++;
+ }
+
virtual void NotifyExcludedFile(const std::string& rLocalPath,
const std::string& rRemotePath)
{
@@ -302,13 +357,16 @@ public:
const std::string &rLocalDir, BoxBackupCompareParams &rParams);
void Compare(int64_t DirID, const std::string &rStoreDir,
const std::string &rLocalDir, BoxBackupCompareParams &rParams);
+ void CompareOneFile(int64_t DirID, BackupStoreDirectory::Entry *pEntry,
+ const std::string& rLocalPath, const std::string& rStorePath,
+ BoxBackupCompareParams &rParams);
public:
class ReturnCode
{
public:
- enum {
+ typedef enum {
Command_OK = 0,
Compare_Same = 1,
Compare_Different,
@@ -317,17 +375,19 @@ public:
} Type;
};
-private:
-
- // Utility functions
+ // Were private, but needed by completion functions:
+ int64_t GetCurrentDirectoryID();
int64_t FindDirectoryObjectID(const std::string &rDirName,
bool AllowOldVersion = false, bool AllowDeletedDirs = false,
std::vector<std::pair<std::string, int64_t> > *pStack = 0);
+
+private:
+
+ // Utility functions
int64_t FindFileID(const std::string& rNameOrIdString,
const bool *opts, int64_t *pDirIdOut,
std::string* pFileNameOut, int16_t flagsInclude,
int16_t flagsExclude, int16_t* pFlagsOut);
- int64_t GetCurrentDirectoryID();
std::string GetCurrentDirectoryName();
void SetReturnCode(int code) {mReturnCode = code;}
@@ -342,5 +402,36 @@ private:
int mReturnCode;
};
+typedef std::vector<std::string> (*CompletionHandler)
+ (BackupQueries::ParsedCommand& rCommand, const std::string& prefix,
+ BackupProtocolClient& rProtocol, const Configuration& rConfig,
+ BackupQueries& rQueries);
+
+std::vector<std::string> CompleteCommand(BackupQueries::ParsedCommand& rCommand,
+ const std::string& prefix, BackupProtocolClient& rProtocol,
+ const Configuration& rConfig, BackupQueries& rQueries);
+std::vector<std::string> CompleteOptions(BackupQueries::ParsedCommand& rCommand,
+ const std::string& prefix, BackupProtocolClient& rProtocol,
+ const Configuration& rConfig, BackupQueries& rQueries);
+
+#define MAX_COMPLETION_HANDLERS 4
+
+struct QueryCommandSpecification
+{
+ const char* name;
+ const char* opts;
+ CommandType type;
+ CompletionHandler complete[MAX_COMPLETION_HANDLERS];
+};
+
+// Data about commands
+extern QueryCommandSpecification commands[];
+
+extern const char *alias[];
+extern const int aliasIs[];
+
+#define LIST_OPTION_ALLOWOLD 'o'
+#define LIST_OPTION_ALLOWDELETED 'd'
+
#endif // BACKUPQUERIES__H
diff --git a/bin/bbackupquery/BoxBackupCompareParams.h b/bin/bbackupquery/BoxBackupCompareParams.h
index c58759a2..655df947 100644
--- a/bin/bbackupquery/BoxBackupCompareParams.h
+++ b/bin/bbackupquery/BoxBackupCompareParams.h
@@ -82,6 +82,11 @@ public:
virtual void NotifyDownloadFailed(const std::string& rLocalPath,
const std::string& rRemotePath, int64_t NumBytes,
BoxException& rException) = 0;
+ virtual void NotifyLocalFileReadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes,
+ std::exception& rException) = 0;
+ virtual void NotifyLocalFileReadFailed(const std::string& rLocalPath,
+ const std::string& rRemotePath, int64_t NumBytes) = 0;
virtual void NotifyDownloadFailed(const std::string& rLocalPath,
const std::string& rRemotePath, int64_t NumBytes,
std::exception& rException) = 0;
diff --git a/bin/bbackupquery/CommandCompletion.cpp b/bin/bbackupquery/CommandCompletion.cpp
new file mode 100644
index 00000000..93c4d3fd
--- /dev/null
+++ b/bin/bbackupquery/CommandCompletion.cpp
@@ -0,0 +1,602 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: CommandCompletion.cpp
+// Purpose: Parts of BackupQueries that depend on readline
+// Created: 2011/01/21
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#ifdef HAVE_LIBREADLINE
+ #ifdef HAVE_READLINE_READLINE_H
+ #include <readline/readline.h>
+ #elif defined(HAVE_EDITLINE_READLINE_H)
+ #include <editline/readline.h>
+ #elif defined(HAVE_READLINE_H)
+ #include <readline.h>
+ #endif
+#endif
+
+#ifdef HAVE_READLINE_HISTORY
+ #ifdef HAVE_READLINE_HISTORY_H
+ #include <readline/history.h>
+ #elif defined(HAVE_HISTORY_H)
+ #include <history.h>
+ #endif
+#endif
+
+#include <cstring>
+
+#include "BackupQueries.h"
+#include "Configuration.h"
+
+#include "autogen_BackupProtocol.h"
+
+#include "MemLeakFindOn.h"
+
+#define COMPARE_RETURN_SAME 1
+#define COMPARE_RETURN_DIFFERENT 2
+#define COMPARE_RETURN_ERROR 3
+#define COMMAND_RETURN_ERROR 4
+
+#define COMPLETION_FUNCTION(name, code) \
+std::vector<std::string> Complete ## name( \
+ BackupQueries::ParsedCommand& rCommand, \
+ const std::string& prefix, \
+ BackupProtocolClient& rProtocol, const Configuration& rConfig, \
+ BackupQueries& rQueries) \
+{ \
+ std::vector<std::string> completions; \
+ \
+ try \
+ { \
+ code \
+ } \
+ catch(std::exception &e) \
+ { \
+ BOX_TRACE("Failed to complete " << prefix << ": " << e.what()); \
+ } \
+ catch(...) \
+ { \
+ BOX_TRACE("Failed to complete " << prefix << ": " \
+ "unknown error"); \
+ } \
+ \
+ return completions; \
+}
+
+#define DELEGATE_COMPLETION(name) \
+ completions = Complete ## name(rCommand, prefix, rProtocol, rConfig, \
+ rQueries);
+
+COMPLETION_FUNCTION(None,)
+
+#ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION
+ #define RL_FILENAME_COMPLETION_FUNCTION rl_filename_completion_function
+ #define HAVE_A_FILENAME_COMPLETION_FUNCTION 1
+#elif defined HAVE_FILENAME_COMPLETION_FUNCTION
+ #define RL_FILENAME_COMPLETION_FUNCTION filename_completion_function
+ #define HAVE_A_FILENAME_COMPLETION_FUNCTION 1
+#endif
+
+#ifdef HAVE_A_FILENAME_COMPLETION_FUNCTION
+COMPLETION_FUNCTION(Default,
+ int i = 0;
+
+ while (const char *match = RL_FILENAME_COMPLETION_FUNCTION(prefix.c_str(), i))
+ {
+ completions.push_back(match);
+ ++i;
+ }
+)
+#else // !HAVE_A_FILENAME_COMPLETION_FUNCTION
+COMPLETION_FUNCTION(Default,)
+#endif // HAVE_A_FILENAME_COMPLETION_FUNCTION
+
+COMPLETION_FUNCTION(Command,
+ int len = prefix.length();
+
+ for(int i = 0; commands[i].name != NULL; i++)
+ {
+ if(::strncmp(commands[i].name, prefix.c_str(), len) == 0)
+ {
+ completions.push_back(commands[i].name);
+ }
+ }
+)
+
+void CompleteOptionsInternal(const std::string& prefix,
+ BackupQueries::ParsedCommand& rCommand,
+ std::vector<std::string>& completions)
+{
+ std::string availableOptions = rCommand.pSpec->opts;
+
+ for(std::string::iterator
+ opt = availableOptions.begin();
+ opt != availableOptions.end(); opt++)
+ {
+ if(rCommand.mOptions.find(*opt) == std::string::npos)
+ {
+ if(prefix == "")
+ {
+ // complete with possible option strings
+ completions.push_back(std::string("-") + *opt);
+ }
+ else
+ {
+ // complete with possible additional options
+ completions.push_back(prefix + *opt);
+ }
+ }
+ }
+}
+
+COMPLETION_FUNCTION(Options,
+ CompleteOptionsInternal(prefix, rCommand, completions);
+)
+
+std::string EncodeFileName(const std::string &rUnEncodedName)
+{
+#ifdef WIN32
+ std::string encodedName;
+ if(!ConvertConsoleToUtf8(rUnEncodedName, encodedName))
+ {
+ return std::string();
+ }
+ return encodedName;
+#else
+ return rUnEncodedName;
+#endif
+}
+
+int16_t GetExcludeFlags(BackupQueries::ParsedCommand& rCommand)
+{
+ int16_t excludeFlags = 0;
+
+ if (rCommand.mOptions.find(LIST_OPTION_ALLOWOLD) == std::string::npos)
+ {
+ excludeFlags |= BackupProtocolListDirectory::Flags_OldVersion;
+ }
+
+ if (rCommand.mOptions.find(LIST_OPTION_ALLOWDELETED) == std::string::npos)
+ {
+ excludeFlags |= BackupProtocolListDirectory::Flags_Deleted;
+ }
+
+ return excludeFlags;
+}
+
+std::vector<std::string> CompleteRemoteFileOrDirectory(
+ BackupQueries::ParsedCommand& rCommand,
+ const std::string& prefix, BackupProtocolClient& rProtocol,
+ BackupQueries& rQueries, int16_t includeFlags)
+{
+ std::vector<std::string> completions;
+
+ // default to using the current directory
+ int64_t listDirId = rQueries.GetCurrentDirectoryID();
+ std::string searchPrefix;
+ std::string listDir = prefix;
+
+ if(rCommand.mCompleteArgCount == rCommand.mCmdElements.size())
+ {
+ // completing an empty name, from the current directory
+ // nothing to change
+ }
+ else
+ {
+ // completing a partially-completed subdirectory name
+ searchPrefix = prefix;
+ listDir = "";
+
+ // do we need to list a subdirectory to complete?
+ size_t lastSlash = searchPrefix.rfind('/');
+ if(lastSlash == std::string::npos)
+ {
+ // no slashes, so the whole name is the prefix
+ // nothing to change
+ }
+ else
+ {
+ // listing a partially-completed subdirectory name
+ listDir = searchPrefix.substr(0, lastSlash);
+
+ listDirId = rQueries.FindDirectoryObjectID(listDir,
+ rCommand.mOptions.find(LIST_OPTION_ALLOWOLD)
+ != std::string::npos,
+ rCommand.mOptions.find(LIST_OPTION_ALLOWDELETED)
+ != std::string::npos);
+
+ if(listDirId == 0)
+ {
+ // no matches for subdir to list,
+ // return empty-handed.
+ return completions;
+ }
+
+ // matched, and updated listDir and listDirId already
+ searchPrefix = searchPrefix.substr(lastSlash + 1);
+ }
+ }
+
+ // Always include directories, because they contain files.
+ // We will append a slash later for each directory if we're
+ // actually looking for files.
+ //
+ // If we're looking for directories, then only list directories.
+
+ bool completeFiles = includeFlags &
+ BackupProtocolListDirectory::Flags_File;
+ bool completeDirs = includeFlags &
+ BackupProtocolListDirectory::Flags_Dir;
+ int16_t listFlags = 0;
+
+ if(completeFiles)
+ {
+ listFlags = BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING;
+ }
+ else if(completeDirs)
+ {
+ listFlags = BackupProtocolListDirectory::Flags_Dir;
+ }
+
+ rProtocol.QueryListDirectory(listDirId,
+ listFlags, GetExcludeFlags(rCommand),
+ false /* no attributes */);
+
+ // Retrieve the directory from the stream following
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(rProtocol.ReceiveStream());
+ dir.ReadFromStream(*dirstream, rProtocol.GetTimeout());
+
+ // Then... display everything
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ BackupStoreFilenameClear clear(en->GetName());
+ std::string name = clear.GetClearFilename().c_str();
+ if(name.compare(0, searchPrefix.length(), searchPrefix) == 0)
+ {
+ bool dir_added = false;
+
+ if(en->IsDir() &&
+ (includeFlags & BackupProtocolListDirectory::Flags_Dir) == 0)
+ {
+ // Was looking for a file, but this is a
+ // directory, so append a slash to the name
+ name += "/";
+ }
+
+ #ifdef HAVE_LIBREADLINE
+ if(strchr(name.c_str(), ' '))
+ {
+ int n_quote = 0;
+
+ for(int k = strlen(rl_line_buffer); k >= 0; k--)
+ {
+ if (rl_line_buffer[k] == '\"') {
+ ++n_quote;
+ }
+ }
+
+ dir_added = false;
+
+ if (!(n_quote % 2))
+ {
+ name = "\"" + (listDir == "" ? name : listDir + "/" + name);
+ dir_added = true;
+ }
+
+ name = name + "\"";
+ }
+ #endif
+
+ if(listDir == "" || dir_added)
+ {
+ completions.push_back(name);
+ }
+ else
+ {
+ completions.push_back(listDir + "/" + name);
+ }
+ }
+ }
+
+ return completions;
+}
+
+COMPLETION_FUNCTION(RemoteDir,
+ completions = CompleteRemoteFileOrDirectory(rCommand, prefix,
+ rProtocol, rQueries,
+ BackupProtocolListDirectory::Flags_Dir);
+)
+
+COMPLETION_FUNCTION(RemoteFile,
+ completions = CompleteRemoteFileOrDirectory(rCommand, prefix,
+ rProtocol, rQueries,
+ BackupProtocolListDirectory::Flags_File);
+)
+
+COMPLETION_FUNCTION(LocalDir,
+ DELEGATE_COMPLETION(Default);
+)
+
+COMPLETION_FUNCTION(LocalFile,
+ DELEGATE_COMPLETION(Default);
+)
+
+COMPLETION_FUNCTION(LocationName,
+ const Configuration &locations(rConfig.GetSubConfiguration(
+ "BackupLocations"));
+
+ std::vector<std::string> locNames =
+ locations.GetSubConfigurationNames();
+
+ for(std::vector<std::string>::iterator
+ pLocName = locNames.begin();
+ pLocName != locNames.end();
+ pLocName++)
+ {
+ if(pLocName->compare(0, pLocName->length(), prefix) == 0)
+ {
+ completions.push_back(*pLocName);
+ }
+ }
+)
+
+COMPLETION_FUNCTION(RemoteFileIdInCurrentDir,
+ int64_t listDirId = rQueries.GetCurrentDirectoryID();
+ int16_t excludeFlags = GetExcludeFlags(rCommand);
+
+ rProtocol.QueryListDirectory(
+ listDirId,
+ BackupProtocolListDirectory::Flags_File,
+ excludeFlags, false /* no attributes */);
+
+ // Retrieve the directory from the stream following
+ BackupStoreDirectory dir;
+ std::auto_ptr<IOStream> dirstream(rProtocol.ReceiveStream());
+ dir.ReadFromStream(*dirstream, rProtocol.GetTimeout());
+
+ // Then... compare each item
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ std::ostringstream hexId;
+ hexId << std::hex << en->GetObjectID();
+ if(hexId.str().compare(0, prefix.length(), prefix) == 0)
+ {
+ completions.push_back(hexId.str());
+ }
+ }
+)
+
+// TODO implement completion of hex IDs up to the maximum according to Usage
+COMPLETION_FUNCTION(RemoteId,)
+
+COMPLETION_FUNCTION(GetFileOrId,
+ if(rCommand.mOptions.find('i') != std::string::npos)
+ {
+ DELEGATE_COMPLETION(RemoteFileIdInCurrentDir);
+ }
+ else
+ {
+ DELEGATE_COMPLETION(RemoteFile);
+ }
+)
+
+COMPLETION_FUNCTION(CompareLocationOrRemoteDir,
+ if(rCommand.mOptions.find('l') != std::string::npos)
+ {
+ DELEGATE_COMPLETION(LocationName);
+ }
+ else
+ {
+ DELEGATE_COMPLETION(RemoteDir);
+ }
+)
+
+COMPLETION_FUNCTION(CompareNoneOrLocalDir,
+ if(rCommand.mOptions.find('l') != std::string::npos)
+ {
+ // no completions
+ DELEGATE_COMPLETION(None);
+ }
+ else
+ {
+ DELEGATE_COMPLETION(LocalDir);
+ }
+)
+
+COMPLETION_FUNCTION(RestoreRemoteDirOrId,
+ if(rCommand.mOptions.find('i') != std::string::npos)
+ {
+ DELEGATE_COMPLETION(RemoteId);
+ }
+ else
+ {
+ DELEGATE_COMPLETION(RemoteDir);
+ }
+)
+
+// Data about commands
+QueryCommandSpecification commands[] =
+{
+ { "quit", "", Command_Quit, {} },
+ { "exit", "", Command_Quit, {} },
+ { "list", "rodIFtTash", Command_List, {CompleteRemoteDir} },
+ { "pwd", "", Command_pwd, {} },
+ { "cd", "od", Command_cd, {CompleteRemoteDir} },
+ { "lcd", "", Command_lcd, {CompleteLocalDir} },
+ { "sh", "", Command_sh, {CompleteDefault} },
+ { "getobject", "", Command_GetObject,
+ {CompleteRemoteId, CompleteLocalDir} },
+ { "get", "i", Command_Get,
+ {CompleteGetFileOrId, CompleteLocalDir} },
+ { "compare", "alcqAEQ", Command_Compare,
+ {CompleteCompareLocationOrRemoteDir, CompleteCompareNoneOrLocalDir} },
+ { "restore", "drif", Command_Restore,
+ {CompleteRestoreRemoteDirOrId, CompleteLocalDir} },
+ { "help", "", Command_Help, {} },
+ { "usage", "m", Command_Usage, {} },
+ { "undelete", "i", Command_Undelete,
+ {CompleteGetFileOrId} },
+ { "delete", "i", Command_Delete, {CompleteGetFileOrId} },
+ { NULL, NULL, Command_Unknown, {} }
+};
+
+const char *alias[] = {"ls", 0};
+const int aliasIs[] = {Command_List, 0};
+
+BackupQueries::ParsedCommand::ParsedCommand(const std::string& Command,
+ bool isFromCommandLine)
+: mInOptions(false),
+ mFailed(false),
+ pSpec(NULL),
+ mCompleteArgCount(0)
+{
+ mCompleteCommand = Command;
+
+ // is the command a shell command?
+ if(Command[0] == 's' && Command[1] == 'h' && Command[2] == ' ' && Command[3] != '\0')
+ {
+ // Yes, run shell command
+ for(int i = 0; commands[i].type != Command_Unknown; i++)
+ {
+ if(commands[i].type == Command_sh)
+ {
+ pSpec = &(commands[i]);
+ break;
+ }
+ }
+
+ mCmdElements[0] = "sh";
+ mCmdElements[1] = Command.c_str() + 3;
+ return;
+ }
+
+ // split command into components
+ bool inQuoted = false;
+ mInOptions = false;
+
+ std::string currentArg;
+ for (std::string::const_iterator c = Command.begin();
+ c != Command.end(); c++)
+ {
+ // Terminating char?
+ if(*c == ((inQuoted)?'"':' '))
+ {
+ if(!currentArg.empty())
+ {
+ mCmdElements.push_back(currentArg);
+
+ // Because we just found a space, and the last
+ // word was not options (otherwise currentArg
+ // would be empty), we've received a complete
+ // command or non-option argument.
+ mCompleteArgCount++;
+ }
+
+ currentArg.resize(0);
+ inQuoted = false;
+ mInOptions = false;
+ }
+ // Start of quoted parameter?
+ else if(currentArg.empty() && *c == '"')
+ {
+ inQuoted = true;
+ }
+ // Start of options?
+ else if(currentArg.empty() && *c == '-')
+ {
+ mInOptions = true;
+ }
+ else if(mInOptions)
+ {
+ // Option char
+ mOptions += *c;
+ }
+ else
+ {
+ // Normal string char, part of current arg
+ currentArg += *c;
+ }
+ }
+
+ if(!currentArg.empty())
+ {
+ mCmdElements.push_back(currentArg);
+ }
+
+ // If there are no commands then there's nothing to do except return
+ if(mCmdElements.empty())
+ {
+ return;
+ }
+
+ // Work out which command it is...
+ int cmd = 0;
+ while(commands[cmd].name != 0 &&
+ mCmdElements[0] != commands[cmd].name)
+ {
+ cmd++;
+ }
+
+ if(commands[cmd].name == 0)
+ {
+ // Check for aliases
+ int a;
+ for(a = 0; alias[a] != 0; ++a)
+ {
+ if(mCmdElements[0] == alias[a])
+ {
+ // Found an alias
+ cmd = aliasIs[a];
+ break;
+ }
+ }
+ }
+
+ if(commands[cmd].name == 0)
+ {
+ mFailed = true;
+ return;
+ }
+
+ pSpec = &(commands[cmd]);
+
+ #ifdef WIN32
+ if(isFromCommandLine)
+ {
+ std::string converted;
+
+ if(!ConvertEncoding(mCompleteCommand, CP_ACP, converted,
+ GetConsoleCP()))
+ {
+ BOX_ERROR("Failed to convert encoding");
+ mFailed = true;
+ }
+
+ mCompleteCommand = converted;
+
+ for(std::vector<std::string>::iterator
+ i = mCmdElements.begin();
+ i != mCmdElements.end(); i++)
+ {
+ if(!ConvertEncoding(*i, CP_ACP, converted,
+ GetConsoleCP()))
+ {
+ BOX_ERROR("Failed to convert encoding");
+ mFailed = true;
+ }
+
+ *i = converted;
+ }
+ }
+ #endif
+}
+
diff --git a/bin/bbackupquery/bbackupquery.cpp b/bin/bbackupquery/bbackupquery.cpp
index 5aa7e97e..5493f49c 100644
--- a/bin/bbackupquery/bbackupquery.cpp
+++ b/bin/bbackupquery/bbackupquery.cpp
@@ -30,6 +30,7 @@
#include <readline.h>
#endif
#endif
+
#ifdef HAVE_READLINE_HISTORY
#ifdef HAVE_READLINE_HISTORY_H
#include <readline/history.h>
@@ -49,7 +50,7 @@
#include "SSLLib.h"
#include "BackupStoreConstants.h"
#include "BackupStoreException.h"
-#include "autogen_BackupProtocolClient.h"
+#include "autogen_BackupProtocol.h"
#include "BackupQueries.h"
#include "FdGetLine.h"
#include "BackupClientCryptoKeys.h"
@@ -60,20 +61,128 @@
void PrintUsageAndExit()
{
- printf("Usage: bbackupquery [-q*|v*|V|W<level>] [-w] "
+ std::ostringstream out;
+ out <<
+ "Usage: bbackupquery [options] [command]...\n"
+ "\n"
+ "Options:\n"
+ " -q Run more quietly, reduce verbosity level by one, can repeat\n"
+ " -Q Run at minimum verbosity, log nothing\n"
+ " -v Run more verbosely, increase verbosity level by one, can repeat\n"
+ " -V Run at maximum verbosity, log everything\n"
+ " -W <level> Set verbosity to error/warning/notice/info/trace/everything\n"
+ " -w Read/write mode, allow changes to store\n"
#ifdef WIN32
- "[-u] "
+ " -u Enable Unicode console, requires font change to Lucida Console\n"
+#endif
+#ifdef HAVE_LIBREADLINE
+ " -E Disable interactive command editing, may fix entering intl chars\n"
#endif
- "\n"
- "\t[-c config_file] [-o log_file] [-O log_file_level]\n"
- "\t[-l protocol_log_file] [commands]\n"
- "\n"
- "As many commands as you require.\n"
- "If commands are multiple words, remember to enclose the command in quotes.\n"
- "Remember to use the quit command unless you want to end up in interactive mode.\n");
+ " -c <file> Use the specified configuration file. If -c is omitted, the last\n"
+ " argument is the configuration file, or else the default \n"
+ " [" << BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE <<
+ "]\n"
+ " -o <file> Write logging output to specified file as well as console\n"
+ " -O <level> Set file verbosity to error/warning/notice/info/trace/everything\n"
+ " -l <file> Write protocol debugging logs to specified file\n"
+ "\n"
+ "Parameters: as many commands as you like. If commands are multiple words,\n"
+ "remember to enclose the command in quotes. Remember to use the quit command\n"
+ "unless you want to end up in interactive mode.\n";
+ printf("%s", out.str().c_str());
exit(1);
}
+#ifdef HAVE_LIBREADLINE
+static BackupProtocolClient* pProtocol;
+static const Configuration* pConfig;
+static BackupQueries* pQueries;
+static std::vector<std::string> completions;
+static std::auto_ptr<BackupQueries::ParsedCommand> sapCmd;
+
+char * completion_generator(const char *text, int state)
+{
+ if(state == 0)
+ {
+ completions.clear();
+
+ std::string partialCommand(rl_line_buffer, rl_point);
+ sapCmd.reset(new BackupQueries::ParsedCommand(partialCommand,
+ false));
+ int currentArg = sapCmd->mCompleteArgCount;
+
+ if(currentArg == 0) // incomplete command
+ {
+ completions = CompleteCommand(*sapCmd, text, *pProtocol,
+ *pConfig, *pQueries);
+ }
+ else if(sapCmd->mInOptions)
+ {
+ completions = CompleteOptions(*sapCmd, text, *pProtocol,
+ *pConfig, *pQueries);
+ }
+ else if(currentArg - 1 < MAX_COMPLETION_HANDLERS)
+ // currentArg must be at least 1 if we're here
+ {
+ CompletionHandler handler =
+ sapCmd->pSpec->complete[currentArg - 1];
+
+ if(handler != NULL)
+ {
+ completions = handler(*sapCmd, text, *pProtocol,
+ *pConfig, *pQueries);
+ }
+
+ if(std::string(text) == "")
+ {
+ // additional options are also allowed here
+ std::vector<std::string> addOpts =
+ CompleteOptions(*sapCmd, text,
+ *pProtocol, *pConfig,
+ *pQueries);
+
+ for(std::vector<std::string>::iterator
+ i = addOpts.begin();
+ i != addOpts.end(); i++)
+ {
+ completions.push_back(*i);
+ }
+ }
+ }
+ }
+
+ if(state < 0 || state >= (int) completions.size())
+ {
+ rl_attempted_completion_over = 1;
+ return NULL;
+ }
+
+ return strdup(completions[state].c_str());
+ // string must be allocated with malloc() and will be freed
+ // by rl_completion_matches().
+}
+
+#ifdef HAVE_RL_COMPLETION_MATCHES
+ #define RL_COMPLETION_MATCHES rl_completion_matches
+#elif defined HAVE_COMPLETION_MATCHES
+ #define RL_COMPLETION_MATCHES completion_matches
+#else
+ char* no_matches[] = {NULL};
+ char** bbackupquery_completion_dummy(const char *text,
+ char * (completion_generator)(const char *text, int state))
+ {
+ return no_matches;
+ }
+ #define RL_COMPLETION_MATCHES bbackupquery_completion_dummy
+#endif
+
+char ** bbackupquery_completion(const char *text, int start, int end)
+{
+ return RL_COMPLETION_MATCHES(text, completion_generator);
+}
+
+#endif // HAVE_LIBREADLINE
+
int main(int argc, const char *argv[])
{
int returnCode = 0;
@@ -103,13 +212,7 @@ int main(int argc, const char *argv[])
FILE *logFile = 0;
// Filename for configuration file?
- std::string configFilename;
-
- #ifdef WIN32
- configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE;
- #else
- configFilename = BOX_FILE_BBACKUPD_DEFAULT_CONFIG;
- #endif
+ std::string configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE;
// Flags
bool readWrite = false;
@@ -123,12 +226,21 @@ int main(int argc, const char *argv[])
#endif
#ifdef WIN32
- const char* validOpts = "qvVwuc:l:o:O:W:";
+ #define WIN32_OPTIONS "u"
bool unicodeConsole = false;
#else
- const char* validOpts = "qvVwc:l:o:O:W:";
+ #define WIN32_OPTIONS
#endif
+#ifdef HAVE_LIBREADLINE
+ #define READLINE_OPTIONS "E"
+ bool useReadline = true;
+#else
+ #define READLINE_OPTIONS
+#endif
+
+ const char* validOpts = "qvVwc:l:o:O:W:" WIN32_OPTIONS READLINE_OPTIONS;
+
std::string fileLogFile;
Log::Level fileLogLevel = Log::INVALID;
@@ -222,6 +334,12 @@ int main(int argc, const char *argv[])
unicodeConsole = true;
break;
#endif
+
+#ifdef HAVE_LIBREADLINE
+ case 'E':
+ useReadline = false;
+ break;
+#endif
case '?':
default:
@@ -317,7 +435,9 @@ int main(int argc, const char *argv[])
// 3. Make a protocol, and handshake
if(!quiet) BOX_INFO("Handshake with store...");
- BackupProtocolClient connection(socket);
+ std::auto_ptr<BackupProtocolClient>
+ apConnection(new BackupProtocolClient(socket));
+ BackupProtocolClient& connection(*(apConnection.get()));
connection.Handshake();
// logging?
@@ -330,15 +450,15 @@ int main(int argc, const char *argv[])
if(!quiet) BOX_INFO("Login to store...");
// Check the version of the server
{
- std::auto_ptr<BackupProtocolClientVersion> serverVersion(connection.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ std::auto_ptr<BackupProtocolVersion> serverVersion(connection.QueryVersion(BACKUP_STORE_SERVER_VERSION));
if(serverVersion->GetVersion() != BACKUP_STORE_SERVER_VERSION)
{
THROW_EXCEPTION(BackupStoreException, WrongServerVersion)
}
}
// Login -- if this fails, the Protocol will exception
- connection.QueryLogin(conf.GetKeyValueInt("AccountNumber"),
- (readWrite)?0:(BackupProtocolClientLogin::Flags_ReadOnly));
+ connection.QueryLogin(conf.GetKeyValueUint32("AccountNumber"),
+ (readWrite)?0:(BackupProtocolLogin::Flags_ReadOnly));
// 5. Tell user.
if(!quiet) printf("Login complete.\n\nType \"help\" for a list of commands.\n\n");
@@ -351,66 +471,102 @@ int main(int argc, const char *argv[])
int c = 0;
while(c < argc && !context.Stop())
{
- context.DoCommand(argv[c++], true);
+ BackupQueries::ParsedCommand cmd(argv[c++], true);
+ context.DoCommand(cmd);
}
}
// Get commands from input
#ifdef HAVE_LIBREADLINE
- // Must initialise the locale before using editline's readline(),
- // otherwise cannot enter international characters.
- if (setlocale(LC_ALL, "") == NULL)
+ if(useReadline)
{
- BOX_ERROR("Failed to initialise locale. International "
- "character support may not work.");
+ // Must initialise the locale before using editline's
+ // readline(), otherwise cannot enter international characters.
+ if (setlocale(LC_ALL, "") == NULL)
+ {
+ BOX_ERROR("Failed to initialise locale. International "
+ "character support may not work.");
+ }
+
+ #ifdef HAVE_READLINE_HISTORY
+ using_history();
+ #endif
+
+ /* Allow conditional parsing of the ~/.inputrc file. */
+ rl_readline_name = strdup("bbackupquery");
+
+ /* Tell the completer that we want a crack first. */
+ rl_attempted_completion_function = bbackupquery_completion;
+
+ pProtocol = &connection;
+ pConfig = &conf;
+ pQueries = &context;
}
-#ifdef HAVE_READLINE_HISTORY
- using_history();
+ std::string last_cmd;
#endif
- char *last_cmd = 0;
- while(!context.Stop())
+
+ std::auto_ptr<FdGetLine> apGetLine;
+ if(fileno(stdin) >= 0)
+ {
+ apGetLine.reset(new FdGetLine(fileno(stdin)));
+ }
+
+ while(!context.Stop() && fileno(stdin) >= 0)
{
- char *command = readline("query > ");
- if(command == NULL)
+ std::string cmd_str;
+
+ #ifdef HAVE_LIBREADLINE
+ if(useReadline)
{
- // Ctrl-D pressed -- terminate now
- break;
+ char *cmd_ptr = readline("query > ");
+
+ if(cmd_ptr == NULL)
+ {
+ // Ctrl-D pressed -- terminate now
+ break;
+ }
+
+ cmd_str = cmd_ptr;
+ free(cmd_ptr);
}
- context.DoCommand(command, false);
- if(last_cmd != 0 && ::strcmp(last_cmd, command) == 0)
+ else
+ #endif // HAVE_LIBREADLINE
{
- free(command);
+ printf("query > ");
+ fflush(stdout);
+
+ try
+ {
+ cmd_str = apGetLine->GetLine();
+ }
+ catch(CommonException &e)
+ {
+ if(e.GetSubType() == CommonException::GetLineEOF)
+ {
+ break;
+ }
+ throw;
+ }
}
- else
+
+ BackupQueries::ParsedCommand cmd_parsed(cmd_str, false);
+ if (cmd_parsed.IsEmpty())
{
-#ifdef HAVE_READLINE_HISTORY
- add_history(command);
-#else
- free(last_cmd);
-#endif
- last_cmd = command;
+ continue;
}
- }
-#ifndef HAVE_READLINE_HISTORY
- free(last_cmd);
- last_cmd = 0;
-#endif
-#else
- // Version for platforms which don't have readline by default
- if(fileno(stdin) >= 0)
- {
- FdGetLine getLine(fileno(stdin));
- while(!context.Stop())
+
+ context.DoCommand(cmd_parsed);
+
+ #ifdef HAVE_READLINE_HISTORY
+ if(last_cmd != cmd_str)
{
- printf("query > ");
- fflush(stdout);
- std::string command(getLine.GetLine());
- context.DoCommand(command.c_str(), false);
+ add_history(cmd_str.c_str());
+ last_cmd = cmd_str;
}
+ #endif // HAVE_READLINE_HISTORY
}
-#endif
// Done... stop nicely
if(!quiet) BOX_INFO("Logging off...");
diff --git a/bin/bbackupquery/documentation.txt b/bin/bbackupquery/documentation.txt
index 96eda158..214fe218 100644
--- a/bin/bbackupquery/documentation.txt
+++ b/bin/bbackupquery/documentation.txt
@@ -119,10 +119,13 @@ compare <store-dir-name> <local-dir-name>
This can be used for automated tests.
<
-> restore [-drif] <directory-name> <local-directory-name>
+> restore [-drif] <directory-name> [<local-directory-name>]
Restores a directory to the local disc. The local directory specified
- must not exist (unless a previous restore is being restarted).
+ must not exist (unless a previous restore is being restarted). If the
+ local directory is omitted, the default is to restore to the same
+ directory name and path, relative to the current local directory,
+ as set with the "lcd" command.
The root cannot be restored -- restore locations individually.
diff --git a/bin/bbstoreaccounts/bbstoreaccounts.cpp b/bin/bbstoreaccounts/bbstoreaccounts.cpp
index a9d2b0af..6a5c2e33 100644
--- a/bin/bbstoreaccounts/bbstoreaccounts.cpp
+++ b/bin/bbstoreaccounts/bbstoreaccounts.cpp
@@ -11,7 +11,10 @@
#include <limits.h>
#include <stdio.h>
-#include <unistd.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
#include <sys/types.h>
@@ -21,17 +24,18 @@
#include <ostream>
#include <vector>
-#include "BoxPortsAndFiles.h"
-#include "BackupStoreConfigVerify.h"
-#include "RaidFileController.h"
#include "BackupStoreAccounts.h"
#include "BackupStoreAccountDatabase.h"
-#include "MainHelper.h"
+#include "BackupStoreCheck.h"
+#include "BackupStoreConfigVerify.h"
#include "BackupStoreInfo.h"
-#include "StoreStructure.h"
+#include "BoxPortsAndFiles.h"
+#include "HousekeepStoreAccount.h"
+#include "MainHelper.h"
#include "NamedLock.h"
+#include "RaidFileController.h"
+#include "StoreStructure.h"
#include "UnixUser.h"
-#include "BackupStoreCheck.h"
#include "Utils.h"
#include "MemLeakFindOn.h"
@@ -59,31 +63,31 @@ void CheckSoftHardLimits(int64_t SoftLimit, int64_t HardLimit)
}
}
-int BlockSizeOfDiscSet(int DiscSet)
+int BlockSizeOfDiscSet(int discSetNum)
{
// Get controller, check disc set number
RaidFileController &controller(RaidFileController::GetController());
- if(DiscSet < 0 || DiscSet >= controller.GetNumDiscSets())
+ if(discSetNum < 0 || discSetNum >= controller.GetNumDiscSets())
{
- BOX_FATAL("Disc set " << DiscSet << " does not exist.");
+ BOX_FATAL("Disc set " << discSetNum << " does not exist.");
exit(1);
}
// Return block size
- return controller.GetDiscSet(DiscSet).GetBlockSize();
+ return controller.GetDiscSet(discSetNum).GetBlockSize();
}
-std::string BlockSizeToString(int64_t Blocks, int64_t MaxBlocks, int DiscSet)
+std::string BlockSizeToString(int64_t Blocks, int64_t MaxBlocks, int discSetNum)
{
- return FormatUsageBar(Blocks, Blocks * BlockSizeOfDiscSet(DiscSet),
- MaxBlocks * BlockSizeOfDiscSet(DiscSet),
+ return FormatUsageBar(Blocks, Blocks * BlockSizeOfDiscSet(discSetNum),
+ MaxBlocks * BlockSizeOfDiscSet(discSetNum),
sMachineReadableOutput);
}
-int64_t SizeStringToBlocks(const char *string, int DiscSet)
+int64_t SizeStringToBlocks(const char *string, int discSetNum)
{
// Find block size
- int blockSize = BlockSizeOfDiscSet(DiscSet);
+ int blockSize = BlockSizeOfDiscSet(discSetNum);
// Get number
char *endptr = (char*)string;
@@ -124,144 +128,174 @@ int64_t SizeStringToBlocks(const char *string, int DiscSet)
}
}
-bool GetWriteLockOnAccount(NamedLock &rLock, const std::string rRootDir, int DiscSetNum)
-{
- std::string writeLockFilename;
- StoreStructure::MakeWriteLockFilename(rRootDir, DiscSetNum, writeLockFilename);
+bool OpenAccount(Configuration &rConfig, int32_t ID, std::string &rRootDirOut,
+ int &rDiscSetOut, std::auto_ptr<UnixUser> apUser, NamedLock* pLock);
- bool gotLock = false;
- int triesLeft = 8;
- do
- {
- gotLock = rLock.TryAndGetLock(writeLockFilename.c_str(), 0600 /* restrictive file permissions */);
-
- if(!gotLock)
- {
- --triesLeft;
- ::sleep(1);
- }
- } while(!gotLock && triesLeft > 0);
+int SetLimit(Configuration &rConfig, int32_t ID, const char *SoftLimitStr,
+ const char *HardLimitStr)
+{
+ std::string rootDir;
+ int discSetNum;
+ std::auto_ptr<UnixUser> user; // used to reset uid when we return
+ NamedLock writeLock;
- if(!gotLock)
+ if(!OpenAccount(rConfig, ID, rootDir, discSetNum, user, &writeLock))
{
- // Couldn't lock the account -- just stop now
- BOX_ERROR("Failed to lock the account, did not change limits. "
- "Try again later.");
+ BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID)
+ << " to change limits.");
+ return 1;
}
+
+ // Load the info
+ std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID, rootDir,
+ discSetNum, false /* Read/Write */));
- return gotLock;
+ // Change the limits
+ int64_t softlimit = SizeStringToBlocks(SoftLimitStr, discSetNum);
+ int64_t hardlimit = SizeStringToBlocks(HardLimitStr, discSetNum);
+ CheckSoftHardLimits(softlimit, hardlimit);
+ info->ChangeLimits(softlimit, hardlimit);
+
+ // Save
+ info->Save();
+
+ BOX_NOTICE("Limits on account " << BOX_FORMAT_ACCOUNT(ID) <<
+ " changed to " << softlimit << " soft, " <<
+ hardlimit << " hard.");
+
+ return 0;
}
-int SetLimit(Configuration &rConfig, const std::string &rUsername, int32_t ID, const char *SoftLimitStr, const char *HardLimitStr)
+int SetAccountName(Configuration &rConfig, int32_t ID,
+ const std::string& rNewAccountName)
{
- // Become the user specified in the config file?
- std::auto_ptr<UnixUser> user;
- if(!rUsername.empty())
- {
- // Username specified, change...
- user.reset(new UnixUser(rUsername.c_str()));
- user->ChangeProcessUser(true /* temporary */);
- // Change will be undone at the end of this function
- }
-
- // Load in the account database
- std::auto_ptr<BackupStoreAccountDatabase> db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str()));
-
- // Already exists?
- if(!db->EntryExists(ID))
- {
- BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) <<
- " does not exist.");
- return 1;
- }
-
- // Load it in
- BackupStoreAccounts acc(*db);
std::string rootDir;
- int discSet;
- acc.GetAccountRoot(ID, rootDir, discSet);
-
- // Attempt to lock
+ int discSetNum;
+ std::auto_ptr<UnixUser> user; // used to reset uid when we return
NamedLock writeLock;
- if(!GetWriteLockOnAccount(writeLock, rootDir, discSet))
+
+ if(!OpenAccount(rConfig, ID, rootDir, discSetNum, user, &writeLock))
{
- // Failed to get lock
+ BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID)
+ << " to change name.");
return 1;
}
// Load the info
- std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID, rootDir, discSet, false /* Read/Write */));
+ std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID,
+ rootDir, discSetNum, false /* Read/Write */));
- // Change the limits
- int64_t softlimit = SizeStringToBlocks(SoftLimitStr, discSet);
- int64_t hardlimit = SizeStringToBlocks(HardLimitStr, discSet);
- CheckSoftHardLimits(softlimit, hardlimit);
- info->ChangeLimits(softlimit, hardlimit);
+ info->SetAccountName(rNewAccountName);
// Save
info->Save();
- BOX_NOTICE("Limits on account " << BOX_FORMAT_ACCOUNT(ID) <<
- " changed to " << softlimit << " soft, " <<
- hardlimit << " hard.");
+ BOX_NOTICE("Account " << BOX_FORMAT_ACCOUNT(ID) <<
+ " name changed to " << rNewAccountName);
return 0;
}
int AccountInfo(Configuration &rConfig, int32_t ID)
{
- // Load in the account database
- std::auto_ptr<BackupStoreAccountDatabase> db(
- BackupStoreAccountDatabase::Read(
- rConfig.GetKeyValue("AccountDatabase").c_str()));
-
- // Exists?
- if(!db->EntryExists(ID))
+ std::string rootDir;
+ int discSetNum;
+ std::auto_ptr<UnixUser> user; // used to reset uid when we return
+
+ if(!OpenAccount(rConfig, ID, rootDir, discSetNum, user,
+ NULL /* no write lock needed for this read-only operation */))
{
- BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) <<
- " does not exist.");
+ BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID)
+ << " to display info.");
return 1;
}
// Load it in
- BackupStoreAccounts acc(*db);
- std::string rootDir;
- int discSet;
- acc.GetAccountRoot(ID, rootDir, discSet);
std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID,
- rootDir, discSet, true /* ReadOnly */));
+ rootDir, discSetNum, true /* ReadOnly */));
// Then print out lots of info
std::cout << FormatUsageLineStart("Account ID", sMachineReadableOutput) <<
BOX_FORMAT_ACCOUNT(ID) << std::endl;
+ std::cout << FormatUsageLineStart("Account Name", sMachineReadableOutput) <<
+ info->GetAccountName() << std::endl;
std::cout << FormatUsageLineStart("Last object ID", sMachineReadableOutput) <<
BOX_FORMAT_OBJECTID(info->GetLastObjectIDUsed()) << std::endl;
std::cout << FormatUsageLineStart("Used", sMachineReadableOutput) <<
BlockSizeToString(info->GetBlocksUsed(),
- info->GetBlocksHardLimit(), discSet) << std::endl;
+ info->GetBlocksHardLimit(), discSetNum) << std::endl;
+ std::cout << FormatUsageLineStart("Current files",
+ sMachineReadableOutput) <<
+ BlockSizeToString(info->GetBlocksInCurrentFiles(),
+ info->GetBlocksHardLimit(), discSetNum) << std::endl;
std::cout << FormatUsageLineStart("Old files", sMachineReadableOutput) <<
BlockSizeToString(info->GetBlocksInOldFiles(),
- info->GetBlocksHardLimit(), discSet) << std::endl;
+ info->GetBlocksHardLimit(), discSetNum) << std::endl;
std::cout << FormatUsageLineStart("Deleted files", sMachineReadableOutput) <<
BlockSizeToString(info->GetBlocksInDeletedFiles(),
- info->GetBlocksHardLimit(), discSet) << std::endl;
+ info->GetBlocksHardLimit(), discSetNum) << std::endl;
std::cout << FormatUsageLineStart("Directories", sMachineReadableOutput) <<
BlockSizeToString(info->GetBlocksInDirectories(),
- info->GetBlocksHardLimit(), discSet) << std::endl;
+ info->GetBlocksHardLimit(), discSetNum) << std::endl;
std::cout << FormatUsageLineStart("Soft limit", sMachineReadableOutput) <<
BlockSizeToString(info->GetBlocksSoftLimit(),
- info->GetBlocksHardLimit(), discSet) << std::endl;
+ info->GetBlocksHardLimit(), discSetNum) << std::endl;
std::cout << FormatUsageLineStart("Hard limit", sMachineReadableOutput) <<
BlockSizeToString(info->GetBlocksHardLimit(),
- info->GetBlocksHardLimit(), discSet) << std::endl;
+ info->GetBlocksHardLimit(), discSetNum) << std::endl;
std::cout << FormatUsageLineStart("Client store marker", sMachineReadableOutput) <<
info->GetLastObjectIDUsed() << std::endl;
+ std::cout << FormatUsageLineStart("Live Files", sMachineReadableOutput) <<
+ info->GetNumFiles() << std::endl;
+ std::cout << FormatUsageLineStart("Old Files", sMachineReadableOutput) <<
+ info->GetNumOldFiles() << std::endl;
+ std::cout << FormatUsageLineStart("Deleted Files", sMachineReadableOutput) <<
+ info->GetNumDeletedFiles() << std::endl;
+ std::cout << FormatUsageLineStart("Directories", sMachineReadableOutput) <<
+ info->GetNumDirectories() << std::endl;
+ std::cout << FormatUsageLineStart("Enabled", sMachineReadableOutput) <<
+ (info->IsAccountEnabled() ? "yes" : "no") << std::endl;
return 0;
}
-int DeleteAccount(Configuration &rConfig, const std::string &rUsername, int32_t ID, bool AskForConfirmation)
+int SetAccountEnabled(Configuration &rConfig, int32_t ID, bool enabled)
{
+ std::string rootDir;
+ int discSetNum;
+ std::auto_ptr<UnixUser> user; // used to reset uid when we return
+ NamedLock writeLock;
+
+ if(!OpenAccount(rConfig, ID, rootDir, discSetNum, user, &writeLock))
+ {
+ BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID)
+ << " to change enabled flag.");
+ return 1;
+ }
+
+ // Load it in
+ std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID,
+ rootDir, discSetNum, false /* ReadOnly */));
+ info->SetAccountEnabled(enabled);
+ info->Save();
+ return 0;
+}
+
+int DeleteAccount(Configuration &rConfig, int32_t ID, bool AskForConfirmation)
+{
+ std::string rootDir;
+ int discSetNum;
+ std::auto_ptr<UnixUser> user; // used to reset uid when we return
+ NamedLock writeLock;
+
+ // Obtain a write lock, as the daemon user
+ if(!OpenAccount(rConfig, ID, rootDir, discSetNum, user, &writeLock))
+ {
+ BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID)
+ << " for deletion.");
+ return 1;
+ }
+
// Check user really wants to do this
if(AskForConfirmation)
{
@@ -275,45 +309,10 @@ int DeleteAccount(Configuration &rConfig, const std::string &rUsername, int32_t
}
}
- // Load in the account database
+ // Back to original user, but write lock is maintained
+ user.reset();
+
std::auto_ptr<BackupStoreAccountDatabase> db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str()));
-
- // Exists?
- if(!db->EntryExists(ID))
- {
- BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) <<
- " does not exist.");
- return 1;
- }
-
- // Get info from the database
- BackupStoreAccounts acc(*db);
- std::string rootDir;
- int discSetNum;
- acc.GetAccountRoot(ID, rootDir, discSetNum);
-
- // Obtain a write lock, as the daemon user
- NamedLock writeLock;
- {
- // Bbecome the user specified in the config file
- std::auto_ptr<UnixUser> user;
- if(!rUsername.empty())
- {
- // Username specified, change...
- user.reset(new UnixUser(rUsername.c_str()));
- user->ChangeProcessUser(true /* temporary */);
- // Change will be undone at the end of this function
- }
-
- // Get a write lock
- if(!GetWriteLockOnAccount(writeLock, rootDir, discSetNum))
- {
- // Failed to get lock
- return 1;
- }
-
- // Back to original user, but write is maintained
- }
// Delete from account database
db->DeleteEntry(ID);
@@ -324,15 +323,24 @@ int DeleteAccount(Configuration &rConfig, const std::string &rUsername, int32_t
// Remove the store files...
// First, become the user specified in the config file
- std::auto_ptr<UnixUser> user;
- if(!rUsername.empty())
+ std::string username;
+ {
+ const Configuration &rserverConfig(rConfig.GetSubConfiguration("Server"));
+ if(rserverConfig.KeyExists("User"))
+ {
+ username = rserverConfig.GetKeyValue("User");
+ }
+ }
+
+ // Become the right user
+ if(!username.empty())
{
// Username specified, change...
- user.reset(new UnixUser(rUsername.c_str()));
+ user.reset(new UnixUser(username));
user->ChangeProcessUser(true /* temporary */);
- // Change will be undone at the end of this function
+ // Change will be undone when user goes out of scope
}
-
+
// Secondly, work out which directories need wiping
std::vector<std::string> toDelete;
RaidFileController &rcontroller(RaidFileController::GetController());
@@ -345,6 +353,11 @@ int DeleteAccount(Configuration &rConfig, const std::string &rUsername, int32_t
}
}
+#ifdef WIN32
+ // Cannot remove files while holding a lock on them
+ writeLock.ReleaseLock();
+#endif
+
int retcode = 0;
// Thirdly, delete the directories...
@@ -367,7 +380,8 @@ int DeleteAccount(Configuration &rConfig, const std::string &rUsername, int32_t
return retcode;
}
-int CheckAccount(Configuration &rConfig, const std::string &rUsername, int32_t ID, bool FixErrors, bool Quiet)
+bool OpenAccount(Configuration &rConfig, int32_t ID, std::string &rRootDirOut,
+ int &rDiscSetOut, std::auto_ptr<UnixUser> apUser, NamedLock* pLock)
{
// Load in the account database
std::auto_ptr<BackupStoreAccountDatabase> db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str()));
@@ -377,23 +391,53 @@ int CheckAccount(Configuration &rConfig, const std::string &rUsername, int32_t I
{
BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) <<
" does not exist.");
- return 1;
+ return false;
}
// Get info from the database
BackupStoreAccounts acc(*db);
- std::string rootDir;
- int discSetNum;
- acc.GetAccountRoot(ID, rootDir, discSetNum);
-
+ acc.GetAccountRoot(ID, rRootDirOut, rDiscSetOut);
+
+ // Get the user under which the daemon runs
+ std::string username;
+ {
+ const Configuration &rserverConfig(rConfig.GetSubConfiguration("Server"));
+ if(rserverConfig.KeyExists("User"))
+ {
+ username = rserverConfig.GetKeyValue("User");
+ }
+ }
+
// Become the right user
- std::auto_ptr<UnixUser> user;
- if(!rUsername.empty())
+ if(!username.empty())
{
// Username specified, change...
- user.reset(new UnixUser(rUsername.c_str()));
- user->ChangeProcessUser(true /* temporary */);
- // Change will be undone at the end of this function
+ apUser.reset(new UnixUser(username));
+ apUser->ChangeProcessUser(true /* temporary */);
+ // Change will be undone when apUser goes out of scope
+ // in the caller.
+ }
+
+ if(pLock)
+ {
+ acc.LockAccount(ID, *pLock);
+ }
+
+ return true;
+}
+
+int CheckAccount(Configuration &rConfig, int32_t ID, bool FixErrors, bool Quiet)
+{
+ std::string rootDir;
+ int discSetNum;
+ std::auto_ptr<UnixUser> user; // used to reset uid when we return
+ NamedLock writeLock;
+
+ if(!OpenAccount(rConfig, ID, rootDir, discSetNum, user, &writeLock))
+ {
+ BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID)
+ << " for checking.");
+ return 1;
}
// Check it
@@ -403,7 +447,8 @@ int CheckAccount(Configuration &rConfig, const std::string &rUsername, int32_t I
return check.ErrorsFound()?1:0;
}
-int CreateAccount(Configuration &rConfig, const std::string &rUsername, int32_t ID, int32_t DiscNumber, int32_t SoftLimit, int32_t HardLimit)
+int CreateAccount(Configuration &rConfig, int32_t ID, int32_t DiscNumber,
+ int32_t SoftLimit, int32_t HardLimit)
{
// Load in the account database
std::auto_ptr<BackupStoreAccountDatabase> db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str()));
@@ -415,16 +460,58 @@ int CreateAccount(Configuration &rConfig, const std::string &rUsername, int32_t
" already exists.");
return 1;
}
+
+ // Get the user under which the daemon runs
+ std::string username;
+ {
+ const Configuration &rserverConfig(rConfig.GetSubConfiguration("Server"));
+ if(rserverConfig.KeyExists("User"))
+ {
+ username = rserverConfig.GetKeyValue("User");
+ }
+ }
// Create it.
BackupStoreAccounts acc(*db);
- acc.Create(ID, DiscNumber, SoftLimit, HardLimit, rUsername);
+ acc.Create(ID, DiscNumber, SoftLimit, HardLimit, username);
BOX_NOTICE("Account " << BOX_FORMAT_ACCOUNT(ID) << " created.");
return 0;
}
+int HousekeepAccountNow(Configuration &rConfig, int32_t ID)
+{
+ std::string rootDir;
+ int discSetNum;
+ std::auto_ptr<UnixUser> user; // used to reset uid when we return
+
+ if(!OpenAccount(rConfig, ID, rootDir, discSetNum, user,
+ NULL /* housekeeping locks the account itself */))
+ {
+ BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID)
+ << " for housekeeping.");
+ return 1;
+ }
+
+ HousekeepStoreAccount housekeeping(ID, rootDir, discSetNum, NULL);
+ bool success = housekeeping.DoHousekeeping();
+
+ if(!success)
+ {
+ BOX_ERROR("Failed to lock account " << BOX_FORMAT_ACCOUNT(ID)
+ << " for housekeeping: perhaps a client is "
+ "still connected?");
+ return 1;
+ }
+ else
+ {
+ BOX_TRACE("Finished housekeeping on account " <<
+ BOX_FORMAT_ACCOUNT(ID));
+ return 0;
+ }
+}
+
void PrintUsageAndExit()
{
printf(
@@ -440,6 +527,8 @@ void PrintUsageAndExit()
" info [-m] <account>\n"
" Prints information about the specified account including number\n"
" of blocks used. The -m option enable machine-readable output.\n"
+" enabled <accounts> <yes|no>\n"
+" Sets the account as enabled or disabled for new logins.\n"
" setlimit <accounts> <softlimit> <hardlimit>\n"
" Changes the limits of the account as specified. Numbers are\n"
" interpreted as for the 'create' command (suffixed with B, M or G)\n"
@@ -451,6 +540,14 @@ void PrintUsageAndExit()
" provided, any errors discovered that can be fixed automatically\n"
" will be fixed. If the 'quiet' option is provided, less output is\n"
" produced.\n"
+" name <account> <new name>\n"
+" Changes the \"name\" of the account to the specified string.\n"
+" The name is purely cosmetic and intended to make it easier to\n"
+" identify your accounts.\n"
+" housekeep <account>\n"
+" Runs housekeeping immediately on the account. If it cannot be locked,\n"
+" bbstoreaccounts returns an error status code (1), otherwise success\n"
+" (0) even if any errors were fixed by housekeeping.\n"
);
exit(2);
}
@@ -465,17 +562,12 @@ int main(int argc, const char *argv[])
Logging::SetProgramName("bbstoreaccounts");
// Filename for configuration file?
- std::string configFilename;
-
- #ifdef WIN32
- configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE;
- #else
- configFilename = BOX_FILE_BBSTORED_DEFAULT_CONFIG;
- #endif
+ std::string configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE;
+ int logLevel = Log::EVERYTHING;
// See if there's another entry on the command line
int c;
- while((c = getopt(argc, (char * const *)argv, "c:m")) != -1)
+ while((c = getopt(argc, (char * const *)argv, "c:W:m")) != -1)
{
switch(c)
{
@@ -484,6 +576,15 @@ int main(int argc, const char *argv[])
configFilename = optarg;
break;
+ case 'W':
+ logLevel = Logging::GetNamedLevel(optarg);
+ if(logLevel == Log::INVALID)
+ {
+ BOX_FATAL("Invalid logging level: " << optarg);
+ return 2;
+ }
+ break;
+
case 'm':
// enable machine readable output
sMachineReadableOutput = true;
@@ -494,6 +595,10 @@ int main(int argc, const char *argv[])
PrintUsageAndExit();
}
}
+
+ Logging::FilterConsole((Log::Level) logLevel);
+ Logging::FilterSyslog (Log::NOTHING);
+
// Adjust arguments
argc -= optind;
argv += optind;
@@ -510,16 +615,6 @@ int main(int argc, const char *argv[])
":" << errs);
}
- // Get the user under which the daemon runs
- std::string username;
- {
- const Configuration &rserverConfig(config->GetSubConfiguration("Server"));
- if(rserverConfig.KeyExists("User"))
- {
- username = rserverConfig.GetKeyValue("User");
- }
- }
-
// Initialise the raid file controller
RaidFileController &rcontroller(RaidFileController::GetController());
rcontroller.Initialise(config->GetKeyValue("RaidFileConf").c_str());
@@ -537,8 +632,10 @@ int main(int argc, const char *argv[])
PrintUsageAndExit();
}
+ std::string command = argv[0];
+
// Now do the command.
- if(::strcmp(argv[0], "create") == 0)
+ if(command == "create")
{
// which disc?
int32_t discnum;
@@ -558,14 +655,39 @@ int main(int argc, const char *argv[])
CheckSoftHardLimits(softlimit, hardlimit);
// Create the account...
- return CreateAccount(*config, username, id, discnum, softlimit, hardlimit);
+ return CreateAccount(*config, id, discnum, softlimit, hardlimit);
}
- else if(::strcmp(argv[0], "info") == 0)
+ else if(command == "info")
{
// Print information on this account
return AccountInfo(*config, id);
}
- else if(::strcmp(argv[0], "setlimit") == 0)
+ else if(command == "enabled")
+ {
+ // Change the AccountEnabled flag on this account
+ if(argc != 3)
+ {
+ PrintUsageAndExit();
+ }
+
+ bool enabled = true;
+ std::string enabled_string = argv[2];
+ if(enabled_string == "yes")
+ {
+ enabled = true;
+ }
+ else if(enabled_string == "no")
+ {
+ enabled = false;
+ }
+ else
+ {
+ PrintUsageAndExit();
+ }
+
+ return SetAccountEnabled(*config, id, enabled);
+ }
+ else if(command == "setlimit")
{
// Change the limits on this account
if(argc < 4)
@@ -574,9 +696,20 @@ int main(int argc, const char *argv[])
return 1;
}
- return SetLimit(*config, username, id, argv[2], argv[3]);
+ return SetLimit(*config, id, argv[2], argv[3]);
+ }
+ else if(command == "name")
+ {
+ // Change the limits on this account
+ if(argc != 3)
+ {
+ BOX_ERROR("name command requires a new name.");
+ return 1;
+ }
+
+ return SetAccountName(*config, id, argv[2]);
}
- else if(::strcmp(argv[0], "delete") == 0)
+ else if(command == "delete")
{
// Delete an account
bool askForConfirmation = true;
@@ -584,9 +717,9 @@ int main(int argc, const char *argv[])
{
askForConfirmation = false;
}
- return DeleteAccount(*config, username, id, askForConfirmation);
+ return DeleteAccount(*config, id, askForConfirmation);
}
- else if(::strcmp(argv[0], "check") == 0)
+ else if(command == "check")
{
bool fixErrors = false;
bool quiet = false;
@@ -610,11 +743,15 @@ int main(int argc, const char *argv[])
}
// Check the account
- return CheckAccount(*config, username, id, fixErrors, quiet);
+ return CheckAccount(*config, id, fixErrors, quiet);
+ }
+ else if(command == "housekeep")
+ {
+ return HousekeepAccountNow(*config, id);
}
else
{
- BOX_ERROR("Unknown command '" << argv[0] << "'.");
+ BOX_ERROR("Unknown command '" << command << "'.");
return 1;
}
@@ -623,4 +760,3 @@ int main(int argc, const char *argv[])
MAINHELPER_END
}
-
diff --git a/bin/bbstored/BBStoreDHousekeeping.cpp b/bin/bbstored/BBStoreDHousekeeping.cpp
index 7f799008..86d6409c 100644
--- a/bin/bbstored/BBStoreDHousekeeping.cpp
+++ b/bin/bbstored/BBStoreDHousekeeping.cpp
@@ -79,8 +79,19 @@ void BackupStoreDaemon::RunHousekeepingIfNeeded()
// Do housekeeping if the time interval has elapsed since the last check
if((timeNow - mLastHousekeepingRun) < housekeepingInterval)
{
+ BOX_TRACE("No need for housekeeping, " <<
+ BoxTimeToSeconds(timeNow - mLastHousekeepingRun) <<
+ " seconds since last run is less than " <<
+ BoxTimeToSeconds(housekeepingInterval));
return;
}
+ else
+ {
+ BOX_TRACE("Running housekeeping now, because " <<
+ BoxTimeToSeconds(timeNow - mLastHousekeepingRun) <<
+ " seconds since last run is more than " <<
+ BoxTimeToSeconds(housekeepingInterval));
+ }
// Store the time
mLastHousekeepingRun = timeNow;
@@ -100,18 +111,28 @@ void BackupStoreDaemon::RunHousekeepingIfNeeded()
{
try
{
- if(mpAccounts)
+ std::string rootDir;
+ int discSet = 0;
+
{
+ // Tag log output to identify account
+ std::ostringstream tag;
+ tag << "hk/" << BOX_FORMAT_ACCOUNT(*i);
+ Logging::Tagger tagWithClientID(tag.str());
+
// Get the account root
- std::string rootDir;
- int discSet = 0;
mpAccounts->GetAccountRoot(*i, rootDir, discSet);
-
- // Do housekeeping on this account
- HousekeepStoreAccount housekeeping(*i, rootDir,
- discSet, this);
- housekeeping.DoHousekeeping();
+
+ // Reset tagging as HousekeepStoreAccount will
+ // do that itself, to avoid duplicate tagging.
+ // Happens automatically when tagWithClientID
+ // goes out of scope.
}
+
+ // Do housekeeping on this account
+ HousekeepStoreAccount housekeeping(*i, rootDir,
+ discSet, this);
+ housekeeping.DoHousekeeping();
}
catch(BoxException &e)
{
diff --git a/bin/bbstored/BackupStoreDaemon.cpp b/bin/bbstored/BackupStoreDaemon.cpp
index 4de0a078..70ca1d67 100644
--- a/bin/bbstored/BackupStoreDaemon.cpp
+++ b/bin/bbstored/BackupStoreDaemon.cpp
@@ -20,7 +20,7 @@
#include "BackupStoreContext.h"
#include "BackupStoreDaemon.h"
#include "BackupStoreConfigVerify.h"
-#include "autogen_BackupProtocolServer.h"
+#include "autogen_BackupProtocol.h"
#include "RaidFileController.h"
#include "BackupStoreAccountDatabase.h"
#include "BackupStoreAccounts.h"
@@ -317,6 +317,8 @@ void BackupStoreDaemon::Connection2(SocketStreamTLS &rStream)
if(::sscanf(clientCommonName.c_str(), "BACKUP-%x", &id) != 1)
{
// Bad! Disconnect immediately
+ BOX_WARNING("Failed login: invalid client common name: " <<
+ clientCommonName);
return;
}
@@ -327,7 +329,7 @@ void BackupStoreDaemon::Connection2(SocketStreamTLS &rStream)
Logging::Tagger tagWithClientID(tag.str());
// Create a context, using this ID
- BackupStoreContext context(id, *this);
+ BackupStoreContext context(id, *this, GetConnectionDetails());
if (mpTestHook)
{
@@ -353,19 +355,22 @@ void BackupStoreDaemon::Connection2(SocketStreamTLS &rStream)
}
catch(...)
{
- LogConnectionStats(clientCommonName.c_str(), rStream);
+ LogConnectionStats(id, context.GetAccountName(), rStream);
throw;
}
- LogConnectionStats(clientCommonName.c_str(), rStream);
+ LogConnectionStats(id, context.GetAccountName(), rStream);
context.CleanUp();
}
-void BackupStoreDaemon::LogConnectionStats(const char *commonName,
- const SocketStreamTLS &s)
+void BackupStoreDaemon::LogConnectionStats(uint32_t accountId,
+ const std::string& accountName, const SocketStreamTLS &s)
{
// Log the amount of data transferred
- BOX_NOTICE("Connection statistics for " << commonName << ":"
+ BOX_NOTICE("Connection statistics for " <<
+ BOX_FORMAT_ACCOUNT(accountId) << " "
+ "(name=" << accountName << "):"
" IN=" << s.GetBytesRead() <<
" OUT=" << s.GetBytesWritten() <<
+ " NET_IN=" << (s.GetBytesRead() - s.GetBytesWritten()) <<
" TOTAL=" << (s.GetBytesRead() + s.GetBytesWritten()));
}
diff --git a/bin/bbstored/BackupStoreDaemon.h b/bin/bbstored/BackupStoreDaemon.h
index 49af5b81..ce538477 100644
--- a/bin/bbstored/BackupStoreDaemon.h
+++ b/bin/bbstored/BackupStoreDaemon.h
@@ -63,11 +63,13 @@ protected:
// Housekeeping functions
void HousekeepingProcess();
- void LogConnectionStats(const char *commonName, const SocketStreamTLS &s);
+ void LogConnectionStats(uint32_t accountId,
+ const std::string& accountName, const SocketStreamTLS &s);
public:
// HousekeepingInterface implementation
virtual bool CheckForInterProcessMsg(int AccountNum = 0, int MaximumWaitTime = 0);
+ void RunHousekeepingIfNeeded();
private:
BackupStoreAccountDatabase *mpAccountDatabase;
@@ -82,7 +84,6 @@ private:
virtual void OnIdle();
void HousekeepingInit();
- void RunHousekeepingIfNeeded();
int64_t mLastHousekeepingRun;
public:
diff --git a/bin/bbstored/bbstored.cpp b/bin/bbstored/bbstored.cpp
index 21a9e5f1..2c18b4fc 100644
--- a/bin/bbstored/bbstored.cpp
+++ b/bin/bbstored/bbstored.cpp
@@ -24,13 +24,7 @@ int main(int argc, const char *argv[])
BackupStoreDaemon daemon;
- #ifdef WIN32
- return daemon.Main(BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE,
- argc, argv);
- #else
- return daemon.Main(BOX_FILE_BBSTORED_DEFAULT_CONFIG,
- argc, argv);
- #endif
+ return daemon.Main(BOX_GET_DEFAULT_BBSTORED_CONFIG_FILE, argc, argv);
MAINHELPER_END
}
diff --git a/configure.ac b/configure.ac
index 48fb510f..c5a05fe4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
-AC_INIT([Box Backup], 0.11, [boxbackup@boxbackup.org])
+AC_INIT([Box Backup], 0.11, [boxbackup@boxbackup.org],[boxbackup])
AC_CONFIG_SRCDIR([lib/common/Box.h])
AC_CONFIG_HEADERS([lib/common/BoxConfig.h])
@@ -13,6 +13,7 @@ test -s install-sh || rm install-sh
### Checks for programs.
AC_LANG([C++])
+AC_PROG_CC
AC_PROG_CXX
AC_CXX_EXCEPTIONS
AC_CXX_NAMESPACES
@@ -21,265 +22,7 @@ if test "x$ac_cv_cxx_exceptions" != "xyes" || \
AC_MSG_ERROR([[basic compile checks failed, the C++ compiler is broken]])
fi
-if test "x$GXX" = "xyes"; then
- # Use -Wall if we have gcc. This gives better warnings
- AC_SUBST([CXXFLAGS_STRICT], ['-Wall -Wundef'])
-
- # Use -rdynamic if we have gcc, but not mingw. This is needed for backtrace
- case $target_os in
- mingw*) ;;
- *) AC_SUBST([LDADD_RDYNAMIC], ['-rdynamic']) ;;
- esac
-fi
-
-AC_PATH_PROG([PERL], [perl], [AC_MSG_ERROR([[perl executable was not found]])])
-
-case $target_os in
-mingw*)
- TARGET_PERL=perl
- ;;
-*)
- TARGET_PERL=$PERL
- ;;
-esac
-
-AC_SUBST([TARGET_PERL])
-AC_DEFINE_UNQUOTED([PERL_EXECUTABLE], ["$TARGET_PERL"],
- [Location of the perl executable])
-
-AC_CHECK_TOOL([AR], [ar],
- [AC_MSG_ERROR([[cannot find ar executable]])])
-AC_CHECK_TOOL([RANLIB], [ranlib],
- [AC_MSG_ERROR([[cannot find ranlib executable]])])
-
-case $target_os in
-mingw*)
- AC_CHECK_TOOL([WINDRES], [windres],
- [AC_MSG_ERROR([[cannot find windres executable]])])
- ;;
-esac
-
-### Checks for libraries.
-
-case $target_os in
-mingw32*) ;;
-winnt) ;;
-*)
- AC_SEARCH_LIBS([nanosleep], [rt], [ac_have_nanosleep=yes],
- [AC_MSG_ERROR([[cannot find a short sleep function (nanosleep)]])])
- ;;
-esac
-
-AC_CHECK_HEADER([zlib.h],, [AC_MSG_ERROR([[cannot find zlib.h]])])
-AC_CHECK_LIB([z], [zlibVersion],, [AC_MSG_ERROR([[cannot find zlib]])])
-VL_LIB_READLINE([have_libreadline=yes], [have_libreadline=no])
-
-## Check for Berkely DB. Restrict to certain versions
-AX_PATH_BDB([1.x or 4.1], [
- LIBS="$BDB_LIBS $LIBS"
- LDFLAGS="$BDB_LDFLAGS $LDFLAGS"
- CPPFLAGS="$CPPFLAGS $BDB_CPPFLAGS"
-
- AX_COMPARE_VERSION([$BDB_VERSION],[ge],[4.1],,
- [AX_COMPARE_VERSION([$BDB_VERSION],[lt],[2],,
- [AC_MSG_ERROR([[only Berkely DB versions 1.x or at least 4.1 are currently supported]])]
- )]
- )
- AX_SPLIT_VERSION([BDB_VERSION], [$BDB_VERSION])
-])
-
-## Check for Open SSL, use old versions only if explicitly requested
-AC_SEARCH_LIBS([gethostbyname], [nsl socket resolv])
-AC_SEARCH_LIBS([shutdown], [nsl socket resolv])
-AX_CHECK_SSL(, [AC_MSG_ERROR([[OpenSSL is not installed but is required]])])
-AC_ARG_ENABLE(
- [old-ssl],
- [AC_HELP_STRING([--enable-old-ssl],
- [Allow use of pre-0.9.7 Open SSL - NOT RECOMMENDED, read the documentation])])
-AC_CHECK_LIB(
- [crypto],
- [EVP_CipherInit_ex],, [
- if test "x$enable_old_ssl" = "xyes"; then
- AC_DEFINE([HAVE_OLD_SSL], 1, [Define to 1 if SSL is pre-0.9.7])
- else
- AC_MSG_ERROR([[found an old (pre 0.9.7) version of SSL.
-Upgrade or read the documentation for alternatives]])
- fi
- ])
-
-
-### Checks for header files.
-
-case $target_os in
-mingw32*) ;;
-winnt*) ;;
-*)
- AC_HEADER_DIRENT
- ;;
-esac
-
-AC_HEADER_STDC
-AC_HEADER_SYS_WAIT
-AC_CHECK_HEADERS([dlfcn.h execinfo.h getopt.h process.h pwd.h signal.h])
-AC_CHECK_HEADERS([syslog.h time.h cxxabi.h])
-AC_CHECK_HEADERS([netinet/in.h])
-AC_CHECK_HEADERS([sys/param.h sys/socket.h sys/time.h sys/types.h sys/wait.h])
-AC_CHECK_HEADERS([sys/uio.h sys/xattr.h])
-AC_CHECK_HEADERS([bsd/unistd.h])
-
-AC_CHECK_HEADER([regex.h], [have_regex_h=yes])
-
-if test "$have_regex_h" = "yes"; then
- AC_DEFINE([HAVE_REGEX_H], [1], [Define to 1 if regex.h is available])
-else
- AC_CHECK_HEADER([pcreposix.h], [have_pcreposix_h=yes])
-fi
-
-if test "$have_pcreposix_h" = "yes"; then
- AC_DEFINE([PCRE_STATIC], [1], [Box Backup always uses static PCRE])
- AC_SEARCH_LIBS([regcomp], ["pcreposix -lpcre"],,[have_pcreposix_h=no_regcomp])
-fi
-
-if test "$have_pcreposix_h" = "yes"; then
- AC_DEFINE([HAVE_PCREPOSIX_H], [1], [Define to 1 if pcreposix.h is available])
-fi
-
-if test "$have_regex_h" = "yes" -o "$have_pcreposix_h" = "yes"; then
- have_regex_support=yes
- AC_DEFINE([HAVE_REGEX_SUPPORT], [1], [Define to 1 if regular expressions are supported])
-else
- have_regex_support=no
-fi
-
-AC_SEARCH_LIBS([dlsym], ["dl"])
-
-### Checks for typedefs, structures, and compiler characteristics.
-
-AC_CHECK_TYPES([u_int8_t, u_int16_t, u_int32_t, u_int64_t])
-AC_CHECK_TYPES([uint8_t, uint16_t, uint32_t, uint64_t])
-
-AC_HEADER_STDBOOL
-AC_C_CONST
-AC_C_BIGENDIAN
-AC_TYPE_UID_T
-AC_TYPE_MODE_T
-AC_TYPE_OFF_T
-AC_TYPE_PID_T
-AC_TYPE_SIZE_T
-
-AC_CHECK_MEMBERS([struct stat.st_flags])
-AC_CHECK_MEMBERS([struct stat.st_mtimespec])
-AC_CHECK_MEMBERS([struct stat.st_atim.tv_nsec])
-AC_CHECK_MEMBERS([struct stat.st_atimensec])
-AC_CHECK_MEMBERS([struct sockaddr_in.sin_len],,, [[
- #include <sys/types.h>
- #include <netinet/in.h>
- ]])
-AC_CHECK_MEMBERS([DIR.d_fd],,, [[#include <dirent.h>]])
-AC_CHECK_MEMBERS([DIR.dd_fd],,, [[#include <dirent.h>]])
-
-AC_CHECK_DECLS([INFTIM],,, [[#include <poll.h>]])
-AC_CHECK_DECLS([SO_PEERCRED],,, [[#include <sys/socket.h>]])
-AC_CHECK_DECLS([O_BINARY],,,)
-
-# Solaris provides getpeerucred() instead of getpeereid() or SO_PEERCRED
-AC_CHECK_HEADERS([ucred.h])
-AC_CHECK_FUNCS([getpeerucred])
-
-AC_CHECK_DECLS([optreset],,, [[#include <getopt.h>]])
-AC_CHECK_DECLS([dirfd],,,
- [[
- #include <getopt.h>
- #include <dirent.h>
- ]])
-
-AC_HEADER_TIME
-AC_STRUCT_TM
-AX_CHECK_DIRENT_D_TYPE
-AC_SYS_LARGEFILE
-AX_CHECK_DEFINE_PRAGMA
-if test "x$ac_cv_c_bigendian" != "xyes"; then
- AX_BSWAP64
-fi
-
-case $target_os in
-mingw32*) ;;
-winnt*) ;;
-*)
- AX_RANDOM_DEVICE
- AX_CHECK_MOUNT_POINT(,[
- AC_MSG_ERROR([[cannot work out how to discover mount points on your platform]])
- ])
- AC_CHECK_MEMBERS([struct dirent.d_ino],,, [[#include <dirent.h>]])
-;;
-esac
-
-AX_CHECK_MALLOC_WORKAROUND
-
-
-### Checks for library functions.
-
-AC_FUNC_CLOSEDIR_VOID
-AC_FUNC_ERROR_AT_LINE
-AC_TYPE_SIGNAL
-AC_FUNC_STAT
-AC_CHECK_FUNCS([getpeereid lchown setproctitle getpid gettimeofday waitpid])
-AC_SEARCH_LIBS([setproctitle], ["bsd"])
-
-# NetBSD implements kqueue too differently for us to get it fixed by 0.10
-# TODO: Remove this when NetBSD kqueue implementation is working
-netbsd_hack=`echo $target_os | sed 's/netbsd.*/netbsd/'`
-if test "$netbsd_hack" != "netbsd"; then
- AC_CHECK_FUNCS([kqueue])
-fi
-
-AX_FUNC_SYSCALL
-AX_CHECK_SYSCALL_LSEEK
-AC_CHECK_FUNCS([listxattr llistxattr getxattr lgetxattr setxattr lsetxattr])
-AC_CHECK_DECLS([XATTR_NOFOLLOW],,, [[#include <sys/xattr.h>]])
-
-
-### Miscellaneous complicated feature checks
-
-## Check for large file support active. AC_SYS_LARGEFILE has already worked
-## out how to enable it if necessary, we just use this to report to the user
-AC_CACHE_CHECK([if we have large file support enabled],
- [box_cv_have_large_file_support],
- [AC_TRY_RUN([
- $ac_includes_default
- int main()
- {
- return sizeof(off_t)==4;
- }
- ],
- [box_cv_have_large_file_support=yes],
- [box_cv_have_large_file_support=no],
- [box_cv_have_large_file_support=no # safe for cross-compile]
- )
- ])
-
-if test "x$box_cv_have_large_file_support" = "xyes"; then
- AC_DEFINE([HAVE_LARGE_FILE_SUPPORT], [1],
- [Define to 1 if large files are supported])
-fi
-
-## Find out how to do file locking
-AC_CHECK_FUNCS([flock])
-AC_CHECK_DECLS([O_EXLOCK],,, [[#include <fcntl.h>]])
-AC_CHECK_DECLS([F_SETLK],,, [[#include <fcntl.h>]])
-
-case $target_os in
-mingw32*) ;;
-winnt*) ;;
-*)
-if test "x$ac_cv_func_flock" != "xyes" && \
- test "x$ac_cv_have_decl_O_EXLOCK" != "xyes" && \
- test "x$ac_cv_have_decl_F_SETLK" != "xyes"
-then
- AC_MSG_ERROR([[cannot work out how to do file locking on your platform]])
-fi
-;;
-esac
+m4_include([infrastructure/m4/boxbackup_tests.m4])
## Get tmpdir
temp_directory_name="/tmp"
@@ -347,6 +90,7 @@ AC_CONFIG_FILES([infrastructure/BoxPlatform.pm
test/bbackupd/testfiles/bbackupd-exclude.conf
test/bbackupd/testfiles/bbackupd-snapshot.conf
test/bbackupd/testfiles/bbackupd-symlink.conf
+ test/bbackupd/testfiles/bbackupd-temploc.conf
])
AX_CONFIG_SCRIPTS([bin/bbackupd/bbackupd-config
bin/bbackupquery/makedocumentation.pl
@@ -384,6 +128,21 @@ if ! $PERL ./infrastructure/makebuildenv.pl \
exit 1
fi
+cat parcels.txt | sed -e 's/#.*//' | while read cmd subdir configure_args; do
+ if test "$cmd" = "subdir"; then
+ echo
+ export CC CXX CFLAGS CXXFLAGS LDFLAGS LIBS
+ args="$configure_args --target=$target_alias"
+ echo "Configuring $subdir with: $args"
+
+ cd $subdir
+ if ! ./configure $args; then
+ echo "Configuring $subdir with $args failed!" >&2
+ exit 1
+ fi
+ fi
+done || exit $?
+
# Write summary of important info
tee config.log.features <<EOC
A summary of the build configuration is below. Box Backup will function
@@ -397,6 +156,13 @@ Readline: $have_libreadline
Extended attributes: $ac_cv_header_sys_xattr_h
EOC
+cat > config.env <<EOC
+CC="$CC"
+CXX="$CXX"
+CXXFLAGS="$CXXFLAGS"
+LDFLAGS="$LDFLAGS"
+LIBS="$LIBS"
+EOC
### Warnings at end for visibility
diff --git a/contrib/mac_osx/org.boxbackup.bbackupd.plist.in b/contrib/mac_osx/org.boxbackup.bbackupd.plist.in
index 803deece..a66f5500 100644
--- a/contrib/mac_osx/org.boxbackup.bbackupd.plist.in
+++ b/contrib/mac_osx/org.boxbackup.bbackupd.plist.in
@@ -4,6 +4,11 @@
<dict>
<key>Label</key>
<string>org.boxbackup.bbackupd</string>
+<<<<<<< HEAD
+=======
+ <key>OnDemand</key>
+ <false/>
+>>>>>>> 0.12
<key>RunAtLoad</key>
<true/>
<key>ProgramArguments</key>
diff --git a/contrib/mac_osx/org.boxbackup.bbstored.plist.in b/contrib/mac_osx/org.boxbackup.bbstored.plist.in
index bfe8c88c..7cd746f3 100644
--- a/contrib/mac_osx/org.boxbackup.bbstored.plist.in
+++ b/contrib/mac_osx/org.boxbackup.bbstored.plist.in
@@ -4,6 +4,11 @@
<dict>
<key>Label</key>
<string>org.boxbackup.bbstored</string>
+<<<<<<< HEAD
+=======
+ <key>OnDemand</key>
+ <false/>
+>>>>>>> 0.12
<key>RunAtLoad</key>
<true/>
<key>ProgramArguments</key>
@@ -12,7 +17,10 @@
<string>-F</string>
<string>@prefix@/etc/boxbackup/bbackupd.conf</string>
</array>
+<<<<<<< HEAD
</array>
+=======
+>>>>>>> 0.12
<key>LowPriorityIO</key>
<true/>
<key>Nice</key>
diff --git a/contrib/suse/bbstored.service b/contrib/suse/bbstored.service
new file mode 100644
index 00000000..55c03ddc
--- /dev/null
+++ b/contrib/suse/bbstored.service
@@ -0,0 +1,26 @@
+# This file is part of systemd.
+#
+# systemd 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.
+#
+# Server daemon for the BoxBackup software, to which bbackupd clients
+# connect.
+#
+# Systemd configuration by Daniel Tihelka <dtihelka at gmail.com>
+
+
+[Unit]
+Description=BoxBackup server side daemon
+After=local-fs.target
+#After=network
+
+[Service]
+ExecStart=/usr/sbin/bbstored -FK
+ExecReload=/bin/kill -HUP $MAINPID
+User=box
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/windows/installer/bbackupd.conf.template b/contrib/windows/installer/bbackupd.conf.template
new file mode 100644
index 00000000..969ca619
--- /dev/null
+++ b/contrib/windows/installer/bbackupd.conf.template
@@ -0,0 +1,176 @@
+StoreHostname = @@ServerName@@
+AccountNumber = 0x@@AccountNo@@
+KeysFile = @@InstallDir@@/conf/@@AccountNo@@-FileEncKeys.raw
+
+CertificateFile = @@InstallDir@@/conf/@@AccountNo@@-cert.pem
+PrivateKeyFile = @@InstallDir@@/conf/@@AccountNo@@-key.pem
+TrustedCAsFile = @@InstallDir@@/conf/serverCA.pem
+
+DataDirectory = @@InstallDir@@/state
+
+
+# This script is run whenever bbackupd changes state or encounters a
+# problem which requires the system administrator to assist:
+#
+# 1) The store is full, and no more data can be uploaded.
+# 2) Some files or directories were not readable.
+# 3) A backup run starts or finishes.
+#
+# The default script emails the system administrator, except for backups
+# starting and stopping, where it does nothing.
+
+NotifyScript = @@InstallDir@@/bin/NotifySysAdmin.vbs
+
+# The number of seconds between backup runs under normal conditions. To avoid
+# cycles of load on the server, this time is randomly adjusted by a small
+# percentage as the daemon runs.
+
+UpdateStoreInterval = 3600
+
+
+# The minimum age of a file, in seconds, that will be uploaded. Avoids
+# repeated uploads of a file which is constantly being modified.
+
+MinimumFileAge = 21600
+
+# If a file is modified repeated, it won't be uploaded immediately in case
+# it's modified again, due to the MinimumFileAge specified above. However, it
+# should be uploaded eventually even if it is being modified repeatedly. This
+# is how long we should wait, in seconds, after first noticing a change.
+# (86400 seconds = 1 day)
+
+MaxUploadWait = 86400
+
+# If the connection is idle for some time (e.g. over 10 minutes or 600
+# seconds, not sure exactly how long) then the server will give up and
+# disconnect the client, resulting in Connection Protocol_Timeout errors
+# on the server and TLSReadFailed or TLSWriteFailed errors on the client.
+# Also, some firewalls and NAT gateways will kill idle connections after
+# similar lengths of time.
+#
+# This can happen for example when most files are backed up already and
+# don't need to be sent to the store again, while scanning a large
+# directory, or while calculating diffs of a large file. To avoid this,
+# KeepAliveTime specifies that special keep-alive messages should be sent
+# when the connection is otherwise idle for a certain length of time,
+# specified here in seconds.
+#
+# The default is that these messages are never sent, equivalent to setting
+# this option to zero, but we recommend that all users enable this.
+
+KeepAliveTime = 120
+
+# Files above this size (in bytes) are tracked, and if they are renamed they will simply be
+# renamed on the server, rather than being uploaded again. (64k - 1)
+
+FileTrackingSizeThreshold = 65535
+
+# The daemon does "changes only" uploads for files above this size (in bytes).
+# Files less than it are uploaded whole without this extra processing.
+
+DiffingUploadSizeThreshold = 8192
+
+# The limit on how much time is spent diffing files, in seconds. Most files
+# shouldn't take very long, but if you have really big files you can use this
+# to limit the time spent diffing them.
+#
+# * Reduce if you are having problems with processor usage.
+#
+# * Increase if you have large files, and think the upload of changes is too
+# large and you want bbackupd to spend more time searching for unchanged
+# blocks.
+
+MaximumDiffingTime = 120
+
+# Uncomment this line to see exactly what the daemon is going when it's connected to the server.
+
+# ExtendedLogging = yes
+
+# This specifies a program or script script which is run just before each
+# sync, and ideally the full path to the interpreter. It will be run as the
+# same user bbackupd is running as, usually root.
+#
+# The script must output (print) either "now" or a number to STDOUT (and a
+# terminating newline, no quotes).
+#
+# If the result was "now", then the sync will happen. If it's a number, then
+# no backup will happen for that number of seconds (bbackupd will pause) and
+# then the script will be run again.
+#
+# Use this to temporarily stop bbackupd from syncronising or connecting to the
+# store. For example, you could use this on a laptop to only backup when on a
+# specific network, or when it has a working Internet connection.
+
+# SyncAllowScript = /path/to/intepreter/or/exe script-name parameters etc
+
+# Where the command socket is created in the filesystem.
+
+CommandSocket = @@CommandSocketNamedPipe@@
+
+# Uncomment the StoreObjectInfoFile to enable the experimental archiving
+# of the daemon's state (including client store marker and configuration)
+# between backup runs. This saves time and increases efficiency when
+# bbackupd is frequently stopped and started, since it removes the need
+# to rescan all directories on the remote server. However, it is new and
+# not yet heavily tested, so use with caution.
+
+StoreObjectInfoFile = @@InstallDir@@/state/bbackupd.state
+
+Server
+{
+ PidFile = @@InstallDir@@/state/bbackupd.pid
+}
+
+# BackupLocations specifies which locations on disc should be backed up. Each
+# directory is in the format
+#
+# name
+# {
+# Path = /path/of/directory
+# (optional exclude directives)
+# }
+#
+# 'name' is derived from the Path by the config script, but should merely be
+# unique.
+#
+# The exclude directives are of the form
+#
+# [Exclude|AlwaysInclude][File|Dir][|sRegex] = regex or full pathname
+#
+# (The regex suffix is shown as 'sRegex' to make File or Dir plural)
+#
+# For example:
+#
+# ExcludeDir = /home/guest-user
+# ExcludeFilesRegex = \.(mp3|MP3)\$
+# AlwaysIncludeFile = /home/username/veryimportant.mp3
+#
+# This excludes the directory /home/guest-user from the backup along with all mp3
+# files, except one MP3 file in particular.
+#
+# In general, Exclude excludes a file or directory, unless the directory is
+# explicitly mentioned in a AlwaysInclude directive. However, Box Backup
+# does NOT scan inside excluded directories and will never back up an
+# AlwaysIncluded file or directory inside an excluded directory or any
+# subdirectory thereof.
+#
+# To back up a directory inside an excluded directory, use a configuration
+# like this, to ensure that each directory in the path to the important
+# files is included, but none of their contents will be backed up except
+# the directories further down that path to the important one.
+#
+# ExcludeDirsRegex = ^/home/user/bigfiles/
+# ExcludeFilesRegex = ^/home/user/bigfiles/
+# AlwaysIncludeDir = /home/user/bigfiles/path
+# AlwaysIncludeDir = /home/user/bigfiles/path/to
+# AlwaysIncludeDir = /home/user/bigfiles/path/important
+# AlwaysIncludeDir = /home/user/bigfiles/path/important/files
+# AlwaysIncludeDirsRegex = ^/home/user/bigfiles/path/important/files/
+# AlwaysIncludeFilesRegex = ^/home/user/bigfiles/path/important/files/
+#
+# If a directive ends in Regex, then it is a regular expression rather than a
+# explicit full pathname. See
+#
+# man 7 re_format
+#
+# for the regex syntax on your platform.
diff --git a/contrib/windows/installer/boxbackup.mpi.in b/contrib/windows/installer/boxbackup.mpi.in
index 12f6d62d..182f8c05 100755
--- a/contrib/windows/installer/boxbackup.mpi.in
+++ b/contrib/windows/installer/boxbackup.mpi.in
@@ -1,5 +1,6 @@
array set info {
AccountNo
+<<<<<<< HEAD
10005005
AllowLanguageSelection
@@ -22,6 +23,30 @@ Yes
BBVersionNo
@box_version@
+=======
+{10005005}
+
+AllowLanguageSelection
+{No}
+
+AppName
+{<%BrandName%>}
+
+ApplicationID
+{E10C6FD9-E524-28BD-B0AB3588F16C}
+
+ApplicationURL
+{http://www.boxbackup.org/}
+
+AutoFileGroups
+{No}
+
+AutoRefreshFiles
+{Yes}
+
+BBVersionNo
+{@box_version@}
+>>>>>>> 0.12
BrandName
{Box Backup}
@@ -33,7 +58,11 @@ CancelledInstallAction
{Rollback and Stop}
CleanupCancelledInstall
+<<<<<<< HEAD
Yes
+=======
+{Yes}
+>>>>>>> 0.12
CommandLineFailureAction
{Fail (recommended)}
@@ -42,10 +71,17 @@ Company
{Tebuco, Inc. and Ben Summers and Contributors}
CompressionLevel
+<<<<<<< HEAD
6
CompressionMethod
zlib
+=======
+{6}
+
+CompressionMethod
+{zlib}
+>>>>>>> 0.12
ConfigFileName
{<%InstallDir%>\bbackupd.conf}
@@ -54,6 +90,7 @@ ConfigFileTemplate
{<%InstallDir%>\templates\template.conf}
Copyright
+<<<<<<< HEAD
{2003-2008 Tebuco, Inc. and Ben Summers and Contributors}
CreateDesktopShortcut
@@ -61,11 +98,21 @@ No
CreateQuickLaunchShortcut
No
+=======
+{2003-2011 Tebuco, Inc. and Ben Summers and Contributors}
+
+CreateDesktopShortcut
+{No}
+
+CreateQuickLaunchShortcut
+{No}
+>>>>>>> 0.12
DefaultDirectoryLocation
{}
DefaultLanguage
+<<<<<<< HEAD
English
EncryptedKeyFilePassword
@@ -76,10 +123,29 @@ Ext
ExtractSolidArchivesOnStartup
No
+=======
+{English}
+
+DefaultToSystemLanguage
+{Yes}
+
+EnableResponseFiles
+{Yes}
+
+EncryptedKeyFilePassword
+{Enter_EncryptedKeys_Password_Here}
+
+Ext
+{.exe}
+
+ExtractSolidArchivesOnStartup
+{No}
+>>>>>>> 0.12
Icon
{}
+<<<<<<< HEAD
Image
@build_dir@/docs/html/images/bblogo.png
@@ -88,11 +154,28 @@ Yes
InstallDirSuffix
<%ShortAppName%>
+=======
+IgnoreDirectories
+{}
+
+IgnoreFiles
+{}
+
+Image
+{@build_dir@/docs/html/images/bblogo.png}
+
+IncludeDebugging
+{Yes}
+
+InstallDirSuffix
+{<%ShortAppName%>}
+>>>>>>> 0.12
InstallPassword
{}
InstallVersion
+<<<<<<< HEAD
@box_version@
Language,de
@@ -127,6 +210,57 @@ Yes
LaunchApplication
No
+=======
+{0.0.0.0}
+
+Language,ca
+{No}
+
+Language,cs
+{No}
+
+Language,de
+{No}
+
+Language,en
+{Yes}
+
+Language,es
+{No}
+
+Language,fr
+{No}
+
+Language,hu
+{No}
+
+Language,it
+{No}
+
+Language,lt
+{No}
+
+Language,nl
+{No}
+
+Language,pl
+{No}
+
+Language,pt_br
+{No}
+
+Language,ru
+{No}
+
+LastIgnoreDirectories
+{}
+
+LastIgnoreFiles
+{}
+
+LaunchApplication
+{No}
+>>>>>>> 0.12
PackageDescription
{<%BrandName%> Backup Service}
@@ -138,18 +272,27 @@ PackageMaintainer
{Tebuco, Inc. and Ben Summers and Contributors}
PackageName
+<<<<<<< HEAD
<%ShortAppName%>
+=======
+{<%ShortAppName%>}
+>>>>>>> 0.12
PackagePackager
{Tebuco, Inc. and Ben Summers and Contributors}
PackageRelease
+<<<<<<< HEAD
<%PatchVersion%>
+=======
+{<%PatchVersion%>}
+>>>>>>> 0.12
PackageSummary
{}
PackageVersion
+<<<<<<< HEAD
<%MajorVersion%>.<%MinorVersion%>
PreserveFileAttributes
@@ -193,12 +336,61 @@ Modern_Wizard
ThemeVersion
1
+=======
+{<%MajorVersion%>.<%MinorVersion%>}
+
+PreserveFileAttributes
+{Yes}
+
+PreserveFilePermissions
+{Yes}
+
+ProjectID
+{140B9882-3327-FEA8-13415A62FBB2}
+
+ProjectVersion
+{1.2.15.2}
+
+SaveOnlyToplevelDirs
+{No}
+
+ScriptExt
+{.bat}
+
+ServiceExeName
+{bbackupd.exe}
+
+ServiceName
+{<%BrandName%>}
+
+ShortAppName
+{<%BrandName%>}
+
+SkipUnusedFileGroups
+{Yes}
+
+SystemLanguage
+{en_us}
+
+Theme
+{Modern_Wizard}
+
+ThemeDir
+{Modern_Wizard}
+
+ThemeVersion
+{1}
+>>>>>>> 0.12
UpgradeApplicationID
{}
UserInfoAcctNo
+<<<<<<< HEAD
<%AccountNo%>
+=======
+{<%AccountNo%>}
+>>>>>>> 0.12
UserInfoCompany
{}
@@ -213,6 +405,7 @@ UserInfoPhone
{}
Version
+<<<<<<< HEAD
@box_version@
ViewReadme
@@ -223,6 +416,18 @@ WizardHeight
WizardWidth
500
+=======
+{@box_version@}
+
+ViewReadme
+{No}
+
+WizardHeight
+{365}
+
+WizardWidth
+{500}
+>>>>>>> 0.12
}
@@ -272,6 +477,7 @@ test
{Testing Switch Yes No {} {run uninstaller without uninstalling any files}}
}
+<<<<<<< HEAD
FileGroup ::481451CC-F49C-D389-8645076F595B -setup Install -active Yes -platforms {Windows MacOS-X} -name {Program Files} -parent FileGroups
File ::B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 -filemethod {Update files with more recent dates} -type dir -directory <%InstallDir%> -name /home/petjal/doc/teb/cli/bu/installer/win/2.2 -location @client_parcel_dir@ -parent 481451CC-F49C-D389-8645076F595B
File ::CDDED10B-2747-DD07-5F9D-42A7FD7BB7E6 -name LICENSE.txt -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2
@@ -333,10 +539,30 @@ File ::5C3EAB34-7CD4-4DF3-9DEB-0FC23A6F5812 -name bbackupquery.exe -parent B9F58
File ::7633DBC3-EACA-4F9B-9A87-AD3AF0EC298E -name installer.iss -active 0 -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2
File ::D3CF86E1-CAFF-4342-8730-463F96EACC39 -targetfilename templates/NotifySysAdmin.original.vbs -name NotifySysAdmin.vbs -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2
File ::A702625F-29C7-4CA0-A8F8-E50DBF5C541B -name uninstall.exe -active 0 -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2
+=======
+FileGroup ::481451CC-F49C-D389-8645076F595B -setup Install -active Yes -platforms {Windows} -name Binaries -parent FileGroups
+File ::0D5FA1BE-D208-402E-A358-978A57513DCE -name @client_parcel_dir@/bbackupctl.exe -parent 481451CC-F49C-D389-8645076F595B
+File ::4BE333C8-23F0-4629-82D6-E655641D4007 -name @client_parcel_dir@/bbackupd.exe -parent 481451CC-F49C-D389-8645076F595B
+File ::3CDCA9AC-7B3B-4FC2-810E-71C1587E5FBC -name @client_parcel_dir@/bbackupquery.exe -parent 481451CC-F49C-D389-8645076F595B
+File ::AE5153FA-44A5-442B-992B-F8039D23065A -name @build_dir@/../openssl/bin/libeay32.dll -parent 481451CC-F49C-D389-8645076F595B
+File ::1C2A58A1-089D-4929-B92D-397C6C945EBC -name @build_dir@/../openssl/bin/openssl.exe -parent 481451CC-F49C-D389-8645076F595B
+File ::8EB5B7FA-A30B-47E2-BEA4-B0240C07F8C6 -name @build_dir@/../openssl/bin/ssleay32.dll -parent 481451CC-F49C-D389-8645076F595B
+File ::F32E15B3-CBF1-46A7-9E1F-0A17EECF9C39 -name @build_dir@/../zlib/zlib1.dll -parent 481451CC-F49C-D389-8645076F595B
+FileGroup ::2C456223-3E1E-4D43-B31A-868EAD3241E1 -setup Install -active Yes -platforms {Windows} -name Documents -parent FileGroups
+File ::F4DD0436-B84B-4FCA-8AF4-F9F0EEED631A -name @build_dir@/COPYING.txt -parent 2C456223-3E1E-4D43-B31A-868EAD3241E1
+File ::34214008-502F-4BCD-A668-383FD13A6182 -name @build_dir@/LICENSE.txt -parent 2C456223-3E1E-4D43-B31A-868EAD3241E1
+File ::30A2FB48-1BDB-445D-BF36-62707DEFBA77 -name @build_dir@/LICENSE-DUAL.txt -parent 2C456223-3E1E-4D43-B31A-868EAD3241E1
+File ::A2D6E0B2-A641-4426-8835-AA06102FB020 -name @build_dir@/LICENSE-GPL.txt -parent 2C456223-3E1E-4D43-B31A-868EAD3241E1
+File ::A72A7844-0245-40C8-B5AE-D10F8654318E -name @build_dir@/distribution/boxbackup/CONTACT.txt -parent 2C456223-3E1E-4D43-B31A-868EAD3241E1
+File ::47390686-C767-4E91-AE69-4A980C67B304 -name @build_dir@/distribution/boxbackup/DOCUMENTATION.txt -parent 2C456223-3E1E-4D43-B31A-868EAD3241E1
+File ::D4EF569E-F14A-41B7-853C-46C652DA51A7 -name @build_dir@/distribution/boxbackup/THANKS.txt -parent 2C456223-3E1E-4D43-B31A-868EAD3241E1
+File ::143FE54A-7743-4AA5-9DF9-084ECA4ABFF9 -name @build_dir@/distribution/boxbackup/VERSION.txt -parent 2C456223-3E1E-4D43-B31A-868EAD3241E1
+>>>>>>> 0.12
Component ::4A9C852B-647E-EED5-5482FFBCC2AF -setup Install -active Yes -platforms {Windows MacOS-X} -name {Default Component} -parent Components
SetupType ::8202CECC-54A0-9B6C-D24D111BA52E -setup Install -active Yes -platforms {Windows MacOS-X} -name Typical -parent SetupTypes
InstallComponent AE3BD5B4-35DE-4240-B79914D43E56 -setup Install -type pane -title {Welcome Screen} -component Welcome -active No -parent StandardInstall
+<<<<<<< HEAD
InstallComponent 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8 -setup Install -type pane -conditions 4EE35849-FAD7-170B-0E45-FA30636467B1 -title {Install Password} -component InstallPassword -command insert -active No -parent StandardInstall
Condition 4EE35849-FAD7-170B-0E45-FA30636467B1 -active Yes -parent 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8 -title {Password Test Condition} -component PasswordTestCondition -TreeObject::id 4EE35849-FAD7-170B-0E45-FA30636467B1
InstallComponent B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D -setup Install -type action -title {Set Install Password} -component SetInstallPassword -active Yes -parent 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8
@@ -344,12 +570,25 @@ InstallComponent 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E -setup Install -type pane
InstallComponent 9013E862-8E81-5290-64F9-D8BCD13EC7E5 -setup Install -type pane -title {User Information Phone Email} -component UserInformation -active Yes -parent StandardInstall
InstallComponent F8FD4BD6-F1DF-3F8D-B857-98310E4B1143 -setup Install -type pane -title {User Information Account No} -component UserInformation -active Yes -parent StandardInstall
InstallComponent 58E1119F-639E-17C9-5D3898F385AA -setup Install -type pane -conditions 84DA7F05-9FB7-CC36-9EC98F8A6826 -title {Select Destination} -component SelectDestination -command insert -active Yes -parent StandardInstall
+=======
+InstallComponent 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8 -setup Install -type pane -conditions 4EE35849-FAD7-170B-0E45-FA30636467B1 -title {Install Password} -component InstallPassword -active No -parent StandardInstall
+Condition 4EE35849-FAD7-170B-0E45-FA30636467B1 -active Yes -parent 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8 -title {Password Test Condition} -component PasswordTestCondition -TreeObject::id 4EE35849-FAD7-170B-0E45-FA30636467B1
+InstallComponent B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D -setup Install -type action -title {Set Install Password} -component SetInstallPassword -active Yes -parent 2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8
+InstallComponent 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E -setup Install -type pane -title {User Information} -component UserInformation -active No -parent StandardInstall
+InstallComponent 9013E862-8E81-5290-64F9-D8BCD13EC7E5 -setup Install -type pane -title {User Information Phone Email} -component UserInformation -active No -parent StandardInstall
+InstallComponent F8FD4BD6-F1DF-3F8D-B857-98310E4B1143 -setup Install -type pane -title {User Information Account No} -component UserInformation -active Yes -parent StandardInstall
+InstallComponent 58E1119F-639E-17C9-5D3898F385AA -setup Install -type pane -conditions 84DA7F05-9FB7-CC36-9EC98F8A6826 -title {Select Destination} -component SelectDestination -active Yes -parent StandardInstall
+>>>>>>> 0.12
Condition 84DA7F05-9FB7-CC36-9EC98F8A6826 -active Yes -parent 58E1119F-639E-17C9-5D3898F385AA -title {File Permission Condition} -component FilePermissionCondition -TreeObject::id 84DA7F05-9FB7-CC36-9EC98F8A6826
InstallComponent 0FDBA082-90AB-808C-478A-A13E7C525336 -setup Install -type action -title BackupLocationNumber -component ExecuteScript -active Yes -parent 58E1119F-639E-17C9-5D3898F385AA
InstallComponent 0047FF40-0139-2A59-AAC0-A44D46D6F5CC -setup Install -type action -title BackupLocationName -component ExecuteScript -active No -parent 58E1119F-639E-17C9-5D3898F385AA
InstallComponent 2BB06B72-DE53-2319-B1B8-351CDCBA2008 -setup Install -type action -title AddBackupLocation -component ExecuteScript -active Yes -parent 58E1119F-639E-17C9-5D3898F385AA
InstallComponent B506E7DA-E7C4-4D42-8C03-FD27BA16D078 -setup Install -type pane -title {License Agreement} -component License -active Yes -parent StandardInstall
+<<<<<<< HEAD
InstallComponent B93D2216-1DDB-484C-A9AC-D6C18ED7DE23 -setup Install -type action -conditions {6D9D1ABC-7146-443F-9EE9-205D5CA6C830 79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C} -title {Modify Widget} -component ModifyWidget -command insert -active Yes -parent B506E7DA-E7C4-4D42-8C03-FD27BA16D078
+=======
+InstallComponent B93D2216-1DDB-484C-A9AC-D6C18ED7DE23 -setup Install -type action -conditions {6D9D1ABC-7146-443F-9EE9-205D5CA6C830 79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C} -title {Modify Widget} -component ModifyWidget -active Yes -parent B506E7DA-E7C4-4D42-8C03-FD27BA16D078
+>>>>>>> 0.12
Condition 6D9D1ABC-7146-443F-9EE9-205D5CA6C830 -active Yes -parent B93D2216-1DDB-484C-A9AC-D6C18ED7DE23 -title {String Is Condition} -component StringIsCondition -TreeObject::id 6D9D1ABC-7146-443F-9EE9-205D5CA6C830
Condition 79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C -active Yes -parent B93D2216-1DDB-484C-A9AC-D6C18ED7DE23 -title {String Is Condition} -component StringIsCondition -TreeObject::id 79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C
InstallComponent 37E627F2-E04B-AEF2-D566C017A4D6 -setup Install -type pane -title {Copying Files} -component CopyFiles -active Yes -parent StandardInstall
@@ -366,7 +605,11 @@ InstallComponent 5F2C1F1C-B9F7-1642-59D9-A18318C1D70B -setup Install -type actio
InstallComponent 2EC82FBD-8294-A3E4-7F39-1CBA0582FA64 -setup Install -type action -title {Write Text To File} -component WriteTextToFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6
InstallComponent 28E76C8B-2605-4739-9FFE-9C2880C17E59 -setup Install -type action -title {Edit config file} -component ExecuteExternalProgram -active No -parent 37E627F2-E04B-AEF2-D566C017A4D6
InstallComponent 52F0A238-57E1-A578-2CE4DA177B32 -setup Install -type action -title {Move Forward} -component MoveForward -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6
+<<<<<<< HEAD
InstallComponent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 -setup Install -type pane -title SetBackupLocations -component CustomBlankPane2 -command reorder -active Yes -parent StandardInstall
+=======
+InstallComponent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 -setup Install -type pane -title SetBackupLocations -component CustomBlankPane2 -active Yes -parent StandardInstall
+>>>>>>> 0.12
InstallComponent 614C45B2-7515-780C-E444-7F165CF02DD7 -setup Install -type action -title {Execute Script} -component ExecuteScript -active No -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7
InstallComponent A5B32DA1-B2FE-C1FA-6057-FBC3059EF076 -setup Install -type action -title {Execute Script} -component ExecuteScript -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7
InstallComponent F9E38720-6ABA-8B99-2471-496902E4CBC2 -setup Install -type action -title {Execute Script} -component ExecuteScript -active No -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7
@@ -380,12 +623,20 @@ InstallComponent 9892B25C-689B-5B8F-F0C9-B14FF6ACC40C -setup Install -type actio
InstallComponent 8419AAAD-5860-F73E-8D11-4D1BDA4D7D37 -setup Install -type action -title AddAnother -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7
InstallComponent C7762473-273F-E3CA-17E3-65789B14CDB0 -setup Install -type action -title {Write Text To File} -component WriteTextToFile -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7
InstallComponent D7FBBEBB-2186-5674-BA87-BB7151859D4E -setup Install -type action -title BackupLocationNumber -component ExecuteScript -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7
+<<<<<<< HEAD
InstallComponent 49E80443-62DB-1C10-392D-1091AEA5ED88 -setup Install -type action -conditions EB532611-5F30-3C24-66EB-F3826D9054FD -title {Move to Pane} -component MoveToPane -command insert -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7
+=======
+InstallComponent 49E80443-62DB-1C10-392D-1091AEA5ED88 -setup Install -type action -conditions EB532611-5F30-3C24-66EB-F3826D9054FD -title {Move to Pane} -component MoveToPane -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7
+>>>>>>> 0.12
Condition EB532611-5F30-3C24-66EB-F3826D9054FD -active Yes -parent 49E80443-62DB-1C10-392D-1091AEA5ED88 -title {String Is Condition} -component StringIsCondition -TreeObject::id EB532611-5F30-3C24-66EB-F3826D9054FD
InstallComponent 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266 -setup Install -type pane -title {Click Next to Continue} -component CustomBlankPane2 -active Yes -parent StandardInstall
InstallComponent DDBBD8A9-13D7-9509-9202-419E989F60A9 -setup Install -type action -title {Add Widget} -component AddWidget -active No -parent 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266
InstallComponent 8E095096-F018-A880-429D-A2177A9B70EA -setup Install -type action -title {Add Widget} -component AddWidget -active No -parent 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266
+<<<<<<< HEAD
InstallComponent 88A50FD5-480F-19A5-DA74-C915EB0A9765 -setup Install -type action -conditions 5EE78EF7-37CA-D440-3DB5-09136CD566B3 -title {Move to Pane} -component MoveToPane -command insert -active No -parent 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266
+=======
+InstallComponent 88A50FD5-480F-19A5-DA74-C915EB0A9765 -setup Install -type action -conditions 5EE78EF7-37CA-D440-3DB5-09136CD566B3 -title {Move to Pane} -component MoveToPane -active No -parent 9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266
+>>>>>>> 0.12
Condition 5EE78EF7-37CA-D440-3DB5-09136CD566B3 -active Yes -parent 88A50FD5-480F-19A5-DA74-C915EB0A9765 -title {String Is Condition} -component StringIsCondition -TreeObject::id 5EE78EF7-37CA-D440-3DB5-09136CD566B3
InstallComponent 908CE221-5A3D-0A78-24A1-E7C91EBE38D4 -setup Install -type pane -title {Next-Build Config} -component CustomBlankPane2 -active No -parent StandardInstall
InstallComponent DA33B826-E633-A845-4646-76DFA78B907B -setup Install -type pane -title {Custom Blank Pane 2} -component CustomBlankPane2 -active Yes -parent StandardInstall
@@ -400,6 +651,7 @@ InstallComponent 8A761DBD-0640-D98C-9B3AD7672A8F -setup Install -type action -ti
InstallComponent 6E70FB1F-6A43-6C23-3242E965A0D0 -setup Install -type action -title {Execute Action} -component ExecuteAction -active Yes -parent 574198A7-7322-2F5E-02EF185D965C
InstallComponent 8E1A5944-5AF5-5906-16D395E386D8 -setup Install -type action -title {Move Forward} -component MoveForward -active Yes -parent 574198A7-7322-2F5E-02EF185D965C
InstallComponent 1F0926EE-6884-1330-B4A1DB11C1BF -setup Install -type pane -title {Setup Complete} -component SetupComplete -active Yes -parent DefaultInstall
+<<<<<<< HEAD
InstallComponent 3B6E2E7C-1A26-27F1-D578E383B128 -setup Install -type action -conditions {13BD88FE-CD71-5AC7-E99C10B6CB28 E02368C5-95B5-03A7-3282740037B0} -title {View Readme Checkbutton} -component AddWidget -command insert -active Yes -parent 1F0926EE-6884-1330-B4A1DB11C1BF
Condition 13BD88FE-CD71-5AC7-E99C10B6CB28 -active Yes -parent 3B6E2E7C-1A26-27F1-D578E383B128 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 13BD88FE-CD71-5AC7-E99C10B6CB28
Condition E02368C5-95B5-03A7-3282740037B0 -active Yes -parent 3B6E2E7C-1A26-27F1-D578E383B128 -title {String Is Condition} -component StringIsCondition -TreeObject::id E02368C5-95B5-03A7-3282740037B0
@@ -410,13 +662,31 @@ InstallComponent 16D53E40-546B-54C3-088B1B5E3BBB -setup Install -type action -co
Condition 4E643D8A-CA31-018D-57D7053C2CE8 -active Yes -parent 16D53E40-546B-54C3-088B1B5E3BBB -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 4E643D8A-CA31-018D-57D7053C2CE8
Condition B39C0455-D1B6-7DDC-E2717F83463E -active Yes -parent 16D53E40-546B-54C3-088B1B5E3BBB -title {String Is Condition} -component StringIsCondition -TreeObject::id B39C0455-D1B6-7DDC-E2717F83463E
InstallComponent 937C3FDD-FB28-98BD-3DAB276E59ED -setup Install -type action -conditions {6B966959-05D9-DB32-8D9C4AD2A3DF 748D673B-DFE6-5F74-329903ACE4DB 3379F80B-36D6-73DC-6FC1D6223A26} -title {Quick Launch Shortcut Checkbutton} -component AddWidget -command insert -active Yes -parent 1F0926EE-6884-1330-B4A1DB11C1BF
+=======
+InstallComponent 3B6E2E7C-1A26-27F1-D578E383B128 -setup Install -type action -conditions {13BD88FE-CD71-5AC7-E99C10B6CB28 E02368C5-95B5-03A7-3282740037B0} -title {View Readme Checkbutton} -component AddWidget -active Yes -parent 1F0926EE-6884-1330-B4A1DB11C1BF
+Condition 13BD88FE-CD71-5AC7-E99C10B6CB28 -active Yes -parent 3B6E2E7C-1A26-27F1-D578E383B128 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 13BD88FE-CD71-5AC7-E99C10B6CB28
+Condition E02368C5-95B5-03A7-3282740037B0 -active Yes -parent 3B6E2E7C-1A26-27F1-D578E383B128 -title {String Is Condition} -component StringIsCondition -TreeObject::id E02368C5-95B5-03A7-3282740037B0
+InstallComponent CFFA27AF-A641-E41C-B4A0E3BB3CBB -setup Install -type action -conditions {592F46AE-8CEE-01F3-0BA7EBDCA4F4 793D8178-0F51-7F07-BC5886586D3C} -title {Launch Application Checkbutton} -component AddWidget -active Yes -parent 1F0926EE-6884-1330-B4A1DB11C1BF
+Condition 592F46AE-8CEE-01F3-0BA7EBDCA4F4 -active Yes -parent CFFA27AF-A641-E41C-B4A0E3BB3CBB -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 592F46AE-8CEE-01F3-0BA7EBDCA4F4
+Condition 793D8178-0F51-7F07-BC5886586D3C -active Yes -parent CFFA27AF-A641-E41C-B4A0E3BB3CBB -title {String Is Condition} -component StringIsCondition -TreeObject::id 793D8178-0F51-7F07-BC5886586D3C
+InstallComponent 16D53E40-546B-54C3-088B1B5E3BBB -setup Install -type action -conditions {4E643D8A-CA31-018D-57D7053C2CE8 B39C0455-D1B6-7DDC-E2717F83463E} -title {Desktop Shortcut Checkbutton} -component AddWidget -active Yes -parent 1F0926EE-6884-1330-B4A1DB11C1BF
+Condition 4E643D8A-CA31-018D-57D7053C2CE8 -active Yes -parent 16D53E40-546B-54C3-088B1B5E3BBB -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 4E643D8A-CA31-018D-57D7053C2CE8
+Condition B39C0455-D1B6-7DDC-E2717F83463E -active Yes -parent 16D53E40-546B-54C3-088B1B5E3BBB -title {String Is Condition} -component StringIsCondition -TreeObject::id B39C0455-D1B6-7DDC-E2717F83463E
+InstallComponent 937C3FDD-FB28-98BD-3DAB276E59ED -setup Install -type action -conditions {6B966959-05D9-DB32-8D9C4AD2A3DF 748D673B-DFE6-5F74-329903ACE4DB 3379F80B-36D6-73DC-6FC1D6223A26} -title {Quick Launch Shortcut Checkbutton} -component AddWidget -active Yes -parent 1F0926EE-6884-1330-B4A1DB11C1BF
+>>>>>>> 0.12
Condition 6B966959-05D9-DB32-8D9C4AD2A3DF -active Yes -parent 937C3FDD-FB28-98BD-3DAB276E59ED -title {Platform Condition} -component PlatformCondition -TreeObject::id 6B966959-05D9-DB32-8D9C4AD2A3DF
Condition 748D673B-DFE6-5F74-329903ACE4DB -active Yes -parent 937C3FDD-FB28-98BD-3DAB276E59ED -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 748D673B-DFE6-5F74-329903ACE4DB
Condition 3379F80B-36D6-73DC-6FC1D6223A26 -active Yes -parent 937C3FDD-FB28-98BD-3DAB276E59ED -title {String Is Condition} -component StringIsCondition -TreeObject::id 3379F80B-36D6-73DC-6FC1D6223A26
InstallComponent 3FE82C17-A3E2-4A57-A563-F80818B00B81 -setup Install -type action -title {Console Ask Yes Or No} -component ConsoleAskYesOrNo -active Yes -parent ConsoleInstall
+<<<<<<< HEAD
InstallComponent 56EE5149-6AA2-4E0C-8841-F66A2EF9276E -setup Install -type action -conditions 241BBFCE-4EB1-432F-94DD-69D444DDB6C0 -title Exit -component Exit -command insert -active Yes -parent ConsoleInstall
Condition 241BBFCE-4EB1-432F-94DD-69D444DDB6C0 -active Yes -parent 56EE5149-6AA2-4E0C-8841-F66A2EF9276E -title {String Is Condition} -component StringIsCondition -TreeObject::id 241BBFCE-4EB1-432F-94DD-69D444DDB6C0
InstallComponent 0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5 -setup Install -type action -conditions BC4EA5FD-50BD-4D6E-953F-5E3EDB957360 -title {Console Get User Input} -component ConsoleGetUserInput -command insert -active Yes -parent ConsoleInstall
+=======
+InstallComponent 56EE5149-6AA2-4E0C-8841-F66A2EF9276E -setup Install -type action -conditions 241BBFCE-4EB1-432F-94DD-69D444DDB6C0 -title Exit -component Exit -active Yes -parent ConsoleInstall
+Condition 241BBFCE-4EB1-432F-94DD-69D444DDB6C0 -active Yes -parent 56EE5149-6AA2-4E0C-8841-F66A2EF9276E -title {String Is Condition} -component StringIsCondition -TreeObject::id 241BBFCE-4EB1-432F-94DD-69D444DDB6C0
+InstallComponent 0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5 -setup Install -type action -conditions BC4EA5FD-50BD-4D6E-953F-5E3EDB957360 -title {Console Get User Input} -component ConsoleGetUserInput -active Yes -parent ConsoleInstall
+>>>>>>> 0.12
Condition BC4EA5FD-50BD-4D6E-953F-5E3EDB957360 -active Yes -parent 0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5 -title {File Permission Condition} -component FilePermissionCondition -TreeObject::id BC4EA5FD-50BD-4D6E-953F-5E3EDB957360
InstallComponent B002A311-F8E7-41DE-B039-521391924E5B -setup Install -type action -title {Console Message} -component ConsoleMessage -active Yes -parent ConsoleInstall
InstallComponent D4FC6EB5-DDEE-4E4A-B8E1-D4B588A7928B -setup Install -type action -title {Execute Action} -component ExecuteAction -active Yes -parent ConsoleInstall
@@ -425,16 +695,26 @@ InstallComponent 6B4CB3C2-4799-4C9F-BA8E-1EE47C4606E1 -setup Install -type actio
InstallComponent D8F0AA0F-AD79-C566-15CC508F503B -setup Install -type action -title {Execute Action} -component ExecuteAction -active Yes -parent SilentInstall
InstallComponent 175CBE81-9EBE-1E21-A91479BEEFAE -setup Install -type action -title Exit -component Exit -active Yes -parent SilentInstall
InstallComponent A1DD1DC2-85D7-9BC6-998AC3D4A3A9 -setup Install -type actiongroup -title {Startup Actions} -active Yes -parent ActionGroupsInstall
+<<<<<<< HEAD
InstallComponent 1F9E8CB8-02C1-0416-1F7445B4147F -setup Install -type action -conditions {3D0D1898-4C65-3E66-F82F56581E87 32F5B0AF-EB83-7A03-D8FAE1ECE473} -title Exit -component Exit -command insert -active Yes -parent A1DD1DC2-85D7-9BC6-998AC3D4A3A9
+=======
+InstallComponent 1F9E8CB8-02C1-0416-1F7445B4147F -setup Install -type action -conditions {3D0D1898-4C65-3E66-F82F56581E87 32F5B0AF-EB83-7A03-D8FAE1ECE473} -title Exit -component Exit -active Yes -parent A1DD1DC2-85D7-9BC6-998AC3D4A3A9
+>>>>>>> 0.12
Condition 3D0D1898-4C65-3E66-F82F56581E87 -active Yes -parent 1F9E8CB8-02C1-0416-1F7445B4147F -title {String Is Condition} -component StringIsCondition -TreeObject::id 3D0D1898-4C65-3E66-F82F56581E87
Condition 32F5B0AF-EB83-7A03-D8FAE1ECE473 -active Yes -parent 1F9E8CB8-02C1-0416-1F7445B4147F -title {Ask Yes or No} -component AskYesOrNo -TreeObject::id 32F5B0AF-EB83-7A03-D8FAE1ECE473
InstallComponent 32DC8FB1-A04B-71AA-EC18496D4BD0 -setup Install -type action -title {Create Install Panes} -component CreateInstallPanes -active Yes -parent A1DD1DC2-85D7-9BC6-998AC3D4A3A9
InstallComponent 198905FB-9FAC-23DE-7422D25B8ECA -setup Install -type actiongroup -title {Install Actions} -active Yes -parent ActionGroupsInstall
InstallComponent 4D4A7BF0-7CCE-46E6-BDE5222F82D7 -setup Install -type action -title {Install Selected Files} -component InstallSelectedFiles -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA
InstallComponent 53588803-6B41-D9FC-A385906A5106 -setup Install -type action -title {Install Uninstaller} -component InstallUninstaller -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA
+<<<<<<< HEAD
InstallComponent 73EA65C1-3BE3-B190-55C3E99F6269 -setup Install -type action -conditions 4EF787E3-0643-DE46-15E64BAF0816 -title {Windows Uninstall Registry} -component AddWindowsUninstallEntry -command insert -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA
Condition 4EF787E3-0643-DE46-15E64BAF0816 -active Yes -parent 73EA65C1-3BE3-B190-55C3E99F6269 -title {Platform Condition} -component PlatformCondition -TreeObject::id 4EF787E3-0643-DE46-15E64BAF0816
InstallComponent 39B2B666-78D8-75E6-6EA071594D34 -setup Install -type action -conditions 18C00430-D6B1-151F-307762B3A045 -title {Uninstall Shortcut} -component InstallWindowsShortcut -command insert -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA
+=======
+InstallComponent 73EA65C1-3BE3-B190-55C3E99F6269 -setup Install -type action -conditions 4EF787E3-0643-DE46-15E64BAF0816 -title {Windows Uninstall Registry} -component AddWindowsUninstallEntry -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA
+Condition 4EF787E3-0643-DE46-15E64BAF0816 -active Yes -parent 73EA65C1-3BE3-B190-55C3E99F6269 -title {Platform Condition} -component PlatformCondition -TreeObject::id 4EF787E3-0643-DE46-15E64BAF0816
+InstallComponent 39B2B666-78D8-75E6-6EA071594D34 -setup Install -type action -conditions 18C00430-D6B1-151F-307762B3A045 -title {Uninstall Shortcut} -component InstallWindowsShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA
+>>>>>>> 0.12
Condition 18C00430-D6B1-151F-307762B3A045 -active Yes -parent 39B2B666-78D8-75E6-6EA071594D34 -title {Platform Condition} -component PlatformCondition -TreeObject::id 18C00430-D6B1-151F-307762B3A045
InstallComponent 6652193C-5D4B-44B6-ABC6-D6E96D89E5DC -setup Install -type action -title {Install Program Folder Shortcut} -component InstallProgramFolderShortcut -active No -parent 198905FB-9FAC-23DE-7422D25B8ECA
InstallComponent 9D101299-B80C-441B-8685-6E3AC61808E8 -setup Install -type action -title {RemoteControl Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA
@@ -452,6 +732,7 @@ InstallComponent 9A663209-495B-ED16-09BE-457B61148022 -setup Install -type actio
InstallComponent C0AF7C05-A31A-8376-BCB9-BA8B3A666252 -setup Install -type action -title SafeQueryAll -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA
InstallComponent 32B08FB1-99DF-234E-8BAF-333E80AAC9F5 -setup Install -type action -title Usage -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA
InstallComponent FEFD090D-C133-BC95-B3564F693CD3 -setup Install -type actiongroup -title {Finish Actions} -active Yes -parent ActionGroupsInstall
+<<<<<<< HEAD
InstallComponent DECC120D-6904-7F17-45A49184A5A3 -setup Install -type action -conditions {E44CFF46-6302-C518-B9C30D2E43F7 B0AA6839-AAB6-A602-C0E4ECA2E4FF} -title {Install Desktop Shortcut} -component InstallDesktopShortcut -command insert -active No -parent FEFD090D-C133-BC95-B3564F693CD3
Condition E44CFF46-6302-C518-B9C30D2E43F7 -active Yes -parent DECC120D-6904-7F17-45A49184A5A3 -title {String Is Condition} -component StringIsCondition -TreeObject::id E44CFF46-6302-C518-B9C30D2E43F7
Condition B0AA6839-AAB6-A602-C0E4ECA2E4FF -active Yes -parent DECC120D-6904-7F17-45A49184A5A3 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id B0AA6839-AAB6-A602-C0E4ECA2E4FF
@@ -464,6 +745,20 @@ Condition 2583A547-11DE-1C27-B6D04B023CC0 -active Yes -parent C105AAAE-7C16-2C9E
Condition A6E1B027-A1B4-5848-4F868D028D00 -active Yes -parent C105AAAE-7C16-2C9E-769FE4535B60 -title {String Is Condition} -component StringIsCondition -TreeObject::id A6E1B027-A1B4-5848-4F868D028D00
Condition 0357FAE9-FCFD-26D8-6541D810CD61 -active Yes -parent C105AAAE-7C16-2C9E-769FE4535B60 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 0357FAE9-FCFD-26D8-6541D810CD61
InstallComponent C33D74B2-26FA-16F5-433A10C6A747 -setup Install -type action -conditions {CC4337CC-F3B5-757C-DFCF5D1D365A 795EE61F-6C0D-4A8B-93E02AA3894A 1528F4F0-145C-A48D-A8526DBB6289} -title {Launch Application} -component ExecuteExternalProgram -command insert -active No -parent FEFD090D-C133-BC95-B3564F693CD3
+=======
+InstallComponent DECC120D-6904-7F17-45A49184A5A3 -setup Install -type action -conditions {E44CFF46-6302-C518-B9C30D2E43F7 B0AA6839-AAB6-A602-C0E4ECA2E4FF} -title {Install Desktop Shortcut} -component InstallDesktopShortcut -active No -parent FEFD090D-C133-BC95-B3564F693CD3
+Condition E44CFF46-6302-C518-B9C30D2E43F7 -active Yes -parent DECC120D-6904-7F17-45A49184A5A3 -title {String Is Condition} -component StringIsCondition -TreeObject::id E44CFF46-6302-C518-B9C30D2E43F7
+Condition B0AA6839-AAB6-A602-C0E4ECA2E4FF -active Yes -parent DECC120D-6904-7F17-45A49184A5A3 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id B0AA6839-AAB6-A602-C0E4ECA2E4FF
+InstallComponent 7B770A07-A785-5215-956FA82CF14E -setup Install -type action -conditions {6F94698F-0839-3ABF-0CF2DF05A4C8 738DD098-7E3B-BC89-875CDB93CBE2 8C866252-8760-9B08-FE569C25B60D} -title {Install Quick Launch Shortcut} -component InstallWindowsShortcut -active No -parent FEFD090D-C133-BC95-B3564F693CD3
+Condition 6F94698F-0839-3ABF-0CF2DF05A4C8 -active Yes -parent 7B770A07-A785-5215-956FA82CF14E -title {String Is Condition} -component StringIsCondition -TreeObject::id 6F94698F-0839-3ABF-0CF2DF05A4C8
+Condition 738DD098-7E3B-BC89-875CDB93CBE2 -active Yes -parent 7B770A07-A785-5215-956FA82CF14E -title {Platform Condition} -component PlatformCondition -TreeObject::id 738DD098-7E3B-BC89-875CDB93CBE2
+Condition 8C866252-8760-9B08-FE569C25B60D -active Yes -parent 7B770A07-A785-5215-956FA82CF14E -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 8C866252-8760-9B08-FE569C25B60D
+InstallComponent C105AAAE-7C16-2C9E-769FE4535B60 -setup Install -type action -conditions {2583A547-11DE-1C27-B6D04B023CC0 A6E1B027-A1B4-5848-4F868D028D00 0357FAE9-FCFD-26D8-6541D810CD61} -title {View Readme Window} -component TextWindow -active No -parent FEFD090D-C133-BC95-B3564F693CD3
+Condition 2583A547-11DE-1C27-B6D04B023CC0 -active Yes -parent C105AAAE-7C16-2C9E-769FE4535B60 -title {String Is Condition} -component StringIsCondition -TreeObject::id 2583A547-11DE-1C27-B6D04B023CC0
+Condition A6E1B027-A1B4-5848-4F868D028D00 -active Yes -parent C105AAAE-7C16-2C9E-769FE4535B60 -title {String Is Condition} -component StringIsCondition -TreeObject::id A6E1B027-A1B4-5848-4F868D028D00
+Condition 0357FAE9-FCFD-26D8-6541D810CD61 -active Yes -parent C105AAAE-7C16-2C9E-769FE4535B60 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 0357FAE9-FCFD-26D8-6541D810CD61
+InstallComponent C33D74B2-26FA-16F5-433A10C6A747 -setup Install -type action -conditions {CC4337CC-F3B5-757C-DFCF5D1D365A 795EE61F-6C0D-4A8B-93E02AA3894A 1528F4F0-145C-A48D-A8526DBB6289} -title {Launch Application} -component ExecuteExternalProgram -active No -parent FEFD090D-C133-BC95-B3564F693CD3
+>>>>>>> 0.12
Condition CC4337CC-F3B5-757C-DFCF5D1D365A -active Yes -parent C33D74B2-26FA-16F5-433A10C6A747 -title {String Is Condition} -component StringIsCondition -TreeObject::id CC4337CC-F3B5-757C-DFCF5D1D365A
Condition 795EE61F-6C0D-4A8B-93E02AA3894A -active Yes -parent C33D74B2-26FA-16F5-433A10C6A747 -title {String Is Condition} -component StringIsCondition -TreeObject::id 795EE61F-6C0D-4A8B-93E02AA3894A
Condition 1528F4F0-145C-A48D-A8526DBB6289 -active Yes -parent C33D74B2-26FA-16F5-433A10C6A747 -title {File Exists Condition} -component FileExistsCondition -TreeObject::id 1528F4F0-145C-A48D-A8526DBB6289
@@ -474,6 +769,7 @@ InstallComponent 7A983CD8-302C-4942-BE59-525C5B5FA2F2 -setup Uninstall -type act
InstallComponent E4DEA723-FC78-45D7-BAB1-A3E4C4C96EA1 -setup Uninstall -type action -title {Stop Service} -component ExecuteExternalProgram -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D
InstallComponent B4D31D1E-ADB1-DE8F-18EB7294DDA8 -setup Uninstall -type action -title {Remove Service} -component ExecuteExternalProgram -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D
InstallComponent D55BA4AF-E73B-60D1-E26F79175227 -setup Uninstall -type action -title {Execute Action} -component ExecuteAction -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D
+<<<<<<< HEAD
InstallComponent 69FD7409-5E2A-143B-DABD1C3B1E67 -setup Uninstall -type action -conditions {96A68CAC-9ED7-806C-086B104720FD E161F216-E597-B340-C1A71C476E2C} -title {Uninstall Leftover Files} -component UninstallLeftoverFiles -command insert -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D
Condition 96A68CAC-9ED7-806C-086B104720FD -active Yes -parent 69FD7409-5E2A-143B-DABD1C3B1E67 -title {String Is Condition} -component StringIsCondition -TreeObject::id 96A68CAC-9ED7-806C-086B104720FD
Condition E161F216-E597-B340-C1A71C476E2C -active Yes -parent 69FD7409-5E2A-143B-DABD1C3B1E67 -title {Ask Yes or No} -component AskYesOrNo -TreeObject::id E161F216-E597-B340-C1A71C476E2C
@@ -483,6 +779,17 @@ Condition EB2B31A1-C111-3582-0C8A5656692A -active Yes -parent 41D3E165-C263-5F80
InstallComponent 3D33AA8C-0037-204B-39A339FD38BD -setup Uninstall -type pane -title {Uninstall Complete} -component UninstallComplete -active Yes -parent StandardUninstall
InstallComponent 49E59F91-27F7-46D1-A1C1-19865C2392D3 -setup Uninstall -type action -title {Console Ask Yes Or No} -component ConsoleAskYesOrNo -active Yes -parent ConsoleUninstall
InstallComponent ADA6EB2F-8820-4366-BBEF-ED1335B7F828 -setup Uninstall -type action -conditions 87DE6D78-81E1-495B-A214-B3FF3E7E5614 -title Exit -component Exit -command insert -active Yes -parent ConsoleUninstall
+=======
+InstallComponent 69FD7409-5E2A-143B-DABD1C3B1E67 -setup Uninstall -type action -conditions {96A68CAC-9ED7-806C-086B104720FD E161F216-E597-B340-C1A71C476E2C} -title {Uninstall Leftover Files} -component UninstallLeftoverFiles -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D
+Condition 96A68CAC-9ED7-806C-086B104720FD -active Yes -parent 69FD7409-5E2A-143B-DABD1C3B1E67 -title {String Is Condition} -component StringIsCondition -TreeObject::id 96A68CAC-9ED7-806C-086B104720FD
+Condition E161F216-E597-B340-C1A71C476E2C -active Yes -parent 69FD7409-5E2A-143B-DABD1C3B1E67 -title {Ask Yes or No} -component AskYesOrNo -TreeObject::id E161F216-E597-B340-C1A71C476E2C
+InstallComponent 05060263-E852-87AB-8D0F2954CAA6 -setup Uninstall -type action -title {Move Forward} -component MoveForward -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D
+InstallComponent 41D3E165-C263-5F80-0FEEC0AEE47A -setup Uninstall -type pane -conditions EB2B31A1-C111-3582-0C8A5656692A -title {Uninstall Details} -component UninstallDetails -active Yes -parent StandardUninstall
+Condition EB2B31A1-C111-3582-0C8A5656692A -active Yes -parent 41D3E165-C263-5F80-0FEEC0AEE47A -title {String Is Condition} -component StringIsCondition -TreeObject::id EB2B31A1-C111-3582-0C8A5656692A
+InstallComponent 3D33AA8C-0037-204B-39A339FD38BD -setup Uninstall -type pane -title {Uninstall Complete} -component UninstallComplete -active Yes -parent StandardUninstall
+InstallComponent 49E59F91-27F7-46D1-A1C1-19865C2392D3 -setup Uninstall -type action -title {Console Ask Yes Or No} -component ConsoleAskYesOrNo -active Yes -parent ConsoleUninstall
+InstallComponent ADA6EB2F-8820-4366-BBEF-ED1335B7F828 -setup Uninstall -type action -conditions 87DE6D78-81E1-495B-A214-B3FF3E7E5614 -title Exit -component Exit -active Yes -parent ConsoleUninstall
+>>>>>>> 0.12
Condition 87DE6D78-81E1-495B-A214-B3FF3E7E5614 -active Yes -parent ADA6EB2F-8820-4366-BBEF-ED1335B7F828 -title {String Is Condition} -component StringIsCondition -TreeObject::id 87DE6D78-81E1-495B-A214-B3FF3E7E5614
InstallComponent B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174 -setup Uninstall -type action -title {Console Message} -component ConsoleMessage -active Yes -parent ConsoleUninstall
InstallComponent 3C7130B3-3206-403D-B09E-59D4A758FBAD -setup Uninstall -type action -title {Execute Action} -component ExecuteAction -active Yes -parent ConsoleUninstall
@@ -491,7 +798,11 @@ InstallComponent 7F85263E-CAE2-46BA-AAC0-6B89D20FD2DE -setup Uninstall -type act
InstallComponent 17D8BA8E-5992-AA5C-F5ECB73A3433 -setup Uninstall -type action -title {Execute Action} -component ExecuteAction -active Yes -parent SilentUninstall
InstallComponent D3D73C76-D9D3-07DA-63D4163A44BE -setup Uninstall -type action -title Exit -component Exit -active Yes -parent SilentUninstall
InstallComponent 848844B5-6103-9343-8B731B0BE4E0 -setup Uninstall -type actiongroup -title {Startup Actions} -active Yes -parent ActionGroupsUninstall
+<<<<<<< HEAD
InstallComponent 97ACF525-C075-8635-E019202A83D8 -setup Uninstall -type action -conditions {DFFF91A9-2CA5-6ABE-8474D814AF88 4ACB0B47-42B3-2B3A-BFE9AA4EC707} -title Exit -component Exit -command insert -active Yes -parent 848844B5-6103-9343-8B731B0BE4E0
+=======
+InstallComponent 97ACF525-C075-8635-E019202A83D8 -setup Uninstall -type action -conditions {DFFF91A9-2CA5-6ABE-8474D814AF88 4ACB0B47-42B3-2B3A-BFE9AA4EC707} -title Exit -component Exit -active Yes -parent 848844B5-6103-9343-8B731B0BE4E0
+>>>>>>> 0.12
Condition DFFF91A9-2CA5-6ABE-8474D814AF88 -active Yes -parent 97ACF525-C075-8635-E019202A83D8 -title {String Is Condition} -component StringIsCondition -TreeObject::id DFFF91A9-2CA5-6ABE-8474D814AF88
Condition 4ACB0B47-42B3-2B3A-BFE9AA4EC707 -active Yes -parent 97ACF525-C075-8635-E019202A83D8 -title {Ask Yes or No} -component AskYesOrNo -TreeObject::id 4ACB0B47-42B3-2B3A-BFE9AA4EC707
InstallComponent F4024A3E-9A6D-2726-5E0CFFA93054 -setup Uninstall -type actiongroup -title {Uninstall Actions} -active Yes -parent ActionGroupsUninstall
@@ -501,7 +812,11 @@ InstallComponent 905DA2E9-988C-2F27-BB1F5F274AC9 -setup Uninstall -type actiongr
array set Properties {
0047FF40-0139-2A59-AAC0-A44D46D6F5CC,Active
+<<<<<<< HEAD
No
+=======
+{No}
+>>>>>>> 0.12
0047FF40-0139-2A59-AAC0-A44D46D6F5CC,Comment
{set BackupLocationName "BackupLocation_${BackupLocationNumber}"}
@@ -513,7 +828,11 @@ No
{Before Next Pane is Displayed}
0047FF40-0139-2A59-AAC0-A44D46D6F5CC,ResultVirtualText
+<<<<<<< HEAD
BackupLocationName
+=======
+{BackupLocationName}
+>>>>>>> 0.12
0047FF40-0139-2A59-AAC0-A44D46D6F5CC,TclScript
{set BackupLocationName "BackupLocation_${BackupLocationNumber}"}
@@ -522,22 +841,37 @@ BackupLocationName
{Before Action is Executed}
0357FAE9-FCFD-26D8-6541D810CD61,Filename
+<<<<<<< HEAD
<%ProgramReadme%>
+=======
+{<%ProgramReadme%>}
+>>>>>>> 0.12
05060263-E852-87AB-8D0F2954CAA6,Conditions
{0 conditions}
0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5,Prompt
+<<<<<<< HEAD
<%ConsoleSelectDestinationText%>
0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5,VirtualText
InstallDir
+=======
+{<%ConsoleSelectDestinationText%>}
+
+0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5,VirtualText
+{InstallDir}
+>>>>>>> 0.12
0D93323D-779D-44A8-1E0614E5285D,Conditions
{0 conditions}
0D93323D-779D-44A8-1E0614E5285D,State
+<<<<<<< HEAD
disabled
+=======
+{disabled}
+>>>>>>> 0.12
0D93323D-779D-44A8-1E0614E5285D,Widget
{Back Button;Next Button}
@@ -549,7 +883,11 @@ disabled
{Before Next Pane is Displayed}
0FDBA082-90AB-808C-478A-A13E7C525336,ResultVirtualText
+<<<<<<< HEAD
BackupLocationNumber
+=======
+{BackupLocationNumber}
+>>>>>>> 0.12
0FDBA082-90AB-808C-478A-A13E7C525336,TclScript
{set BackupLocationNumber 1}
@@ -558,13 +896,21 @@ BackupLocationNumber
{Before Action is Executed}
13BD88FE-CD71-5AC7-E99C10B6CB28,Filename
+<<<<<<< HEAD
<%ProgramReadme%>
+=======
+{<%ProgramReadme%>}
+>>>>>>> 0.12
1528F4F0-145C-A48D-A8526DBB6289,CheckCondition
{Before Action is Executed}
1528F4F0-145C-A48D-A8526DBB6289,Filename
+<<<<<<< HEAD
<%ProgramExecutable%>
+=======
+{<%ProgramExecutable%>}
+>>>>>>> 0.12
1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,Alias
{Stop Backup Windows Process}
@@ -573,12 +919,17 @@ BackupLocationNumber
{0 conditions}
1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-killbackupprocess
+=======
+{<%ShortAppName%>-program-killbackupprocess}
+>>>>>>> 0.12
1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,ShortcutName
{Stop backup process}
1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,TargetFileName
+<<<<<<< HEAD
<%InstallDir%>/tools/KillBackupProcess.bat
1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,WorkingDirectory
@@ -586,11 +937,21 @@ BackupLocationNumber
16D53E40-546B-54C3-088B1B5E3BBB,Background
white
+=======
+{<%InstallDir%>/tools/KillBackupProcess.bat}
+
+1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,WorkingDirectory
+{<%InstallDir%>}
+
+16D53E40-546B-54C3-088B1B5E3BBB,Background
+{white}
+>>>>>>> 0.12
16D53E40-546B-54C3-088B1B5E3BBB,Conditions
{2 conditions}
16D53E40-546B-54C3-088B1B5E3BBB,Text,subst
+<<<<<<< HEAD
1
16D53E40-546B-54C3-088B1B5E3BBB,Type
@@ -607,6 +968,24 @@ CreateDesktopShortcut
175CBE81-9EBE-1E21-A91479BEEFAE,ExitType
Finish
+=======
+{1}
+
+16D53E40-546B-54C3-088B1B5E3BBB,Type
+{checkbutton}
+
+16D53E40-546B-54C3-088B1B5E3BBB,VirtualText
+{CreateDesktopShortcut}
+
+16D53E40-546B-54C3-088B1B5E3BBB,X
+{185}
+
+16D53E40-546B-54C3-088B1B5E3BBB,Y
+{180}
+
+175CBE81-9EBE-1E21-A91479BEEFAE,ExitType
+{Finish}
+>>>>>>> 0.12
17D8BA8E-5992-AA5C-F5ECB73A3433,Action
{Uninstall Actions}
@@ -618,7 +997,11 @@ Finish
{Before Action is Executed}
18C00430-D6B1-151F-307762B3A045,Platform
+<<<<<<< HEAD
Windows
+=======
+{Windows}
+>>>>>>> 0.12
198905FB-9FAC-23DE-7422D25B8ECA,Alias
{Install Actions}
@@ -630,13 +1013,21 @@ Windows
{0 conditions}
19ADBDDB-1690-4A57-913E32A026C4,State
+<<<<<<< HEAD
disabled
+=======
+{disabled}
+>>>>>>> 0.12
19ADBDDB-1690-4A57-913E32A026C4,Widget
{NextButton; CancelButton}
1AF5CD58-65C0-49CB-9A9D-994816CF414E,Active
+<<<<<<< HEAD
No
+=======
+{No}
+>>>>>>> 0.12
1AF5CD58-65C0-49CB-9A9D-994816CF414E,Alias
{Upload File Listing}
@@ -648,12 +1039,17 @@ No
{0 conditions}
1AF5CD58-65C0-49CB-9A9D-994816CF414E,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-TebucoSafeQuerypload
+=======
+{<%ShortAppName%>-program-TebucoSafeQuerypload}
+>>>>>>> 0.12
1AF5CD58-65C0-49CB-9A9D-994816CF414E,ShortcutName
{Upload Filelisting to TebucoSafe for review}
1AF5CD58-65C0-49CB-9A9D-994816CF414E,TargetFileName
+<<<<<<< HEAD
<%InstallDir%>/tools/TebucoSafeQueryUpload.bat
1AF5CD58-65C0-49CB-9A9D-994816CF414E,WorkingDirectory
@@ -673,11 +1069,30 @@ Yes
1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,CompanyLabel,subst
0
+=======
+{<%InstallDir%>/tools/TebucoSafeQueryUpload.bat}
+
+1AF5CD58-65C0-49CB-9A9D-994816CF414E,WorkingDirectory
+{<%InstallDir%>}
+
+1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,BackButton,subst
+{1}
+
+1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,CancelButton,subst
+{1}
+
+1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Caption,subst
+{1}
+
+1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,CompanyLabel,subst
+{0}
+>>>>>>> 0.12
1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Conditions
{0 conditions}
1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Message,subst
+<<<<<<< HEAD
1
1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,NextButton,subst
@@ -694,6 +1109,24 @@ Yes
1C14291C-0971-4283-92E9-3808401303F5,Active
No
+=======
+{1}
+
+1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,NextButton,subst
+{1}
+
+1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Subtitle,subst
+{1}
+
+1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Title,subst
+{1}
+
+1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,UserNameLabel,subst
+{0}
+
+1C14291C-0971-4283-92E9-3808401303F5,Active
+{No}
+>>>>>>> 0.12
1C14291C-0971-4283-92E9-3808401303F5,Comment
{Don't start it yet, need to install keys by hand.}
@@ -705,6 +1138,7 @@ No
{net start <%ServiceName%>}
1C14291C-0971-4283-92E9-3808401303F5,WorkingDirectory
+<<<<<<< HEAD
<%InstallDir%>
1F0926EE-6884-1330-B4A1DB11C1BF,BackButton,subst
@@ -721,6 +1155,24 @@ No
1F0926EE-6884-1330-B4A1DB11C1BF,NextButton,subst
1
+=======
+{<%InstallDir%>}
+
+1F0926EE-6884-1330-B4A1DB11C1BF,BackButton,subst
+{1}
+
+1F0926EE-6884-1330-B4A1DB11C1BF,CancelButton,subst
+{1}
+
+1F0926EE-6884-1330-B4A1DB11C1BF,Caption,subst
+{1}
+
+1F0926EE-6884-1330-B4A1DB11C1BF,Message,subst
+{1}
+
+1F0926EE-6884-1330-B4A1DB11C1BF,NextButton,subst
+{1}
+>>>>>>> 0.12
1F9E8CB8-02C1-0416-1F7445B4147F,Comment
{Ask the user if they want to proceed with the install.}
@@ -729,25 +1181,43 @@ No
{2 conditions}
20CBDBEA-2217-457B-8D98-D692C4F591E9,Message,subst
+<<<<<<< HEAD
1
+=======
+{1}
+>>>>>>> 0.12
241BBFCE-4EB1-432F-94DD-69D444DDB6C0,CheckCondition
{Before Action is Executed}
241BBFCE-4EB1-432F-94DD-69D444DDB6C0,Operator
+<<<<<<< HEAD
false
241BBFCE-4EB1-432F-94DD-69D444DDB6C0,String
<%Answer%>
+=======
+{false}
+
+241BBFCE-4EB1-432F-94DD-69D444DDB6C0,String
+{<%Answer%>}
+>>>>>>> 0.12
2583A547-11DE-1C27-B6D04B023CC0,CheckCondition
{Before Action is Executed}
2583A547-11DE-1C27-B6D04B023CC0,Operator
+<<<<<<< HEAD
false
2583A547-11DE-1C27-B6D04B023CC0,String
<%SilentMode%>
+=======
+{false}
+
+2583A547-11DE-1C27-B6D04B023CC0,String
+{<%SilentMode%>}
+>>>>>>> 0.12
25AA533E-02FC-47D9-9273-25266B8FA1F9,Alias
{Remove Backup Service}
@@ -759,12 +1229,17 @@ false
{0 conditions}
25AA533E-02FC-47D9-9273-25266B8FA1F9,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-removeService
+=======
+{<%ShortAppName%>-program-removeService}
+>>>>>>> 0.12
25AA533E-02FC-47D9-9273-25266B8FA1F9,ShortcutName
{Remove Service}
25AA533E-02FC-47D9-9273-25266B8FA1F9,TargetFileName
+<<<<<<< HEAD
<%InstallDir%>/tools/RemoveService.bat
25AA533E-02FC-47D9-9273-25266B8FA1F9,WorkingDirectory
@@ -772,6 +1247,15 @@ false
28E76C8B-2605-4739-9FFE-9C2880C17E59,Active
No
+=======
+{<%InstallDir%>/tools/RemoveService.bat}
+
+25AA533E-02FC-47D9-9273-25266B8FA1F9,WorkingDirectory
+{<%InstallDir%>}
+
+28E76C8B-2605-4739-9FFE-9C2880C17E59,Active
+{No}
+>>>>>>> 0.12
28E76C8B-2605-4739-9FFE-9C2880C17E59,Conditions
{0 conditions}
@@ -780,6 +1264,7 @@ No
{notepad <%ConfigFileName%>}
28E76C8B-2605-4739-9FFE-9C2880C17E59,WorkingDirectory
+<<<<<<< HEAD
<%InstallDir%>
2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,BackButton,subst
@@ -790,11 +1275,24 @@ No
2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Caption,subst
1
+=======
+{<%InstallDir%>}
+
+2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,BackButton,subst
+{1}
+
+2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,CancelButton,subst
+{1}
+
+2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Caption,subst
+{1}
+>>>>>>> 0.12
2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Conditions
{1 condition}
2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Message,subst
+<<<<<<< HEAD
1
2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,NextButton,subst
@@ -805,6 +1303,18 @@ No
2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Title,subst
1
+=======
+{1}
+
+2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,NextButton,subst
+{1}
+
+2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Subtitle,subst
+{1}
+
+2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Title,subst
+{1}
+>>>>>>> 0.12
2BB06B72-DE53-2319-B1B8-351CDCBA2008,Conditions
{0 conditions}
@@ -813,18 +1323,33 @@ No
{Before Next Pane is Displayed}
2BB06B72-DE53-2319-B1B8-351CDCBA2008,ResultVirtualText
+<<<<<<< HEAD
AddBackupLocation
+=======
+{AddBackupLocation}
+>>>>>>> 0.12
2BB06B72-DE53-2319-B1B8-351CDCBA2008,TclScript
{set AddBackupLocation no}
2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message,subst
+<<<<<<< HEAD
1
+=======
+{1}
+
+2C456223-3E1E-4D43-B31A-868EAD3241E1,Destination
+{<%InstallDir%>}
+
+2C456223-3E1E-4D43-B31A-868EAD3241E1,Name
+{Documents}
+>>>>>>> 0.12
2E2963BD-DDBD-738D-A910-B7F3F04946F9,Conditions
{0 conditions}
2E2963BD-DDBD-738D-A910-B7F3F04946F9,Text,subst
+<<<<<<< HEAD
1
2E2963BD-DDBD-738D-A910-B7F3F04946F9,Value
@@ -838,6 +1363,21 @@ AddBackupLocation
2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,AppendNewline
No
+=======
+{1}
+
+2E2963BD-DDBD-738D-A910-B7F3F04946F9,Value
+{<%AddBackupLocation%>}
+
+2E2963BD-DDBD-738D-A910-B7F3F04946F9,X
+{400}
+
+2E2963BD-DDBD-738D-A910-B7F3F04946F9,Y
+{70}
+
+2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,AppendNewline
+{No}
+>>>>>>> 0.12
2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,Comment
{.conf doesn't exist yet}
@@ -849,15 +1389,23 @@ No
{Append to file}
2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,Files
+<<<<<<< HEAD
<%ConfigFileTemplate%>
2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,TextToWrite,subst
1
+=======
+{<%ConfigFileTemplate%>}
+
+2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,TextToWrite,subst
+{1}
+>>>>>>> 0.12
32B08FB1-99DF-234E-8BAF-333E80AAC9F5,Conditions
{0 conditions}
32B08FB1-99DF-234E-8BAF-333E80AAC9F5,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-Usage
32B08FB1-99DF-234E-8BAF-333E80AAC9F5,ShortcutName
@@ -868,6 +1416,18 @@ Usage
32B08FB1-99DF-234E-8BAF-333E80AAC9F5,WorkingDirectory
<%InstallDir%>
+=======
+{<%ShortAppName%>-program-Usage}
+
+32B08FB1-99DF-234E-8BAF-333E80AAC9F5,ShortcutName
+{Usage}
+
+32B08FB1-99DF-234E-8BAF-333E80AAC9F5,TargetFileName
+{<%InstallDir%>/tools/ShowUsage.bat}
+
+32B08FB1-99DF-234E-8BAF-333E80AAC9F5,WorkingDirectory
+{<%InstallDir%>}
+>>>>>>> 0.12
32DC8FB1-A04B-71AA-EC18496D4BD0,Conditions
{0 conditions}
@@ -876,6 +1436,7 @@ Usage
{Before Action is Executed}
32F5B0AF-EB83-7A03-D8FAE1ECE473,Message,subst
+<<<<<<< HEAD
1
32F5B0AF-EB83-7A03-D8FAE1ECE473,Title,subst
@@ -883,11 +1444,21 @@ Usage
32F5B0AF-EB83-7A03-D8FAE1ECE473,TrueValue
No
+=======
+{1}
+
+32F5B0AF-EB83-7A03-D8FAE1ECE473,Title,subst
+{1}
+
+32F5B0AF-EB83-7A03-D8FAE1ECE473,TrueValue
+{No}
+>>>>>>> 0.12
3379F80B-36D6-73DC-6FC1D6223A26,CheckCondition
{Before Action is Executed}
3379F80B-36D6-73DC-6FC1D6223A26,Operator
+<<<<<<< HEAD
false
3379F80B-36D6-73DC-6FC1D6223A26,String
@@ -895,17 +1466,31 @@ false
362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,Active
No
+=======
+{false}
+
+3379F80B-36D6-73DC-6FC1D6223A26,String
+{<%InstallStopped%>}
+
+362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,Active
+{No}
+>>>>>>> 0.12
362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,Conditions
{0 conditions}
362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,ResultVirtualText
+<<<<<<< HEAD
BackupLocationExclusions
+=======
+{BackupLocationExclusions}
+>>>>>>> 0.12
362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,TclScript
{set BackupLocationExclusions ""}
37E627F2-E04B-AEF2-D566C017A4D6,BackButton,subst
+<<<<<<< HEAD
1
37E627F2-E04B-AEF2-D566C017A4D6,CancelButton,subst
@@ -913,11 +1498,21 @@ BackupLocationExclusions
37E627F2-E04B-AEF2-D566C017A4D6,Caption,subst
1
+=======
+{1}
+
+37E627F2-E04B-AEF2-D566C017A4D6,CancelButton,subst
+{1}
+
+37E627F2-E04B-AEF2-D566C017A4D6,Caption,subst
+{1}
+>>>>>>> 0.12
37E627F2-E04B-AEF2-D566C017A4D6,Conditions
{0 conditions}
37E627F2-E04B-AEF2-D566C017A4D6,FileLabel,subst
+<<<<<<< HEAD
1
37E627F2-E04B-AEF2-D566C017A4D6,Message,subst
@@ -934,6 +1529,24 @@ BackupLocationExclusions
37E627F2-E04B-AEF2-D566C017A4D6,Title,subst
1
+=======
+{1}
+
+37E627F2-E04B-AEF2-D566C017A4D6,Message,subst
+{1}
+
+37E627F2-E04B-AEF2-D566C017A4D6,NextButton,subst
+{1}
+
+37E627F2-E04B-AEF2-D566C017A4D6,ProgressValue,subst
+{1}
+
+37E627F2-E04B-AEF2-D566C017A4D6,Subtitle,subst
+{1}
+
+37E627F2-E04B-AEF2-D566C017A4D6,Title,subst
+{1}
+>>>>>>> 0.12
39270FD8-932E-6132-7EF795ED9B93,Alias
{Finish Actions}
@@ -948,21 +1561,33 @@ BackupLocationExclusions
{Uninstall <%BrandName%>}
39B2B666-78D8-75E6-6EA071594D34,TargetFileName
+<<<<<<< HEAD
<%Uninstaller%>
39B2B666-78D8-75E6-6EA071594D34,WorkingDirectory
<%InstallDir%>
+=======
+{<%Uninstaller%>}
+
+39B2B666-78D8-75E6-6EA071594D34,WorkingDirectory
+{<%InstallDir%>}
+>>>>>>> 0.12
39D7394E-04E9-CA70-0034DB830BFE,Conditions
{0 conditions}
3B6E2E7C-1A26-27F1-D578E383B128,Background
+<<<<<<< HEAD
white
+=======
+{white}
+>>>>>>> 0.12
3B6E2E7C-1A26-27F1-D578E383B128,Conditions
{2 conditions}
3B6E2E7C-1A26-27F1-D578E383B128,Text,subst
+<<<<<<< HEAD
1
3B6E2E7C-1A26-27F1-D578E383B128,Type
@@ -985,11 +1610,36 @@ ViewReadme
3B8CDC8E-1239-D2E9-DF4CA6B1756D,Caption,subst
1
+=======
+{1}
+
+3B6E2E7C-1A26-27F1-D578E383B128,Type
+{checkbutton}
+
+3B6E2E7C-1A26-27F1-D578E383B128,VirtualText
+{ViewReadme}
+
+3B6E2E7C-1A26-27F1-D578E383B128,X
+{185}
+
+3B6E2E7C-1A26-27F1-D578E383B128,Y
+{140}
+
+3B8CDC8E-1239-D2E9-DF4CA6B1756D,BackButton,subst
+{1}
+
+3B8CDC8E-1239-D2E9-DF4CA6B1756D,CancelButton,subst
+{1}
+
+3B8CDC8E-1239-D2E9-DF4CA6B1756D,Caption,subst
+{1}
+>>>>>>> 0.12
3B8CDC8E-1239-D2E9-DF4CA6B1756D,Conditions
{0 conditions}
3B8CDC8E-1239-D2E9-DF4CA6B1756D,FileValue,subst
+<<<<<<< HEAD
1
3B8CDC8E-1239-D2E9-DF4CA6B1756D,Message,subst
@@ -1006,6 +1656,24 @@ ViewReadme
3B8CDC8E-1239-D2E9-DF4CA6B1756D,Title,subst
1
+=======
+{1}
+
+3B8CDC8E-1239-D2E9-DF4CA6B1756D,Message,subst
+{1}
+
+3B8CDC8E-1239-D2E9-DF4CA6B1756D,NextButton,subst
+{1}
+
+3B8CDC8E-1239-D2E9-DF4CA6B1756D,ProgressValue,subst
+{1}
+
+3B8CDC8E-1239-D2E9-DF4CA6B1756D,Subtitle,subst
+{1}
+
+3B8CDC8E-1239-D2E9-DF4CA6B1756D,Title,subst
+{1}
+>>>>>>> 0.12
3C7130B3-3206-403D-B09E-59D4A758FBAD,Action
{Uninstall Actions}
@@ -1014,21 +1682,33 @@ ViewReadme
{0 conditions}
3CFFF099-6122-46DD-9CE4-F5819434AC53,IgnoreErrors
+<<<<<<< HEAD
Yes
+=======
+{Yes}
+>>>>>>> 0.12
3CFFF099-6122-46DD-9CE4-F5819434AC53,ProgramCommandLine
{net stop <%ServiceName%>}
3CFFF099-6122-46DD-9CE4-F5819434AC53,ProgressiveOutputWidget
+<<<<<<< HEAD
Message
3CFFF099-6122-46DD-9CE4-F5819434AC53,WorkingDirectory
<%Temp%>
+=======
+{Message}
+
+3CFFF099-6122-46DD-9CE4-F5819434AC53,WorkingDirectory
+{<%Temp%>}
+>>>>>>> 0.12
3D0D1898-4C65-3E66-F82F56581E87,CheckCondition
{Before Action is Executed}
3D0D1898-4C65-3E66-F82F56581E87,Operator
+<<<<<<< HEAD
false
3D0D1898-4C65-3E66-F82F56581E87,String
@@ -1042,11 +1722,27 @@ false
3D33AA8C-0037-204B-39A339FD38BD,Caption,subst
1
+=======
+{false}
+
+3D0D1898-4C65-3E66-F82F56581E87,String
+{<%SilentMode%>}
+
+3D33AA8C-0037-204B-39A339FD38BD,BackButton,subst
+{1}
+
+3D33AA8C-0037-204B-39A339FD38BD,CancelButton,subst
+{1}
+
+3D33AA8C-0037-204B-39A339FD38BD,Caption,subst
+{1}
+>>>>>>> 0.12
3D33AA8C-0037-204B-39A339FD38BD,Conditions
{0 conditions}
3D33AA8C-0037-204B-39A339FD38BD,Message,subst
+<<<<<<< HEAD
1
3D33AA8C-0037-204B-39A339FD38BD,NextButton,subst
@@ -1066,11 +1762,33 @@ SetBackupLocations
3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Caption,subst
1
+=======
+{1}
+
+3D33AA8C-0037-204B-39A339FD38BD,NextButton,subst
+{1}
+
+3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Active
+{Yes}
+
+3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Alias
+{SetBackupLocations}
+
+3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,BackButton,subst
+{1}
+
+3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,CancelButton,subst
+{1}
+
+3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Caption,subst
+{1}
+>>>>>>> 0.12
3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Conditions
{0 conditions}
3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Message,subst
+<<<<<<< HEAD
1
3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,NextButton,subst
@@ -1081,20 +1799,40 @@ SetBackupLocations
3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Title,subst
1
+=======
+{1}
+
+3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,NextButton,subst
+{1}
+
+3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Subtitle,subst
+{1}
+
+3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Title,subst
+{1}
+>>>>>>> 0.12
3FDB57ED-598D-8A4E-CEF7-D90833305558,Conditions
{0 conditions}
3FDB57ED-598D-8A4E-CEF7-D90833305558,LabelSide
+<<<<<<< HEAD
left
3FDB57ED-598D-8A4E-CEF7-D90833305558,Text,subst
1
+=======
+{left}
+
+3FDB57ED-598D-8A4E-CEF7-D90833305558,Text,subst
+{1}
+>>>>>>> 0.12
3FDB57ED-598D-8A4E-CEF7-D90833305558,Type
{browse entry}
3FDB57ED-598D-8A4E-CEF7-D90833305558,VirtualText
+<<<<<<< HEAD
BackupLocationPath
3FDB57ED-598D-8A4E-CEF7-D90833305558,Y
@@ -1105,6 +1843,18 @@ Yes
3FE82C17-A3E2-4A57-A563-F80818B00B81,Prompt
<%InstallStartupText%>
+=======
+{BackupLocationPath}
+
+3FDB57ED-598D-8A4E-CEF7-D90833305558,Y
+{70}
+
+3FE82C17-A3E2-4A57-A563-F80818B00B81,Default
+{Yes}
+
+3FE82C17-A3E2-4A57-A563-F80818B00B81,Prompt
+{<%InstallStartupText%>}
+>>>>>>> 0.12
41CDE776-2667-5CEB-312A-FC4C33A83E7F,Conditions
{0 conditions}
@@ -1113,6 +1863,7 @@ Yes
{*/*.conf;*/*.txt;*/*.pem;*/*.raw;*/*.exe;*/*.bat;*/*.dll}
41CDE776-2667-5CEB-312A-FC4C33A83E7F,RenameFiles
+<<<<<<< HEAD
Yes
41D3E165-C263-5F80-0FEEC0AEE47A,BackButton,subst
@@ -1123,11 +1874,24 @@ Yes
41D3E165-C263-5F80-0FEEC0AEE47A,Caption,subst
1
+=======
+{Yes}
+
+41D3E165-C263-5F80-0FEEC0AEE47A,BackButton,subst
+{1}
+
+41D3E165-C263-5F80-0FEEC0AEE47A,CancelButton,subst
+{1}
+
+41D3E165-C263-5F80-0FEEC0AEE47A,Caption,subst
+{1}
+>>>>>>> 0.12
41D3E165-C263-5F80-0FEEC0AEE47A,Conditions
{1 condition}
41D3E165-C263-5F80-0FEEC0AEE47A,Message,subst
+<<<<<<< HEAD
1
41D3E165-C263-5F80-0FEEC0AEE47A,NextButton,subst
@@ -1147,15 +1911,43 @@ Yes
481451CC-F49C-D389-8645076F595B,FileSize
15288767
+=======
+{1}
+
+41D3E165-C263-5F80-0FEEC0AEE47A,NextButton,subst
+{1}
+
+41D3E165-C263-5F80-0FEEC0AEE47A,Subtitle,subst
+{1}
+
+41D3E165-C263-5F80-0FEEC0AEE47A,Text,subst
+{1}
+
+41D3E165-C263-5F80-0FEEC0AEE47A,Title,subst
+{1}
+
+481451CC-F49C-D389-8645076F595B,Destination
+{<%InstallDir%>}
+
+481451CC-F49C-D389-8645076F595B,FileSize
+{7893504}
+>>>>>>> 0.12
481451CC-F49C-D389-8645076F595B,Name
{Program Files}
49E59F91-27F7-46D1-A1C1-19865C2392D3,Default
+<<<<<<< HEAD
Yes
49E59F91-27F7-46D1-A1C1-19865C2392D3,Prompt
<%UninstallStartupText%>
+=======
+{Yes}
+
+49E59F91-27F7-46D1-A1C1-19865C2392D3,Prompt
+{<%UninstallStartupText%>}
+>>>>>>> 0.12
49E80443-62DB-1C10-392D-1091AEA5ED88,Conditions
{1 condition}
@@ -1164,6 +1956,7 @@ Yes
{Before Next Pane is Displayed}
49E80443-62DB-1C10-392D-1091AEA5ED88,Pane
+<<<<<<< HEAD
3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7
4A9C852B-647E-EED5-5482FFBCC2AF,Description,subst
@@ -1174,17 +1967,34 @@ Yes
4A9C852B-647E-EED5-5482FFBCC2AF,FileGroups
481451CC-F49C-D389-8645076F595B
+=======
+{3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7}
+
+4A9C852B-647E-EED5-5482FFBCC2AF,Description,subst
+{1}
+
+4A9C852B-647E-EED5-5482FFBCC2AF,DisplayName,subst
+{1}
+
+4A9C852B-647E-EED5-5482FFBCC2AF,FileGroups
+{481451CC-F49C-D389-8645076F595B}
+>>>>>>> 0.12
4A9C852B-647E-EED5-5482FFBCC2AF,Name
{Default Component}
4A9C852B-647E-EED5-5482FFBCC2AF,RequiredComponent
+<<<<<<< HEAD
Yes
+=======
+{Yes}
+>>>>>>> 0.12
4ACB0B47-42B3-2B3A-BFE9AA4EC707,CheckCondition
{Before Action is Executed}
4ACB0B47-42B3-2B3A-BFE9AA4EC707,Message,subst
+<<<<<<< HEAD
1
4ACB0B47-42B3-2B3A-BFE9AA4EC707,Title,subst
@@ -1192,42 +2002,77 @@ Yes
4ACB0B47-42B3-2B3A-BFE9AA4EC707,TrueValue
No
+=======
+{1}
+
+4ACB0B47-42B3-2B3A-BFE9AA4EC707,Title,subst
+{1}
+
+4ACB0B47-42B3-2B3A-BFE9AA4EC707,TrueValue
+{No}
+>>>>>>> 0.12
4D4A7BF0-7CCE-46E6-BDE5222F82D7,Conditions
{0 conditions}
4D4A7BF0-7CCE-46E6-BDE5222F82D7,UpdateFilePercentage
+<<<<<<< HEAD
Yes
4D4A7BF0-7CCE-46E6-BDE5222F82D7,UpdateFileText
Yes
+=======
+{Yes}
+
+4D4A7BF0-7CCE-46E6-BDE5222F82D7,UpdateFileText
+{Yes}
+>>>>>>> 0.12
4E643D8A-CA31-018D-57D7053C2CE8,CheckCondition
{Before Action is Executed}
4E643D8A-CA31-018D-57D7053C2CE8,Filename
+<<<<<<< HEAD
<%ProgramExecutable%>
+=======
+{<%ProgramExecutable%>}
+>>>>>>> 0.12
4EE35849-FAD7-170B-0E45-FA30636467B1,CheckCondition
{Before Next Pane is Displayed}
4EE35849-FAD7-170B-0E45-FA30636467B1,EncryptedPassword
+<<<<<<< HEAD
<%InstallPasswordEncrypted%>
+=======
+{<%InstallPasswordEncrypted%>}
+>>>>>>> 0.12
4EE35849-FAD7-170B-0E45-FA30636467B1,FailureFocus
{Password Entry}
4EE35849-FAD7-170B-0E45-FA30636467B1,FailureMessage
+<<<<<<< HEAD
<%PasswordIncorrectText%>
4EE35849-FAD7-170B-0E45-FA30636467B1,UnencryptedPassword
<%InstallPassword%>
+=======
+{<%PasswordIncorrectText%>}
+
+4EE35849-FAD7-170B-0E45-FA30636467B1,UnencryptedPassword
+{<%InstallPassword%>}
+>>>>>>> 0.12
4EF787E3-0643-DE46-15E64BAF0816,CheckCondition
{Before Action is Executed}
4EF787E3-0643-DE46-15E64BAF0816,Platform
+<<<<<<< HEAD
Windows
+=======
+{Windows}
+>>>>>>> 0.12
52F0A238-57E1-A578-2CE4DA177B32,Conditions
{0 conditions}
@@ -1236,6 +2081,7 @@ Windows
{0 conditions}
574198A7-7322-2F5E-02EF185D965C,BackButton,subst
+<<<<<<< HEAD
1
574198A7-7322-2F5E-02EF185D965C,CancelButton,subst
@@ -1243,11 +2089,21 @@ Windows
574198A7-7322-2F5E-02EF185D965C,Caption,subst
1
+=======
+{1}
+
+574198A7-7322-2F5E-02EF185D965C,CancelButton,subst
+{1}
+
+574198A7-7322-2F5E-02EF185D965C,Caption,subst
+{1}
+>>>>>>> 0.12
574198A7-7322-2F5E-02EF185D965C,Conditions
{0 conditions}
574198A7-7322-2F5E-02EF185D965C,FileLabel,subst
+<<<<<<< HEAD
1
574198A7-7322-2F5E-02EF185D965C,Message,subst
@@ -1279,10 +2135,44 @@ Windows
58E1119F-639E-17C9-5D3898F385AA,Caption,subst
1
+=======
+{1}
+
+574198A7-7322-2F5E-02EF185D965C,Message,subst
+{1}
+
+574198A7-7322-2F5E-02EF185D965C,NextButton,subst
+{1}
+
+574198A7-7322-2F5E-02EF185D965C,ProgressValue,subst
+{1}
+
+574198A7-7322-2F5E-02EF185D965C,Subtitle,subst
+{1}
+
+574198A7-7322-2F5E-02EF185D965C,Title,subst
+{1}
+
+58E1119F-639E-17C9-5D3898F385AA,BackButton,subst
+{1}
+
+58E1119F-639E-17C9-5D3898F385AA,BrowseButton,subst
+{1}
+
+58E1119F-639E-17C9-5D3898F385AA,BrowseText,subst
+{1}
+
+58E1119F-639E-17C9-5D3898F385AA,CancelButton,subst
+{1}
+
+58E1119F-639E-17C9-5D3898F385AA,Caption,subst
+{1}
+>>>>>>> 0.12
58E1119F-639E-17C9-5D3898F385AA,Conditions
{1 condition}
+<<<<<<< HEAD
58E1119F-639E-17C9-5D3898F385AA,DestinationLabel,subst
0
@@ -1297,12 +2187,35 @@ Windows
58E1119F-639E-17C9-5D3898F385AA,Title,subst
1
+=======
+58E1119F-639E-17C9-5D3898F385AA,Destination,subst
+{1}
+
+58E1119F-639E-17C9-5D3898F385AA,DestinationLabel,subst
+{0}
+
+58E1119F-639E-17C9-5D3898F385AA,Message,subst
+{1}
+
+58E1119F-639E-17C9-5D3898F385AA,NextButton,subst
+{1}
+
+58E1119F-639E-17C9-5D3898F385AA,Subtitle,subst
+{1}
+
+58E1119F-639E-17C9-5D3898F385AA,Title,subst
+{1}
+>>>>>>> 0.12
592F46AE-8CEE-01F3-0BA7EBDCA4F4,CheckCondition
{Before Action is Executed}
592F46AE-8CEE-01F3-0BA7EBDCA4F4,Filename
+<<<<<<< HEAD
<%ProgramExecutable%>
+=======
+{<%ProgramExecutable%>}
+>>>>>>> 0.12
5CA3EA16-E37C-AABE-E576C4636EB0,Action
{Install Actions}
@@ -1314,7 +2227,11 @@ Windows
{Before Action is Executed}
5EE78EF7-37CA-D440-3DB5-09136CD566B3,String
+<<<<<<< HEAD
<%AddBackupLocation%>
+=======
+{<%AddBackupLocation%>}
+>>>>>>> 0.12
5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,Conditions
{0 conditions}
@@ -1323,7 +2240,11 @@ Windows
{<%ConfigFileTemplate%>;*/*.bat;<%InstallDir%>/*.vbs}
5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,LineFeed
+<<<<<<< HEAD
Windows
+=======
+{Windows}
+>>>>>>> 0.12
5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,StringMap
{"@@CUSTOMERCOMPANY@@" <%UserInfoCompany%>
@@ -1336,19 +2257,31 @@ Windows
"@@INSTALLDIR@@" <%InstallDir%>}
614C45B2-7515-780C-E444-7F165CF02DD7,Active
+<<<<<<< HEAD
No
+=======
+{No}
+>>>>>>> 0.12
614C45B2-7515-780C-E444-7F165CF02DD7,Conditions
{0 conditions}
614C45B2-7515-780C-E444-7F165CF02DD7,ResultVirtualText
+<<<<<<< HEAD
BackupLocationShortName
+=======
+{BackupLocationShortName}
+>>>>>>> 0.12
614C45B2-7515-780C-E444-7F165CF02DD7,TclScript
{set BackupLocationShortName ""}
6652193C-5D4B-44B6-ABC6-D6E96D89E5DC,Active
+<<<<<<< HEAD
No
+=======
+{No}
+>>>>>>> 0.12
6652193C-5D4B-44B6-ABC6-D6E96D89E5DC,Comment
{PJ removed. Is this the one at the top leve?}
@@ -1360,12 +2293,17 @@ No
{2 conditions}
6B4CB3C2-4799-4C9F-BA8E-1EE47C4606E1,ExitType
+<<<<<<< HEAD
Finish
+=======
+{Finish}
+>>>>>>> 0.12
6B966959-05D9-DB32-8D9C4AD2A3DF,CheckCondition
{Before Action is Executed}
6B966959-05D9-DB32-8D9C4AD2A3DF,Platform
+<<<<<<< HEAD
Windows
6C323815-B9AB-FA94-4F5D152EBC51,BackButton,subst
@@ -1376,15 +2314,34 @@ Windows
6C323815-B9AB-FA94-4F5D152EBC51,Caption,subst
1
+=======
+{Windows}
+
+6C323815-B9AB-FA94-4F5D152EBC51,BackButton,subst
+{1}
+
+6C323815-B9AB-FA94-4F5D152EBC51,CancelButton,subst
+{1}
+
+6C323815-B9AB-FA94-4F5D152EBC51,Caption,subst
+{1}
+>>>>>>> 0.12
6C323815-B9AB-FA94-4F5D152EBC51,Conditions
{0 conditions}
6C323815-B9AB-FA94-4F5D152EBC51,Message,subst
+<<<<<<< HEAD
1
6C323815-B9AB-FA94-4F5D152EBC51,NextButton,subst
1
+=======
+{1}
+
+6C323815-B9AB-FA94-4F5D152EBC51,NextButton,subst
+{1}
+>>>>>>> 0.12
6D9D1ABC-7146-443F-9EE9-205D5CA6C830,CheckCondition
{Before Action is Executed}
@@ -1405,25 +2362,43 @@ Windows
{0 conditions}
6F61CDA8-30C9-454F-82A3-9987E1203079,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-sync
+=======
+{<%ShortAppName%>-program-sync}
+>>>>>>> 0.12
6F61CDA8-30C9-454F-82A3-9987E1203079,ShortcutName
{Sync now}
6F61CDA8-30C9-454F-82A3-9987E1203079,TargetFileName
+<<<<<<< HEAD
<%InstallDir%>/tools/Sync.bat
6F61CDA8-30C9-454F-82A3-9987E1203079,WorkingDirectory
<%InstallDir%>
+=======
+{<%InstallDir%>/tools/Sync.bat}
+
+6F61CDA8-30C9-454F-82A3-9987E1203079,WorkingDirectory
+{<%InstallDir%>}
+>>>>>>> 0.12
6F94698F-0839-3ABF-0CF2DF05A4C8,CheckCondition
{Before Action is Executed}
6F94698F-0839-3ABF-0CF2DF05A4C8,String
+<<<<<<< HEAD
<%CreateQuickLaunchShortcut%>
6FEE2889-0338-1D49-60BF-1471F465AB26,AppendNewline
No
+=======
+{<%CreateQuickLaunchShortcut%>}
+
+6FEE2889-0338-1D49-60BF-1471F465AB26,AppendNewline
+{No}
+>>>>>>> 0.12
6FEE2889-0338-1D49-60BF-1471F465AB26,Comment
{Closing final BackupLocations bracket}
@@ -1435,6 +2410,7 @@ No
{Append to file}
6FEE2889-0338-1D49-60BF-1471F465AB26,Files
+<<<<<<< HEAD
<%ConfigFileTemplate%>
6FEE2889-0338-1D49-60BF-1471F465AB26,LineFeed
@@ -1442,21 +2418,41 @@ Windows
6FEE2889-0338-1D49-60BF-1471F465AB26,TextToWrite,subst
1
+=======
+{<%ConfigFileTemplate%>}
+
+6FEE2889-0338-1D49-60BF-1471F465AB26,LineFeed
+{Windows}
+
+6FEE2889-0338-1D49-60BF-1471F465AB26,TextToWrite,subst
+{1}
+>>>>>>> 0.12
738DD098-7E3B-BC89-875CDB93CBE2,CheckCondition
{Before Action is Executed}
738DD098-7E3B-BC89-875CDB93CBE2,Platform
+<<<<<<< HEAD
Windows
+=======
+{Windows}
+>>>>>>> 0.12
73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8,Conditions
{0 conditions}
73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8,Destination
+<<<<<<< HEAD
<%ConfigFileName%>
73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8,Source
<%ConfigFileTemplate%>
+=======
+{<%ConfigFileName%>}
+
+73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8,Source
+{<%ConfigFileTemplate%>}
+>>>>>>> 0.12
73EA65C1-3BE3-B190-55C3E99F6269,Conditions
{1 condition}
@@ -1465,31 +2461,53 @@ Windows
{Before Action is Executed}
748D673B-DFE6-5F74-329903ACE4DB,Filename
+<<<<<<< HEAD
<%ProgramExecutable%>
+=======
+{<%ProgramExecutable%>}
+>>>>>>> 0.12
793D8178-0F51-7F07-BC5886586D3C,CheckCondition
{Before Action is Executed}
793D8178-0F51-7F07-BC5886586D3C,Operator
+<<<<<<< HEAD
false
793D8178-0F51-7F07-BC5886586D3C,String
<%InstallStopped%>
+=======
+{false}
+
+793D8178-0F51-7F07-BC5886586D3C,String
+{<%InstallStopped%>}
+>>>>>>> 0.12
795EE61F-6C0D-4A8B-93E02AA3894A,CheckCondition
{Before Action is Executed}
795EE61F-6C0D-4A8B-93E02AA3894A,String
+<<<<<<< HEAD
<%LaunchApplication%>
+=======
+{<%LaunchApplication%>}
+>>>>>>> 0.12
79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C,CheckCondition
{Before Action is Executed}
79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C,Operator
+<<<<<<< HEAD
false
79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C,String
<%LicenseAccepted%>
+=======
+{false}
+
+79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C,String
+{<%LicenseAccepted%>}
+>>>>>>> 0.12
7A983CD8-302C-4942-BE59-525C5B5FA2F2,Conditions
{0 conditions}
@@ -1498,15 +2516,23 @@ false
{ServiceControl terminate}
7A983CD8-302C-4942-BE59-525C5B5FA2F2,WorkingDirectory
+<<<<<<< HEAD
<%InstallDir%>
7B770A07-A785-5215-956FA82CF14E,Active
No
+=======
+{<%InstallDir%>}
+
+7B770A07-A785-5215-956FA82CF14E,Active
+{No}
+>>>>>>> 0.12
7B770A07-A785-5215-956FA82CF14E,Conditions
{3 conditions}
7B770A07-A785-5215-956FA82CF14E,ShortcutDirectory
+<<<<<<< HEAD
<%QUICK_LAUNCH%>
7B770A07-A785-5215-956FA82CF14E,ShortcutName
@@ -1517,6 +2543,18 @@ No
7B770A07-A785-5215-956FA82CF14E,WorkingDirectory
<%InstallDir%>
+=======
+{<%QUICK_LAUNCH%>}
+
+7B770A07-A785-5215-956FA82CF14E,ShortcutName
+{<%BrandName%>}
+
+7B770A07-A785-5215-956FA82CF14E,TargetFileName
+{<%ProgramExecutable%>}
+
+7B770A07-A785-5215-956FA82CF14E,WorkingDirectory
+{<%InstallDir%>}
+>>>>>>> 0.12
7D8E1902-2BC4-80D8-2C18771E7C22,Conditions
{0 conditions}
@@ -1525,6 +2563,7 @@ No
{<%ServiceExeName%> -i -S <%ServiceName%> -c "<%ConfigFileName%>"}
7D8E1902-2BC4-80D8-2C18771E7C22,ProgressiveOutputWidget
+<<<<<<< HEAD
Message
7D8E1902-2BC4-80D8-2C18771E7C22,ShowProgressiveOutput
@@ -1550,11 +2589,39 @@ Typical
8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Checked
No
+=======
+{Message}
+
+7D8E1902-2BC4-80D8-2C18771E7C22,ShowProgressiveOutput
+{Yes}
+
+7D8E1902-2BC4-80D8-2C18771E7C22,WorkingDirectory
+{<%InstallDir%>}
+
+7F85263E-CAE2-46BA-AAC0-6B89D20FD2DE,ExitType
+{Finish}
+
+8202CECC-54A0-9B6C-D24D111BA52E,Components
+{4A9C852B-647E-EED5-5482FFBCC2AF}
+
+8202CECC-54A0-9B6C-D24D111BA52E,Description,subst
+{1}
+
+8202CECC-54A0-9B6C-D24D111BA52E,DisplayName,subst
+{1}
+
+8202CECC-54A0-9B6C-D24D111BA52E,Name
+{Typical}
+
+8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Checked
+{No}
+>>>>>>> 0.12
8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Conditions
{0 conditions}
8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Text,subst
+<<<<<<< HEAD
1
8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Type
@@ -1568,6 +2635,21 @@ AddBackupLocation
8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Y
250
+=======
+{1}
+
+8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Type
+{checkbutton}
+
+8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Value
+{Yes}
+
+8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,VirtualText
+{AddBackupLocation}
+
+8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Y
+{250}
+>>>>>>> 0.12
848844B5-6103-9343-8B731B0BE4E0,Alias
{Startup Actions}
@@ -1579,10 +2661,17 @@ AddBackupLocation
{Before Next Pane is Displayed}
84DA7F05-9FB7-CC36-9EC98F8A6826,FailureMessage
+<<<<<<< HEAD
<%DirectoryPermissionText%>
84DA7F05-9FB7-CC36-9EC98F8A6826,Filename
<%InstallDir%>
+=======
+{<%DirectoryPermissionText%>}
+
+84DA7F05-9FB7-CC36-9EC98F8A6826,Filename
+{<%InstallDir%>}
+>>>>>>> 0.12
84DA7F05-9FB7-CC36-9EC98F8A6826,Permission
{can create}
@@ -1591,6 +2680,7 @@ AddBackupLocation
{0 conditions}
855DE408-060E-3D35-08B5-1D9AB05C2865,Height
+<<<<<<< HEAD
100
855DE408-060E-3D35-08B5-1D9AB05C2865,Text,subst
@@ -1604,11 +2694,27 @@ BackupLocationExclusions
855DE408-060E-3D35-08B5-1D9AB05C2865,Y
130
+=======
+{100}
+
+855DE408-060E-3D35-08B5-1D9AB05C2865,Text,subst
+{1}
+
+855DE408-060E-3D35-08B5-1D9AB05C2865,Type
+{text}
+
+855DE408-060E-3D35-08B5-1D9AB05C2865,VirtualText
+{BackupLocationExclusions}
+
+855DE408-060E-3D35-08B5-1D9AB05C2865,Y
+{130}
+>>>>>>> 0.12
87DE6D78-81E1-495B-A214-B3FF3E7E5614,CheckCondition
{Before Action is Executed}
87DE6D78-81E1-495B-A214-B3FF3E7E5614,Operator
+<<<<<<< HEAD
false
87DE6D78-81E1-495B-A214-B3FF3E7E5614,String
@@ -1616,6 +2722,15 @@ false
88A50FD5-480F-19A5-DA74-C915EB0A9765,Active
No
+=======
+{false}
+
+87DE6D78-81E1-495B-A214-B3FF3E7E5614,String
+{<%Answer%>}
+
+88A50FD5-480F-19A5-DA74-C915EB0A9765,Active
+{No}
+>>>>>>> 0.12
88A50FD5-480F-19A5-DA74-C915EB0A9765,Conditions
{1 condition}
@@ -1624,13 +2739,21 @@ No
{After Pane is Finished}
88A50FD5-480F-19A5-DA74-C915EB0A9765,Pane
+<<<<<<< HEAD
3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7
+=======
+{3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7}
+>>>>>>> 0.12
8A761DBD-0640-D98C-9B3AD7672A8F,Conditions
{0 conditions}
8A761DBD-0640-D98C-9B3AD7672A8F,State
+<<<<<<< HEAD
disabled
+=======
+{disabled}
+>>>>>>> 0.12
8A761DBD-0640-D98C-9B3AD7672A8F,Widget
{Back Button;Next Button}
@@ -1639,15 +2762,23 @@ disabled
{Before Action is Executed}
8C866252-8760-9B08-FE569C25B60D,Filename
+<<<<<<< HEAD
<%ProgramExecutable%>
8E095096-F018-A880-429D-A2177A9B70EA,Active
No
+=======
+{<%ProgramExecutable%>}
+
+8E095096-F018-A880-429D-A2177A9B70EA,Active
+{No}
+>>>>>>> 0.12
8E095096-F018-A880-429D-A2177A9B70EA,Conditions
{0 conditions}
8E095096-F018-A880-429D-A2177A9B70EA,Text,subst
+<<<<<<< HEAD
1
8E095096-F018-A880-429D-A2177A9B70EA,X
@@ -1655,10 +2786,20 @@ No
8E095096-F018-A880-429D-A2177A9B70EA,Y
150
+=======
+{1}
+
+8E095096-F018-A880-429D-A2177A9B70EA,X
+{50}
+
+8E095096-F018-A880-429D-A2177A9B70EA,Y
+{150}
+>>>>>>> 0.12
8E1A5944-5AF5-5906-16D395E386D8,Conditions
{0 conditions}
+<<<<<<< HEAD
9013E862-8E81-5290-64F9-D8BCD13EC7E5,Active
Yes
@@ -1673,11 +2814,25 @@ Yes
9013E862-8E81-5290-64F9-D8BCD13EC7E5,CompanyLabel,subst
1
+=======
+9013E862-8E81-5290-64F9-D8BCD13EC7E5,BackButton,subst
+{1}
+
+9013E862-8E81-5290-64F9-D8BCD13EC7E5,CancelButton,subst
+{1}
+
+9013E862-8E81-5290-64F9-D8BCD13EC7E5,Caption,subst
+{1}
+
+9013E862-8E81-5290-64F9-D8BCD13EC7E5,CompanyLabel,subst
+{1}
+>>>>>>> 0.12
9013E862-8E81-5290-64F9-D8BCD13EC7E5,Conditions
{0 conditions}
9013E862-8E81-5290-64F9-D8BCD13EC7E5,Message,subst
+<<<<<<< HEAD
1
9013E862-8E81-5290-64F9-D8BCD13EC7E5,NextButton,subst
@@ -1691,6 +2846,21 @@ Yes
9013E862-8E81-5290-64F9-D8BCD13EC7E5,UserNameLabel,subst
1
+=======
+{1}
+
+9013E862-8E81-5290-64F9-D8BCD13EC7E5,NextButton,subst
+{1}
+
+9013E862-8E81-5290-64F9-D8BCD13EC7E5,Subtitle,subst
+{1}
+
+9013E862-8E81-5290-64F9-D8BCD13EC7E5,Title,subst
+{1}
+
+9013E862-8E81-5290-64F9-D8BCD13EC7E5,UserNameLabel,subst
+{1}
+>>>>>>> 0.12
905DA2E9-988C-2F27-BB1F5F274AC9,Alias
{Cancel Actions}
@@ -1699,6 +2869,7 @@ Yes
{0 conditions}
908CE221-5A3D-0A78-24A1-E7C91EBE38D4,BackButton,subst
+<<<<<<< HEAD
1
908CE221-5A3D-0A78-24A1-E7C91EBE38D4,CancelButton,subst
@@ -1706,11 +2877,21 @@ Yes
908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Caption,subst
1
+=======
+{1}
+
+908CE221-5A3D-0A78-24A1-E7C91EBE38D4,CancelButton,subst
+{1}
+
+908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Caption,subst
+{1}
+>>>>>>> 0.12
908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Conditions
{0 conditions}
908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Message,subst
+<<<<<<< HEAD
1
908CE221-5A3D-0A78-24A1-E7C91EBE38D4,NextButton,subst
@@ -1724,11 +2905,27 @@ Yes
937C3FDD-FB28-98BD-3DAB276E59ED,Background
white
+=======
+{1}
+
+908CE221-5A3D-0A78-24A1-E7C91EBE38D4,NextButton,subst
+{1}
+
+908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Subtitle,subst
+{1}
+
+908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Title,subst
+{1}
+
+937C3FDD-FB28-98BD-3DAB276E59ED,Background
+{white}
+>>>>>>> 0.12
937C3FDD-FB28-98BD-3DAB276E59ED,Conditions
{3 conditions}
937C3FDD-FB28-98BD-3DAB276E59ED,Text,subst
+<<<<<<< HEAD
1
937C3FDD-FB28-98BD-3DAB276E59ED,Type
@@ -1745,6 +2942,24 @@ CreateQuickLaunchShortcut
93AA298C-B64E-5683-14D2-7B86F7DEFD2C,Active
No
+=======
+{1}
+
+937C3FDD-FB28-98BD-3DAB276E59ED,Type
+{checkbutton}
+
+937C3FDD-FB28-98BD-3DAB276E59ED,VirtualText
+{CreateQuickLaunchShortcut}
+
+937C3FDD-FB28-98BD-3DAB276E59ED,X
+{185}
+
+937C3FDD-FB28-98BD-3DAB276E59ED,Y
+{200}
+
+93AA298C-B64E-5683-14D2-7B86F7DEFD2C,Active
+{No}
+>>>>>>> 0.12
93AA298C-B64E-5683-14D2-7B86F7DEFD2C,Comment
{set BackupLocationName "BackupLocation_${BackupLocationNumber}"}
@@ -1753,7 +2968,11 @@ No
{0 conditions}
93AA298C-B64E-5683-14D2-7B86F7DEFD2C,ResultVirtualText
+<<<<<<< HEAD
BackupLocationName
+=======
+{BackupLocationName}
+>>>>>>> 0.12
93AA298C-B64E-5683-14D2-7B86F7DEFD2C,TclScript
{set BackupLocationName "BackupLocation_${BackupLocationNumber}"}
@@ -1762,24 +2981,37 @@ BackupLocationName
{Before Action is Executed}
96A68CAC-9ED7-806C-086B104720FD,String
+<<<<<<< HEAD
<%ErrorsOccurred%>
+=======
+{<%ErrorsOccurred%>}
+>>>>>>> 0.12
97ACF525-C075-8635-E019202A83D8,Comment
{Ask the user if they want to proceed with the uninstall.}
9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,Active
+<<<<<<< HEAD
No
+=======
+{No}
+>>>>>>> 0.12
9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,Conditions
{0 conditions}
9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,ResultVirtualText
+<<<<<<< HEAD
AddBackupLocation
+=======
+{AddBackupLocation}
+>>>>>>> 0.12
9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,TclScript
{set AddBackupLocation no}
9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Active
+<<<<<<< HEAD
Yes
9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,BackButton,subst
@@ -1790,11 +3022,24 @@ Yes
9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Caption,subst
1
+=======
+{Yes}
+
+9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,BackButton,subst
+{1}
+
+9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,CancelButton,subst
+{1}
+
+9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Caption,subst
+{1}
+>>>>>>> 0.12
9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Conditions
{0 conditions}
9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Message,subst
+<<<<<<< HEAD
1
9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,NextButton,subst
@@ -1805,21 +3050,44 @@ Yes
9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Title,subst
1
+=======
+{1}
+
+9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,NextButton,subst
+{1}
+
+9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Subtitle,subst
+{1}
+
+9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Title,subst
+{1}
+>>>>>>> 0.12
9A663209-495B-ED16-09BE-457B61148022,Conditions
{0 conditions}
9A663209-495B-ED16-09BE-457B61148022,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-QueryCurrent
+=======
+{<%ShortAppName%>-program-QueryCurrent}
+>>>>>>> 0.12
9A663209-495B-ED16-09BE-457B61148022,ShortcutName
{Query Current Only}
9A663209-495B-ED16-09BE-457B61148022,TargetFileName
+<<<<<<< HEAD
<%InstallDir%>/tools/QueryOutputCurrent.bat
9A663209-495B-ED16-09BE-457B61148022,WorkingDirectory
<%InstallDir%>
+=======
+{<%InstallDir%>/tools/QueryOutputCurrent.bat}
+
+9A663209-495B-ED16-09BE-457B61148022,WorkingDirectory
+{<%InstallDir%>}
+>>>>>>> 0.12
9D101299-B80C-441B-8685-6E3AC61808E8,Alias
{Remote Control}
@@ -1831,6 +3099,7 @@ Yes
{0 conditions}
9D101299-B80C-441B-8685-6E3AC61808E8,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-RemoteControl
9D101299-B80C-441B-8685-6E3AC61808E8,ShortcutName
@@ -1841,6 +3110,18 @@ RemoteControl
9D101299-B80C-441B-8685-6E3AC61808E8,WorkingDirectory
<%InstallDir%>
+=======
+{<%ShortAppName%>-program-RemoteControl}
+
+9D101299-B80C-441B-8685-6E3AC61808E8,ShortcutName
+{RemoteControl}
+
+9D101299-B80C-441B-8685-6E3AC61808E8,TargetFileName
+{<%InstallDir%>/tools/RemoteControl.exe}
+
+9D101299-B80C-441B-8685-6E3AC61808E8,WorkingDirectory
+{<%InstallDir%>}
+>>>>>>> 0.12
A1DD1DC2-85D7-9BC6-998AC3D4A3A9,Alias
{Startup Actions}
@@ -1852,7 +3133,11 @@ A5B32DA1-B2FE-C1FA-6057-FBC3059EF076,Conditions
{0 conditions}
A5B32DA1-B2FE-C1FA-6057-FBC3059EF076,ResultVirtualText
+<<<<<<< HEAD
AddBackupLocation
+=======
+{AddBackupLocation}
+>>>>>>> 0.12
A5B32DA1-B2FE-C1FA-6057-FBC3059EF076,TclScript
{set AddBackupLocation no }
@@ -1861,12 +3146,17 @@ A6E1B027-A1B4-5848-4F868D028D00,CheckCondition
{Before Action is Executed}
A6E1B027-A1B4-5848-4F868D028D00,String
+<<<<<<< HEAD
<%ViewReadme%>
+=======
+{<%ViewReadme%>}
+>>>>>>> 0.12
ADA6EB2F-8820-4366-BBEF-ED1335B7F828,Conditions
{1 condition}
AE3BD5B4-35DE-4240-B79914D43E56,Active
+<<<<<<< HEAD
No
AE3BD5B4-35DE-4240-B79914D43E56,BackButton,subst
@@ -1877,11 +3167,24 @@ AE3BD5B4-35DE-4240-B79914D43E56,CancelButton,subst
AE3BD5B4-35DE-4240-B79914D43E56,Caption,subst
1
+=======
+{No}
+
+AE3BD5B4-35DE-4240-B79914D43E56,BackButton,subst
+{1}
+
+AE3BD5B4-35DE-4240-B79914D43E56,CancelButton,subst
+{1}
+
+AE3BD5B4-35DE-4240-B79914D43E56,Caption,subst
+{1}
+>>>>>>> 0.12
AE3BD5B4-35DE-4240-B79914D43E56,Conditions
{0 conditions}
AE3BD5B4-35DE-4240-B79914D43E56,Message,subst
+<<<<<<< HEAD
1
AE3BD5B4-35DE-4240-B79914D43E56,NextButton,subst
@@ -1910,11 +3213,45 @@ Standard
AIX-ppc,InstallType
Typical
+=======
+{1}
+
+AE3BD5B4-35DE-4240-B79914D43E56,NextButton,subst
+{1}
+
+AIX-ppc,Active
+{No}
+
+AIX-ppc,BuildSeparateArchives
+{No}
+
+AIX-ppc,DefaultDirectoryPermission
+{0755}
+
+AIX-ppc,DefaultFilePermission
+{0755}
+
+AIX-ppc,Executable
+{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
+
+AIX-ppc,FallBackToConsole
+{Yes}
+
+AIX-ppc,InstallDir
+{<%Home%>/<%ShortAppName%>}
+
+AIX-ppc,InstallMode
+{Standard}
+
+AIX-ppc,InstallType
+{Typical}
+>>>>>>> 0.12
AIX-ppc,ProgramExecutable
{}
AIX-ppc,ProgramFolderAllUsers
+<<<<<<< HEAD
No
AIX-ppc,ProgramFolderName
@@ -1922,11 +3259,21 @@ AIX-ppc,ProgramFolderName
AIX-ppc,ProgramLicense
<%InstallDir%>/LICENSE.txt
+=======
+{No}
+
+AIX-ppc,ProgramFolderName
+{<%AppName%>}
+
+AIX-ppc,ProgramLicense
+{<%InstallDir%>/LICENSE.txt}
+>>>>>>> 0.12
AIX-ppc,ProgramName
{}
AIX-ppc,ProgramReadme
+<<<<<<< HEAD
<%InstallDir%>/README.txt
AIX-ppc,PromptForRoot
@@ -1940,6 +3287,21 @@ AIX-ppc,RootInstallDir
B002A311-F8E7-41DE-B039-521391924E5B,Message,subst
1
+=======
+{<%InstallDir%>/README.txt}
+
+AIX-ppc,PromptForRoot
+{Yes}
+
+AIX-ppc,RequireRoot
+{No}
+
+AIX-ppc,RootInstallDir
+{/usr/local/<%ShortAppName%>}
+
+B002A311-F8E7-41DE-B039-521391924E5B,Message,subst
+{1}
+>>>>>>> 0.12
B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,Alias
{Stop Backup Service}
@@ -1951,31 +3313,53 @@ B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,Conditions
{0 conditions}
B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-stopservice
+=======
+{<%ShortAppName%>-program-stopservice}
+>>>>>>> 0.12
B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,ShortcutName
{Stop Service}
B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,TargetFileName
+<<<<<<< HEAD
<%InstallDir%>/tools/StopService.bat
B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,WorkingDirectory
<%InstallDir%>
+=======
+{<%InstallDir%>/tools/StopService.bat}
+
+B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,WorkingDirectory
+{<%InstallDir%>}
+>>>>>>> 0.12
B0AA6839-AAB6-A602-C0E4ECA2E4FF,CheckCondition
{Before Action is Executed}
B0AA6839-AAB6-A602-C0E4ECA2E4FF,Filename
+<<<<<<< HEAD
<%ProgramExecutable%>
+=======
+{<%ProgramExecutable%>}
+>>>>>>> 0.12
B39C0455-D1B6-7DDC-E2717F83463E,CheckCondition
{Before Action is Executed}
B39C0455-D1B6-7DDC-E2717F83463E,Operator
+<<<<<<< HEAD
false
B39C0455-D1B6-7DDC-E2717F83463E,String
<%InstallStopped%>
+=======
+{false}
+
+B39C0455-D1B6-7DDC-E2717F83463E,String
+{<%InstallStopped%>}
+>>>>>>> 0.12
B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D,Conditions
{0 conditions}
@@ -1984,7 +3368,11 @@ B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D,ExecuteAction
{Before Next Pane is Displayed}
B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D,Password
+<<<<<<< HEAD
<%InstallPassword%>
+=======
+{<%InstallPassword%>}
+>>>>>>> 0.12
B4D31D1E-ADB1-DE8F-18EB7294DDA8,Conditions
{0 conditions}
@@ -1993,6 +3381,7 @@ B4D31D1E-ADB1-DE8F-18EB7294DDA8,ProgramCommandLine
{<%ServiceExeName%> -r -S <%ServiceName%>}
B4D31D1E-ADB1-DE8F-18EB7294DDA8,WorkingDirectory
+<<<<<<< HEAD
<%InstallDir%>
B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message,subst
@@ -2012,11 +3401,33 @@ B506E7DA-E7C4-4D42-8C03-FD27BA16D078,CancelButton,subst
B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Caption,subst
1
+=======
+{<%InstallDir%>}
+
+B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message,subst
+{1}
+
+B506E7DA-E7C4-4D42-8C03-FD27BA16D078,AcceptRadiobutton,subst
+{0}
+
+B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Active
+{Yes}
+
+B506E7DA-E7C4-4D42-8C03-FD27BA16D078,BackButton,subst
+{1}
+
+B506E7DA-E7C4-4D42-8C03-FD27BA16D078,CancelButton,subst
+{1}
+
+B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Caption,subst
+{1}
+>>>>>>> 0.12
B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Conditions
{0 conditions}
B506E7DA-E7C4-4D42-8C03-FD27BA16D078,DeclineRadiobutton,subst
+<<<<<<< HEAD
0
B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Message,subst
@@ -2033,6 +3444,24 @@ B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Text,subst
B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Title,subst
1
+=======
+{0}
+
+B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Message,subst
+{1}
+
+B506E7DA-E7C4-4D42-8C03-FD27BA16D078,NextButton,subst
+{1}
+
+B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Subtitle,subst
+{1}
+
+B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Text,subst
+{1}
+
+B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Title,subst
+{1}
+>>>>>>> 0.12
B5DFEC63-92A9-4686-909E-0CE78A7069D6,Alias
{Restart Backup Service}
@@ -2044,7 +3473,11 @@ B5DFEC63-92A9-4686-909E-0CE78A7069D6,Conditions
{0 conditions}
B5DFEC63-92A9-4686-909E-0CE78A7069D6,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-restartservice
+=======
+{<%ShortAppName%>-program-restartservice}
+>>>>>>> 0.12
B5DFEC63-92A9-4686-909E-0CE78A7069D6,ShortcutName
{Restart Service}
@@ -2053,12 +3486,17 @@ B5DFEC63-92A9-4686-909E-0CE78A7069D6,TargetFileName
{<%InstallDir%>\tools\RestartService.bat}
B5DFEC63-92A9-4686-909E-0CE78A7069D6,WorkingDirectory
+<<<<<<< HEAD
<%InstallDir%>
+=======
+{<%InstallDir%>}
+>>>>>>> 0.12
B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Conditions
{0 conditions}
B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,LabelSide
+<<<<<<< HEAD
left
B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Text,subst
@@ -2072,24 +3510,53 @@ BackupLocationShortName
B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Y
100
+=======
+{left}
+
+B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Text,subst
+{1}
+
+B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Type
+{entry}
+
+B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,VirtualText
+{BackupLocationShortName}
+
+B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Y
+{100}
+>>>>>>> 0.12
B93D2216-1DDB-484C-A9AC-D6C18ED7DE23,Conditions
{2 conditions}
B93D2216-1DDB-484C-A9AC-D6C18ED7DE23,State
+<<<<<<< HEAD
disabled
B93D2216-1DDB-484C-A9AC-D6C18ED7DE23,Widget
NextButton
+=======
+{disabled}
+
+B93D2216-1DDB-484C-A9AC-D6C18ED7DE23,Widget
+{NextButton}
+>>>>>>> 0.12
BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,CheckCondition
{Before Next Action is Executed}
BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,FailureMessage
+<<<<<<< HEAD
<%DirectoryPermissionText%>
BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,Filename
<%InstallDir%>
+=======
+{<%DirectoryPermissionText%>}
+
+BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,Filename
+{<%InstallDir%>}
+>>>>>>> 0.12
BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,Permission
{can create}
@@ -2104,27 +3571,43 @@ C0452595-F3EB-43AD-BCA2-661437584636,Conditions
{0 conditions}
C0452595-F3EB-43AD-BCA2-661437584636,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-editconfig
+=======
+{<%ShortAppName%>-program-editconfig}
+>>>>>>> 0.12
C0452595-F3EB-43AD-BCA2-661437584636,ShortcutName
{Edit Config File}
C0452595-F3EB-43AD-BCA2-661437584636,TargetFileName
+<<<<<<< HEAD
<%InstallDir%>/tools/EditConfig.bat
C0452595-F3EB-43AD-BCA2-661437584636,WorkingDirectory
<%InstallDir%>
+=======
+{<%InstallDir%>/tools/EditConfig.bat}
+
+C0452595-F3EB-43AD-BCA2-661437584636,WorkingDirectory
+{<%InstallDir%>}
+>>>>>>> 0.12
C0AF7C05-A31A-8376-BCB9-BA8B3A666252,Conditions
{0 conditions}
C0AF7C05-A31A-8376-BCB9-BA8B3A666252,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-QueryAll
+=======
+{<%ShortAppName%>-program-QueryAll}
+>>>>>>> 0.12
C0AF7C05-A31A-8376-BCB9-BA8B3A666252,ShortcutName
{Query All}
C0AF7C05-A31A-8376-BCB9-BA8B3A666252,TargetFileName
+<<<<<<< HEAD
<%InstallDir%>/tools/QueryOutputAll.bat
C0AF7C05-A31A-8376-BCB9-BA8B3A666252,WorkingDirectory
@@ -2138,11 +3621,27 @@ C105AAAE-7C16-2C9E-769FE4535B60,Caption,subst
C105AAAE-7C16-2C9E-769FE4535B60,CloseButton,subst
1
+=======
+{<%InstallDir%>/tools/QueryOutputAll.bat}
+
+C0AF7C05-A31A-8376-BCB9-BA8B3A666252,WorkingDirectory
+{<%InstallDir%>}
+
+C105AAAE-7C16-2C9E-769FE4535B60,Active
+{No}
+
+C105AAAE-7C16-2C9E-769FE4535B60,Caption,subst
+{1}
+
+C105AAAE-7C16-2C9E-769FE4535B60,CloseButton,subst
+{1}
+>>>>>>> 0.12
C105AAAE-7C16-2C9E-769FE4535B60,Conditions
{3 conditions}
C105AAAE-7C16-2C9E-769FE4535B60,Message,subst
+<<<<<<< HEAD
1
C105AAAE-7C16-2C9E-769FE4535B60,TextFile
@@ -2153,11 +3652,24 @@ C105AAAE-7C16-2C9E-769FE4535B60,Title,subst
C33D74B2-26FA-16F5-433A10C6A747,Active
No
+=======
+{1}
+
+C105AAAE-7C16-2C9E-769FE4535B60,TextFile
+{<%ProgramReadme%>}
+
+C105AAAE-7C16-2C9E-769FE4535B60,Title,subst
+{1}
+
+C33D74B2-26FA-16F5-433A10C6A747,Active
+{No}
+>>>>>>> 0.12
C33D74B2-26FA-16F5-433A10C6A747,Conditions
{3 conditions}
C33D74B2-26FA-16F5-433A10C6A747,ProgramCommandLine
+<<<<<<< HEAD
<%ProgramExecutable%>
C33D74B2-26FA-16F5-433A10C6A747,WaitForProgram
@@ -2165,6 +3677,15 @@ No
C33D74B2-26FA-16F5-433A10C6A747,WorkingDirectory
<%InstallDir%>
+=======
+{<%ProgramExecutable%>}
+
+C33D74B2-26FA-16F5-433A10C6A747,WaitForProgram
+{No}
+
+C33D74B2-26FA-16F5-433A10C6A747,WorkingDirectory
+{<%InstallDir%>}
+>>>>>>> 0.12
C7762473-273F-E3CA-17E3-65789B14CDB0,Conditions
{0 conditions}
@@ -2176,19 +3697,33 @@ C7762473-273F-E3CA-17E3-65789B14CDB0,FileOpenAction
{Append to file}
C7762473-273F-E3CA-17E3-65789B14CDB0,Files
+<<<<<<< HEAD
<%ConfigFileTemplate%>
C7762473-273F-E3CA-17E3-65789B14CDB0,TextToWrite,subst
1
+=======
+{<%ConfigFileTemplate%>}
+
+C7762473-273F-E3CA-17E3-65789B14CDB0,TextToWrite,subst
+{1}
+>>>>>>> 0.12
CC4337CC-F3B5-757C-DFCF5D1D365A,CheckCondition
{Before Action is Executed}
CC4337CC-F3B5-757C-DFCF5D1D365A,Operator
+<<<<<<< HEAD
false
CC4337CC-F3B5-757C-DFCF5D1D365A,String
<%SilentMode%>
+=======
+{false}
+
+CC4337CC-F3B5-757C-DFCF5D1D365A,String
+{<%SilentMode%>}
+>>>>>>> 0.12
CDD84DE3-C970-458F-9162-1A3CE0AA716B,Alias
{Start Backup Service}
@@ -2200,7 +3735,11 @@ CDD84DE3-C970-458F-9162-1A3CE0AA716B,Conditions
{0 conditions}
CDD84DE3-C970-458F-9162-1A3CE0AA716B,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-startservice
+=======
+{<%ShortAppName%>-program-startservice}
+>>>>>>> 0.12
CDD84DE3-C970-458F-9162-1A3CE0AA716B,ShortcutName
{Start Service}
@@ -2209,15 +3748,23 @@ CDD84DE3-C970-458F-9162-1A3CE0AA716B,TargetFileName
{<%InstallDir%>\tools\StartService.bat}
CDD84DE3-C970-458F-9162-1A3CE0AA716B,WorkingDirectory
+<<<<<<< HEAD
<%InstallDir%>
CFFA27AF-A641-E41C-B4A0E3BB3CBB,Background
white
+=======
+{<%InstallDir%>}
+
+CFFA27AF-A641-E41C-B4A0E3BB3CBB,Background
+{white}
+>>>>>>> 0.12
CFFA27AF-A641-E41C-B4A0E3BB3CBB,Conditions
{2 conditions}
CFFA27AF-A641-E41C-B4A0E3BB3CBB,Text,subst
+<<<<<<< HEAD
1
CFFA27AF-A641-E41C-B4A0E3BB3CBB,Type
@@ -2231,6 +3778,21 @@ CFFA27AF-A641-E41C-B4A0E3BB3CBB,X
CFFA27AF-A641-E41C-B4A0E3BB3CBB,Y
160
+=======
+{1}
+
+CFFA27AF-A641-E41C-B4A0E3BB3CBB,Type
+{checkbutton}
+
+CFFA27AF-A641-E41C-B4A0E3BB3CBB,VirtualText
+{LaunchApplication}
+
+CFFA27AF-A641-E41C-B4A0E3BB3CBB,X
+{185}
+
+CFFA27AF-A641-E41C-B4A0E3BB3CBB,Y
+{160}
+>>>>>>> 0.12
D23DD94C-E517-7F34-FD59-802CB18AB887,Comment
{Need to do before starting anything else.}
@@ -2242,13 +3804,21 @@ D23DD94C-E517-7F34-FD59-802CB18AB887,Files
{*/*.conf;*/*.txt;*/*.bat}
D23DD94C-E517-7F34-FD59-802CB18AB887,LineFeed
+<<<<<<< HEAD
Windows
+=======
+{Windows}
+>>>>>>> 0.12
D3D73C76-D9D3-07DA-63D4163A44BE,Conditions
{0 conditions}
D3D73C76-D9D3-07DA-63D4163A44BE,ExitType
+<<<<<<< HEAD
Finish
+=======
+{Finish}
+>>>>>>> 0.12
D4FC6EB5-DDEE-4E4A-B8E1-D4B588A7928B,Action
{Install Actions}
@@ -2263,7 +3833,11 @@ D7FBBEBB-2186-5674-BA87-BB7151859D4E,Conditions
{0 conditions}
D7FBBEBB-2186-5674-BA87-BB7151859D4E,ResultVirtualText
+<<<<<<< HEAD
BackupLocationNumber
+=======
+{BackupLocationNumber}
+>>>>>>> 0.12
D7FBBEBB-2186-5674-BA87-BB7151859D4E,TclScript
{incr BackupLocationNumber}
@@ -2275,16 +3849,27 @@ D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,Conditions
{0 conditions}
D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-reloadconfig
+=======
+{<%ShortAppName%>-program-reloadconfig}
+>>>>>>> 0.12
D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,ShortcutName
{Reload configuration file}
D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,TargetFileName
+<<<<<<< HEAD
<%InstallDir%>/tools/ReloadConfig.bat
D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,WorkingDirectory
<%InstallDir%>
+=======
+{<%InstallDir%>/tools/ReloadConfig.bat}
+
+D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,WorkingDirectory
+{<%InstallDir%>}
+>>>>>>> 0.12
D8F0AA0F-AD79-C566-15CC508F503B,Action
{Install Actions}
@@ -2296,6 +3881,7 @@ D9F88AC1-3D2D-F6DB-871E-3A0E016770B1,Conditions
{0 conditions}
D9F88AC1-3D2D-F6DB-871E-3A0E016770B1,Destination
+<<<<<<< HEAD
<%ConfigFileTemplate%>
D9F88AC1-3D2D-F6DB-871E-3A0E016770B1,Source
@@ -2312,11 +3898,30 @@ DA33B826-E633-A845-4646-76DFA78B907B,CancelButton,subst
DA33B826-E633-A845-4646-76DFA78B907B,Caption,subst
1
+=======
+{<%ConfigFileTemplate%>}
+
+D9F88AC1-3D2D-F6DB-871E-3A0E016770B1,Source
+{<%InstallDir%>/templates/original.conf}
+
+DA33B826-E633-A845-4646-76DFA78B907B,Active
+{Yes}
+
+DA33B826-E633-A845-4646-76DFA78B907B,BackButton,subst
+{1}
+
+DA33B826-E633-A845-4646-76DFA78B907B,CancelButton,subst
+{1}
+
+DA33B826-E633-A845-4646-76DFA78B907B,Caption,subst
+{1}
+>>>>>>> 0.12
DA33B826-E633-A845-4646-76DFA78B907B,Conditions
{0 conditions}
DA33B826-E633-A845-4646-76DFA78B907B,Message,subst
+<<<<<<< HEAD
1
DA33B826-E633-A845-4646-76DFA78B907B,NextButton,subst
@@ -2333,11 +3938,30 @@ No
DDBBD8A9-13D7-9509-9202-419E989F60A9,Checked
No
+=======
+{1}
+
+DA33B826-E633-A845-4646-76DFA78B907B,NextButton,subst
+{1}
+
+DA33B826-E633-A845-4646-76DFA78B907B,Subtitle,subst
+{1}
+
+DA33B826-E633-A845-4646-76DFA78B907B,Title,subst
+{1}
+
+DDBBD8A9-13D7-9509-9202-419E989F60A9,Active
+{No}
+
+DDBBD8A9-13D7-9509-9202-419E989F60A9,Checked
+{No}
+>>>>>>> 0.12
DDBBD8A9-13D7-9509-9202-419E989F60A9,Conditions
{0 conditions}
DDBBD8A9-13D7-9509-9202-419E989F60A9,Text,subst
+<<<<<<< HEAD
1
DDBBD8A9-13D7-9509-9202-419E989F60A9,Type
@@ -2351,6 +3975,21 @@ DDBBD8A9-13D7-9509-9202-419E989F60A9,X
DDBBD8A9-13D7-9509-9202-419E989F60A9,Y
200
+=======
+{1}
+
+DDBBD8A9-13D7-9509-9202-419E989F60A9,Type
+{checkbutton}
+
+DDBBD8A9-13D7-9509-9202-419E989F60A9,VirtualText
+{AddBackupLocation}
+
+DDBBD8A9-13D7-9509-9202-419E989F60A9,X
+{50}
+
+DDBBD8A9-13D7-9509-9202-419E989F60A9,Y
+{200}
+>>>>>>> 0.12
DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,Alias
{Install Backup Service}
@@ -2362,12 +4001,17 @@ DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,Conditions
{0 conditions}
DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,FileName
+<<<<<<< HEAD
<%ShortAppName%>-program-installService
+=======
+{<%ShortAppName%>-program-installService}
+>>>>>>> 0.12
DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,ShortcutName
{Install Service}
DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,TargetFileName
+<<<<<<< HEAD
<%InstallDir%>/tools/InstallService.bat
DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,WorkingDirectory
@@ -2375,11 +4019,21 @@ DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,WorkingDirectory
DECC120D-6904-7F17-45A49184A5A3,Active
No
+=======
+{<%InstallDir%>/tools/InstallService.bat}
+
+DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,WorkingDirectory
+{<%InstallDir%>}
+
+DECC120D-6904-7F17-45A49184A5A3,Active
+{No}
+>>>>>>> 0.12
DECC120D-6904-7F17-45A49184A5A3,Conditions
{2 conditions}
DECC120D-6904-7F17-45A49184A5A3,ShortcutName
+<<<<<<< HEAD
<%AppName%>
DECC120D-6904-7F17-45A49184A5A3,TargetFileName
@@ -2387,33 +4041,63 @@ DECC120D-6904-7F17-45A49184A5A3,TargetFileName
DECC120D-6904-7F17-45A49184A5A3,WorkingDirectory
<%InstallDir%>
+=======
+{<%AppName%>}
+
+DECC120D-6904-7F17-45A49184A5A3,TargetFileName
+{<%ProgramExecutable%>}
+
+DECC120D-6904-7F17-45A49184A5A3,WorkingDirectory
+{<%InstallDir%>}
+>>>>>>> 0.12
DFFF91A9-2CA5-6ABE-8474D814AF88,CheckCondition
{Before Action is Executed}
DFFF91A9-2CA5-6ABE-8474D814AF88,Operator
+<<<<<<< HEAD
false
DFFF91A9-2CA5-6ABE-8474D814AF88,String
<%SilentMode%>
+=======
+{false}
+
+DFFF91A9-2CA5-6ABE-8474D814AF88,String
+{<%SilentMode%>}
+>>>>>>> 0.12
E02368C5-95B5-03A7-3282740037B0,CheckCondition
{Before Action is Executed}
E02368C5-95B5-03A7-3282740037B0,Operator
+<<<<<<< HEAD
false
E02368C5-95B5-03A7-3282740037B0,String
<%InstallStopped%>
+=======
+{false}
+
+E02368C5-95B5-03A7-3282740037B0,String
+{<%InstallStopped%>}
+>>>>>>> 0.12
E161F216-E597-B340-C1A71C476E2C,CheckCondition
{Before Action is Executed}
E161F216-E597-B340-C1A71C476E2C,Message,subst
+<<<<<<< HEAD
1
E161F216-E597-B340-C1A71C476E2C,Title,subst
1
+=======
+{1}
+
+E161F216-E597-B340-C1A71C476E2C,Title,subst
+{1}
+>>>>>>> 0.12
E23AC50D-7CFB-800E-A99C6F4068F8,Alias
{Cancel Actions}
@@ -2425,7 +4109,11 @@ E44CFF46-6302-C518-B9C30D2E43F7,CheckCondition
{Before Action is Executed}
E44CFF46-6302-C518-B9C30D2E43F7,String
+<<<<<<< HEAD
<%CreateDesktopShortcut%>
+=======
+{<%CreateDesktopShortcut%>}
+>>>>>>> 0.12
E4DEA723-FC78-45D7-BAB1-A3E4C4C96EA1,Conditions
{0 conditions}
@@ -2443,13 +4131,21 @@ E56ADFF4-C15E-AEDB-A599-C468AF72C4BB,Source
{<%InstallDir%>\templates\NotifySysAdmin.template.vbs}
EB2B31A1-C111-3582-0C8A5656692A,String
+<<<<<<< HEAD
<%ErrorsOccurred%>
+=======
+{<%ErrorsOccurred%>}
+>>>>>>> 0.12
EB532611-5F30-3C24-66EB-F3826D9054FD,CheckCondition
{Before Action is Executed}
EB532611-5F30-3C24-66EB-F3826D9054FD,String
+<<<<<<< HEAD
<%AddBackupLocation%>
+=======
+{<%AddBackupLocation%>}
+>>>>>>> 0.12
F4024A3E-9A6D-2726-5E0CFFA93054,Alias
{Uninstall Actions}
@@ -2458,7 +4154,11 @@ F4024A3E-9A6D-2726-5E0CFFA93054,Conditions
{0 conditions}
F5F21749-8B3A-49C6-9138-9C4D6D703D26,Active
+<<<<<<< HEAD
No
+=======
+{No}
+>>>>>>> 0.12
F5F21749-8B3A-49C6-9138-9C4D6D703D26,Conditions
{0 conditions}
@@ -2467,6 +4167,7 @@ F5F21749-8B3A-49C6-9138-9C4D6D703D26,ProgramCommandLine
{cmd /k tools/7za.exe encrypted_keys.exe -p<%InstallPassword%>}
F5F21749-8B3A-49C6-9138-9C4D6D703D26,ProgressiveOutputWidget
+<<<<<<< HEAD
Message
F5F21749-8B3A-49C6-9138-9C4D6D703D26,ShowProgressiveOutput
@@ -2489,11 +4190,36 @@ F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Caption,subst
F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,CompanyLabel,subst
0
+=======
+{Message}
+
+F5F21749-8B3A-49C6-9138-9C4D6D703D26,ShowProgressiveOutput
+{Yes}
+
+F5F21749-8B3A-49C6-9138-9C4D6D703D26,WorkingDirectory
+{<%InstallDir%>}
+
+F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Active
+{Yes}
+
+F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,BackButton,subst
+{1}
+
+F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,CancelButton,subst
+{1}
+
+F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Caption,subst
+{1}
+
+F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,CompanyLabel,subst
+{0}
+>>>>>>> 0.12
F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Conditions
{0 conditions}
F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Message,subst
+<<<<<<< HEAD
1
F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,NextButton,subst
@@ -2510,39 +4236,80 @@ F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,UserNameLabel,subst
F9E38720-6ABA-8B99-2471-496902E4CBC2,Active
No
+=======
+{1}
+
+F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,NextButton,subst
+{1}
+
+F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Subtitle,subst
+{1}
+
+F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Title,subst
+{1}
+
+F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,UserNameLabel,subst
+{1}
+
+F9E38720-6ABA-8B99-2471-496902E4CBC2,Active
+{No}
+>>>>>>> 0.12
F9E38720-6ABA-8B99-2471-496902E4CBC2,Conditions
{0 conditions}
F9E38720-6ABA-8B99-2471-496902E4CBC2,ResultVirtualText
+<<<<<<< HEAD
BackupLocationPath
+=======
+{BackupLocationPath}
+>>>>>>> 0.12
F9E38720-6ABA-8B99-2471-496902E4CBC2,TclScript
{set BackupLocationPath "" }
FB697A88-2842-468E-9776-85E84B009340,Active
+<<<<<<< HEAD
No
+=======
+{No}
+>>>>>>> 0.12
FB697A88-2842-468E-9776-85E84B009340,Conditions
{0 conditions}
FB697A88-2842-468E-9776-85E84B009340,IgnoreErrors
+<<<<<<< HEAD
Yes
+=======
+{Yes}
+>>>>>>> 0.12
FB697A88-2842-468E-9776-85E84B009340,ProgramCommandLine
{"<%InstallDir%>\<%ServiceExeName%> -r -S <%ServiceName%>}
FB697A88-2842-468E-9776-85E84B009340,WorkingDirectory
+<<<<<<< HEAD
<%Temp%>
FDF68FD6-BEA8-4A74-867D-5139F4D9E793,Active
No
+=======
+{<%Temp%>}
+
+FDF68FD6-BEA8-4A74-867D-5139F4D9E793,Active
+{No}
+>>>>>>> 0.12
FDF68FD6-BEA8-4A74-867D-5139F4D9E793,Conditions
{0 conditions}
FDF68FD6-BEA8-4A74-867D-5139F4D9E793,WaitTime
+<<<<<<< HEAD
2000
+=======
+{2000}
+>>>>>>> 0.12
FEFD090D-C133-BC95-B3564F693CD3,Alias
{Finish Actions}
@@ -2551,6 +4318,7 @@ FEFD090D-C133-BC95-B3564F693CD3,Conditions
{0 conditions}
FreeBSD-4-x86,Active
+<<<<<<< HEAD
No
FreeBSD-4-x86,DefaultDirectoryPermission
@@ -2573,11 +4341,39 @@ Standard
FreeBSD-4-x86,InstallType
Typical
+=======
+{No}
+
+FreeBSD-4-x86,BuildSeparateArchives
+{No}
+
+FreeBSD-4-x86,DefaultDirectoryPermission
+{0755}
+
+FreeBSD-4-x86,DefaultFilePermission
+{0755}
+
+FreeBSD-4-x86,Executable
+{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
+
+FreeBSD-4-x86,FallBackToConsole
+{Yes}
+
+FreeBSD-4-x86,InstallDir
+{<%Home%>/<%ShortAppName%>}
+
+FreeBSD-4-x86,InstallMode
+{Standard}
+
+FreeBSD-4-x86,InstallType
+{Typical}
+>>>>>>> 0.12
FreeBSD-4-x86,ProgramExecutable
{}
FreeBSD-4-x86,ProgramFolderAllUsers
+<<<<<<< HEAD
No
FreeBSD-4-x86,ProgramFolderName
@@ -2585,11 +4381,21 @@ FreeBSD-4-x86,ProgramFolderName
FreeBSD-4-x86,ProgramLicense
<%InstallDir%>/LICENSE.txt
+=======
+{No}
+
+FreeBSD-4-x86,ProgramFolderName
+{<%AppName%>}
+
+FreeBSD-4-x86,ProgramLicense
+{<%InstallDir%>/LICENSE.txt}
+>>>>>>> 0.12
FreeBSD-4-x86,ProgramName
{}
FreeBSD-4-x86,ProgramReadme
+<<<<<<< HEAD
<%InstallDir%>/README.txt
FreeBSD-4-x86,PromptForRoot
@@ -2675,11 +4481,213 @@ Standard
HPUX-hppa,InstallType
Typical
+=======
+{<%InstallDir%>/README.txt}
+
+FreeBSD-4-x86,PromptForRoot
+{Yes}
+
+FreeBSD-4-x86,RequireRoot
+{No}
+
+FreeBSD-4-x86,RootInstallDir
+{/usr/local/<%ShortAppName%>}
+
+FreeBSD-5-x86,Active
+{No}
+
+FreeBSD-5-x86,BuildSeparateArchives
+{No}
+
+FreeBSD-5-x86,DefaultDirectoryPermission
+{0755}
+
+FreeBSD-5-x86,DefaultFilePermission
+{0755}
+
+FreeBSD-5-x86,Executable
+{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
+
+FreeBSD-5-x86,FallBackToConsole
+{Yes}
+
+FreeBSD-5-x86,InstallDir
+{<%Home%>/<%ShortAppName%>}
+
+FreeBSD-5-x86,InstallMode
+{Standard}
+
+FreeBSD-5-x86,InstallType
+{Typical}
+
+FreeBSD-5-x86,ProgramExecutable
+{}
+
+FreeBSD-5-x86,ProgramFolderAllUsers
+{No}
+
+FreeBSD-5-x86,ProgramFolderName
+{<%AppName%>}
+
+FreeBSD-5-x86,ProgramLicense
+{<%InstallDir%>/LICENSE.txt}
+
+FreeBSD-5-x86,ProgramName
+{}
+
+FreeBSD-5-x86,ProgramReadme
+{<%InstallDir%>/README.txt}
+
+FreeBSD-5-x86,PromptForRoot
+{Yes}
+
+FreeBSD-5-x86,RequireRoot
+{No}
+
+FreeBSD-5-x86,RootInstallDir
+{/usr/local/<%ShortAppName%>}
+
+FreeBSD-6-x86,Active
+{No}
+
+FreeBSD-6-x86,BuildSeparateArchives
+{No}
+
+FreeBSD-6-x86,DefaultDirectoryPermission
+{0755}
+
+FreeBSD-6-x86,DefaultFilePermission
+{0755}
+
+FreeBSD-6-x86,Executable
+{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
+
+FreeBSD-6-x86,FallBackToConsole
+{Yes}
+
+FreeBSD-6-x86,InstallDir
+{<%Home%>/<%ShortAppName%>}
+
+FreeBSD-6-x86,InstallMode
+{Standard}
+
+FreeBSD-6-x86,InstallType
+{Typical}
+
+FreeBSD-6-x86,ProgramExecutable
+{}
+
+FreeBSD-6-x86,ProgramFolderAllUsers
+{No}
+
+FreeBSD-6-x86,ProgramFolderName
+{<%AppName%>}
+
+FreeBSD-6-x86,ProgramLicense
+{<%InstallDir%>/LICENSE.txt}
+
+FreeBSD-6-x86,ProgramName
+{}
+
+FreeBSD-6-x86,ProgramReadme
+{<%InstallDir%>/README.txt}
+
+FreeBSD-6-x86,PromptForRoot
+{Yes}
+
+FreeBSD-6-x86,RequireRoot
+{No}
+
+FreeBSD-6-x86,RootInstallDir
+{/usr/local/<%ShortAppName%>}
+
+FreeBSD-7-x86,Active
+{No}
+
+FreeBSD-7-x86,BuildSeparateArchives
+{No}
+
+FreeBSD-7-x86,DefaultDirectoryPermission
+{0755}
+
+FreeBSD-7-x86,DefaultFilePermission
+{0755}
+
+FreeBSD-7-x86,Executable
+{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
+
+FreeBSD-7-x86,FallBackToConsole
+{Yes}
+
+FreeBSD-7-x86,InstallDir
+{<%Home%>/<%ShortAppName%>}
+
+FreeBSD-7-x86,InstallMode
+{Standard}
+
+FreeBSD-7-x86,InstallType
+{Typical}
+
+FreeBSD-7-x86,ProgramExecutable
+{}
+
+FreeBSD-7-x86,ProgramFolderAllUsers
+{No}
+
+FreeBSD-7-x86,ProgramFolderName
+{<%AppName%>}
+
+FreeBSD-7-x86,ProgramLicense
+{<%InstallDir%>/LICENSE.txt}
+
+FreeBSD-7-x86,ProgramName
+{}
+
+FreeBSD-7-x86,ProgramReadme
+{<%InstallDir%>/README.txt}
+
+FreeBSD-7-x86,PromptForRoot
+{Yes}
+
+FreeBSD-7-x86,RequireRoot
+{No}
+
+FreeBSD-7-x86,RootInstallDir
+{/usr/local/<%ShortAppName%>}
+
+HPUX-hppa,Active
+{No}
+
+HPUX-hppa,BuildSeparateArchives
+{No}
+
+HPUX-hppa,DefaultDirectoryPermission
+{0755}
+
+HPUX-hppa,DefaultFilePermission
+{0755}
+
+HPUX-hppa,Executable
+{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
+
+HPUX-hppa,FallBackToConsole
+{Yes}
+
+HPUX-hppa,InstallDir
+{<%Home%>/<%ShortAppName%>}
+
+HPUX-hppa,InstallMode
+{Standard}
+
+HPUX-hppa,InstallType
+{Typical}
+>>>>>>> 0.12
HPUX-hppa,ProgramExecutable
{}
HPUX-hppa,ProgramFolderAllUsers
+<<<<<<< HEAD
No
HPUX-hppa,ProgramFolderName
@@ -2687,11 +4695,21 @@ HPUX-hppa,ProgramFolderName
HPUX-hppa,ProgramLicense
<%InstallDir%>/LICENSE.txt
+=======
+{No}
+
+HPUX-hppa,ProgramFolderName
+{<%AppName%>}
+
+HPUX-hppa,ProgramLicense
+{<%InstallDir%>/LICENSE.txt}
+>>>>>>> 0.12
HPUX-hppa,ProgramName
{}
HPUX-hppa,ProgramReadme
+<<<<<<< HEAD
<%InstallDir%>/README.txt
HPUX-hppa,PromptForRoot
@@ -2741,11 +4759,66 @@ Linux-x86,ProgramFolderName
Linux-x86,ProgramLicense
<%InstallDir%>/LICENSE.txt
+=======
+{<%InstallDir%>/README.txt}
+
+HPUX-hppa,PromptForRoot
+{Yes}
+
+HPUX-hppa,RequireRoot
+{No}
+
+HPUX-hppa,RootInstallDir
+{/usr/local/<%ShortAppName%>}
+
+Linux-x86,Active
+{No}
+
+Linux-x86,BuildSeparateArchives
+{No}
+
+Linux-x86,BuildType
+{dynamic}
+
+Linux-x86,DefaultDirectoryPermission
+{00755}
+
+Linux-x86,DefaultFilePermission
+{00755}
+
+Linux-x86,Executable
+{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
+
+Linux-x86,FallBackToConsole
+{Yes}
+
+Linux-x86,InstallDir
+{<%Home%>/<%ShortAppName%>}
+
+Linux-x86,InstallMode
+{Standard}
+
+Linux-x86,InstallType
+{Typical}
+
+Linux-x86,ProgramExecutable
+{<%InstallDir%>/TebucoSafe}
+
+Linux-x86,ProgramFolderAllUsers
+{No}
+
+Linux-x86,ProgramFolderName
+{<%AppName%>}
+
+Linux-x86,ProgramLicense
+{<%InstallDir%>/LICENSE.txt}
+>>>>>>> 0.12
Linux-x86,ProgramName
{}
Linux-x86,ProgramReadme
+<<<<<<< HEAD
<%InstallDir%>/README.txt
Linux-x86,PromptForRoot
@@ -2780,11 +4853,105 @@ Standard
Solaris-sparc,InstallType
Typical
+=======
+{<%InstallDir%>/README.txt}
+
+Linux-x86,PromptForRoot
+{Yes}
+
+Linux-x86,RequireRoot
+{No}
+
+Linux-x86,RootInstallDir
+{/usr/local/<%ShortAppName%>}
+
+Linux-x86_64,Active
+{No}
+
+Linux-x86_64,BuildSeparateArchives
+{No}
+
+Linux-x86_64,DefaultDirectoryPermission
+{0755}
+
+Linux-x86_64,DefaultFilePermission
+{0755}
+
+Linux-x86_64,Executable
+{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
+
+Linux-x86_64,FallBackToConsole
+{Yes}
+
+Linux-x86_64,InstallDir
+{<%Home%>/<%ShortAppName%>}
+
+Linux-x86_64,InstallMode
+{Standard}
+
+Linux-x86_64,InstallType
+{Typical}
+
+Linux-x86_64,ProgramExecutable
+{}
+
+Linux-x86_64,ProgramFolderAllUsers
+{No}
+
+Linux-x86_64,ProgramFolderName
+{<%AppName%>}
+
+Linux-x86_64,ProgramLicense
+{<%InstallDir%>/LICENSE.txt}
+
+Linux-x86_64,ProgramName
+{}
+
+Linux-x86_64,ProgramReadme
+{<%InstallDir%>/README.txt}
+
+Linux-x86_64,PromptForRoot
+{Yes}
+
+Linux-x86_64,RequireRoot
+{No}
+
+Linux-x86_64,RootInstallDir
+{/usr/local/<%ShortAppName%>}
+
+Solaris-sparc,Active
+{No}
+
+Solaris-sparc,BuildSeparateArchives
+{No}
+
+Solaris-sparc,DefaultDirectoryPermission
+{0755}
+
+Solaris-sparc,DefaultFilePermission
+{0755}
+
+Solaris-sparc,Executable
+{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
+
+Solaris-sparc,FallBackToConsole
+{Yes}
+
+Solaris-sparc,InstallDir
+{<%Home%>/<%ShortAppName%>}
+
+Solaris-sparc,InstallMode
+{Standard}
+
+Solaris-sparc,InstallType
+{Typical}
+>>>>>>> 0.12
Solaris-sparc,ProgramExecutable
{}
Solaris-sparc,ProgramFolderAllUsers
+<<<<<<< HEAD
No
Solaris-sparc,ProgramFolderName
@@ -2792,11 +4959,21 @@ Solaris-sparc,ProgramFolderName
Solaris-sparc,ProgramLicense
<%InstallDir%>/LICENSE.txt
+=======
+{No}
+
+Solaris-sparc,ProgramFolderName
+{<%AppName%>}
+
+Solaris-sparc,ProgramLicense
+{<%InstallDir%>/LICENSE.txt}
+>>>>>>> 0.12
Solaris-sparc,ProgramName
{}
Solaris-sparc,ProgramReadme
+<<<<<<< HEAD
<%InstallDir%>/README.txt
Solaris-sparc,PromptForRoot
@@ -2837,11 +5014,111 @@ Typical
TarArchive,OutputFileName
<%ShortAppName%>-<%Version%>.tar.gz
+=======
+{<%InstallDir%>/README.txt}
+
+Solaris-sparc,PromptForRoot
+{Yes}
+
+Solaris-sparc,RequireRoot
+{No}
+
+Solaris-sparc,RootInstallDir
+{/usr/local/<%ShortAppName%>}
+
+Solaris-x86,Active
+{No}
+
+Solaris-x86,BuildSeparateArchives
+{No}
+
+Solaris-x86,DefaultDirectoryPermission
+{0755}
+
+Solaris-x86,DefaultFilePermission
+{0755}
+
+Solaris-x86,Executable
+{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
+
+Solaris-x86,FallBackToConsole
+{Yes}
+
+Solaris-x86,InstallDir
+{<%Home%>/<%ShortAppName%>}
+
+Solaris-x86,InstallMode
+{Standard}
+
+Solaris-x86,InstallType
+{Typical}
+
+Solaris-x86,ProgramExecutable
+{}
+
+Solaris-x86,ProgramFolderAllUsers
+{No}
+
+Solaris-x86,ProgramFolderName
+{<%AppName%>}
+
+Solaris-x86,ProgramLicense
+{<%InstallDir%>/LICENSE.txt}
+
+Solaris-x86,ProgramName
+{}
+
+Solaris-x86,ProgramReadme
+{<%InstallDir%>/README.txt}
+
+Solaris-x86,PromptForRoot
+{Yes}
+
+Solaris-x86,RequireRoot
+{No}
+
+Solaris-x86,RootInstallDir
+{/usr/local/<%ShortAppName%>}
+
+TarArchive,Active
+{No}
+
+TarArchive,BuildSeparateArchives
+{No}
+
+TarArchive,CompressionLevel
+{6}
+
+TarArchive,DefaultDirectoryPermission
+{0755}
+
+TarArchive,DefaultFilePermission
+{0755}
+
+TarArchive,Executable
+{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
+
+TarArchive,FallBackToConsole
+{Yes}
+
+TarArchive,InstallDir
+{<%Home%>/<%ShortAppName%>}
+
+TarArchive,InstallMode
+{Standard}
+
+TarArchive,InstallType
+{Typical}
+
+TarArchive,OutputFileName
+{<%ShortAppName%>-<%Version%>.tar.gz}
+>>>>>>> 0.12
TarArchive,ProgramExecutable
{}
TarArchive,ProgramFolderAllUsers
+<<<<<<< HEAD
No
TarArchive,ProgramFolderName
@@ -2849,11 +5126,21 @@ TarArchive,ProgramFolderName
TarArchive,ProgramLicense
<%InstallDir%>/LICENSE.txt
+=======
+{No}
+
+TarArchive,ProgramFolderName
+{<%AppName%>}
+
+TarArchive,ProgramLicense
+{<%InstallDir%>/LICENSE.txt}
+>>>>>>> 0.12
TarArchive,ProgramName
{}
TarArchive,ProgramReadme
+<<<<<<< HEAD
<%InstallDir%>/README.txt
TarArchive,PromptForRoot
@@ -2864,39 +5151,85 @@ No
TarArchive,RootInstallDir
/usr/local/<%ShortAppName%>
+=======
+{<%InstallDir%>/README.txt}
+
+TarArchive,PromptForRoot
+{Yes}
+
+TarArchive,RequireRoot
+{No}
+
+TarArchive,RootInstallDir
+{/usr/local/<%ShortAppName%>}
+>>>>>>> 0.12
TarArchive,VirtualTextMap
{<%InstallDir%> <%ShortAppName%>}
Windows,Active
+<<<<<<< HEAD
Yes
+=======
+{Yes}
+
+Windows,BuildSeparateArchives
+{No}
+>>>>>>> 0.12
Windows,BuildType
{}
Windows,Executable
+<<<<<<< HEAD
installer.exe
Windows,IncludeTWAPI
No
+=======
+{installer.exe}
+
+Windows,FileDescription
+{<%AppName%> <%Version%> Setup}
+
+Windows,IncludeTWAPI
+{No}
+>>>>>>> 0.12
Windows,InstallDir
{C:\Program Files\<%BrandName%>}
Windows,InstallMode
+<<<<<<< HEAD
Standard
Windows,InstallType
Typical
+=======
+{Standard}
+
+Windows,InstallType
+{Typical}
+
+Windows,LastRequireAdministrator
+{Yes}
+>>>>>>> 0.12
Windows,ProgramExecutable
{}
Windows,ProgramFolderAllUsers
+<<<<<<< HEAD
No
Windows,ProgramFolderName
<%BrandName%>
+=======
+{No}
+
+Windows,ProgramFolderName
+{<%BrandName%>}
+>>>>>>> 0.12
Windows,ProgramLicense
{<%InstallDir%>\LICENSE.txt}
@@ -2907,10 +5240,20 @@ Windows,ProgramName
Windows,ProgramReadme
{}
+<<<<<<< HEAD
+=======
+Windows,RequireAdministrator
+{Yes}
+
+Windows,UseUncompressedBinaries
+{No}
+
+>>>>>>> 0.12
Windows,WindowsIcon
{}
ZipArchive,Active
+<<<<<<< HEAD
No
ZipArchive,CompressionLevel
@@ -2939,11 +5282,45 @@ Typical
ZipArchive,OutputFileName
<%ShortAppName%>-<%Version%>.zip
+=======
+{No}
+
+ZipArchive,BuildSeparateArchives
+{No}
+
+ZipArchive,CompressionLevel
+{6}
+
+ZipArchive,DefaultDirectoryPermission
+{0755}
+
+ZipArchive,DefaultFilePermission
+{0755}
+
+ZipArchive,Executable
+{<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%>}
+
+ZipArchive,FallBackToConsole
+{Yes}
+
+ZipArchive,InstallDir
+{<%Home%>/<%ShortAppName%>}
+
+ZipArchive,InstallMode
+{Standard}
+
+ZipArchive,InstallType
+{Typical}
+
+ZipArchive,OutputFileName
+{<%ShortAppName%>-<%Version%>.zip}
+>>>>>>> 0.12
ZipArchive,ProgramExecutable
{}
ZipArchive,ProgramFolderAllUsers
+<<<<<<< HEAD
No
ZipArchive,ProgramFolderName
@@ -2951,11 +5328,21 @@ ZipArchive,ProgramFolderName
ZipArchive,ProgramLicense
<%InstallDir%>/LICENSE.txt
+=======
+{No}
+
+ZipArchive,ProgramFolderName
+{<%AppName%>}
+
+ZipArchive,ProgramLicense
+{<%InstallDir%>/LICENSE.txt}
+>>>>>>> 0.12
ZipArchive,ProgramName
{}
ZipArchive,ProgramReadme
+<<<<<<< HEAD
<%InstallDir%>/README.txt
ZipArchive,PromptForRoot
@@ -2966,6 +5353,18 @@ No
ZipArchive,RootInstallDir
/usr/local/<%ShortAppName%>
+=======
+{<%InstallDir%>/README.txt}
+
+ZipArchive,PromptForRoot
+{Yes}
+
+ZipArchive,RequireRoot
+{No}
+
+ZipArchive,RootInstallDir
+{/usr/local/<%ShortAppName%>}
+>>>>>>> 0.12
ZipArchive,VirtualTextMap
{<%InstallDir%> <%ShortAppName%>}
@@ -2974,6 +5373,7 @@ ZipArchive,VirtualTextMap
::msgcat::mcmset de {
20CBDBEA-2217-457B-8D98-D692C4F591E9,Message
+<<<<<<< HEAD
<%UninstallCompleteText%>
2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message
@@ -2984,10 +5384,23 @@ B002A311-F8E7-41DE-B039-521391924E5B,Message
B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message
<%UninstallingApplicationText%>
+=======
+{<%UninstallCompleteText%>}
+
+2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message
+{<%InstallationCompleteText%>}
+
+B002A311-F8E7-41DE-B039-521391924E5B,Message
+{<%InstallingApplicationText%>}
+
+B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message
+{<%UninstallingApplicationText%>}
+>>>>>>> 0.12
}
::msgcat::mcmset en {
16D53E40-546B-54C3-088B1B5E3BBB,Text
+<<<<<<< HEAD
<%CreateDesktopShortcutText%>
20CBDBEA-2217-457B-8D98-D692C4F591E9,Message
@@ -2995,6 +5408,15 @@ B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message
2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message
<%InstallationCompleteText%>
+=======
+{<%CreateDesktopShortcutText%>}
+
+20CBDBEA-2217-457B-8D98-D692C4F591E9,Message
+{<%UninstallCompleteText%>}
+
+2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message
+{<%InstallationCompleteText%>}
+>>>>>>> 0.12
2E2963BD-DDBD-738D-A910-B7F3F04946F9,Text
{No:<%BackupLocationNumber%> }
@@ -3003,6 +5425,7 @@ B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message
BackupLocations\n\{\n
32F5B0AF-EB83-7A03-D8FAE1ECE473,Message
+<<<<<<< HEAD
<%InstallStartupText%>
32F5B0AF-EB83-7A03-D8FAE1ECE473,Title
@@ -3013,6 +5436,18 @@ BackupLocations\n\{\n
3B6E2E7C-1A26-27F1-D578E383B128,Text
<%ViewReadmeText%>
+=======
+{<%InstallStartupText%>}
+
+32F5B0AF-EB83-7A03-D8FAE1ECE473,Title
+{<%InstallApplicationText%>}
+
+36FF8915-8148-0F1F-27D7239CBFA1,Text
+{<%ViewReadmeText%>}
+
+3B6E2E7C-1A26-27F1-D578E383B128,Text
+{<%ViewReadmeText%>}
+>>>>>>> 0.12
3D33AA8C-0037-204B-39A339FD38BD,Message
{<%BrandName%> has been removed from your system. Thank you for using the <%BrandName%> Backup Service. If you need further assistance, please contact us at http://<%BrandName%>.com or support@<%BrandName%>.com.}
@@ -3033,6 +5468,7 @@ BackupLocations\n\{\n
{Backup Directory}
4A9C852B-647E-EED5-5482FFBCC2AF,Description
+<<<<<<< HEAD
<%ProgramFilesDescription%>
4ACB0B47-42B3-2B3A-BFE9AA4EC707,Message
@@ -3040,6 +5476,15 @@ BackupLocations\n\{\n
4ACB0B47-42B3-2B3A-BFE9AA4EC707,Title
<%UninstallApplicationText%>
+=======
+{<%ProgramFilesDescription%>}
+
+4ACB0B47-42B3-2B3A-BFE9AA4EC707,Message
+{<%UninstallStartupText%>}
+
+4ACB0B47-42B3-2B3A-BFE9AA4EC707,Title
+{<%UninstallApplicationText%>}
+>>>>>>> 0.12
58E1119F-639E-17C9-5D3898F385AA,Caption
{Setup will install <%ShortAppName%> in the following folder.
@@ -3088,13 +5533,21 @@ You will be presented with the current configuration file so that you may make a
\}\n
8202CECC-54A0-9B6C-D24D111BA52E,Description
+<<<<<<< HEAD
<%TypicalInstallDescription%>
+=======
+{<%TypicalInstallDescription%>}
+>>>>>>> 0.12
8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Text
{Add another backup location?}
855DE408-060E-3D35-08B5-1D9AB05C2865,Text
+<<<<<<< HEAD
Exclusions
+=======
+{Exclusions}
+>>>>>>> 0.12
8E095096-F018-A880-429D-A2177A9B70EA,Text
{AddAnother: <%AddBackupLocation%>}
@@ -3103,6 +5556,7 @@ Exclusions
{Please enter your phone number and email address.}
9013E862-8E81-5290-64F9-D8BCD13EC7E5,CompanyLabel
+<<<<<<< HEAD
Email:
9013E862-8E81-5290-64F9-D8BCD13EC7E5,UserNameLabel
@@ -3110,6 +5564,15 @@ Phone:
937C3FDD-FB28-98BD-3DAB276E59ED,Text
<%CreateQuickLaunchShortcutText%>
+=======
+{Email:}
+
+9013E862-8E81-5290-64F9-D8BCD13EC7E5,UserNameLabel
+{Phone:}
+
+937C3FDD-FB28-98BD-3DAB276E59ED,Text
+{<%CreateQuickLaunchShortcutText%>}
+>>>>>>> 0.12
9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Caption
{Click Next to continue...}
@@ -3121,16 +5584,27 @@ Phone:
{}
9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Title
+<<<<<<< HEAD
Continue
+=======
+{Continue}
+>>>>>>> 0.12
9BAB328D-414B-D351-CA8D-824DF94B9DCA,Text
{Add another backup location after this one?}
A18C2977-1409-C1FB-892415711F72,Text
+<<<<<<< HEAD
<%LaunchApplicationText%>
AAF2142A-9FC9-4664-DFF2-13B9EB7BA0E1,CompanyLabel
Company:
+=======
+{<%LaunchApplicationText%>}
+
+AAF2142A-9FC9-4664-DFF2-13B9EB7BA0E1,CompanyLabel
+{Company:}
+>>>>>>> 0.12
AE3BD5B4-35DE-4240-B79914D43E56,Caption
{Welcome to the Installation Wizard for <%BrandName%> Backup Service!}
@@ -3148,6 +5622,7 @@ Click Next to continue or Cancel to exit Setup.
}
B002A311-F8E7-41DE-B039-521391924E5B,Message
+<<<<<<< HEAD
<%InstallingApplicationText%>
B4404713-AF4F-4F4B-670F3115517F,Description
@@ -3174,6 +5649,94 @@ Redistribution and use in source and binary forms, with or without modification,
[Where legally impermissible the Authors do not disclaim liability for direct physical injury or death caused solely by defects in the software unless it is modified by a third party.]
THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE}
+=======
+{<%InstallingApplicationText%>}
+
+B4404713-AF4F-4F4B-670F3115517F,Description
+{<%CustomInstallDescription%>}
+
+B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message
+{<%UninstallingApplicationText%>}
+
+B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Text
+{Box Backup, http://www.boxbackup.org/
+
+Copyright (c) 2003-2010, Ben Summers and contributors.
+All rights reserved.
+
+The license of the code was changed on 23-Jan-2010 in order to meet the
+Fedora Project's definition of Free Software, and therefore allow inclusion
+in Fedora, Red Hat Linux and CentOS. This also solves a long-standing
+incompatibility with the GNU Readline library that prevented us from
+distributing Box Backup binaries compiled against that library. You can
+review our discussions of the change in the mailing list archives at:
+http://lists.boxbackup.org/pipermail/boxbackup/2010-January/000005.html
+
+Note that this project uses mixed licensing. Different parts of the project
+may be used and distributed under different licenses, as described below.
+The two licenses used are "Box Backup GPL" and a BSD-style license.
+
+Unless stated otherwise in the file, all files in the following directories
+fall under the "Box Backup GPL" license, described below:
+
+bin/bbackupctl
+bin/bbackupd
+bin/bbackupobjdump
+bin/bbackupquery
+bin/bbstoreaccounts
+bin/bbstored
+bin/s3simulator
+lib/backupclient
+lib/backupstore
+test/backupdiff
+test/backupstore
+test/backupstorefix
+test/backupstorepatch
+test/bbackupd
+contrib/bbadmin
+contrib/bbreporter
+contrib/cygwin
+contrib/debian
+contrib/mac_osx
+contrib/redhat
+contrib/rpm
+contrib/solaris
+contrib/suse
+contrib/windows
+distribution/boxbackup
+
+The "Box Backup GPL" license text may be found in the file
+LICENSE-GPL.txt, or online at:
+[https://www.boxbackup.org/svn/box/trunk/LICENSE-GPL.txt]
+
+Unless stated otherwise in the file, all files in the following directories
+are dual licensed under the BSD and GPL licenses. You may use and distribute
+them providing that you comply EITHER with the terms of the BSD license,
+OR the GPL license. It is not necessary to comply with both licenses,
+only one.
+
+lib/common
+lib/compress
+lib/crypto
+lib/httpserver
+lib/intercept
+lib/raidfile
+lib/server
+lib/win32
+test/basicserver
+test/common
+test/compress
+test/crypto
+test/httpserver
+test/raidfile
+test/win32
+infrastructure
+distribution
+
+The dual license text may be found in the file
+LICENSE-DUAL.txt, or online at:
+[https://www.boxbackup.org/svn/box/trunk/LICENSE-DUAL.txt]}
+>>>>>>> 0.12
B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,Caption
{<%BrandName%> will backup the following folder.
@@ -3199,19 +5762,31 @@ B9B85EF1-1D76-4BF5-ABB9-092A8DB35851,Caption
{Please enter the agreed-upon password for your encrypted key file...}
B9B85EF1-1D76-4BF5-ABB9-092A8DB35851,CompanyLabel
+<<<<<<< HEAD
Password:
+=======
+{Password:}
+>>>>>>> 0.12
B9B85EF1-1D76-4BF5-ABB9-092A8DB35851,Subtitle
{Please enter your encrypted key file password.}
C105AAAE-7C16-2C9E-769FE4535B60,Caption
+<<<<<<< HEAD
<%ApplicationReadmeText%>
+=======
+{<%ApplicationReadmeText%>}
+>>>>>>> 0.12
C105AAAE-7C16-2C9E-769FE4535B60,Message
{}
C105AAAE-7C16-2C9E-769FE4535B60,Title
+<<<<<<< HEAD
<%ApplicationReadmeText%>
+=======
+{<%ApplicationReadmeText%>}
+>>>>>>> 0.12
C7762473-273F-E3CA-17E3-65789B14CDB0,TextToWrite
{<%BackupLocationShortName%>
@@ -3237,10 +5812,17 @@ CB058DBA-C3B7-2F48-D985-BE2F7107A76D,Title
{Choose Backup Location}
CFFA27AF-A641-E41C-B4A0E3BB3CBB,Text
+<<<<<<< HEAD
<%LaunchApplicationText%>
D4625CA6-9864-D8EF-F252D7B7DC87,Text
<%CreateDesktopShortcutText%>
+=======
+{<%LaunchApplicationText%>}
+
+D4625CA6-9864-D8EF-F252D7B7DC87,Text
+{<%CreateDesktopShortcutText%>}
+>>>>>>> 0.12
D47BE952-79F2-844E-D2E5-8F22044E7A9D,Text
{Account Number:}
@@ -3255,7 +5837,11 @@ DA33B826-E633-A845-4646-76DFA78B907B,Subtitle
{}
DA33B826-E633-A845-4646-76DFA78B907B,Title
+<<<<<<< HEAD
Continue
+=======
+{Continue}
+>>>>>>> 0.12
DDBBD8A9-13D7-9509-9202-419E989F60A9,Text
{Add another Backup Location?}
@@ -3264,7 +5850,11 @@ E0CADC4E-08A6-E429-3B49-BB8CFB7B097F,Text
{Simple name for this Backup Location (short, no spaces or special characters)}
E161F216-E597-B340-C1A71C476E2C,Message
+<<<<<<< HEAD
<%UninstallLeftoverText%>
+=======
+{<%UninstallLeftoverText%>}
+>>>>>>> 0.12
E161F216-E597-B340-C1A71C476E2C,Title
{Uninstall <%BrandName%>}
@@ -3291,7 +5881,11 @@ F98784B1-1965-0F42-6BB0542AE1A9,Message
{Click Next to install the TebucoSafe Backup Service as an operating system service on your computer (see services.msc), and start up that service. }
FC678E76-6823-2E55-204CA01C35EF,Text
+<<<<<<< HEAD
<%CreateQuickLaunchShortcutText%>
+=======
+{<%CreateQuickLaunchShortcutText%>}
+>>>>>>> 0.12
FF4F6EEA-F4CC-428E-AF33-EB0E88E2147E,Text
{
@@ -3335,6 +5929,7 @@ POSSIBILITY OF SUCH DAMAGE.
}
::msgcat::mcmset es {
20CBDBEA-2217-457B-8D98-D692C4F591E9,Message
+<<<<<<< HEAD
<%UninstallCompleteText%>
2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message
@@ -3345,10 +5940,23 @@ B002A311-F8E7-41DE-B039-521391924E5B,Message
B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message
<%UninstallingApplicationText%>
+=======
+{<%UninstallCompleteText%>}
+
+2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message
+{<%InstallationCompleteText%>}
+
+B002A311-F8E7-41DE-B039-521391924E5B,Message
+{<%InstallingApplicationText%>}
+
+B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message
+{<%UninstallingApplicationText%>}
+>>>>>>> 0.12
}
::msgcat::mcmset fr {
20CBDBEA-2217-457B-8D98-D692C4F591E9,Message
+<<<<<<< HEAD
<%UninstallCompleteText%>
2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message
@@ -3359,10 +5967,23 @@ B002A311-F8E7-41DE-B039-521391924E5B,Message
B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message
<%UninstallingApplicationText%>
+=======
+{<%UninstallCompleteText%>}
+
+2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message
+{<%InstallationCompleteText%>}
+
+B002A311-F8E7-41DE-B039-521391924E5B,Message
+{<%InstallingApplicationText%>}
+
+B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message
+{<%UninstallingApplicationText%>}
+>>>>>>> 0.12
}
::msgcat::mcmset pl {
20CBDBEA-2217-457B-8D98-D692C4F591E9,Message
+<<<<<<< HEAD
<%UninstallCompleteText%>
2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message
@@ -3373,10 +5994,23 @@ B002A311-F8E7-41DE-B039-521391924E5B,Message
B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message
<%UninstallingApplicationText%>
+=======
+{<%UninstallCompleteText%>}
+
+2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message
+{<%InstallationCompleteText%>}
+
+B002A311-F8E7-41DE-B039-521391924E5B,Message
+{<%InstallingApplicationText%>}
+
+B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message
+{<%UninstallingApplicationText%>}
+>>>>>>> 0.12
}
::msgcat::mcmset pt_br {
20CBDBEA-2217-457B-8D98-D692C4F591E9,Message
+<<<<<<< HEAD
<%UninstallCompleteText%>
2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message
@@ -3387,6 +6021,18 @@ B002A311-F8E7-41DE-B039-521391924E5B,Message
B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message
<%UninstallingApplicationText%>
+=======
+{<%UninstallCompleteText%>}
+
+2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message
+{<%InstallationCompleteText%>}
+
+B002A311-F8E7-41DE-B039-521391924E5B,Message
+{<%InstallingApplicationText%>}
+
+B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message
+{<%UninstallingApplicationText%>}
+>>>>>>> 0.12
}
diff --git a/distribution/boxbackup/DISTRIBUTION-MANIFEST.txt b/distribution/boxbackup/DISTRIBUTION-MANIFEST.txt
index 3677d376..2fca9572 100644
--- a/distribution/boxbackup/DISTRIBUTION-MANIFEST.txt
+++ b/distribution/boxbackup/DISTRIBUTION-MANIFEST.txt
@@ -1,4 +1,8 @@
LICENSE-GPL.txt
+<<<<<<< HEAD
+=======
+qdbm
+>>>>>>> 0.12
LICENSE DUAL
lib/intercept
@@ -27,8 +31,11 @@ test/backupstorepatch
test/bbackupd
test/bbackupd/testfiles
test/backupdiff
+<<<<<<< HEAD
test/httpserver/testfiles
test/httpserver/testfiles/photos
+=======
+>>>>>>> 0.12
docs/Makefile
docs/tools
diff --git a/distribution/boxbackup/VERSION.txt b/distribution/boxbackup/VERSION.txt
index 129b41fa..feeac86a 100644
--- a/distribution/boxbackup/VERSION.txt
+++ b/distribution/boxbackup/VERSION.txt
@@ -1,2 +1,6 @@
+<<<<<<< HEAD
0.11.1
+=======
+0.12
+>>>>>>> 0.12
boxbackup
diff --git a/docs/api-notes/win32_build_on_cygwin_using_mingw.txt b/docs/api-notes/win32_build_on_cygwin_using_mingw.txt
index e93af6cd..34dbac0b 100644
--- a/docs/api-notes/win32_build_on_cygwin_using_mingw.txt
+++ b/docs/api-notes/win32_build_on_cygwin_using_mingw.txt
@@ -10,26 +10,30 @@ Start by installing Cygwin on your Windows machine from [http://www.cygwin.org/c
Make sure to select the following packages during installation:
+ * Archive/unzip
* Devel/automake
* Devel/autoconf
* Devel/gcc-mingw
- * Devel/gcc-mingw-core
* Devel/gcc-mingw-g++
* Devel/make
- * Devel/mingw-runtime
- * Lib/libxml2
- * Lib/libxslt (for xsltproc)
- * Mingw/mingw-zlib
+ * Libs/libxml2
+ * Libs/libxslt (for xsltproc)
+ * Mingw/mingw-zlib-devel
* Perl/Perl
If you already have Cygwin installed, please re-run the installer and
ensure that those packages are installed.
-You may also want to install the debugger, Devel/gdb.
+You may also want to install:
+
+ * Devel/gdb (the debugger)
+ * Devel/subversion (the command-line Subversion client)
+ * Editors/vim (the Vim text editor)
== Base Directory ==
-Choose a directory where you will unpack and compile OpenSSL, Zlib and Box Backup. We will call this the ''base directory''. An example might be:
+Choose a directory where you will unpack and compile OpenSSL, Zlib and
+Box Backup. We will call this the ''base directory''. An example might be:
C:\Cygwin\Home\YourUsername
@@ -41,46 +45,84 @@ your user's home directory in /etc/passwd to match.
== OpenSSL ==
-Download OpenSSL from [http://www.openssl.org/source/openssl-1.0.0a.tar.gz]
+Download OpenSSL from [http://www.openssl.org/source/openssl-1.0.0e.tar.gz]
Open a Cygwin shell, go to the base directory, and unpack OpenSSL:
- tar xzvf openssl-1.0.0a.tar.gz
+ tar xzvf openssl-1.0.0e.tar.gz
Configure OpenSSL for MinGW compilation, and build and install it:
- cd openssl-1.0.0a
+ cd openssl-1.0.0e
./Configure --prefix=/usr/i686-pc-mingw32/ mingw
make
- make install
+ make install_sw
== PCRE ==
This step is only required to support regular expressions in including/excluding files from backups. However, this is a very useful feature.
Download PCRE from
-[http://prdownloads.sourceforge.net/pcre/pcre-8.10.tar.bz2?download]
-
-Open a Cygwin shell, go to the base directory, and unpack PCRE:
+[http://prdownloads.sourceforge.net/pcre/pcre-8.12.tar.bz2?download].
- tar xjvf pcre-8.10.tar.bz2
+Open a Cygwin shell, go to the base directory, and unpack, build and
+install PCRE:
-Configure PCRE for MinGW compilation, and build and install it:
-
- cd pcre-8.10
+ tar xjvf pcre-8.12.tar.bz2
+ cd pcre-8.12
export CFLAGS="-mno-cygwin"
export CXXFLAGS="-mno-cygwin"
- ./configure --prefix=/usr/i686-pc-mingw32
+ ./configure --prefix=/usr/i686-pc-mingw32 --disable-shared
make
make install
+Note: we must disable shared libraries on Windows because otherwise
+libpcreposix.a is built to depend on libpcre.dll, even if you define
+PCRE_STATIC, and since /usr/i686-pc-mingw32/bin (the location of the DLL)
+is not normally on the PATH, the DLL can't be found, which stops you
+from running any executables.
+
+== Readline (Optional) ==
+
+Readline enables editing and completion of commands in bbackupquery.
+It is optional, but it makes bbackupquery easier to use. However, please
+do not enable readline if you intend to distribute the resulting Box
+Backup binaries, as this would be a violation of the GPL license of
+readline.
+
+If you want Readline support, download PDCurses from
+[http://sourceforge.net/projects/pdcurses/files/pdcurses/3.4/PDCurses-3.4.tar.gz/download]. Save it in your base directory, then open a Cygwin shell there,
+unpack it, compile and install it:
+
+ tar xzvf PDCurses-3.4.tar.gz
+ cd PDCurses-3.4/win32
+ make -f gccwin32.mak CC="gcc -mno-cygwin" LINK="gcc -mno-cygwin"
+ cp pdcurses.a /usr/i686-pc-mingw32/lib/libpdcurses.a
+
+Download Readline version 6.2 from
+[ftp://ftp.cwru.edu/pub/bash/readline-6.2.tar.gz], and unpack
+and install it thus:
+
+ tar xzvf readline-6.2.tar.gz
+ cd readline-6.2
+ ./configure --prefix=/usr/i686-pc-mingw32 \
+ CFLAGS="-mno-cygwin" \
+ CPPFLAGS="-mno-cygwin" \
+ LDFLAGS="-mno-cygwin" \
+ LIBS="-lpdcurses" \
+ --with-curses --disable-shared
+ make
+ make install
+
== Download Box Backup ==
Go back to the base directory, and download the latest Box Backup sources:
svn co https://www.boxbackup.org/svn/box/trunk/ trunk
-Note: If you have problems during the configure or make stage, please try to eliminate one potential source of problems by running the following command to convert all line endings to Unix format:
+Note: If you have problems during the configure or make stage, please try to
+eliminate one potential source of problems by running the following command
+to convert all line endings to Unix format:
find -type f -not \( -wholename .*svn*. \) -exec dos2unix {} \;
@@ -89,14 +131,16 @@ Note: If you have problems during the configure or make stage, please try to eli
Enter the source directory and configure like this:
cd trunk
- ./infrastructure/mingw/configure.sh
+ ./infrastructure/mingw/configure.sh [--enable-gnu-readline]
make
== Installation ==
Create the destination directory, ''C:\Program Files\Box Backup\bbackupd''.
-Write a configuration file, keys and certificate on a Unix machine, and copy them into the ''Box Backup'' directory, together with the following files from the ''base directory'':
+Write a configuration file, keys and certificate on a Unix machine, and
+copy them into the ''Box Backup'' directory, together with the following
+files from the ''base directory'':
* boxbackup\release\bin\bbackupd\bbackupd.exe
* boxbackup\release\bin\bbackupquery\bbackupquery.exe
@@ -105,9 +149,13 @@ Write a configuration file, keys and certificate on a Unix machine, and copy the
* openssl\out32dll\ssleay32.dll
* zlib\zlib1.dll
-Ensure that the user running Box Backup can read from the ''Box Backup'' directory, and write to the ''bbackupd'' directory inside it.
+Ensure that the user running Box Backup can read from the ''Box Backup''
+directory, and write to the ''bbackupd'' directory inside it.
-Run Box Backup by double-clicking on it, and check that it connects to the server. If the window opens and closes immediately, it's probably due to a problem with the configuration file - check the Windows Event Viewer for details.
+Run Box Backup by double-clicking on it, and check that it connects to
+the server. If the window opens and closes immediately, it's probably
+due to a problem with the configuration file. Check the Windows Event
+Viewer for details.
See also the service installation and upgrade instructions at
[https://www.boxbackup.org/trac/wiki/CompilationOnWindows].
diff --git a/docs/xsl-generic/html/biblio-iso690.xsl b/docs/xsl-generic/html/biblio-iso690.xsl
index 366212f5..d3f43198 100644
--- a/docs/xsl-generic/html/biblio-iso690.xsl
+++ b/docs/xsl-generic/html/biblio-iso690.xsl
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="windows-1250"?>
+<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version='1.0'>
diff --git a/infrastructure/BoxPlatform.pm.in b/infrastructure/BoxPlatform.pm.in
index 59ab5d85..f3c71308 100644
--- a/infrastructure/BoxPlatform.pm.in
+++ b/infrastructure/BoxPlatform.pm.in
@@ -24,11 +24,25 @@ BEGIN
# Cygwin Builds usually something like CYGWIN_NT-5.0, CYGWIN_NT-5.1
# Box Backup tried on Win2000,XP only :)
$build_os = 'CYGWIN' if $build_os =~ m/CYGWIN/;
+<<<<<<< HEAD
$make_command = ($build_os eq 'Darwin') ? 'bsdmake' : ($build_os eq 'SunOS') ? 'gmake' : 'make';
$bsd_make = ($build_os ne 'Linux' && $build_os ne 'CYGWIN' &&
$build_os ne "SunOS" && $build_os ne 'GNU/kFreeBSD');
+=======
+ $build_os = 'MINGW32' if $build_os =~ m/MINGW32/;
+
+ if ($build_os eq 'Darwin') {
+ $xcode_ver = `xcodebuild -version | awk '/^Xcode/ {print \$2}'`
+ }
+
+ $make_command = ($build_os eq 'Darwin' && $xcode_ver < 4) ? 'bsdmake' : ($build_os eq 'SunOS') ? 'gmake' : 'make';
+
+ $bsd_make = ($build_os ne 'Linux' && $build_os ne 'CYGWIN' &&
+ $build_os ne "MINGW32" && $build_os ne "SunOS"
+ && $build_os ne 'GNU/kFreeBSD' && $xcode_ver < 4);
+>>>>>>> 0.12
# blank extra flags by default
$platform_compile_line_extra = '';
@@ -82,6 +96,7 @@ BEGIN
# where to put the files
$install_into_dir = '@sbindir_expanded@';
+<<<<<<< HEAD
# if it's Darwin,
if($build_os eq 'Darwin')
{
@@ -93,6 +108,32 @@ BEGIN
$sub_make_options = ' -j '.($1 + 1);
}
+=======
+ # see how many processors there are, and set make flags accordingly
+ if($build_os eq 'Darwin' || $build_os =~ /(Free|Net|Open)BSD/)
+ {
+ $cpus = `sysctl -n hw.ncpu`;
+ }
+ elsif($build_os eq 'Linux')
+ {
+ $cpus = `grep -c ^processor /proc/cpuinfo`;
+ }
+ elsif($build_os eq 'SunOS')
+ {
+ $cpus = `psrinfo -p`;
+ }
+
+ chomp $cpus;
+ if($cpus > 1)
+ {
+ print STDERR "$cpus processors detected, will set make to perform concurrent jobs\n";
+ $sub_make_options = ' -j '.($cpus + 1);
+ }
+
+ # if it's Darwin,
+ if($build_os eq 'Darwin')
+ {
+>>>>>>> 0.12
# test for fink installation
if(-d '/sw/include' && -d '/sw/lib')
{
diff --git a/infrastructure/buildenv-testmain-template.cpp b/infrastructure/buildenv-testmain-template.cpp
index b646a27b..287c4bff 100644
--- a/infrastructure/buildenv-testmain-template.cpp
+++ b/infrastructure/buildenv-testmain-template.cpp
@@ -29,9 +29,23 @@
#include <getopt.h>
#endif
+<<<<<<< HEAD
#include <sys/stat.h>
#include <sys/types.h>
+=======
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifdef HAVE_SYS_UN_H
+# include <sys/un.h>
+#endif
+
+>>>>>>> 0.12
#include <exception>
#include <string>
@@ -76,7 +90,19 @@ inline bool checkfilesleftopen() { return false; }
#define FILEDES_MAX 256
+<<<<<<< HEAD
bool filedes_open[FILEDES_MAX];
+=======
+typedef enum
+{
+ OPEN,
+ CLOSED,
+ SYSLOG
+}
+filedes_t;
+
+filedes_t filedes_open[FILEDES_MAX];
+>>>>>>> 0.12
bool check_filedes(bool report)
{
@@ -87,11 +113,59 @@ bool check_filedes(bool report)
{
if(::fcntl(d, F_GETFD) != -1)
{
+<<<<<<< HEAD
// File descriptor obviously exists
if (report && !filedes_open[d])
{
struct stat st;
if (fstat(d, &st) == 0)
+=======
+ // File descriptor obviously exists, but is it /dev/log?
+
+ struct stat st;
+ bool stat_success = false;
+ bool is_syslog_socket = false;
+
+ if(fstat(d, &st) == 0)
+ {
+ stat_success = true;
+ }
+
+ if(stat_success && (st.st_mode & S_IFSOCK))
+ {
+ char buffer[256];
+ socklen_t addrlen = sizeof(buffer);
+
+#ifdef HAVE_GETPEERNAME
+ if(getpeername(d, (sockaddr*)buffer, &addrlen) != 0)
+ {
+ BOX_WARNING("Failed to getpeername(" <<
+ d << "), cannot identify /dev/log");
+ }
+ else
+ {
+ struct sockaddr_un *sa =
+ (struct sockaddr_un *)buffer;
+ if(sa->sun_family == PF_UNIX &&
+ !strcmp(sa->sun_path, "/dev/log"))
+ {
+ is_syslog_socket = true;
+ }
+ }
+#endif // HAVE_GETPEERNAME
+ }
+
+ if(report && filedes_open[d] != OPEN)
+ {
+ if(filedes_open[d] == SYSLOG)
+ {
+ // Different libcs have different ideas
+ // about when to open and close this
+ // socket, and it's not a leak, so
+ // ignore it.
+ }
+ else if(stat_success)
+>>>>>>> 0.12
{
int m = st.st_mode;
#define flag(x) ((m & x) ? #x " " : "")
@@ -105,11 +179,16 @@ bool check_filedes(bool report)
flag(S_IFLNK) <<
flag(S_IFSOCK) <<
" or " << m << ")");
+<<<<<<< HEAD
+=======
+ allOk = false;
+>>>>>>> 0.12
}
else
{
BOX_FATAL("File descriptor " << d <<
" left open (and stat failed)");
+<<<<<<< HEAD
}
allOk = false;
@@ -118,10 +197,19 @@ bool check_filedes(bool report)
else if (!report)
{
filedes_open[d] = true;
+=======
+ allOk = false;
+ }
+ }
+ else if (!report)
+ {
+ filedes_open[d] = is_syslog_socket ? SYSLOG : OPEN;
+>>>>>>> 0.12
}
}
else
{
+<<<<<<< HEAD
if (report && filedes_open[d])
{
BOX_FATAL("File descriptor " << d <<
@@ -131,6 +219,27 @@ bool check_filedes(bool report)
else
{
filedes_open[d] = false;
+=======
+ if (report && filedes_open[d] != CLOSED)
+ {
+ if (filedes_open[d] == SYSLOG)
+ {
+ // Different libcs have different ideas
+ // about when to open and close this
+ // socket, and it's not a leak, so
+ // ignore it.
+ }
+ else if(filedes_open[d] == OPEN)
+ {
+ BOX_FATAL("File descriptor " << d <<
+ " was open, now closed");
+ allOk = false;
+ }
+ }
+ else
+ {
+ filedes_open[d] = CLOSED;
+>>>>>>> 0.12
}
}
}
@@ -183,7 +292,11 @@ int main(int argc, char * const * argv)
int ch;
+<<<<<<< HEAD
while ((ch = getopt_long(argc, argv, "c:d:qs:t:vPTUV", longopts, NULL))
+=======
+ while ((ch = getopt_long(argc, argv, "c:d:qs:t:vPTUVW:", longopts, NULL))
+>>>>>>> 0.12
!= -1)
{
switch(ch)
@@ -255,6 +368,20 @@ int main(int argc, char * const * argv)
}
break;
+<<<<<<< HEAD
+=======
+ case 'W':
+ {
+ logLevel = Logging::GetNamedLevel(optarg);
+ if (logLevel == Log::INVALID)
+ {
+ BOX_FATAL("Invalid logging level: " << optarg);
+ return 2;
+ }
+ }
+ break;
+
+>>>>>>> 0.12
case 't':
{
Logging::SetProgramName(optarg);
@@ -292,6 +419,10 @@ int main(int argc, char * const * argv)
}
Logging::SetGlobalLevel((Log::Level)logLevel);
+<<<<<<< HEAD
+=======
+ Logging::FilterConsole((Log::Level)logLevel);
+>>>>>>> 0.12
argc -= optind - 1;
argv += optind - 1;
@@ -369,6 +500,15 @@ int main(int argc, char * const * argv)
return returncode;
}
+<<<<<<< HEAD
+=======
+ catch(BoxException &e)
+ {
+ printf("FAILED: Exception caught: %s: %s\n", e.what(),
+ e.GetMessage().c_str());
+ return 1;
+ }
+>>>>>>> 0.12
catch(std::exception &e)
{
printf("FAILED: Exception caught: %s\n", e.what());
diff --git a/infrastructure/m4/ax_check_ssl.m4 b/infrastructure/m4/ax_check_ssl.m4
index 03362bb6..1714080c 100644
--- a/infrastructure/m4/ax_check_ssl.m4
+++ b/infrastructure/m4/ax_check_ssl.m4
@@ -27,7 +27,12 @@ AC_DEFUN([AX_CHECK_SSL], [
ax_check_ssl_found=yes
AC_CHECK_HEADERS([openssl/ssl.h],, [ax_check_ssl_found=no])
+<<<<<<< HEAD
AC_CHECK_LIB([ssl], [SSL_read],, [ax_check_ssl_found=no], [-lcrypto])
+=======
+ AC_SEARCH_LIBS([HMAC_CTX_init], [crypto])
+ AC_SEARCH_LIBS([SSL_read], [ssl],, [ax_check_ssl_found=no])
+>>>>>>> 0.12
if test "x$ax_check_ssl_found" = "xyes"; then
AC_DEFINE([HAVE_SSL], 1, [Define to 1 if SSL is available])
diff --git a/infrastructure/m4/boxbackup_tests.m4 b/infrastructure/m4/boxbackup_tests.m4
new file mode 100644
index 00000000..9e6bc2f2
--- /dev/null
+++ b/infrastructure/m4/boxbackup_tests.m4
@@ -0,0 +1,315 @@
+dnl All Box Backup configury magic is here, to be shared with Boxi
+
+case $build_os in
+solaris*)
+ isa_bits=`isainfo -b`
+ AC_MSG_NOTICE([setting compiler to use -m$isa_bits on Solaris])
+ CFLAGS="$CFLAGS -m$isa_bits"
+ CXXFLAGS="$CXXFLAGS -m$isa_bits"
+ LDFLAGS="$LDFLAGS -m$isa_bits"
+ ;;
+esac
+
+if test "x$GXX" = "xyes"; then
+ # Use -Wall if we have gcc. This gives better warnings
+ AC_SUBST([CXXFLAGS_STRICT], ['-Wall -Wundef'])
+
+ # Don't check for gcc -rdynamic on Solaris as it's broken, but returns 0.
+ # On Cygwin it does nothing except cause gcc to emit a warning message.
+ case $build_os in
+ solaris*|cygwin)
+ AC_MSG_NOTICE([skipping check for -rdynamic on $build_os])
+ ;;
+ *)
+ # Check whether gcc supports -rdynamic, thanks to Steve Ellcey
+ # [http://readlist.com/lists/gcc.gnu.org/gcc/6/31502.html]
+ # This is needed to get symbols in backtraces.
+ # Note that this apparently fails on HP-UX and Solaris
+ LDFLAGS="$LDFLAGS -rdynamic"
+ AC_MSG_CHECKING([whether gcc accepts -rdynamic])
+ AC_TRY_LINK([], [return 0;],
+ [AC_MSG_RESULT([yes]); have_rdynamic=yes],
+ [AC_MSG_RESULT([no])])
+ if test x"$have_rdynamic" = x"yes" ; then
+ AC_SUBST([LDADD_RDYNAMIC], ['-rdynamic'])
+ fi
+ ;;
+ esac
+fi
+
+AC_PATH_PROG([PERL], [perl], [AC_MSG_ERROR([[perl executable was not found]])])
+
+case $target_os in
+mingw*)
+ TARGET_PERL=perl
+ ;;
+*)
+ TARGET_PERL=$PERL
+ ;;
+esac
+
+AC_SUBST([TARGET_PERL])
+AC_DEFINE_UNQUOTED([PERL_EXECUTABLE], ["$TARGET_PERL"],
+ [Location of the perl executable])
+
+AC_CHECK_TOOL([AR], [ar],
+ [AC_MSG_ERROR([[cannot find ar executable]])])
+AC_CHECK_TOOL([RANLIB], [ranlib],
+ [AC_MSG_ERROR([[cannot find ranlib executable]])])
+
+case $target_os in
+mingw*)
+ AC_CHECK_TOOL([WINDRES], [windres],
+ [AC_MSG_ERROR([[cannot find windres executable]])])
+ ;;
+esac
+
+### Checks for libraries.
+
+case $target_os in
+mingw32*)
+ AC_CHECK_LIB([crypto -lws2_32 -lgdi32], [CRYPTO_lock])
+ ;;
+winnt)
+ ;;
+*)
+ AC_SEARCH_LIBS([nanosleep], [rt], [ac_have_nanosleep=yes],
+ [AC_MSG_ERROR([[cannot find a short sleep function (nanosleep)]])])
+ ;;
+esac
+
+AC_CHECK_HEADER([zlib.h],, [AC_MSG_ERROR([[cannot find zlib.h]])])
+AC_CHECK_LIB([z], [zlibVersion],, [AC_MSG_ERROR([[cannot find zlib]])])
+VL_LIB_READLINE([have_libreadline=yes], [have_libreadline=no])
+AC_CHECK_FUNCS([rl_filename_completion_function])
+
+## Check for Berkely DB. Restrict to certain versions
+AX_PATH_BDB([1.x or 4.1], [
+ LIBS="$BDB_LIBS $LIBS"
+ LDFLAGS="$BDB_LDFLAGS $LDFLAGS"
+ CPPFLAGS="$CPPFLAGS $BDB_CPPFLAGS"
+
+ AX_COMPARE_VERSION([$BDB_VERSION],[ge],[4.1],,
+ [AX_COMPARE_VERSION([$BDB_VERSION],[lt],[2],,
+ [AC_MSG_ERROR([[only Berkely DB versions 1.x or at least 4.1 are currently supported]])]
+ )]
+ )
+ AX_SPLIT_VERSION([BDB_VERSION], [$BDB_VERSION])
+])
+
+# need to find libdl before trying to link openssl, apparently
+AC_SEARCH_LIBS([dlsym], ["dl"])
+AC_CHECK_FUNCS([dlsym dladdr])
+
+## Check for Open SSL, use old versions only if explicitly requested
+AC_SEARCH_LIBS([gethostbyname], [nsl socket resolv])
+AC_SEARCH_LIBS([shutdown], [nsl socket resolv])
+AX_CHECK_SSL(, [AC_MSG_ERROR([[OpenSSL is not installed but is required]])])
+AC_ARG_ENABLE(
+ [old-ssl],
+ [AC_HELP_STRING([--enable-old-ssl],
+ [Allow use of pre-0.9.7 Open SSL - NOT RECOMMENDED, read the documentation])])
+AC_SEARCH_LIBS(
+ [EVP_CipherInit_ex],
+ [crypto],, [
+ if test "x$enable_old_ssl" = "xyes"; then
+ AC_DEFINE([HAVE_OLD_SSL], 1, [Define to 1 if SSL is pre-0.9.7])
+ else
+ AC_MSG_ERROR([[found an old (pre 0.9.7) version of SSL.
+Upgrade or read the documentation for alternatives]])
+ fi
+ ])
+
+
+### Checks for header files.
+
+case $target_os in
+mingw32*) ;;
+winnt*) ;;
+*)
+ AC_HEADER_DIRENT
+ ;;
+esac
+
+AC_HEADER_STDC
+AC_HEADER_SYS_WAIT
+AC_CHECK_HEADERS([dlfcn.h fcntl.h getopt.h process.h pwd.h signal.h])
+AC_CHECK_HEADERS([syslog.h time.h cxxabi.h])
+AC_CHECK_HEADERS([netinet/in.h netinet/tcp.h])
+AC_CHECK_HEADERS([sys/file.h sys/param.h sys/socket.h sys/time.h sys/types.h])
+AC_CHECK_HEADERS([sys/uio.h sys/un.h sys/wait.h sys/xattr.h])
+AC_CHECK_HEADERS([bsd/unistd.h])
+AC_CHECK_HEADERS([sys/socket.h], [have_sys_socket_h=yes])
+AC_CHECK_HEADERS([winsock2.h], [have_winsock2_h=yes])
+AC_CHECK_HEADERS([execinfo.h], [have_execinfo_h=yes])
+
+if test "$have_execinfo_h" = "yes"; then
+ AC_SEARCH_LIBS([backtrace],[execinfo])
+fi
+
+AC_CHECK_HEADER([regex.h], [have_regex_h=yes])
+
+if test "$have_regex_h" = "yes"; then
+ AC_DEFINE([HAVE_REGEX_H], [1], [Define to 1 if regex.h is available])
+else
+ AC_CHECK_HEADER([pcreposix.h], [have_pcreposix_h=yes])
+fi
+
+if test "$have_pcreposix_h" = "yes"; then
+ AC_DEFINE([PCRE_STATIC], [1], [Box Backup always uses static PCRE])
+ AC_SEARCH_LIBS([regcomp], ["pcreposix -lpcre"],,[have_pcreposix_h=no_regcomp])
+fi
+
+if test "$have_pcreposix_h" = "yes"; then
+ AC_DEFINE([HAVE_PCREPOSIX_H], [1], [Define to 1 if pcreposix.h is available])
+fi
+
+if test "$have_regex_h" = "yes" -o "$have_pcreposix_h" = "yes"; then
+ have_regex_support=yes
+ AC_DEFINE([HAVE_REGEX_SUPPORT], [1], [Define to 1 if regular expressions are supported])
+else
+ have_regex_support=no
+fi
+
+### Checks for typedefs, structures, and compiler characteristics.
+
+AC_CHECK_TYPES([u_int8_t, u_int16_t, u_int32_t, u_int64_t])
+AC_CHECK_TYPES([uint8_t, uint16_t, uint32_t, uint64_t])
+
+AC_HEADER_STDBOOL
+AC_C_CONST
+AC_C_BIGENDIAN
+AC_TYPE_UID_T
+AC_TYPE_MODE_T
+AC_TYPE_OFF_T
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+
+AC_CHECK_MEMBERS([struct stat.st_flags])
+AC_CHECK_MEMBERS([struct stat.st_mtimespec])
+AC_CHECK_MEMBERS([struct stat.st_atim.tv_nsec])
+AC_CHECK_MEMBERS([struct stat.st_atimensec])
+AC_CHECK_MEMBERS([struct sockaddr_in.sin_len],,, [[
+ #include <sys/types.h>
+ #include <netinet/in.h>
+ ]])
+AC_CHECK_MEMBERS([DIR.d_fd],,, [[#include <dirent.h>]])
+AC_CHECK_MEMBERS([DIR.dd_fd],,, [[#include <dirent.h>]])
+AC_CHECK_MEMBERS([struct tcp_info.tcpi_rtt],,, [[#include <netinet/tcp.h>]])
+
+AC_CHECK_DECLS([INFTIM],,, [[#include <poll.h>]])
+AC_CHECK_DECLS([SO_PEERCRED],,, [[#include <sys/socket.h>]])
+AC_CHECK_DECLS([O_BINARY],,,)
+AC_CHECK_DECLS([SOL_TCP],,, [[#include <netinet/tcp.h>]])
+AC_CHECK_DECLS([TCP_INFO],,, [[#include <netinet/tcp.h>]])
+
+if test -n "$have_sys_socket_h"; then
+ AC_CHECK_DECLS([SO_SNDBUF],,, [[#include <sys/socket.h>]])
+elif test -n "$have_winsock2_h"; then
+ AC_CHECK_DECLS([SO_SNDBUF],,, [[#include <winsock2.h>]])
+else
+ # unlikely to succeed, but defined HAVE_DECL_SO_SNDBUF to 0 instead
+ # of leaving it undefined, which makes cpp #ifdefs simpler.
+ AC_CHECK_DECLS([SO_SNDBUF])
+fi
+
+# Solaris provides getpeerucred() instead of getpeereid() or SO_PEERCRED
+AC_CHECK_HEADERS([ucred.h])
+AC_CHECK_FUNCS([getpeerucred])
+
+AC_CHECK_DECLS([optreset],,, [[#include <getopt.h>]])
+AC_CHECK_DECLS([dirfd],,,
+ [[
+ #include <getopt.h>
+ #include <dirent.h>
+ ]])
+
+AC_HEADER_TIME
+AC_STRUCT_TM
+AX_CHECK_DIRENT_D_TYPE
+AC_SYS_LARGEFILE
+AX_CHECK_DEFINE_PRAGMA
+if test "x$ac_cv_c_bigendian" != "xyes"; then
+ AX_BSWAP64
+fi
+
+case $target_os in
+mingw32*) ;;
+winnt*) ;;
+*)
+ AX_RANDOM_DEVICE
+ AX_CHECK_MOUNT_POINT(,[
+ AC_MSG_ERROR([[cannot work out how to discover mount points on your platform]])
+ ])
+ AC_CHECK_MEMBERS([struct dirent.d_ino],,, [[#include <dirent.h>]])
+;;
+esac
+
+AX_CHECK_MALLOC_WORKAROUND
+
+
+### Checks for library functions.
+
+AC_FUNC_CLOSEDIR_VOID
+AC_FUNC_ERROR_AT_LINE
+AC_TYPE_SIGNAL
+AC_FUNC_STAT
+AC_CHECK_FUNCS([getpeereid getpeername lchown setproctitle getpid gettimeofday waitpid ftruncate])
+AC_SEARCH_LIBS([setproctitle], ["bsd"])
+
+# NetBSD implements kqueue too differently for us to get it fixed by 0.10
+# TODO: Remove this when NetBSD kqueue implementation is working
+netbsd_hack=`echo $target_os | sed 's/netbsd.*/netbsd/'`
+if test "$netbsd_hack" != "netbsd"; then
+ AC_CHECK_FUNCS([kqueue])
+fi
+
+AX_FUNC_SYSCALL
+AX_CHECK_SYSCALL_LSEEK
+AC_CHECK_FUNCS([listxattr llistxattr getxattr lgetxattr setxattr lsetxattr])
+AC_CHECK_DECLS([XATTR_NOFOLLOW],,, [[#include <sys/xattr.h>]])
+
+
+### Miscellaneous complicated feature checks
+
+## Check for large file support active. AC_SYS_LARGEFILE has already worked
+## out how to enable it if necessary, we just use this to report to the user
+AC_CACHE_CHECK([if we have large file support enabled],
+ [box_cv_have_large_file_support],
+ [AC_TRY_RUN([
+ $ac_includes_default
+ int main()
+ {
+ return sizeof(off_t)==4;
+ }
+ ],
+ [box_cv_have_large_file_support=yes],
+ [box_cv_have_large_file_support=no],
+ [box_cv_have_large_file_support=no # safe for cross-compile]
+ )
+ ])
+
+if test "x$box_cv_have_large_file_support" = "xyes"; then
+ AC_DEFINE([HAVE_LARGE_FILE_SUPPORT], [1],
+ [Define to 1 if large files are supported])
+fi
+
+## Find out how to do file locking
+AC_CHECK_FUNCS([flock fcntl])
+AC_CHECK_DECLS([O_EXLOCK],,, [[#include <fcntl.h>]])
+AC_CHECK_DECLS([F_SETLK],,, [[#include <fcntl.h>]])
+
+case $target_os in
+mingw32*) ;;
+winnt*) ;;
+*)
+if test "x$ac_cv_func_flock" != "xyes" && \
+ test "x$ac_cv_have_decl_O_EXLOCK" != "xyes" && \
+ test "x$ac_cv_have_decl_F_SETLK" != "xyes"
+then
+ AC_MSG_ERROR([[cannot work out how to do file locking on your platform]])
+fi
+;;
+esac
+
+
diff --git a/infrastructure/m4/vl_lib_readline.m4 b/infrastructure/m4/vl_lib_readline.m4
index a0571bfa..e04a5f5f 100644
--- a/infrastructure/m4/vl_lib_readline.m4
+++ b/infrastructure/m4/vl_lib_readline.m4
@@ -79,6 +79,7 @@ AC_DEFUN([VL_LIB_READLINE], [
fi
])
+<<<<<<< HEAD
dnl VL_LIB_READLINE_CHECK(name, libraries, headers, history headers)
AC_DEFUN([VL_LIB_READLINE_CHECK], [
AC_CACHE_CHECK([for $1 library],
@@ -87,6 +88,26 @@ AC_DEFUN([VL_LIB_READLINE_CHECK], [
vl_cv_lib_$1=""
for readline_lib in $2; do
for termcap_lib in "" termcap curses ncurses; do
+=======
+dnl BOX_CHECK_VAR(name, where, headers)
+AC_DEFUN([BOX_CHECK_VAR], [
+ AC_CACHE_CHECK([for $1 $2], [vl_cv_var_$1],
+ [AC_TRY_LINK([$3], [(void) $1], [vl_cv_var_$1=yes], [vl_cv_var_$1=no])
+ ])
+ if test "${vl_cv_var_$1}" = "yes"; then
+ AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_$1), 1, [Define if you have $1 $2])
+ fi
+ ])
+
+dnl VL_LIB_READLINE_CHECK(name, libraries, headers, history headers)
+AC_DEFUN([VL_LIB_READLINE_CHECK], [
+ ORIG_LIBS="$LIBS"
+ AC_CACHE_CHECK([for $1 library],
+ [vl_cv_lib_$1], [
+ vl_cv_lib_$1=""
+ for readline_lib in $2; do
+ for termcap_lib in "" termcap curses ncurses pdcurses; do
+>>>>>>> 0.12
if test -z "$termcap_lib"; then
TRY_LIB="-l$readline_lib"
else
@@ -108,12 +129,44 @@ AC_DEFUN([VL_LIB_READLINE_CHECK], [
fi
])
+<<<<<<< HEAD
vl_cv_lib_readline_compat_found=no
if test "x$vl_cv_lib_$1" != "xno"; then
AC_CHECK_HEADERS([$3], [vl_cv_lib_readline_compat_found=yes])
fi
if test "x$vl_cv_lib_readline_compat_found" = "xyes"; then
+=======
+ vl_cv_lib_includes=""
+
+ vl_cv_lib_readline_compat_found=no
+ if test "x$vl_cv_lib_$1" != "xno"; then
+ AC_CHECK_HEADERS([$3], [
+ vl_cv_lib_readline_compat_found=yes
+ vl_cv_lib_includes="$vl_cv_lib_headers #include <$ac_header>"
+ ])
+ fi
+
+ AC_TRY_LINK([$vl_cv_lib_includes], [(void) readline;],
+ [vl_compiles=yes], [vl_compiles=no])
+ if test "x$vl_compiles" = "xno"; then
+ AC_TRY_LINK([#include <stdio.h>
+ $vl_cv_lib_includes], [(void) readline;],
+ [vl_compiles_with_stdio=yes], [vl_compiles_with_stdio=no])
+ if test "x$vl_compiles_with_stdio" = "xyes"; then
+ vl_cv_lib_includes="#include <stdio.h>
+$vl_cv_lib_includes"
+ fi
+ fi
+
+ if test "x$vl_cv_lib_readline_compat_found" = "xyes"; then
+ BOX_CHECK_VAR([rl_completion_matches], [in readline headers],
+ [$vl_cv_lib_includes])
+
+ BOX_CHECK_VAR([completion_matches], [in readline headers],
+ [$vl_cv_lib_includes])
+
+>>>>>>> 0.12
AC_DEFINE([HAVE_LIBREADLINE], 1,
[Define if you have a readline compatible library])
diff --git a/infrastructure/makebuildenv.pl.in b/infrastructure/makebuildenv.pl.in
index 33b0b635..ed963c93 100755
--- a/infrastructure/makebuildenv.pl.in
+++ b/infrastructure/makebuildenv.pl.in
@@ -261,7 +261,11 @@ for(@modules_files)
push @modules,$mod;
my @md; # module dependencies
my @lo; # link line options
+<<<<<<< HEAD
for(@deps)
+=======
+ for (@deps)
+>>>>>>> 0.12
{
if(/\A-l/)
{
@@ -277,7 +281,11 @@ for(@modules_files)
# make directories, but not if we're using an external library and this a library module
my ($s,$d) = split /\//,$mod;
+<<<<<<< HEAD
if($s ne 'lib' || $external_lib eq '')
+=======
+ if ($s ne 'lib' or $external_lib eq '')
+>>>>>>> 0.12
{
mkdir "release/$s",0755;
mkdir "release/$s/$d",0755;
@@ -346,7 +354,12 @@ for my $mod (@modules, @implicit_deps)
for(grep /\.h\Z/i, @items)
{
next if /\A\._/; # Temp Mac OS Resource hack
+<<<<<<< HEAD
die "Header file $_ already used in module ".$hfiles{$_}."\n" if exists $hfiles{$_};
+=======
+ die "Header file $_ already used in module ".$hfiles{$_}.
+ ", cannot add to $mod\n" if exists $hfiles{$_};
+>>>>>>> 0.12
$hfiles{$_} = $mod
}
}
@@ -375,11 +388,18 @@ for my $mod (@modules, @implicit_deps)
closedir DIR;
}
+<<<<<<< HEAD
+=======
+# Then write a makefile for each module
+>>>>>>> 0.12
print "done\n\nGenerating Makefiles...\n";
my %module_resources_win32;
+<<<<<<< HEAD
# Then write a makefile for each module
+=======
+>>>>>>> 0.12
for my $mod (@implicit_deps, @modules)
{
print $mod,"\n";
@@ -483,6 +503,10 @@ __E
}
my @all_deps_for_module;
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
{
# work out what dependencies need to be run
my @deps_raw;
@@ -566,16 +590,27 @@ WINDRES = @WINDRES@
DEFAULT_CXXFLAGS = @CPPFLAGS@ $default_cxxflags @CXXFLAGS_STRICT@ \\
$include_paths $extra_platform_defines \\
-DBOX_VERSION="\\"$product_version\\""
+<<<<<<< HEAD
LDFLAGS = @LDFLAGS@ @LDADD_RDYNAMIC@
.ifdef RELEASE
CXXFLAGS = -DBOX_RELEASE_BUILD $release_flags \$(DEFAULT_CXXFLAGS)
+=======
+LDFLAGS += @LDFLAGS@ @LDADD_RDYNAMIC@
+
+.ifdef RELEASE
+CXXFLAGS += -DBOX_RELEASE_BUILD $release_flags \$(DEFAULT_CXXFLAGS)
+>>>>>>> 0.12
OUTBASE = ../../release
OUTDIR = ../../release/$mod
DEPENDMAKEFLAGS = -D RELEASE
VARIENT = RELEASE
.else
+<<<<<<< HEAD
CXXFLAGS = -g \$(DEFAULT_CXXFLAGS)
+=======
+CXXFLAGS += -g \$(DEFAULT_CXXFLAGS)
+>>>>>>> 0.12
OUTBASE = ../../debug
OUTDIR = ../../debug/$mod
DEPENDMAKEFLAGS =
@@ -620,6 +655,15 @@ _PERL = \$(if \$(V),\$(PERL), @ echo " [PERL] \$@" && \$(PERL) >/dev/n
__E
}
+<<<<<<< HEAD
+=======
+
+ # if there is a Makefile.pre, include it now
+ if(-e "$mod/Makefile.pre")
+ {
+ print MAKE ".include <Makefile.pre>\n\n";
+ }
+>>>>>>> 0.12
# read directory
opendir DIR,$mod;
@@ -740,7 +784,11 @@ __E
$has_deps = 1;
$has_deps = 0 if $target_is_library;
+<<<<<<< HEAD
# Depenency stuff
+=======
+ # Dependency stuff
+>>>>>>> 0.12
my $deps_makeinfo;
if($has_deps)
{
@@ -766,15 +814,56 @@ __E
# run make for things we require
for my $dep (@all_deps_for_module)
{
+<<<<<<< HEAD
$deps_makeinfo .= "\t\t\$(HIDE) (cd ../../$dep; \$(MAKE)$sub_make_options -q \$(DEPENDMAKEFLAGS) -D NODEPS || \$(MAKE)$sub_make_options \$(DEPENDMAKEFLAGS) -D NODEPS)\n";
+=======
+ my $dep_target = "";
+ if ($dep =~ m|^lib/(.*)|)
+ {
+ $dep_target = "\$(OUTBASE)/$dep/$1.a";
+ }
+ elsif ($dep =~ m|^.*/(.*)|)
+ {
+ $dep_target = "\$(OUTBASE)/$dep/$1$platform_exe_ext";
+ }
+ else
+ {
+ $dep_target = "lib$dep.a";
+ }
+
+ $deps_makeinfo .= <<EOF;
+ \$(HIDE) ( \\
+ cd ../../$dep; \\
+ \$(MAKE) $sub_make_options -q \$(DEPENDMAKEFLAGS) -D NODEPS $dep_target \\
+ || \$(MAKE) $sub_make_options \$(DEPENDMAKEFLAGS) -D NODEPS $dep_target \\
+ )
+EOF
+>>>>>>> 0.12
}
$deps_makeinfo .= ".\tendif\n.endif\n\n";
}
print MAKE $deps_makeinfo if $bsd_make;
+<<<<<<< HEAD
# get the list of library things to add -- in order of dependency so things link properly
my $lib_files = join(' ',map {($_ =~ m/lib\/(.+)\Z/)?('$(OUTBASE)/'.$_.'/'.$1.'.a'):undef} (reverse(@all_deps_for_module)));
+=======
+ # get the list of library things to add -- in order of dependency
+ # so things link properly
+ my @lib_files;
+ foreach my $dep (reverse @all_deps_for_module)
+ {
+ if ($dep =~ m|^lib\/(.+)$|)
+ {
+ push @lib_files, "\$(OUTBASE)/$dep/$1.a";
+ }
+ elsif ($dep =~ m|^([^/]+)$|)
+ {
+ push @lib_files, "../../$dep/lib$1.a";
+ }
+ }
+>>>>>>> 0.12
# need to see if the extra makefile fragments require extra object files
# or include any more makefiles
@@ -793,7 +882,11 @@ __E
}
print MAKE $end_target,': ',$o_file_list;
+<<<<<<< HEAD
print MAKE " ",$lib_files unless $target_is_library;
+=======
+ print MAKE " @lib_files" unless $target_is_library;
+>>>>>>> 0.12
print MAKE "\n";
if ($target_windows)
@@ -820,6 +913,7 @@ __E
# work out library options
# need to be... least used first, in absolute order they appear in the modules.txt file
my @libops;
+<<<<<<< HEAD
sub libops_fill
{
my ($m,$r) = @_;
@@ -827,6 +921,28 @@ __E
libops_fill($_,$r) for(@{$module_dependency{$m}});
}
libops_fill($mod,\@libops);
+=======
+
+ sub libops_fill
+ {
+ my ($module, $libops_ref) = @_;
+
+ my $library_link_opts = $module_library_link_opts{$module};
+ if ($library_link_opts)
+ {
+ push @$libops_ref, @$library_link_opts;
+ }
+
+ my $deps = $module_dependency{$module};
+ foreach my $dep (@$deps)
+ {
+ libops_fill($dep, $libops_ref);
+ }
+ }
+
+ libops_fill($mod,\@libops);
+
+>>>>>>> 0.12
my $lo = '';
my %ldone;
for(@libops)
@@ -839,14 +955,24 @@ __E
# link line...
print MAKE "\t\$(_LINK) \$(LDFLAGS) $link_line_extra " .
"-o $end_target $o_file_list " .
+<<<<<<< HEAD
"$lib_files$lo $platform_lib_files\n";
}
+=======
+ "@lib_files $lo $platform_lib_files\n";
+ }
+
+>>>>>>> 0.12
# tests need to copy the test file over
if($type eq 'test')
{
print MAKE "\tcp _t \$(OUTDIR)/t\n\tchmod u+x \$(OUTDIR)/t\n";
print MAKE "\tcp _t-gdb \$(OUTDIR)/t-gdb\n\tchmod u+x \$(OUTDIR)/t-gdb\n";
}
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
# dependency line?
print MAKE "\n";
diff --git a/infrastructure/makeparcels.pl.in b/infrastructure/makeparcels.pl.in
index 4dc94925..f468dc1f 100755
--- a/infrastructure/makeparcels.pl.in
+++ b/infrastructure/makeparcels.pl.in
@@ -123,7 +123,11 @@ release/common/test:
.PHONY: docs
docs:
+<<<<<<< HEAD
\$(MAKE) -C docs
+=======
+ cd docs; \$(MAKE)
+>>>>>>> 0.12
__END_OF_FRAGMENT
@@ -137,6 +141,12 @@ for my $parcel (@parcels)
my $dir = BoxPlatform::parcel_dir($parcel);
my @parcel_deps;
+<<<<<<< HEAD
+=======
+ # Need to use BSD install on Solaris
+ my $install_bin = $build_os eq 'SunOS' ? '/usr/ucb/install' : 'install';
+
+>>>>>>> 0.12
unless ($target_windows)
{
open SCRIPT,">parcels/scripts/install-$parcel" or die
@@ -217,10 +227,18 @@ $dir/${name}.gz: docs/man/${name}.gz
EOF
# Releases have the docs pre-made, but users
# may want to rebuild them for some reason.
+<<<<<<< HEAD
print MAKE <<EOF;
.PHONY: docs/man/${name}.gz
docs/man/${name}.gz:
\$(MAKE) -C docs man/${name}.gz
+=======
+ my $docbook_source = "docs/docbook/${name}";
+ $docbook_source =~ s/\.[58]$/.xml/;
+ print MAKE <<EOF;
+docs/man/${name}.gz: $docbook_source docs/docbook/bb-man.xsl
+ cd docs; \$(MAKE) man/${name}.gz
+>>>>>>> 0.12
EOF
push @parcel_deps, "$dir/${name}.gz";
@@ -235,24 +253,43 @@ $dir/docs/${name}.html: docs/htmlguide/man-html/${name}.html
EOF
# Releases have the docs pre-made, but users
# may want to rebuild them for some reason.
+<<<<<<< HEAD
print MAKE <<EOF;
.PHONY: docs/htmlguide/man-html/${name}.html
docs/htmlguide/man-html/${name}.html:
\$(MAKE) -C docs htmlguide/man-html/${name}.html
+=======
+ my $docbook_source = "docs/docbook/${name}.xml";
+ print MAKE <<EOF;
+docs/htmlguide/man-html/${name}.html: $docbook_source docs/docbook/bb-nochunk-book.xsl
+ cd docs; \$(MAKE) htmlguide/man-html/${name}.html
+>>>>>>> 0.12
EOF
push @parcel_deps, "$dir/docs/${name}.html";
}
elsif ($type eq 'subdir')
{
+<<<<<<< HEAD
+=======
+ shift @args;
+ my $subdir = shift @args;
+>>>>>>> 0.12
print MAKE <<EOF;
.PHONY: $name-build $name-clean
$name-build:
+<<<<<<< HEAD
\$(MAKE) -C $name
$name-clean:
\$(MAKE) -C $name clean
+=======
+ cd $subdir; \$(MAKE) @args
+
+$name-clean:
+ cd $name; \$(MAKE) clean
+>>>>>>> 0.12
EOF
push @parcel_deps, "$name-build";
push @clean_deps, "$name-clean";
@@ -297,7 +334,11 @@ EOF
if ($type eq 'html')
{
+<<<<<<< HEAD
$dest = "share/doc/$version";
+=======
+ $dest = "share/doc/@PACKAGE_TARNAME@";
+>>>>>>> 0.12
$name = "docs/$name.html";
}
@@ -308,7 +349,11 @@ EOF
$name =~ s/$/\.gz/;
}
+<<<<<<< HEAD
if ($install and not $target_windows)
+=======
+ if ($install and not $target_windows and not $type eq "subdir")
+>>>>>>> 0.12
{
my $local_install_dir = $install_into_dir;
if (defined $dest)
@@ -325,7 +370,11 @@ EOF
}
print SCRIPT "mkdir -p " .
"\${DESTDIR}$local_install_dir/\n";
+<<<<<<< HEAD
print SCRIPT "install $name " .
+=======
+ print SCRIPT "$install_bin $name " .
+>>>>>>> 0.12
"\${DESTDIR}$local_install_dir\n";
}
}
@@ -359,22 +408,42 @@ install:
cat local/install.msg
clean: @clean_deps
+<<<<<<< HEAD
\$(MAKE) -C docs clean
+=======
+ cd docs; \$(MAKE) clean
+>>>>>>> 0.12
EOF
if ($build_os eq 'CYGWIN')
{
+<<<<<<< HEAD
print MAKE "\tfind release debug -type f | xargs -r rm -f\n";
}
else
{
print MAKE "\tfind release debug -type f -exec rm -f {} \\;\n";
+=======
+ print MAKE "\tfind release debug -type f | xargs -r rm\n";
+ print MAKE "\tfind . -name 'autogen_*' -type f | xargs -r rm\n";
+}
+else
+{
+ print MAKE "\tfind release debug -type f -exec rm -f {} \\;\n";
+ print MAKE "\tfind . -name 'autogen_*' -type f -exec rm -f {} \\;\n";
+>>>>>>> 0.12
}
for my $parcel (@parcels)
{
+<<<<<<< HEAD
print MAKE "\trm -rf ", BoxPlatform::parcel_dir($parcel), "\n";
print MAKE "\trm -f ", BoxPlatform::parcel_target($parcel), "\n";
+=======
+ # need to use -f to avoid error if they don't exist (already cleaned)
+ print MAKE "\trm -rf ", BoxPlatform::parcel_dir($parcel), "\n";
+ print MAKE "\trm -f ", BoxPlatform::parcel_target($parcel), "\n";
+>>>>>>> 0.12
}
close MAKE;
diff --git a/infrastructure/mingw/configure.sh b/infrastructure/mingw/configure.sh
index 0486b20d..5f16c558 100755
--- a/infrastructure/mingw/configure.sh
+++ b/infrastructure/mingw/configure.sh
@@ -16,12 +16,21 @@ if [ ! -r "$DEP_PATH/lib/libpcreposix.a" \
exit 2
fi
+<<<<<<< HEAD
export CXX="g++ -mno-cygwin"
export LD="g++ -mno-cygwin"
export CFLAGS="-mno-cygwin -mthreads"
export CXXFLAGS="-mno-cygwin -mthreads"
export LDFLAGS="-mno-cygwin -mthreads"
export LIBS="-lcrypto -lws2_32 -lgdi32"
+=======
+LIBZ_PATH="${DEP_PATH}/sys-root/mingw/lib"
+
+if [ ! -r "$LIBZ_PATH/libz.dll.a" ]; then
+ echo "Error: upgrade your Cygwin mingw-zlib-devel package" >&2
+ exit 2
+fi
+>>>>>>> 0.12
if [ ! -x "configure" ]; then
if ! ./bootstrap; then
@@ -30,7 +39,16 @@ if [ ! -x "configure" ]; then
fi
fi
+<<<<<<< HEAD
if ! ./configure --target=i686-pc-mingw32; then
+=======
+if ! ./configure "$@" --target=i686-pc-mingw32 \
+ CFLAGS="-mno-cygwin -mthreads" \
+ CPPFLAGS="-mno-cygwin" \
+ CXXFLAGS="-mno-cygwin -mthreads" \
+ LDFLAGS="-Wl,-Bstatic -mno-cygwin -mthreads -L${DEP_PATH}/lib -L${LIBZ_PATH}"
+then
+>>>>>>> 0.12
echo "Error: configure failed, aborting." >&2
exit 1
fi
diff --git a/infrastructure/msvc/2010/bbackupctl.vcxproj b/infrastructure/msvc/2010/bbackupctl.vcxproj
new file mode 100644
index 00000000..5751b4d8
--- /dev/null
+++ b/infrastructure/msvc/2010/bbackupctl.vcxproj
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{9FD51412-E945-4457-A17A-CA3C505CF431}</ProjectGuid>
+ <RootNamespace>bbackupctl</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)..\..\..\$(Configuration)\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</IntDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\..\Release\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\..\Release\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\lib\backupclient;$(ProjectDir)..\..\..\lib\common;$(ProjectDir)..\..\..\lib\compress;$(ProjectDir)..\..\..\lib\crypto;$(ProjectDir)..\..\..\lib\server;$(ProjectDir)..\..\..\lib\win32;$(ProjectDir)..\..\..\..\openssl\include;$(ProjectDir)..\..\..\..\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;QDBM_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(OutDir)bbackupctl.pdb</ProgramDatabaseFile>
+ <SubSystem>Console</SubSystem>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
+ <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\lib\backupclient;$(ProjectDir)..\..\..\lib\server;$(ProjectDir)..\..\..\lib\crypto;$(ProjectDir)..\..\..\..\openssl\inc32;$(ProjectDir)..\..\..\lib\compress;$(ProjectDir)..\..\..\..\zlib\include;$(ProjectDir)..\..\..\lib\win32;$(ProjectDir)..\..\..\lib\common\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>Ws2_32.lib;$(ProjectDir)..\..\..\..\zlib\lib\zdll.lib;$(ProjectDir)..\..\..\..\openssl\out32dll\libeay32.lib;$(ProjectDir)..\..\..\..\openssl\out32dll\ssleay32.lib;$(ProjectDir)..\..\..\Release\common.lib;$(ProjectDir)..\..\..\..\pcre\bin\release\lib_pcreposix.lib;$(ProjectDir)..\..\..\..\pcre\bin\release\lib_pcre.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)bbackupctl.exe</OutputFile>
+ <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\bin\bbackupctl\bbackupctl.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="..\..\..\lib\win32\messages.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="common.vcxproj">
+ <Project>{a089cee6-ebf0-4232-a0c0-74850a8127a6}</Project>
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="libbackupclient.vcxproj">
+ <Project>{32604097-c934-4711-b1ad-206336640e70}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/infrastructure/msvc/2010/bbackupd.vcxproj b/infrastructure/msvc/2010/bbackupd.vcxproj
new file mode 100644
index 00000000..a98bd852
--- /dev/null
+++ b/infrastructure/msvc/2010/bbackupd.vcxproj
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{22D325FB-9131-4BD6-B390-968F0491D687}</ProjectGuid>
+ <RootNamespace>bbackupd</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ <UseOfMfc>false</UseOfMfc>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)..\..\..\$(Configuration)\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\..\Release\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\..\Release\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>$(SolutionDir)..\..\..\lib\backupstore;$(SolutionDir)..\..\..\lib\backupclient;$(SolutionDir)..\..\..\lib\common;$(SolutionDir)..\..\..\lib\compress;$(SolutionDir)..\..\..\lib\crypto;$(SolutionDir)..\..\..\lib\server;$(SolutionDir)..\..\..\lib\win32;$(SolutionDir)..\..\..\qdbm;$(SolutionDir)..\..\..\..\openssl\include;$(SolutionDir)..\..\..\..\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>ENABLE_VSS;WIN32;_DEBUG;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;QDBM_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>VssApi.lib;$(OutDir)\libbackupclient.lib;$(OutDir)\libbackupstore.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(OutDir)bbackupd.pdb</ProgramDatabaseFile>
+ <SubSystem>Console</SubSystem>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
+ <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\lib\backupclient;$(ProjectDir)..\..\..\lib\server;$(ProjectDir)..\..\..\lib\crypto;$(SolutionDir)..\..\..\..\openssl\inc32;$(ProjectDir)..\..\..\lib\compress;$(ProjectDir)..\..\..\..\zlib\include;$(ProjectDir)..\..\..\lib\win32;$(ProjectDir)..\..\..\lib\common\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>Ws2_32.lib;$(ProjectDir)..\..\..\..\zlib\lib\zdll.lib;$(ProjectDir)..\..\..\..\openssl\out32dll\libeay32.lib;$(ProjectDir)..\..\..\..\openssl\out32dll\ssleay32.lib;$(ProjectDir)..\..\..\Release\common.lib;$(ProjectDir)..\..\..\..\pcre\bin\release\lib_pcreposix.lib;$(ProjectDir)..\..\..\..\pcre\bin\release\lib_pcre.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)bbackupd.exe</OutputFile>
+ <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\bin\bbackupd\autogen_ClientException.cpp" />
+ <ClCompile Include="..\..\..\bin\bbackupd\BackupClientContext.cpp" />
+ <ClCompile Include="..\..\..\bin\bbackupd\BackupClientDeleteList.cpp" />
+ <ClCompile Include="..\..\..\bin\bbackupd\BackupClientDirectoryRecord.cpp" />
+ <ClCompile Include="..\..\..\bin\bbackupd\BackupClientInodeToIDMap.cpp" />
+ <ClCompile Include="..\..\..\bin\bbackupd\BackupDaemon.cpp" />
+ <ClCompile Include="..\..\..\bin\bbackupd\bbackupd.cpp" />
+ <ClCompile Include="..\..\..\bin\bbackupd\Win32BackupService.cpp" />
+ <ClCompile Include="..\..\..\bin\bbackupd\Win32ServiceFunctions.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\bin\bbackupd\autogen_ClientException.h" />
+ <ClInclude Include="..\..\..\bin\bbackupd\BackupClientContext.h" />
+ <ClInclude Include="..\..\..\bin\bbackupd\BackupClientDeleteList.h" />
+ <ClInclude Include="..\..\..\bin\bbackupd\BackupClientDirectoryRecord.h" />
+ <ClInclude Include="..\..\..\bin\bbackupd\BackupClientInodeToIDMap.h" />
+ <ClInclude Include="..\..\..\bin\bbackupd\BackupDaemon.h" />
+ <ClInclude Include="..\..\..\bin\bbackupd\Win32BackupService.h" />
+ <ClInclude Include="..\..\..\bin\bbackupd\Win32ServiceFunctions.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="..\..\..\lib\win32\messages.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="common.vcxproj">
+ <Project>{a089cee6-ebf0-4232-a0c0-74850a8127a6}</Project>
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ <Private>true</Private>
+ <CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
+ <LinkLibraryDependencies>true</LinkLibraryDependencies>
+ <UseLibraryDependencyInputs>false</UseLibraryDependencyInputs>
+ </ProjectReference>
+ <ProjectReference Include="qdbm.vcxproj">
+ <Project>{72af22a7-b339-4fdf-b6ae-ca6522d4bb8d}</Project>
+ <Private>true</Private>
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ <CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
+ <LinkLibraryDependencies>true</LinkLibraryDependencies>
+ <UseLibraryDependencyInputs>false</UseLibraryDependencyInputs>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/infrastructure/msvc/2010/bbstoreaccounts.vcxproj b/infrastructure/msvc/2010/bbstoreaccounts.vcxproj
new file mode 100644
index 00000000..abaf06db
--- /dev/null
+++ b/infrastructure/msvc/2010/bbstoreaccounts.vcxproj
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{C8A1509C-F91B-4140-BD51-B87FF24FB95F}</ProjectGuid>
+ <RootNamespace>bbstoreaccounts</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <OutDir>$(ProjectDir)..\..\..\$(Configuration)\</OutDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <IntDir>$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</IntDir>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>$(SolutionDir)..\..\..\lib\backupclient;$(SolutionDir)..\..\..\lib\backupstore;$(SolutionDir)..\..\..\lib\raidfile;$(SolutionDir)..\..\..\lib\common;$(SolutionDir)..\..\..\lib\compress;$(SolutionDir)..\..\..\lib\crypto;$(SolutionDir)..\..\..\lib\server;$(SolutionDir)..\..\..\lib\win32;$(SolutionDir)..\..\..\qdbm;$(SolutionDir)..\..\..\..\openssl\include;$(SolutionDir)..\..\..\..\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;QDBM_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\bin\bbstoreaccounts\bbstoreaccounts.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="common.vcxproj">
+ <Project>{a089cee6-ebf0-4232-a0c0-74850a8127a6}</Project>
+ </ProjectReference>
+ <ProjectReference Include="libbackupstore.vcxproj">
+ <Project>{97d89aef-2be4-4e34-8703-03ba67bf4494}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/infrastructure/msvc/2010/bbstored.vcxproj b/infrastructure/msvc/2010/bbstored.vcxproj
new file mode 100644
index 00000000..7d09f75d
--- /dev/null
+++ b/infrastructure/msvc/2010/bbstored.vcxproj
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{D8404314-73DD-4270-8205-BE677F8FDAC7}</ProjectGuid>
+ <RootNamespace>bbstored</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <OutDir>$(ProjectDir)..\..\..\$(Configuration)\</OutDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <IntDir>$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</IntDir>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;QDBM_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <AdditionalIncludeDirectories>$(SolutionDir)..\..\..\lib\backupclient;$(SolutionDir)..\..\..\lib\backupstore;$(SolutionDir)..\..\..\lib\raidfile;$(SolutionDir)..\..\..\lib\common;$(SolutionDir)..\..\..\lib\compress;$(SolutionDir)..\..\..\lib\crypto;$(SolutionDir)..\..\..\lib\server;$(SolutionDir)..\..\..\lib\win32;$(SolutionDir)..\..\..\qdbm;$(SolutionDir)..\..\..\..\openssl\include;$(SolutionDir)..\..\..\..\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ProjectReference Include="common.vcxproj">
+ <Project>{a089cee6-ebf0-4232-a0c0-74850a8127a6}</Project>
+ </ProjectReference>
+ <ProjectReference Include="libbackupstore.vcxproj">
+ <Project>{97d89aef-2be4-4e34-8703-03ba67bf4494}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\bin\bbstored\BackupStoreDaemon.cpp" />
+ <ClCompile Include="..\..\..\bin\bbstored\bbstored.cpp" />
+ <ClCompile Include="..\..\..\bin\bbstored\BBStoreDHousekeeping.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\bin\bbstored\BackupStoreDaemon.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="..\..\..\bin\bbstored\bbstored-certs.in" />
+ <None Include="..\..\..\bin\bbstored\bbstored-config.in" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/infrastructure/msvc/2010/boxbackup.sln b/infrastructure/msvc/2010/boxbackup.sln
new file mode 100644
index 00000000..384ff58c
--- /dev/null
+++ b/infrastructure/msvc/2010/boxbackup.sln
@@ -0,0 +1,89 @@
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual C++ Express 2010
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupquery", "boxquery.vcxproj", "{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}"
+ ProjectSection(ProjectDependencies) = postProject
+ {32604097-C934-4711-B1AD-206336640E70} = {32604097-C934-4711-B1AD-206336640E70}
+ {97D89AEF-2BE4-4E34-8703-03BA67BF4494} = {97D89AEF-2BE4-4E34-8703-03BA67BF4494}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "common.vcxproj", "{A089CEE6-EBF0-4232-A0C0-74850A8127A6}"
+ ProjectSection(ProjectDependencies) = postProject
+ {72AF22A7-B339-4FDF-B6AE-CA6522D4BB8D} = {72AF22A7-B339-4FDF-B6AE-CA6522D4BB8D}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupd", "bbackupd.vcxproj", "{22D325FB-9131-4BD6-B390-968F0491D687}"
+ ProjectSection(ProjectDependencies) = postProject
+ {32604097-C934-4711-B1AD-206336640E70} = {32604097-C934-4711-B1AD-206336640E70}
+ {72AF22A7-B339-4FDF-B6AE-CA6522D4BB8D} = {72AF22A7-B339-4FDF-B6AE-CA6522D4BB8D}
+ {97D89AEF-2BE4-4E34-8703-03BA67BF4494} = {97D89AEF-2BE4-4E34-8703-03BA67BF4494}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "win32test", "win32test.vcxproj", "{28C29E72-76A2-4D0C-B35B-12D446733D2E}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupctl", "bbackupctl.vcxproj", "{9FD51412-E945-4457-A17A-CA3C505CF431}"
+ ProjectSection(ProjectDependencies) = postProject
+ {32604097-C934-4711-B1AD-206336640E70} = {32604097-C934-4711-B1AD-206336640E70}
+ {97D89AEF-2BE4-4E34-8703-03BA67BF4494} = {97D89AEF-2BE4-4E34-8703-03BA67BF4494}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "qdbm", "qdbm.vcxproj", "{72AF22A7-B339-4FDF-B6AE-CA6522D4BB8D}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbstored", "bbstored.vcxproj", "{D8404314-73DD-4270-8205-BE677F8FDAC7}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbackupstore", "libbackupstore.vcxproj", "{97D89AEF-2BE4-4E34-8703-03BA67BF4494}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbstoreaccounts", "bbstoreaccounts.vcxproj", "{C8A1509C-F91B-4140-BD51-B87FF24FB95F}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbackupclient", "libbackupclient.vcxproj", "{32604097-C934-4711-B1AD-206336640E70}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Debug|Win32.ActiveCfg = Debug|Win32
+ {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Debug|Win32.Build.0 = Debug|Win32
+ {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Release|Win32.ActiveCfg = Release|Win32
+ {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Release|Win32.Build.0 = Release|Win32
+ {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Debug|Win32.ActiveCfg = Debug|Win32
+ {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Debug|Win32.Build.0 = Debug|Win32
+ {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Release|Win32.ActiveCfg = Release|Win32
+ {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Release|Win32.Build.0 = Release|Win32
+ {22D325FB-9131-4BD6-B390-968F0491D687}.Debug|Win32.ActiveCfg = Debug|Win32
+ {22D325FB-9131-4BD6-B390-968F0491D687}.Debug|Win32.Build.0 = Debug|Win32
+ {22D325FB-9131-4BD6-B390-968F0491D687}.Release|Win32.ActiveCfg = Release|Win32
+ {22D325FB-9131-4BD6-B390-968F0491D687}.Release|Win32.Build.0 = Release|Win32
+ {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Debug|Win32.ActiveCfg = Debug|Win32
+ {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Debug|Win32.Build.0 = Debug|Win32
+ {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Release|Win32.ActiveCfg = Release|Win32
+ {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Release|Win32.Build.0 = Release|Win32
+ {9FD51412-E945-4457-A17A-CA3C505CF431}.Debug|Win32.ActiveCfg = Debug|Win32
+ {9FD51412-E945-4457-A17A-CA3C505CF431}.Debug|Win32.Build.0 = Debug|Win32
+ {9FD51412-E945-4457-A17A-CA3C505CF431}.Release|Win32.ActiveCfg = Release|Win32
+ {9FD51412-E945-4457-A17A-CA3C505CF431}.Release|Win32.Build.0 = Release|Win32
+ {72AF22A7-B339-4FDF-B6AE-CA6522D4BB8D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {72AF22A7-B339-4FDF-B6AE-CA6522D4BB8D}.Debug|Win32.Build.0 = Debug|Win32
+ {72AF22A7-B339-4FDF-B6AE-CA6522D4BB8D}.Release|Win32.ActiveCfg = Release|Win32
+ {72AF22A7-B339-4FDF-B6AE-CA6522D4BB8D}.Release|Win32.Build.0 = Release|Win32
+ {D8404314-73DD-4270-8205-BE677F8FDAC7}.Debug|Win32.ActiveCfg = Debug|Win32
+ {D8404314-73DD-4270-8205-BE677F8FDAC7}.Debug|Win32.Build.0 = Debug|Win32
+ {D8404314-73DD-4270-8205-BE677F8FDAC7}.Release|Win32.ActiveCfg = Release|Win32
+ {D8404314-73DD-4270-8205-BE677F8FDAC7}.Release|Win32.Build.0 = Release|Win32
+ {97D89AEF-2BE4-4E34-8703-03BA67BF4494}.Debug|Win32.ActiveCfg = Debug|Win32
+ {97D89AEF-2BE4-4E34-8703-03BA67BF4494}.Debug|Win32.Build.0 = Debug|Win32
+ {97D89AEF-2BE4-4E34-8703-03BA67BF4494}.Release|Win32.ActiveCfg = Release|Win32
+ {97D89AEF-2BE4-4E34-8703-03BA67BF4494}.Release|Win32.Build.0 = Release|Win32
+ {C8A1509C-F91B-4140-BD51-B87FF24FB95F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {C8A1509C-F91B-4140-BD51-B87FF24FB95F}.Debug|Win32.Build.0 = Debug|Win32
+ {C8A1509C-F91B-4140-BD51-B87FF24FB95F}.Release|Win32.ActiveCfg = Release|Win32
+ {C8A1509C-F91B-4140-BD51-B87FF24FB95F}.Release|Win32.Build.0 = Release|Win32
+ {32604097-C934-4711-B1AD-206336640E70}.Debug|Win32.ActiveCfg = Debug|Win32
+ {32604097-C934-4711-B1AD-206336640E70}.Debug|Win32.Build.0 = Debug|Win32
+ {32604097-C934-4711-B1AD-206336640E70}.Release|Win32.ActiveCfg = Release|Win32
+ {32604097-C934-4711-B1AD-206336640E70}.Release|Win32.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/infrastructure/msvc/2010/boxquery.vcxproj b/infrastructure/msvc/2010/boxquery.vcxproj
new file mode 100644
index 00000000..c419c4b7
--- /dev/null
+++ b/infrastructure/msvc/2010/boxquery.vcxproj
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}</ProjectGuid>
+ <RootNamespace>boxquery</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ <ProjectName>bbackupquery</ProjectName>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)..\..\..\$(Configuration)\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</IntDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\..\Release\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\..\Release\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\lib\backupstore;$(ProjectDir)..\..\..\lib\backupclient;$(ProjectDir)..\..\..\lib\common;$(ProjectDir)..\..\..\lib\compress;$(ProjectDir)..\..\..\lib\crypto;$(ProjectDir)..\..\..\lib\server;$(ProjectDir)..\..\..\lib\win32;$(ProjectDir)..\..\..\..\pcre;$(ProjectDir)..\..\..\..\openssl\include;$(ProjectDir)..\..\..\..\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;QDBM_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(OutDir)boxquery.pdb</ProgramDatabaseFile>
+ <SubSystem>Console</SubSystem>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
+ <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\lib\backupclient;$(ProjectDir)..\..\..\lib\server;$(ProjectDir)..\..\..\lib\crypto;$(ProjectDir)..\..\..\..\openssl\include;$(ProjectDir)..\..\..\lib\compress;$(ProjectDir)..\..\..\..\zlib\include;$(ProjectDir)..\..\..\lib\win32;$(ProjectDir)..\..\..\lib\common\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>Ws2_32.lib;$(ProjectDir)..\..\..\..\zlib\lib\zdll.lib;$(ProjectDir)..\..\..\Release\common.lib;$(ProjectDir)..\..\..\..\openssl\out32dll\libeay32.lib;$(ProjectDir)..\..\..\..\openssl\out32dll\ssleay32.lib;$(ProjectDir)..\..\..\..\pcre\bin\release\lib_pcreposix.lib;$(ProjectDir)..\..\..\..\pcre\bin\release\lib_pcre.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)bbackupquery.exe</OutputFile>
+ <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\bin\bbackupquery\autogen_Documentation.cpp" />
+ <ClCompile Include="..\..\..\bin\bbackupquery\BackupQueries.cpp" />
+ <ClCompile Include="..\..\..\bin\bbackupquery\bbackupquery.cpp" />
+ <ClCompile Include="..\..\..\bin\bbackupquery\CommandCompletion.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\bin\bbackupquery\BackupQueries.h" />
+ <ClInclude Include="..\..\..\bin\bbackupquery\BoxBackupCompareParams.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="..\..\..\lib\win32\messages.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="..\..\..\ReadMe.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="common.vcxproj">
+ <Project>{a089cee6-ebf0-4232-a0c0-74850a8127a6}</Project>
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ </ProjectReference>
+ <ProjectReference Include="libbackupclient.vcxproj">
+ <Project>{32604097-c934-4711-b1ad-206336640e70}</Project>
+ </ProjectReference>
+ <ProjectReference Include="libbackupstore.vcxproj">
+ <Project>{97d89aef-2be4-4e34-8703-03ba67bf4494}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/infrastructure/msvc/2010/common.vcxproj b/infrastructure/msvc/2010/common.vcxproj
new file mode 100644
index 00000000..a644891a
--- /dev/null
+++ b/infrastructure/msvc/2010/common.vcxproj
@@ -0,0 +1,251 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{A089CEE6-EBF0-4232-A0C0-74850A8127A6}</ProjectGuid>
+ <RootNamespace>common</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)..\..\..\$(Configuration)\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</IntDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\..\Release\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\..\Release\</IntDir>
+ <IncludePath Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <PreBuildEvent />
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\qdbm;$(ProjectDir)..\..\..\lib\common;$(ProjectDir)..\..\..\lib\compress;$(ProjectDir)..\..\..\lib\crypto;$(ProjectDir)..\..\..\lib\server;$(ProjectDir)..\..\..\lib\win32;$(ProjectDir)..\..\..\..\openssl\include;$(ProjectDir)..\..\..\..\zlib\include;$(ProjectDir)..\..\..\..\pcre</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;QDBM_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <TreatWarningAsError>false</TreatWarningAsError>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ </ClCompile>
+ <Lib />
+ <CustomBuildStep />
+ <CustomBuildStep />
+ <CustomBuildStep />
+ <Lib>
+ <AdditionalDependencies>Ws2_32.lib;$(ProjectDir)..\..\..\..\pcre\build\vc2010\debug\pcre.lib;$(ProjectDir)..\..\..\..\pcre\build\vc2010\debug\pcreposix.lib;$(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib;$(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib;$(ProjectDir)..\..\..\..\zlib\lib\zdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Lib>
+ <PreBuildEvent>
+ <Command>perl $(ProjectDir)..\getversion.pl</Command>
+ </PreBuildEvent>
+ <PreBuildEvent>
+ <Message>Determining Version Number</Message>
+ </PreBuildEvent>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
+ <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\lib\common\;$(ProjectDir)..\..\..\lib\compress;$(ProjectDir)..\..\..\lib\crypto;$(ProjectDir)..\..\..\lib\server;$(ProjectDir)..\..\..\lib\win32;$(ProjectDir)..\..\..\..\openssl\inc32;$(ProjectDir)..\..\..\..\zlib\include;$(ProjectDir)..\..\..\..\pcre\pcre-6.7\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;BOX_RELEASE_BUILD;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ </ClCompile>
+ <Lib>
+ <OutputFile>$(OutDir)common.lib</OutputFile>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\lib\common\BufferedStream.cpp" />
+ <ClCompile Include="..\..\..\lib\common\BufferedWriteStream.cpp" />
+ <ClCompile Include="..\..\..\lib\common\FileModificationTime.cpp" />
+ <ClCompile Include="..\..\..\lib\common\GetLine.cpp" />
+ <ClCompile Include="..\..\..\lib\common\InvisibleTempFileStream.cpp" />
+ <ClCompile Include="..\..\..\lib\common\NamedLock.cpp" />
+ <ClCompile Include="..\..\..\lib\common\RateLimitingStream.cpp" />
+ <ClCompile Include="..\..\..\lib\compress\autogen_CompressException.cpp" />
+ <ClCompile Include="..\..\..\lib\compress\CompressStream.cpp" />
+ <ClCompile Include="..\..\..\lib\common\autogen_CommonException.cpp" />
+ <ClCompile Include="..\..\..\lib\common\autogen_ConversionException.cpp" />
+ <ClCompile Include="..\..\..\lib\common\BoxException.cpp" />
+ <ClCompile Include="..\..\..\lib\common\BoxTime.cpp" />
+ <ClCompile Include="..\..\..\lib\common\BoxTimeToText.cpp" />
+ <ClCompile Include="..\..\..\lib\common\CollectInBufferStream.cpp" />
+ <ClCompile Include="..\..\..\lib\common\Configuration.cpp" />
+ <ClCompile Include="..\..\..\lib\common\ConversionString.cpp" />
+ <ClCompile Include="..\..\..\lib\common\DebugAssertFailed.cpp" />
+ <ClCompile Include="..\..\..\lib\common\DebugMemLeakFinder.cpp" />
+ <ClCompile Include="..\..\..\lib\common\DebugPrintf.cpp" />
+ <ClCompile Include="..\..\..\lib\common\EventWatchFilesystemObject.cpp" />
+ <ClCompile Include="..\..\..\lib\common\ExcludeList.cpp" />
+ <ClCompile Include="..\..\..\lib\common\FdGetLine.cpp" />
+ <ClCompile Include="..\..\..\lib\common\FileStream.cpp" />
+ <ClCompile Include="..\..\..\lib\common\IOStream.cpp" />
+ <ClCompile Include="..\..\..\lib\common\IOStreamGetLine.cpp" />
+ <ClCompile Include="..\..\..\lib\common\Logging.cpp" />
+ <ClCompile Include="..\..\..\lib\common\MemBlockStream.cpp" />
+ <ClCompile Include="..\..\..\lib\common\PartialReadStream.cpp" />
+ <ClCompile Include="..\..\..\lib\common\PathUtils.cpp" />
+ <ClCompile Include="..\..\..\lib\common\ReadGatherStream.cpp" />
+ <ClCompile Include="..\..\..\lib\common\ReadLoggingStream.cpp" />
+ <ClCompile Include="..\..\..\lib\common\StreamableMemBlock.cpp" />
+ <ClCompile Include="..\..\..\lib\common\Timer.cpp" />
+ <ClCompile Include="..\..\..\lib\common\UnixUser.cpp" />
+ <ClCompile Include="..\..\..\lib\common\Utils.cpp" />
+ <ClCompile Include="..\..\..\lib\common\WaitForEvent.cpp" />
+ <ClCompile Include="..\..\..\lib\crypto\autogen_CipherException.cpp" />
+ <ClCompile Include="..\..\..\lib\crypto\CipherAES.cpp" />
+ <ClCompile Include="..\..\..\lib\crypto\CipherBlowfish.cpp" />
+ <ClCompile Include="..\..\..\lib\crypto\CipherContext.cpp" />
+ <ClCompile Include="..\..\..\lib\crypto\CipherDescription.cpp" />
+ <ClCompile Include="..\..\..\lib\crypto\CryptoUtils.cpp" />
+ <ClCompile Include="..\..\..\lib\crypto\MD5Digest.cpp" />
+ <ClCompile Include="..\..\..\lib\crypto\Random.cpp" />
+ <ClCompile Include="..\..\..\lib\crypto\RollingChecksum.cpp" />
+ <ClCompile Include="..\..\..\lib\server\Message.cpp" />
+ <ClCompile Include="..\..\..\lib\server\TcpNice.cpp" />
+ <ClCompile Include="..\..\..\lib\win32\emu.cpp" />
+ <ClCompile Include="..\..\..\lib\win32\getopt_long.cpp" />
+ <ClCompile Include="..\..\..\lib\server\autogen_ConnectionException.cpp" />
+ <ClCompile Include="..\..\..\lib\server\autogen_ServerException.cpp" />
+ <ClCompile Include="..\..\..\lib\server\Daemon.cpp" />
+ <ClCompile Include="..\..\..\lib\server\LocalProcessStream.cpp" />
+ <ClCompile Include="..\..\..\lib\server\Protocol.cpp" />
+ <ClCompile Include="..\..\..\lib\server\ProtocolUncertainStream.cpp" />
+ <ClCompile Include="..\..\..\lib\server\Socket.cpp" />
+ <ClCompile Include="..\..\..\lib\server\SocketStream.cpp" />
+ <ClCompile Include="..\..\..\lib\server\SocketStreamTLS.cpp" />
+ <ClCompile Include="..\..\..\lib\server\SSLLib.cpp" />
+ <ClCompile Include="..\..\..\lib\server\TLSContext.cpp" />
+ <ClCompile Include="..\..\..\lib\server\WinNamedPipeStream.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\lib\common\BufferedStream.h" />
+ <ClInclude Include="..\..\..\lib\common\BufferedWriteStream.h" />
+ <ClInclude Include="..\..\..\lib\common\GetLine.h" />
+ <ClInclude Include="..\..\..\lib\common\InvisibleTempFileStream.h" />
+ <ClInclude Include="..\..\..\lib\common\RateLimitingStream.h" />
+ <ClInclude Include="..\..\..\lib\compress\autogen_CompressException.h" />
+ <ClInclude Include="..\..\..\lib\compress\Compress.h" />
+ <ClInclude Include="..\..\..\lib\compress\CompressException.h" />
+ <ClInclude Include="..\..\..\lib\compress\CompressStream.h" />
+ <ClInclude Include="..\..\..\lib\common\autogen_CommonException.h" />
+ <ClInclude Include="..\..\..\lib\common\autogen_ConversionException.h" />
+ <ClInclude Include="..\..\..\lib\common\BannerText.h" />
+ <ClInclude Include="..\..\..\lib\common\BeginStructPackForWire.h" />
+ <ClInclude Include="..\..\..\lib\common\Box.h" />
+ <ClInclude Include="..\..\..\lib\common\BoxConfig-MSVC.h" />
+ <ClInclude Include="..\..\..\lib\common\BoxException.h" />
+ <ClInclude Include="..\..\..\lib\common\BoxPlatform.h" />
+ <ClInclude Include="..\..\..\lib\common\BoxPortsAndFiles.h" />
+ <ClInclude Include="..\..\..\lib\common\BoxTime.h" />
+ <ClInclude Include="..\..\..\lib\common\BoxTimeToText.h" />
+ <ClInclude Include="..\..\..\lib\common\BoxTimeToUnix.h" />
+ <ClInclude Include="..\..\..\lib\common\BoxVersion.h" />
+ <ClInclude Include="..\..\..\lib\common\CollectInBufferStream.h" />
+ <ClInclude Include="..\..\..\lib\common\CommonException.h" />
+ <ClInclude Include="..\..\..\lib\common\Configuration.h" />
+ <ClInclude Include="..\..\..\lib\common\Conversion.h" />
+ <ClInclude Include="..\..\..\lib\common\EndStructPackForWire.h" />
+ <ClInclude Include="..\..\..\lib\common\EventWatchFilesystemObject.h" />
+ <ClInclude Include="..\..\..\lib\common\ExcludeList.h" />
+ <ClInclude Include="..\..\..\lib\common\FdGetLine.h" />
+ <ClInclude Include="..\..\..\lib\common\FileModificationTime.h" />
+ <ClInclude Include="..\..\..\lib\common\FileStream.h" />
+ <ClInclude Include="..\..\..\lib\common\Guards.h" />
+ <ClInclude Include="..\..\..\lib\common\IOStream.h" />
+ <ClInclude Include="..\..\..\lib\common\IOStreamGetLine.h" />
+ <ClInclude Include="..\..\..\lib\crypto\CryptoUtils.h" />
+ <ClInclude Include="..\..\..\lib\server\LocalProcessStream.h" />
+ <ClInclude Include="..\..\..\lib\common\Logging.h" />
+ <ClInclude Include="..\..\..\lib\common\MainHelper.h" />
+ <ClInclude Include="..\..\..\lib\common\MemBlockStream.h" />
+ <ClInclude Include="..\..\..\lib\common\MemLeakFinder.h" />
+ <ClInclude Include="..\..\..\lib\common\MemLeakFindOff.h" />
+ <ClInclude Include="..\..\..\lib\common\MemLeakFindOn.h" />
+ <ClInclude Include="..\..\..\lib\common\NamedLock.h" />
+ <ClInclude Include="..\..\..\lib\common\PartialReadStream.h" />
+ <ClInclude Include="..\..\..\lib\common\PathUtils.h" />
+ <ClInclude Include="..\..\..\lib\common\ReadGatherStream.h" />
+ <ClInclude Include="..\..\..\lib\common\ReadLoggingStream.h" />
+ <ClInclude Include="..\..\..\lib\common\StreamableMemBlock.h" />
+ <ClInclude Include="..\..\..\lib\common\TemporaryDirectory.h" />
+ <ClInclude Include="..\..\..\lib\common\Test.h" />
+ <ClInclude Include="..\..\..\lib\common\Timer.h" />
+ <ClInclude Include="..\..\..\lib\common\UnixUser.h" />
+ <ClInclude Include="..\..\..\lib\common\Utils.h" />
+ <ClInclude Include="..\..\..\lib\common\WaitForEvent.h" />
+ <ClInclude Include="..\..\..\lib\crypto\autogen_CipherException.h" />
+ <ClInclude Include="..\..\..\lib\crypto\CipherAES.h" />
+ <ClInclude Include="..\..\..\lib\crypto\CipherBlowfish.h" />
+ <ClInclude Include="..\..\..\lib\crypto\CipherContext.h" />
+ <ClInclude Include="..\..\..\lib\crypto\CipherDescription.h" />
+ <ClInclude Include="..\..\..\lib\crypto\CipherException.h" />
+ <ClInclude Include="..\..\..\lib\crypto\MD5Digest.h" />
+ <ClInclude Include="..\..\..\lib\crypto\Random.h" />
+ <ClInclude Include="..\..\..\lib\crypto\RollingChecksum.h" />
+ <ClInclude Include="..\..\..\lib\server\Message.h" />
+ <ClInclude Include="..\..\..\lib\server\TcpNice.h" />
+ <ClInclude Include="..\..\..\lib\win32\emu.h" />
+ <ClInclude Include="..\..\..\lib\win32\getopt.h" />
+ <ClInclude Include="..\..\..\lib\server\autogen_ConnectionException.h" />
+ <ClInclude Include="..\..\..\lib\server\autogen_ServerException.h" />
+ <ClInclude Include="..\..\..\lib\server\Daemon.h" />
+ <ClInclude Include="..\..\..\lib\server\Protocol.h" />
+ <ClInclude Include="..\..\..\lib\server\ProtocolUncertainStream.h" />
+ <ClInclude Include="..\..\..\lib\server\ProtocolWire.h" />
+ <ClInclude Include="..\..\..\lib\server\ServerException.h" />
+ <ClInclude Include="..\..\..\lib\server\ServerStream.h" />
+ <ClInclude Include="..\..\..\lib\server\ServerTLS.h" />
+ <ClInclude Include="..\..\..\lib\server\Socket.h" />
+ <ClInclude Include="..\..\..\lib\server\SocketListen.h" />
+ <ClInclude Include="..\..\..\lib\server\SocketStream.h" />
+ <ClInclude Include="..\..\..\lib\server\SocketStreamTLS.h" />
+ <ClInclude Include="..\..\..\lib\server\SSLLib.h" />
+ <ClInclude Include="..\..\..\lib\server\TLSContext.h" />
+ <ClInclude Include="..\..\..\lib\server\WinNamedPipeStream.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="qdbm.vcxproj">
+ <Project>{72af22a7-b339-4fdf-b6ae-ca6522d4bb8d}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/infrastructure/msvc/2010/libbackupclient.vcxproj b/infrastructure/msvc/2010/libbackupclient.vcxproj
new file mode 100644
index 00000000..a38b3cf5
--- /dev/null
+++ b/infrastructure/msvc/2010/libbackupclient.vcxproj
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\lib\backupclient\BackupClientCryptoKeys.cpp" />
+ <ClCompile Include="..\..\..\lib\backupclient\BackupClientMakeExcludeList.cpp" />
+ <ClCompile Include="..\..\..\lib\backupclient\BackupClientRestore.cpp" />
+ <ClCompile Include="..\..\..\lib\backupclient\BackupDaemonConfigVerify.cpp" />
+ <ClCompile Include="..\..\..\lib\backupclient\BackupStoreObjectDump.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\lib\backupclient\BackupClientCryptoKeys.h" />
+ <ClInclude Include="..\..\..\lib\backupclient\BackupClientMakeExcludeList.h" />
+ <ClInclude Include="..\..\..\lib\backupclient\BackupClientRestore.h" />
+ <ClInclude Include="..\..\..\lib\backupclient\BackupDaemonConfigVerify.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="libbackupstore.vcxproj">
+ <Project>{97d89aef-2be4-4e34-8703-03ba67bf4494}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{32604097-C934-4711-B1AD-206336640E70}</ProjectGuid>
+ <RootNamespace>libbackupstore</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <OutDir>$(ProjectDir)..\..\..\$(Configuration)\</OutDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <TargetExt>.lib</TargetExt>
+ <IntDir>$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</IntDir>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>$(SolutionDir)..\..\..\lib\backupclient;$(SolutionDir)..\..\..\lib\backupstore;$(SolutionDir)..\..\..\lib\raidfile;$(SolutionDir)..\..\..\lib\common;$(SolutionDir)..\..\..\lib\compress;$(SolutionDir)..\..\..\lib\crypto;$(SolutionDir)..\..\..\lib\server;$(SolutionDir)..\..\..\lib\win32;$(SolutionDir)..\..\..\qdbm;$(SolutionDir)..\..\..\..\openssl\include;$(SolutionDir)..\..\..\..\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;QDBM_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>Ws2_32.lib;Advapi32.lib;User32.lib;$(ProjectDir)..\..\..\..\zlib\lib\zdll.lib;$(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib;$(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib;$(ProjectDir)..\..\..\$(Configuration)\common.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ <Lib />
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/infrastructure/msvc/2010/libbackupstore.vcxproj b/infrastructure/msvc/2010/libbackupstore.vcxproj
new file mode 100644
index 00000000..7f83d04a
--- /dev/null
+++ b/infrastructure/msvc/2010/libbackupstore.vcxproj
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{97D89AEF-2BE4-4E34-8703-03BA67BF4494}</ProjectGuid>
+ <RootNamespace>libbackupstore</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <OutDir>$(ProjectDir)..\..\..\$(Configuration)\</OutDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <TargetExt>.lib</TargetExt>
+ <IntDir>$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</IntDir>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>$(SolutionDir)..\..\..\lib\backupclient;$(SolutionDir)..\..\..\lib\backupstore;$(SolutionDir)..\..\..\lib\raidfile;$(SolutionDir)..\..\..\lib\common;$(SolutionDir)..\..\..\lib\compress;$(SolutionDir)..\..\..\lib\crypto;$(SolutionDir)..\..\..\lib\server;$(SolutionDir)..\..\..\lib\win32;$(SolutionDir)..\..\..\qdbm;$(SolutionDir)..\..\..\..\openssl\include;$(SolutionDir)..\..\..\..\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;QDBM_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>Ws2_32.lib;Advapi32.lib;User32.lib;$(ProjectDir)..\..\..\..\zlib\lib\zdll.lib;$(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib;$(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib;$(ProjectDir)..\..\..\$(Configuration)\common.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ <Lib />
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\lib\backupstore\autogen_BackupProtocol.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\autogen_BackupStoreException.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupClientFileAttributes.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupCommands.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreAccountDatabase.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreAccounts.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreCheck.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreCheck2.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreCheckData.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreConfigVerify.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreContext.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreDirectory.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreFile.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreFileCmbDiff.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreFileCmbIdx.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreFileCombine.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreFileCryptVar.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreFileDiff.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreFileEncodeStream.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreFilename.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreFilenameClear.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreFileRevDiff.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreInfo.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\BackupStoreRefCountDatabase.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\HousekeepStoreAccount.cpp" />
+ <ClCompile Include="..\..\..\lib\backupstore\StoreStructure.cpp" />
+ <ClCompile Include="..\..\..\lib\raidfile\autogen_RaidFileException.cpp" />
+ <ClCompile Include="..\..\..\lib\raidfile\RaidFileController.cpp" />
+ <ClCompile Include="..\..\..\lib\raidfile\RaidFileRead.cpp" />
+ <ClCompile Include="..\..\..\lib\raidfile\RaidFileUtil.cpp" />
+ <ClCompile Include="..\..\..\lib\raidfile\RaidFileWrite.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\lib\backupstore\autogen_BackupProtocol.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\autogen_BackupStoreException.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupClientFileAttributes.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupConstants.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreAccountDatabase.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreAccounts.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreCheck.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreConfigVerify.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreConstants.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreContext.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreDirectory.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreException.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreFile.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreFileCryptVar.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreFileEncodeStream.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreFilename.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreFilenameClear.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreFileWire.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreInfo.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreObjectMagic.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\BackupStoreRefCountDatabase.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\HousekeepStoreAccount.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\RunStatusProvider.h" />
+ <ClInclude Include="..\..\..\lib\backupstore\StoreStructure.h" />
+ <ClInclude Include="..\..\..\lib\raidfile\autogen_RaidFileException.h" />
+ <ClInclude Include="..\..\..\lib\raidfile\RaidFileController.h" />
+ <ClInclude Include="..\..\..\lib\raidfile\RaidFileException.h" />
+ <ClInclude Include="..\..\..\lib\raidfile\RaidFileRead.h" />
+ <ClInclude Include="..\..\..\lib\raidfile\RaidFileUtil.h" />
+ <ClInclude Include="..\..\..\lib\raidfile\RaidFileWrite.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="..\..\..\lib\backupstore\backupprotocol.txt" />
+ <None Include="..\..\..\lib\backupstore\BackupStoreException.txt" />
+ <None Include="..\..\..\lib\backupstore\Makefile.extra" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="common.vcxproj">
+ <Project>{a089cee6-ebf0-4232-a0c0-74850a8127a6}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/infrastructure/msvc/2010/qdbm.vcxproj b/infrastructure/msvc/2010/qdbm.vcxproj
new file mode 100644
index 00000000..77bf8205
--- /dev/null
+++ b/infrastructure/msvc/2010/qdbm.vcxproj
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{72AF22A7-B339-4FDF-B6AE-CA6522D4BB8D}</ProjectGuid>
+ <RootNamespace>qdbm</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <TargetExt>.lib</TargetExt>
+ <OutDir>$(ProjectDir)..\..\..\$(Configuration)\</OutDir>
+ <IntDir>$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</IntDir>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <CompileAsManaged>false</CompileAsManaged>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <PreprocessorDefinitions>QDBM_STATIC;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ <Lib />
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\qdbm\depot.h" />
+ <ClInclude Include="..\..\..\qdbm\myconf.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\qdbm\depot.c" />
+ <ClCompile Include="..\..\..\qdbm\myconf.c" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/infrastructure/msvc/2010/win32test.vcxproj b/infrastructure/msvc/2010/win32test.vcxproj
new file mode 100644
index 00000000..0c61adb3
--- /dev/null
+++ b/infrastructure/msvc/2010/win32test.vcxproj
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{28C29E72-76A2-4D0C-B35B-12D446733D2E}</ProjectGuid>
+ <RootNamespace>win32test</RootNamespace>
+ <Keyword>Win32Proj</Keyword>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)..\..\..\$(Configuration)\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</IntDir>
+ <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\..\Release\</OutDir>
+ <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">..\..\..\Release\</IntDir>
+ <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\bin\bbackupd;$(ProjectDir)..\..\..\lib\backupstore;$(ProjectDir)..\..\..\lib\backupclient;$(ProjectDir)..\..\..\lib\common;$(ProjectDir)..\..\..\lib\compress;$(ProjectDir)..\..\..\lib\crypto;$(ProjectDir)..\..\..\lib\server;$(ProjectDir)..\..\..\lib\win32;$(ProjectDir)..\..\..\..\openssl\include;$(ProjectDir)..\..\..\..\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;QDBM_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ </ClCompile>
+ <Link>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(OutDir)win32test.pdb</ProgramDatabaseFile>
+ <SubSystem>Console</SubSystem>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ShowProgress>NotSet</ShowProgress>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
+ <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\bin\bbackupd;$(ProjectDir)..\..\..\lib\backupclient;$(ProjectDir)..\..\..\lib\common\;$(ProjectDir)..\..\..\lib\compress;$(ProjectDir)..\..\..\lib\crypto;$(ProjectDir)..\..\..\lib\server;$(ProjectDir)..\..\..\lib\win32;$(ProjectDir)..\..\..\..\openssl\inc32;$(ProjectDir)..\..\..\..\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>Ws2_32.lib;$(ProjectDir)..\..\..\..\zlib\lib\zdll.lib;$(ProjectDir)..\..\..\..\openssl\out32dll\libeay32.lib;$(ProjectDir)..\..\..\..\openssl\out32dll\ssleay32.lib;$(ProjectDir)..\..\..\Release\common.lib;$(ProjectDir)..\..\..\..\pcre\bin\release\lib_pcreposix.lib;$(ProjectDir)..\..\..\..\pcre\bin\release\lib_pcre.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)win32test.exe</OutputFile>
+ <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <SubSystem>Console</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <TargetMachine>MachineX86</TargetMachine>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\lib\win32\emu.cpp" />
+ <ClCompile Include="..\..\..\test\win32\testlibwin32.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="..\..\..\ReadMe.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="common.vcxproj">
+ <Project>{a089cee6-ebf0-4232-a0c0-74850a8127a6}</Project>
+ <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/infrastructure/msvc/fake-config.sub.pl b/infrastructure/msvc/fake-config.sub.pl
new file mode 100644
index 00000000..fa9b6839
--- /dev/null
+++ b/infrastructure/msvc/fake-config.sub.pl
@@ -0,0 +1,18 @@
+#!perl
+
+use strict;
+use warnings;
+use Cwd;
+
+require "infrastructure\\BoxPlatform.pm.in";
+my $wd = getcwd();
+my $dummy = $BoxPlatform::product_version;
+
+while(<>)
+{
+ s|\@build_dir@|$wd|;
+ s|\@client_parcel_dir@|$wd/Debug|;
+ s|\@box_version@|$BoxPlatform::product_version|;
+ m|[^@](@[^@]+@)| and die "Unknown variable: $1";
+ print;
+} \ No newline at end of file
diff --git a/infrastructure/msvc/getversion.pl b/infrastructure/msvc/getversion.pl
index 12554d01..42ae4b63 100644
--- a/infrastructure/msvc/getversion.pl
+++ b/infrastructure/msvc/getversion.pl
@@ -11,9 +11,32 @@ chdir $basedir or die "$basedir: $!";
require "$basedir\\infrastructure\\BoxPlatform.pm.in";
+<<<<<<< HEAD
open VERSIONFILE, "> $basedir/lib/common/BoxVersion.h"
or die "BoxVersion.h: $!";
print VERSIONFILE "#define BOX_VERSION \"$BoxPlatform::product_version\"\n";
close VERSIONFILE;
+=======
+my $verfile = "$basedir/lib/common/BoxVersion.h";
+my $newver = "#define BOX_VERSION \"$BoxPlatform::product_version\"\n";
+my $oldver = "";
+
+if (-r $verfile)
+{
+ open VERSIONFILE, "< $verfile" or die "$verfile: $!";
+ $oldver = <VERSIONFILE>;
+ close VERSIONFILE;
+
+}
+
+if ($newver ne $oldver)
+{
+ open VERSIONFILE, "> $verfile" or die "BoxVersion.h: $!";
+ print VERSIONFILE $newver;
+ close VERSIONFILE;
+}
+
+print $BoxPlatform::product_version;
+>>>>>>> 0.12
exit 0;
diff --git a/lib/backupclient/BackupClientRestore.cpp b/lib/backupclient/BackupClientRestore.cpp
index fa61bb59..f6b42af9 100644
--- a/lib/backupclient/BackupClientRestore.cpp
+++ b/lib/backupclient/BackupClientRestore.cpp
@@ -22,7 +22,11 @@
#include <errno.h>
#include "BackupClientRestore.h"
+<<<<<<< HEAD
#include "autogen_BackupProtocolClient.h"
+=======
+#include "autogen_BackupProtocol.h"
+>>>>>>> 0.12
#include "CommonException.h"
#include "BackupClientFileAttributes.h"
#include "IOStream.h"
@@ -443,8 +447,13 @@ static int BackupClientRestoreDir(BackupProtocolClient &rConnection,
// list of files which is appropriate to the restore type
rConnection.QueryListDirectory(
DirectoryID,
+<<<<<<< HEAD
Params.RestoreDeleted?(BackupProtocolClientListDirectory::Flags_Deleted):(BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING),
BackupProtocolClientListDirectory::Flags_OldVersion | (Params.RestoreDeleted?(0):(BackupProtocolClientListDirectory::Flags_Deleted)),
+=======
+ Params.RestoreDeleted?(BackupProtocolListDirectory::Flags_Deleted):(BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING),
+ BackupProtocolListDirectory::Flags_OldVersion | (Params.RestoreDeleted?(0):(BackupProtocolListDirectory::Flags_Deleted)),
+>>>>>>> 0.12
true /* want attributes */);
// Retrieve the directory from the stream following
@@ -569,6 +578,11 @@ static int BackupClientRestoreDir(BackupProtocolClient &rConnection,
if (Params.ContinueAfterErrors)
{
Params.ContinuedAfterError = true;
+<<<<<<< HEAD
+=======
+ // ensure that protocol remains usable
+ objectStream->Flush();
+>>>>>>> 0.12
}
else
{
@@ -839,8 +853,13 @@ static int BackupClientRestoreDir(BackupProtocolClient &rConnection,
//
// --------------------------------------------------------------------------
int BackupClientRestore(BackupProtocolClient &rConnection,
+<<<<<<< HEAD
int64_t DirectoryID, const char *RemoteDirectoryName,
const char *LocalDirectoryName, bool PrintDots, bool RestoreDeleted,
+=======
+ int64_t DirectoryID, const std::string& RemoteDirectoryName,
+ const std::string& LocalDirectoryName, bool PrintDots, bool RestoreDeleted,
+>>>>>>> 0.12
bool UndeleteAfterRestoreDeleted, bool Resume,
bool ContinueAfterErrors)
{
diff --git a/lib/backupclient/BackupClientRestore.h b/lib/backupclient/BackupClientRestore.h
index 311a15bd..79765f63 100644
--- a/lib/backupclient/BackupClientRestore.h
+++ b/lib/backupclient/BackupClientRestore.h
@@ -24,6 +24,7 @@ enum
int BackupClientRestore(BackupProtocolClient &rConnection,
int64_t DirectoryID,
+<<<<<<< HEAD
const char *RemoteDirectoryName,
const char *LocalDirectoryName,
bool PrintDots = false,
@@ -31,6 +32,15 @@ int BackupClientRestore(BackupProtocolClient &rConnection,
bool UndeleteAfterRestoreDeleted = false,
bool Resume = false,
bool ContinueAfterErrors = false);
+=======
+ const std::string& RemoteDirectoryName,
+ const std::string& LocalDirectoryName,
+ bool PrintDots,
+ bool RestoreDeleted,
+ bool UndeleteAfterRestoreDeleted,
+ bool Resume,
+ bool ContinueAfterErrors);
+>>>>>>> 0.12
#endif // BACKUPSCLIENTRESTORE__H
diff --git a/lib/backupclient/BackupDaemonConfigVerify.cpp b/lib/backupclient/BackupDaemonConfigVerify.cpp
index dfef5b03..3104ae8e 100644
--- a/lib/backupclient/BackupDaemonConfigVerify.cpp
+++ b/lib/backupclient/BackupDaemonConfigVerify.cpp
@@ -114,12 +114,25 @@ static const ConfigurationVerifyKey verifyrootkeys[] =
ConfigurationVerifyKey("NotifyAlways", ConfigTest_IsBool, false),
// option to disable the suppression of duplicate notifications
+<<<<<<< HEAD
+=======
+ ConfigurationVerifyKey("MaxUploadRate", ConfigTest_IsInt),
+ // optional maximum speed of uploads in kbytes per second
+
+ ConfigurationVerifyKey("TcpNice", ConfigTest_IsBool, false),
+ // optional enable of tcp nice/background mode
+
+>>>>>>> 0.12
ConfigurationVerifyKey("CertificateFile", ConfigTest_Exists),
ConfigurationVerifyKey("PrivateKeyFile", ConfigTest_Exists),
ConfigurationVerifyKey("TrustedCAsFile", ConfigTest_Exists),
ConfigurationVerifyKey("KeysFile", ConfigTest_Exists),
ConfigurationVerifyKey("DataDirectory",
ConfigTest_Exists | ConfigTest_LastEntry),
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
};
const ConfigurationVerify BackupDaemonConfigVerify =
diff --git a/lib/backupstore/BackupClientFileAttributes.cpp b/lib/backupstore/BackupClientFileAttributes.cpp
new file mode 100644
index 00000000..d76432ba
--- /dev/null
+++ b/lib/backupstore/BackupClientFileAttributes.cpp
@@ -0,0 +1,1223 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupClientFileAttributes.cpp
+// Purpose: Storage of file attributes
+// Created: 2003/10/07
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#ifdef HAVE_UNISTD_H
+ #include <unistd.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <algorithm>
+#include <cstring>
+#include <new>
+#include <vector>
+
+#ifdef HAVE_SYS_XATTR_H
+#include <cerrno>
+#include <sys/xattr.h>
+#endif
+
+#include <cstring>
+
+#include "BackupClientFileAttributes.h"
+#include "CommonException.h"
+#include "FileModificationTime.h"
+#include "BoxTimeToUnix.h"
+#include "BackupStoreException.h"
+#include "CipherContext.h"
+#include "CipherBlowfish.h"
+#include "MD5Digest.h"
+
+#include "MemLeakFindOn.h"
+
+// set packing to one byte
+#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS
+#include "BeginStructPackForWire.h"
+#else
+BEGIN_STRUCTURE_PACKING_FOR_WIRE
+#endif
+
+#define ATTRIBUTETYPE_GENERIC_UNIX 1
+
+#define ATTRIBUTE_ENCODING_BLOWFISH 2
+
+typedef struct
+{
+ int32_t AttributeType;
+ u_int32_t UID;
+ u_int32_t GID;
+ u_int64_t ModificationTime;
+ u_int64_t AttrModificationTime;
+ u_int32_t UserDefinedFlags;
+ u_int32_t FileGenerationNumber;
+ u_int16_t Mode;
+ // Symbolic link filename may follow
+ // Extended attribute (xattr) information may follow, format is:
+ // u_int32_t Size of extended attribute block (excluding this word)
+ // For each of NumberOfAttributes (sorted by AttributeName):
+ // u_int16_t AttributeNameLength
+ // char AttributeName[AttributeNameLength]
+ // u_int32_t AttributeValueLength
+ // unsigned char AttributeValue[AttributeValueLength]
+ // AttributeName is 0 terminated, AttributeValue is not (and may be binary data)
+} attr_StreamFormat;
+
+// This has wire packing so it's compatible across platforms
+// Use wider than necessary sizes, just to be careful.
+typedef struct
+{
+ int32_t uid, gid, mode;
+ #ifdef WIN32
+ int64_t fileCreationTime;
+ #endif
+} attributeHashData;
+
+// Use default packing
+#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS
+#include "EndStructPackForWire.h"
+#else
+END_STRUCTURE_PACKING_FOR_WIRE
+#endif
+
+
+#define MAX_ATTRIBUTE_HASH_SECRET_LENGTH 256
+
+// Hide private static variables from the rest of the world
+// -- don't put them as static class variables to avoid openssl/evp.h being
+// included all over the project.
+namespace
+{
+ CipherContext sBlowfishEncrypt;
+ CipherContext sBlowfishDecrypt;
+ uint8_t sAttributeHashSecret[MAX_ATTRIBUTE_HASH_SECRET_LENGTH];
+ int sAttributeHashSecretLength = 0;
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::BackupClientFileAttributes()
+// Purpose: Default constructor
+// Created: 2003/10/07
+//
+// --------------------------------------------------------------------------
+BackupClientFileAttributes::BackupClientFileAttributes()
+ : mpClearAttributes(0)
+{
+ ASSERT(sizeof(u_int64_t) == sizeof(box_time_t));
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::BackupClientFileAttributes()
+// Purpose: Artifical constructor
+// Created: 2011/12/06
+//
+// --------------------------------------------------------------------------
+BackupClientFileAttributes::BackupClientFileAttributes(const EMU_STRUCT_STAT &st)
+: mpClearAttributes(0)
+{
+ ASSERT(sizeof(u_int64_t) == sizeof(box_time_t));
+ StreamableMemBlock *pnewAttr = new StreamableMemBlock;
+ FillAttributes(*pnewAttr, (const char *)NULL, st, true);
+
+ // Attributes ready. Encrypt into this block
+ EncryptAttr(*pnewAttr);
+
+ // Store the new attributes
+ RemoveClear();
+ mpClearAttributes = pnewAttr;
+ pnewAttr = 0;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::BackupClientFileAttributes(const BackupClientFileAttributes &)
+// Purpose: Copy constructor
+// Created: 2003/10/07
+//
+// --------------------------------------------------------------------------
+BackupClientFileAttributes::BackupClientFileAttributes(const BackupClientFileAttributes &rToCopy)
+ : StreamableMemBlock(rToCopy), // base class does the hard work
+ mpClearAttributes(0)
+{
+}
+BackupClientFileAttributes::BackupClientFileAttributes(const StreamableMemBlock &rToCopy)
+ : StreamableMemBlock(rToCopy), // base class does the hard work
+ mpClearAttributes(0)
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::~BackupClientFileAttributes()
+// Purpose: Destructor
+// Created: 2003/10/07
+//
+// --------------------------------------------------------------------------
+BackupClientFileAttributes::~BackupClientFileAttributes()
+{
+ if(mpClearAttributes)
+ {
+ delete mpClearAttributes;
+ mpClearAttributes = 0;
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes &operator=(const BackupClientFileAttributes &)
+// Purpose: Assignment operator
+// Created: 2003/10/07
+//
+// --------------------------------------------------------------------------
+BackupClientFileAttributes &BackupClientFileAttributes::operator=(const BackupClientFileAttributes &rAttr)
+{
+ StreamableMemBlock::Set(rAttr);
+ RemoveClear(); // make sure no decrypted version held
+ return *this;
+}
+// Assume users play nice
+BackupClientFileAttributes &BackupClientFileAttributes::operator=(const StreamableMemBlock &rAttr)
+{
+ StreamableMemBlock::Set(rAttr);
+ RemoveClear(); // make sure no decrypted version held
+ return *this;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::operator==(const BackupClientFileAttributes &)
+// Purpose: Comparison operator
+// Created: 2003/10/09
+//
+// --------------------------------------------------------------------------
+bool BackupClientFileAttributes::operator==(const BackupClientFileAttributes &rAttr) const
+{
+ EnsureClearAvailable();
+ rAttr.EnsureClearAvailable();
+
+ return mpClearAttributes->operator==(*rAttr.mpClearAttributes);
+}
+// Too dangerous to allow -- put the two names the wrong way round, and it compares encrypted data.
+/*bool BackupClientFileAttributes::operator==(const StreamableMemBlock &rAttr) const
+{
+ StreamableMemBlock *pDecoded = 0;
+
+ try
+ {
+ EnsureClearAvailable();
+ StreamableMemBlock *pDecoded = MakeClear(rAttr);
+
+ // Compare using clear version
+ bool compared = mpClearAttributes->operator==(rAttr);
+
+ // Delete temporary
+ delete pDecoded;
+
+ return compared;
+ }
+ catch(...)
+ {
+ delete pDecoded;
+ throw;
+ }
+}*/
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::Compare(const BackupClientFileAttributes &, bool)
+// Purpose: Compare, optionally ignoring the attribute
+// modification time and/or modification time, and some
+// data which is irrelevant in practise (eg file
+// generation number)
+// Created: 10/12/03
+//
+// --------------------------------------------------------------------------
+bool BackupClientFileAttributes::Compare(const BackupClientFileAttributes &rAttr,
+ bool IgnoreAttrModTime, bool IgnoreModTime) const
+{
+ EnsureClearAvailable();
+ rAttr.EnsureClearAvailable();
+
+ // Check sizes are the same, as a first check
+ if(mpClearAttributes->GetSize() != rAttr.mpClearAttributes->GetSize())
+ {
+ BOX_TRACE("Attribute Compare: Attributes objects are "
+ "different sizes, cannot compare them: local " <<
+ mpClearAttributes->GetSize() << " bytes, remote " <<
+ rAttr.mpClearAttributes->GetSize() << " bytes");
+ return false;
+ }
+
+ // Then check the elements of the two things
+ // Bytes are checked in network order, but this doesn't matter as we're only checking for equality.
+ attr_StreamFormat *a1 = (attr_StreamFormat*)mpClearAttributes->GetBuffer();
+ attr_StreamFormat *a2 = (attr_StreamFormat*)rAttr.mpClearAttributes->GetBuffer();
+
+ #define COMPARE(attribute, message) \
+ if (a1->attribute != a2->attribute) \
+ { \
+ BOX_TRACE("Attribute Compare: " << message << " differ: " \
+ "local " << ntoh(a1->attribute) << ", " \
+ "remote " << ntoh(a2->attribute)); \
+ return false; \
+ }
+ COMPARE(AttributeType, "Attribute types");
+ COMPARE(UID, "UIDs");
+ COMPARE(GID, "GIDs");
+ COMPARE(UserDefinedFlags, "User-defined flags");
+ COMPARE(Mode, "Modes");
+
+ if(!IgnoreModTime)
+ {
+ uint64_t t1 = box_ntoh64(a1->ModificationTime);
+ uint64_t t2 = box_ntoh64(a2->ModificationTime);
+ time_t s1 = BoxTimeToSeconds(t1);
+ time_t s2 = BoxTimeToSeconds(t2);
+ if(s1 != s2)
+ {
+ BOX_TRACE("Attribute Compare: File modification "
+ "times differ: local " <<
+ FormatTime(t1, true) << " (" << s1 << "), "
+ "remote " <<
+ FormatTime(t2, true) << " (" << s2 << ")");
+ return false;
+ }
+ }
+
+ if(!IgnoreAttrModTime)
+ {
+ uint64_t t1 = box_ntoh64(a1->AttrModificationTime);
+ uint64_t t2 = box_ntoh64(a2->AttrModificationTime);
+ time_t s1 = BoxTimeToSeconds(t1);
+ time_t s2 = BoxTimeToSeconds(t2);
+ if(s1 != s2)
+ {
+ BOX_TRACE("Attribute Compare: Attribute modification "
+ "times differ: local " <<
+ FormatTime(t1, true) << " (" << s1 << "), "
+ "remote " <<
+ FormatTime(t2, true) << " (" << s2 << ")");
+ return false;
+ }
+ }
+
+ // Check symlink string?
+ unsigned int size = mpClearAttributes->GetSize();
+ if(size > sizeof(attr_StreamFormat))
+ {
+ // Symlink strings don't match. This also compares xattrs
+ int datalen = size - sizeof(attr_StreamFormat);
+
+ if(::memcmp(a1 + 1, a2 + 1, datalen) != 0)
+ {
+ std::string s1((char *)(a1 + 1), datalen);
+ std::string s2((char *)(a2 + 1), datalen);
+ BOX_TRACE("Attribute Compare: Symbolic link target "
+ "or extended attributes differ: "
+ "local " << PrintEscapedBinaryData(s1) << ", "
+ "remote " << PrintEscapedBinaryData(s2));
+ return false;
+ }
+ }
+
+ // Passes all test, must be OK
+ return true;
+}
+
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::ReadAttributes(
+// const char *Filename, bool ZeroModificationTimes,
+// box_time_t *pModTime, box_time_t *pAttrModTime,
+// int64_t *pFileSize, InodeRefType *pInodeNumber,
+// bool *pHasMultipleLinks)
+// Purpose: Read the attributes of the file, and store them
+// ready for streaming. Optionally retrieve the
+// modification time and attribute modification time.
+// Created: 2003/10/07
+//
+// --------------------------------------------------------------------------
+void BackupClientFileAttributes::ReadAttributes(const std::string& Filename,
+ bool ZeroModificationTimes, box_time_t *pModTime,
+ box_time_t *pAttrModTime, int64_t *pFileSize,
+ InodeRefType *pInodeNumber, bool *pHasMultipleLinks)
+{
+ StreamableMemBlock *pnewAttr = 0;
+ try
+ {
+ EMU_STRUCT_STAT st;
+ if(EMU_LSTAT(Filename.c_str(), &st) != 0)
+ {
+ THROW_SYS_FILE_ERROR("Failed to stat file",
+ Filename, CommonException, OSFileError)
+ }
+
+ // Modification times etc
+ if(pModTime) {*pModTime = FileModificationTime(st);}
+ if(pAttrModTime) {*pAttrModTime = FileAttrModificationTime(st);}
+ if(pFileSize) {*pFileSize = st.st_size;}
+ if(pInodeNumber) {*pInodeNumber = st.st_ino;}
+ if(pHasMultipleLinks) {*pHasMultipleLinks = (st.st_nlink > 1);}
+
+ pnewAttr = new StreamableMemBlock;
+
+ FillAttributes(*pnewAttr, Filename, st, ZeroModificationTimes);
+
+#ifndef WIN32
+ // Is it a link?
+ if((st.st_mode & S_IFMT) == S_IFLNK)
+ {
+ FillAttributesLink(*pnewAttr, Filename, st);
+ }
+#endif
+
+ FillExtendedAttr(*pnewAttr, Filename);
+
+#ifdef WIN32
+ //this is to catch those problems with invalid time stamps stored...
+ //need to find out the reason why - but also a catch as well.
+
+ attr_StreamFormat *pattr =
+ (attr_StreamFormat*)pnewAttr->GetBuffer();
+ ASSERT(pattr != 0);
+
+ // __time64_t winTime = BoxTimeToSeconds(
+ // pnewAttr->ModificationTime);
+
+ u_int64_t modTime = box_ntoh64(pattr->ModificationTime);
+ box_time_t modSecs = BoxTimeToSeconds(modTime);
+ __time64_t winTime = modSecs;
+
+ // _MAX__TIME64_T doesn't seem to be defined, but the code below
+ // will throw an assertion failure if we exceed it :-)
+ // Microsoft says dates up to the year 3000 are valid, which
+ // is a bit more than 15 * 2^32. Even that doesn't seem
+ // to be true (still aborts), but it can at least hold 2^32.
+ if (winTime >= 0x100000000LL || _gmtime64(&winTime) == 0)
+ {
+ BOX_ERROR("Invalid Modification Time caught for "
+ "file: '" << Filename << "'");
+ pattr->ModificationTime = 0;
+ }
+
+ modTime = box_ntoh64(pattr->AttrModificationTime);
+ modSecs = BoxTimeToSeconds(modTime);
+ winTime = modSecs;
+
+ if (winTime > 0x100000000LL || _gmtime64(&winTime) == 0)
+ {
+ BOX_ERROR("Invalid Attribute Modification Time "
+ "caught for file: '" << Filename << "'");
+ pattr->AttrModificationTime = 0;
+ }
+#endif
+
+ // Attributes ready. Encrypt into this block
+ EncryptAttr(*pnewAttr);
+
+ // Store the new attributes
+ RemoveClear();
+ mpClearAttributes = pnewAttr;
+ pnewAttr = 0;
+ }
+ catch(...)
+ {
+ // clean up
+ delete pnewAttr;
+ pnewAttr = 0;
+ throw;
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::FillAttributes()
+// Purpose: Private function, handles standard attributes for all objects
+// Created: 2003/10/07
+//
+// --------------------------------------------------------------------------
+void BackupClientFileAttributes::FillAttributes(
+ StreamableMemBlock &outputBlock, const std::string& rFilename,
+ const EMU_STRUCT_STAT &st, bool ZeroModificationTimes
+)
+{
+ outputBlock.ResizeBlock(sizeof(attr_StreamFormat));
+ attr_StreamFormat *pattr = (attr_StreamFormat*)outputBlock.GetBuffer();
+ ASSERT(pattr != 0);
+
+ // Fill in the entries
+ pattr->AttributeType = htonl(ATTRIBUTETYPE_GENERIC_UNIX);
+ pattr->UID = htonl(st.st_uid);
+ pattr->GID = htonl(st.st_gid);
+ if(ZeroModificationTimes)
+ {
+ pattr->ModificationTime = 0;
+ pattr->AttrModificationTime = 0;
+ }
+ else
+ {
+ pattr->ModificationTime = box_hton64(FileModificationTime(st));
+ pattr->AttrModificationTime = box_hton64(FileAttrModificationTime(st));
+ }
+ pattr->Mode = htons(st.st_mode);
+
+#ifndef HAVE_STRUCT_STAT_ST_FLAGS
+ pattr->UserDefinedFlags = 0;
+ pattr->FileGenerationNumber = 0;
+#else
+ pattr->UserDefinedFlags = htonl(st.st_flags);
+ pattr->FileGenerationNumber = htonl(st.st_gen);
+#endif
+}
+#ifndef WIN32
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::FillAttributesLink(
+// StreamableMemBlock &outputBlock,
+// const char *Filename, struct stat &st)
+// Purpose: Private function, handles the case where a symbolic link is needed
+// Created: 2003/10/07
+//
+// --------------------------------------------------------------------------
+void BackupClientFileAttributes::FillAttributesLink(
+ StreamableMemBlock &outputBlock, const std::string& Filename,
+ struct stat &st)
+{
+ // Make sure we're only called for symbolic links
+ ASSERT((st.st_mode & S_IFMT) == S_IFLNK);
+
+ // Get the filename the link is linked to
+ char linkedTo[PATH_MAX+4];
+ int linkedToSize = ::readlink(Filename.c_str(), linkedTo, PATH_MAX);
+ if(linkedToSize == -1)
+ {
+ BOX_LOG_SYS_ERROR("Failed to readlink '" << Filename << "'");
+ THROW_EXCEPTION(CommonException, OSFileError);
+ }
+
+ int oldSize = outputBlock.GetSize();
+ outputBlock.ResizeBlock(oldSize+linkedToSize+1);
+ char* buffer = static_cast<char*>(outputBlock.GetBuffer());
+
+ // Add the path name for the symbolic link, and add 0 termination
+ std::memcpy(buffer+oldSize, linkedTo, linkedToSize);
+ buffer[oldSize+linkedToSize] = '\0';
+}
+#endif
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::FillExtendedAttr(const char *, unsigned char**)
+// Purpose: Private function, read the extended attributes of the file into the block
+// Created: 2005/06/12
+//
+// --------------------------------------------------------------------------
+void BackupClientFileAttributes::FillExtendedAttr(StreamableMemBlock &outputBlock,
+ const std::string& Filename)
+{
+#ifdef HAVE_SYS_XATTR_H
+ int listBufferSize = 10000;
+ char* list = new char[listBufferSize];
+
+ try
+ {
+ // This returns an unordered list of attribute names, each 0 terminated,
+ // concatenated together
+ int listSize = ::llistxattr(Filename.c_str(), list, listBufferSize);
+
+ if(listSize>listBufferSize)
+ {
+ delete[] list, list = NULL;
+ list = new char[listSize];
+ listSize = ::llistxattr(Filename.c_str(), list, listSize);
+ }
+
+ if(listSize>0)
+ {
+ // Extract list of attribute names so we can sort them
+ std::vector<std::string> attrKeys;
+ for(int i = 0; i<listSize; ++i)
+ {
+ std::string attrKey(list+i);
+ i += attrKey.size();
+ attrKeys.push_back(attrKey);
+ }
+ sort(attrKeys.begin(), attrKeys.end());
+
+ // Make initial space in block
+ int xattrSize = outputBlock.GetSize();
+ int xattrBufferSize = (xattrSize+listSize)>500 ? (xattrSize+listSize)*2 : 1000;
+ outputBlock.ResizeBlock(xattrBufferSize);
+ unsigned char* buffer = static_cast<unsigned char*>(outputBlock.GetBuffer());
+
+ // Leave space for attr block size later
+ int xattrBlockSizeOffset = xattrSize;
+ xattrSize += sizeof(u_int32_t);
+
+ // Loop for each attribute
+ for(std::vector<std::string>::const_iterator attrKeyI = attrKeys.begin(); attrKeyI!=attrKeys.end(); ++attrKeyI)
+ {
+ std::string attrKey(*attrKeyI);
+
+ if(xattrSize+sizeof(u_int16_t)+attrKey.size()+1+sizeof(u_int32_t)>static_cast<unsigned int>(xattrBufferSize))
+ {
+ xattrBufferSize = (xattrBufferSize+sizeof(u_int16_t)+attrKey.size()+1+sizeof(u_int32_t))*2;
+ outputBlock.ResizeBlock(xattrBufferSize);
+ buffer = static_cast<unsigned char*>(outputBlock.GetBuffer());
+ }
+
+ // Store length and text for attibute name
+ u_int16_t keyLength = htons(attrKey.size()+1);
+ std::memcpy(buffer+xattrSize, &keyLength, sizeof(u_int16_t));
+ xattrSize += sizeof(u_int16_t);
+ std::memcpy(buffer+xattrSize, attrKey.c_str(), attrKey.size()+1);
+ xattrSize += attrKey.size()+1;
+
+ // Leave space for value size
+ int valueSizeOffset = xattrSize;
+ xattrSize += sizeof(u_int32_t);
+
+ // Find size of attribute (must call with buffer and length 0 on some platforms,
+ // as -1 is returned if the data doesn't fit.)
+ int valueSize = ::lgetxattr(Filename.c_str(), attrKey.c_str(), 0, 0);
+ if(valueSize<0)
+ {
+ BOX_LOG_SYS_ERROR("Failed to get "
+ "extended attribute size of "
+ "'" << Filename << "': " <<
+ attrKey);
+ THROW_EXCEPTION(CommonException, OSFileError);
+ }
+
+ // Resize block, if needed
+ if(xattrSize+valueSize>xattrBufferSize)
+ {
+ xattrBufferSize = (xattrBufferSize+valueSize)*2;
+ outputBlock.ResizeBlock(xattrBufferSize);
+ buffer = static_cast<unsigned char*>(outputBlock.GetBuffer());
+ }
+
+ // This gets the attribute value (may be text or binary), no termination
+ valueSize = ::lgetxattr(Filename.c_str(),
+ attrKey.c_str(), buffer+xattrSize,
+ xattrBufferSize-xattrSize);
+ if(valueSize<0)
+ {
+ BOX_LOG_SYS_ERROR("Failed to get "
+ "extended attribute of "
+ "'" << Filename << "': " <<
+ attrKey);
+ THROW_EXCEPTION(CommonException, OSFileError);
+ }
+ xattrSize += valueSize;
+
+ // Fill in value size
+ u_int32_t valueLength = htonl(valueSize);
+ std::memcpy(buffer+valueSizeOffset, &valueLength, sizeof(u_int32_t));
+ }
+
+ // Fill in attribute block size
+ u_int32_t xattrBlockLength = htonl(xattrSize-xattrBlockSizeOffset-sizeof(u_int32_t));
+ std::memcpy(buffer+xattrBlockSizeOffset, &xattrBlockLength, sizeof(u_int32_t));
+
+ outputBlock.ResizeBlock(xattrSize);
+ }
+ else if(listSize<0)
+ {
+ if(errno == EOPNOTSUPP || errno == EACCES)
+ {
+ // fail silently
+ }
+ else if(errno == ERANGE)
+ {
+ BOX_ERROR("Failed to list extended "
+ "attributes of '" << Filename << "': "
+ "buffer too small, not backed up");
+ }
+ else
+ {
+ BOX_LOG_SYS_ERROR("Failed to list extended "
+ "attributes of '" << Filename << "', "
+ "not backed up");
+ THROW_EXCEPTION(CommonException, OSFileError);
+ }
+ }
+ }
+ catch(...)
+ {
+ delete[] list;
+ throw;
+ }
+ delete[] list;
+#endif
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::GetModificationTimes()
+// Purpose: Returns the modification time embedded in the
+// attributes.
+// Created: 2010/02/24
+//
+// --------------------------------------------------------------------------
+void BackupClientFileAttributes::GetModificationTimes(
+ box_time_t *pModificationTime,
+ box_time_t *pAttrModificationTime) const
+{
+ // Got something loaded
+ if(GetSize() <= 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded);
+ }
+
+ // Make sure there are clear attributes to use
+ EnsureClearAvailable();
+ ASSERT(mpClearAttributes != 0);
+
+ // Check if the decrypted attributes are small enough, and the type of attributes stored
+ if(mpClearAttributes->GetSize() < (int)sizeof(int32_t))
+ {
+ THROW_EXCEPTION(BackupStoreException, AttributesNotUnderstood);
+ }
+ int32_t *type = (int32_t*)mpClearAttributes->GetBuffer();
+ ASSERT(type != 0);
+ if(ntohl(*type) != ATTRIBUTETYPE_GENERIC_UNIX)
+ {
+ // Don't know what to do with these
+ THROW_EXCEPTION(BackupStoreException, AttributesNotUnderstood);
+ }
+
+ // Check there is enough space for an attributes block
+ if(mpClearAttributes->GetSize() < (int)sizeof(attr_StreamFormat))
+ {
+ // Too small
+ THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded);
+ }
+
+ // Get pointer to structure
+ attr_StreamFormat *pattr = (attr_StreamFormat*)mpClearAttributes->GetBuffer();
+
+ if(pModificationTime)
+ {
+ *pModificationTime = box_ntoh64(pattr->ModificationTime);
+ }
+
+ if(pAttrModificationTime)
+ {
+ *pAttrModificationTime = box_ntoh64(pattr->AttrModificationTime);
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::WriteAttributes(const char *)
+// Purpose: Apply the stored attributes to the file
+// Created: 2003/10/07
+//
+// --------------------------------------------------------------------------
+void BackupClientFileAttributes::WriteAttributes(const std::string& Filename,
+ bool MakeUserWritable) const
+{
+ // Got something loaded
+ if(GetSize() <= 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded);
+ }
+
+ // Make sure there are clear attributes to use
+ EnsureClearAvailable();
+ ASSERT(mpClearAttributes != 0);
+
+ // Check if the decrypted attributes are small enough, and the type of attributes stored
+ if(mpClearAttributes->GetSize() < (int)sizeof(int32_t))
+ {
+ THROW_EXCEPTION(BackupStoreException, AttributesNotUnderstood);
+ }
+ int32_t *type = (int32_t*)mpClearAttributes->GetBuffer();
+ ASSERT(type != 0);
+ if(ntohl(*type) != ATTRIBUTETYPE_GENERIC_UNIX)
+ {
+ // Don't know what to do with these
+ THROW_EXCEPTION(BackupStoreException, AttributesNotUnderstood);
+ }
+
+ // Check there is enough space for an attributes block
+ if(mpClearAttributes->GetSize() < (int)sizeof(attr_StreamFormat))
+ {
+ // Too small
+ THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded);
+ }
+
+ // Get pointer to structure
+ attr_StreamFormat *pattr = (attr_StreamFormat*)mpClearAttributes->GetBuffer();
+ int xattrOffset = sizeof(attr_StreamFormat);
+
+ // is it a symlink?
+ int16_t mode = ntohs(pattr->Mode);
+ if((mode & S_IFMT) == S_IFLNK)
+ {
+ // Check things are sensible
+ if(mpClearAttributes->GetSize() < (int)sizeof(attr_StreamFormat) + 1)
+ {
+ // Too small
+ THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded);
+ }
+
+#ifdef WIN32
+ BOX_WARNING("Cannot create symbolic links on Windows: '" <<
+ Filename << "'");
+#else
+ // Make a symlink, first deleting anything in the way
+ ::unlink(Filename.c_str());
+ if(::symlink((char*)(pattr + 1), Filename.c_str()) != 0)
+ {
+ BOX_LOG_SYS_ERROR("Failed to symlink '" << Filename <<
+ "' to '" << (char*)(pattr + 1) << "'");
+ THROW_EXCEPTION(CommonException, OSFileError)
+ }
+#endif
+
+ xattrOffset += std::strlen(reinterpret_cast<char*>(pattr+1))+1;
+ }
+
+ // If working as root, set user IDs
+ if(::geteuid() == 0)
+ {
+ #ifndef HAVE_LCHOWN
+ // only if not a link, can't set their owner on this platform
+ if((mode & S_IFMT) != S_IFLNK)
+ {
+ // Not a link, use normal chown
+ if(::chown(Filename.c_str(), ntohl(pattr->UID), ntohl(pattr->GID)) != 0)
+ {
+ BOX_LOG_SYS_ERROR("Failed to change "
+ "owner of file "
+ "'" << Filename << "'");
+ THROW_EXCEPTION(CommonException, OSFileError)
+ }
+ }
+ #else
+ // use the version which sets things on symlinks
+ if(::lchown(Filename.c_str(), ntohl(pattr->UID), ntohl(pattr->GID)) != 0)
+ {
+ BOX_LOG_SYS_ERROR("Failed to change owner of "
+ "symbolic link '" << Filename << "'");
+ THROW_EXCEPTION(CommonException, OSFileError)
+ }
+ #endif
+ }
+
+ if(static_cast<int>(xattrOffset+sizeof(u_int32_t))<=mpClearAttributes->GetSize())
+ {
+ WriteExtendedAttr(Filename, xattrOffset);
+ }
+
+ // Stop now if symlink, because otherwise it'll just be applied to the target
+ if((mode & S_IFMT) == S_IFLNK)
+ {
+ return;
+ }
+
+ // Set modification time?
+ box_time_t modtime = box_ntoh64(pattr->ModificationTime);
+ if(modtime != 0)
+ {
+ // Work out times as timevals
+ struct timeval times[2];
+
+ #ifdef WIN32
+ BoxTimeToTimeval(box_ntoh64(pattr->ModificationTime),
+ times[1]);
+ BoxTimeToTimeval(box_ntoh64(pattr->AttrModificationTime),
+ times[0]);
+ // Because stat() returns the creation time in the ctime
+ // field under Windows, and this gets saved in the
+ // AttrModificationTime field of the serialised attributes,
+ // we subvert the first parameter of emu_utimes() to allow
+ // it to be reset to the right value on the restored file.
+ #else
+ BoxTimeToTimeval(modtime, times[1]);
+ // Copy access time as well, why not, got to set it to something
+ times[0] = times[1];
+ // Attr modification time will be changed anyway,
+ // nothing that can be done about it
+ #endif
+
+ // Try to apply
+ if(::utimes(Filename.c_str(), times) != 0)
+ {
+ BOX_LOG_SYS_WARNING("Failed to change times of "
+ "file '" << Filename << "' to ctime=" <<
+ BOX_FORMAT_TIMESPEC(times[0]) << ", mtime=" <<
+ BOX_FORMAT_TIMESPEC(times[1]));
+ }
+ }
+
+ if (MakeUserWritable)
+ {
+ mode |= S_IRWXU;
+ }
+
+ // Apply everything else... (allowable mode flags only)
+ // Mode must be done last (think setuid)
+ if(::chmod(Filename.c_str(), mode & (S_IRWXU | S_IRWXG | S_IRWXO |
+ S_ISUID | S_ISGID | S_ISVTX)) != 0)
+ {
+ BOX_LOG_SYS_ERROR("Failed to change permissions of file "
+ "'" << Filename << "'");
+ THROW_EXCEPTION(CommonException, OSFileError)
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::IsSymLink()
+// Purpose: Do these attributes represent a symbolic link?
+// Created: 2003/10/07
+//
+// --------------------------------------------------------------------------
+bool BackupClientFileAttributes::IsSymLink() const
+{
+ EnsureClearAvailable();
+
+ // Got the right kind of thing?
+ if(mpClearAttributes->GetSize() < (int)sizeof(int32_t))
+ {
+ THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded);
+ }
+
+ // Get the type of attributes stored
+ int32_t *type = (int32_t*)mpClearAttributes->GetBuffer();
+ ASSERT(type != 0);
+ if(ntohl(*type) == ATTRIBUTETYPE_GENERIC_UNIX && mpClearAttributes->GetSize() > (int)sizeof(attr_StreamFormat))
+ {
+ // Check link
+ attr_StreamFormat *pattr = (attr_StreamFormat*)mpClearAttributes->GetBuffer();
+ return ((ntohs(pattr->Mode)) & S_IFMT) == S_IFLNK;
+ }
+
+ return false;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::RemoveClear()
+// Purpose: Private. Deletes any clear version of the attributes that may be held
+// Created: 3/12/03
+//
+// --------------------------------------------------------------------------
+void BackupClientFileAttributes::RemoveClear() const
+{
+ if(mpClearAttributes)
+ {
+ delete mpClearAttributes;
+ }
+ mpClearAttributes = 0;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::EnsureClearAvailable()
+// Purpose: Private. Makes sure the clear version is available
+// Created: 3/12/03
+//
+// --------------------------------------------------------------------------
+void BackupClientFileAttributes::EnsureClearAvailable() const
+{
+ if(mpClearAttributes == 0)
+ {
+ mpClearAttributes = MakeClear(*this);
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::WriteExtendedAttr(const char *Filename, int xattrOffset)
+// Purpose: Private function, apply the stored extended attributes to the file
+// Created: 2005/06/13
+//
+// --------------------------------------------------------------------------
+void BackupClientFileAttributes::WriteExtendedAttr(const std::string& Filename, int xattrOffset) const
+{
+#ifdef HAVE_SYS_XATTR_H
+ const char* buffer = static_cast<char*>(mpClearAttributes->GetBuffer());
+
+ u_int32_t xattrBlockLength = 0;
+ std::memcpy(&xattrBlockLength, buffer+xattrOffset, sizeof(u_int32_t));
+ int xattrBlockSize = ntohl(xattrBlockLength);
+ xattrOffset += sizeof(u_int32_t);
+
+ int xattrEnd = xattrOffset+xattrBlockSize;
+ if(xattrEnd>mpClearAttributes->GetSize())
+ {
+ // Too small
+ THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded);
+ }
+
+ while(xattrOffset<xattrEnd)
+ {
+ u_int16_t keyLength = 0;
+ std::memcpy(&keyLength, buffer+xattrOffset, sizeof(u_int16_t));
+ int keySize = ntohs(keyLength);
+ xattrOffset += sizeof(u_int16_t);
+
+ const char* key = buffer+xattrOffset;
+ xattrOffset += keySize;
+
+ u_int32_t valueLength = 0;
+ std::memcpy(&valueLength, buffer+xattrOffset, sizeof(u_int32_t));
+ int valueSize = ntohl(valueLength);
+ xattrOffset += sizeof(u_int32_t);
+
+ // FIXME: Warn on EOPNOTSUPP
+ if(::lsetxattr(Filename.c_str(), key, buffer+xattrOffset,
+ valueSize, 0)!=0 && errno!=EOPNOTSUPP)
+ {
+ BOX_LOG_SYS_ERROR("Failed to set extended attributes "
+ "on file '" << Filename << "'");
+ THROW_EXCEPTION(CommonException, OSFileError);
+ }
+
+ xattrOffset += valueSize;
+ }
+
+ ASSERT(xattrOffset==xattrEnd);
+#endif
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::MakeClear(const StreamableMemBlock &)
+// Purpose: Static. Decrypts stored attributes.
+// Created: 3/12/03
+//
+// --------------------------------------------------------------------------
+StreamableMemBlock *BackupClientFileAttributes::MakeClear(const StreamableMemBlock &rEncrypted)
+{
+ // New block
+ StreamableMemBlock *pdecrypted = 0;
+
+ try
+ {
+ // Check the block is big enough for IV and header
+ int ivSize = sBlowfishEncrypt.GetIVLength();
+ if(rEncrypted.GetSize() <= (ivSize + 1))
+ {
+ THROW_EXCEPTION(BackupStoreException, BadEncryptedAttributes);
+ }
+
+ // How much space is needed for the output?
+ int maxDecryptedSize = sBlowfishDecrypt.MaxOutSizeForInBufferSize(rEncrypted.GetSize() - ivSize);
+
+ // Allocate it
+ pdecrypted = new StreamableMemBlock(maxDecryptedSize);
+
+ // ptr to block
+ uint8_t *encBlock = (uint8_t*)rEncrypted.GetBuffer();
+
+ // Check that the header has right type
+ if(encBlock[0] != ATTRIBUTE_ENCODING_BLOWFISH)
+ {
+ THROW_EXCEPTION(BackupStoreException, EncryptedAttributesHaveUnknownEncoding);
+ }
+
+ // Set IV
+ sBlowfishDecrypt.SetIV(encBlock + 1);
+
+ // Decrypt
+ int decryptedSize = sBlowfishDecrypt.TransformBlock(pdecrypted->GetBuffer(), maxDecryptedSize, encBlock + 1 + ivSize, rEncrypted.GetSize() - (ivSize + 1));
+
+ // Resize block to fit
+ pdecrypted->ResizeBlock(decryptedSize);
+ }
+ catch(...)
+ {
+ delete pdecrypted;
+ pdecrypted = 0;
+ }
+
+ return pdecrypted;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::SetBlowfishKey(const void *, int)
+// Purpose: Static. Sets the key to use for encryption and decryption.
+// Created: 3/12/03
+//
+// --------------------------------------------------------------------------
+void BackupClientFileAttributes::SetBlowfishKey(const void *pKey, int KeyLength)
+{
+ // IVs set later
+ sBlowfishEncrypt.Reset();
+ sBlowfishEncrypt.Init(CipherContext::Encrypt, CipherBlowfish(CipherDescription::Mode_CBC, pKey, KeyLength));
+ sBlowfishDecrypt.Reset();
+ sBlowfishDecrypt.Init(CipherContext::Decrypt, CipherBlowfish(CipherDescription::Mode_CBC, pKey, KeyLength));
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::EncryptAttr(const StreamableMemBlock &)
+// Purpose: Private. Encrypt the given attributes into this block.
+// Created: 3/12/03
+//
+// --------------------------------------------------------------------------
+void BackupClientFileAttributes::EncryptAttr(const StreamableMemBlock &rToEncrypt)
+{
+ // Free any existing block
+ FreeBlock();
+
+ // Work out the maximum amount of space we need
+ int maxEncryptedSize = sBlowfishEncrypt.MaxOutSizeForInBufferSize(rToEncrypt.GetSize());
+ // And the size of the IV
+ int ivSize = sBlowfishEncrypt.GetIVLength();
+
+ // Allocate this space
+ AllocateBlock(maxEncryptedSize + ivSize + 1);
+
+ // Store the encoding byte
+ uint8_t *block = (uint8_t*)GetBuffer();
+ block[0] = ATTRIBUTE_ENCODING_BLOWFISH;
+
+ // Generate and store an IV for this attribute block
+ int ivSize2 = 0;
+ const void *iv = sBlowfishEncrypt.SetRandomIV(ivSize2);
+ ASSERT(ivSize == ivSize2);
+
+ // Copy into the encrypted block
+ ::memcpy(block + 1, iv, ivSize);
+
+ // Do the transform
+ int encrytedSize = sBlowfishEncrypt.TransformBlock(block + 1 + ivSize, maxEncryptedSize, rToEncrypt.GetBuffer(), rToEncrypt.GetSize());
+
+ // Resize this block
+ ResizeBlock(encrytedSize + ivSize + 1);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::SetAttributeHashSecret(const void *, int)
+// Purpose: Set the secret for the filename attribute hash
+// Created: 25/4/04
+//
+// --------------------------------------------------------------------------
+void BackupClientFileAttributes::SetAttributeHashSecret(const void *pSecret, int SecretLength)
+{
+ if(SecretLength > (int)sizeof(sAttributeHashSecret))
+ {
+ SecretLength = sizeof(sAttributeHashSecret);
+ }
+ if(SecretLength < 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, Internal)
+ }
+
+ // Copy
+ ::memcpy(sAttributeHashSecret, pSecret, SecretLength);
+ sAttributeHashSecretLength = SecretLength;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupClientFileAttributes::GenerateAttributeHash(
+// struct stat &, const std::string &,
+// const std::string &)
+// Purpose: Generate a 64 bit hash from the attributes, used to
+// detect changes. Include filename in the hash, so
+// that it changes from one file to another, so don't
+// reveal identical attributes.
+// Created: 25/4/04
+//
+// --------------------------------------------------------------------------
+uint64_t BackupClientFileAttributes::GenerateAttributeHash(EMU_STRUCT_STAT &st,
+ const std::string &filename, const std::string &leafname)
+{
+ if(sAttributeHashSecretLength == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, AttributeHashSecretNotSet)
+ }
+
+ // Assemble stuff we're interested in
+ attributeHashData hashData;
+ memset(&hashData, 0, sizeof(hashData));
+ // Use network byte order and large sizes to be cross platform
+ hashData.uid = htonl(st.st_uid);
+ hashData.gid = htonl(st.st_gid);
+ hashData.mode = htonl(st.st_mode);
+
+ #ifdef WIN32
+ // On Windows, the "file attribute modification time" is the
+ // file creation time, and we want to back this up, restore
+ // it and compare it.
+ //
+ // On other platforms, it's not very important and can't
+ // reliably be set to anything other than the current time.
+ hashData.fileCreationTime = box_hton64(st.st_ctime);
+ #endif
+
+ StreamableMemBlock xattr;
+ FillExtendedAttr(xattr, filename.c_str());
+
+ // Create a MD5 hash of the data, filename, and secret
+ MD5Digest digest;
+ digest.Add(&hashData, sizeof(hashData));
+ digest.Add(xattr.GetBuffer(), xattr.GetSize());
+ digest.Add(leafname.c_str(), leafname.size());
+ digest.Add(sAttributeHashSecret, sAttributeHashSecretLength);
+ digest.Finish();
+
+ // Return the first 64 bits of the hash
+ uint64_t result;
+ memcpy(&result, digest.DigestAsData(), sizeof(result));
+ return result;
+}
diff --git a/lib/backupstore/BackupClientFileAttributes.h b/lib/backupstore/BackupClientFileAttributes.h
new file mode 100644
index 00000000..662529ec
--- /dev/null
+++ b/lib/backupstore/BackupClientFileAttributes.h
@@ -0,0 +1,82 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupClientFileAttributes.h
+// Purpose: Storage of file attributes
+// Created: 2003/10/07
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPCLIENTFILEATTRIBUTES__H
+#define BACKUPCLIENTFILEATTRIBUTES__H
+
+#include <string>
+
+#include "StreamableMemBlock.h"
+#include "BoxTime.h"
+
+EMU_STRUCT_STAT; // declaration
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: BackupClientFileAttributes
+// Purpose: Storage, streaming and application of file attributes
+// Created: 2003/10/07
+//
+// --------------------------------------------------------------------------
+class BackupClientFileAttributes : public StreamableMemBlock
+{
+public:
+ BackupClientFileAttributes();
+ BackupClientFileAttributes(const EMU_STRUCT_STAT &st);
+ BackupClientFileAttributes(const BackupClientFileAttributes &rToCopy);
+ BackupClientFileAttributes(const StreamableMemBlock &rToCopy);
+ ~BackupClientFileAttributes();
+ BackupClientFileAttributes &operator=(const BackupClientFileAttributes &rAttr);
+ BackupClientFileAttributes &operator=(const StreamableMemBlock &rAttr);
+ bool operator==(const BackupClientFileAttributes &rAttr) const;
+// bool operator==(const StreamableMemBlock &rAttr) const; // too dangerous?
+
+ bool Compare(const BackupClientFileAttributes &rAttr, bool IgnoreAttrModTime = false, bool IgnoreModTime = false) const;
+
+ // Prevent access to base class members accidently
+ void Set();
+
+ void ReadAttributes(const std::string& Filename, bool ZeroModificationTimes = false,
+ box_time_t *pModTime = 0, box_time_t *pAttrModTime = 0, int64_t *pFileSize = 0,
+ InodeRefType *pInodeNumber = 0, bool *pHasMultipleLinks = 0);
+ void WriteAttributes(const std::string& Filename,
+ bool MakeUserWritable = false) const;
+ void GetModificationTimes(box_time_t *pModificationTime,
+ box_time_t *pAttrModificationTime) const;
+
+ bool IsSymLink() const;
+
+ static void SetBlowfishKey(const void *pKey, int KeyLength);
+ static void SetAttributeHashSecret(const void *pSecret, int SecretLength);
+
+ static uint64_t GenerateAttributeHash(EMU_STRUCT_STAT &st,
+ const std::string& Filename, const std::string &leafname);
+ static void FillExtendedAttr(StreamableMemBlock &outputBlock,
+ const std::string& Filename);
+
+private:
+ static void FillAttributes(StreamableMemBlock &outputBlock,
+ const std::string& Filename, const EMU_STRUCT_STAT &st,
+ bool ZeroModificationTimes);
+ static void FillAttributesLink(StreamableMemBlock &outputBlock,
+ const std::string& Filename, struct stat &st);
+ void WriteExtendedAttr(const std::string& Filename, int xattrOffset) const;
+
+ void RemoveClear() const;
+ void EnsureClearAvailable() const;
+ static StreamableMemBlock *MakeClear(const StreamableMemBlock &rEncrypted);
+ void EncryptAttr(const StreamableMemBlock &rToEncrypt);
+
+private:
+ mutable StreamableMemBlock *mpClearAttributes;
+};
+
+#endif // BACKUPCLIENTFILEATTRIBUTES__H
+
diff --git a/lib/backupstore/BackupCommands.cpp b/lib/backupstore/BackupCommands.cpp
new file mode 100644
index 00000000..318ce55a
--- /dev/null
+++ b/lib/backupstore/BackupCommands.cpp
@@ -0,0 +1,970 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupCommands.cpp
+// Purpose: Implement commands for the Backup store protocol
+// Created: 2003/08/20
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <set>
+#include <sstream>
+
+#include "autogen_BackupProtocol.h"
+#include "autogen_RaidFileException.h"
+#include "BackupConstants.h"
+#include "BackupStoreContext.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreDirectory.h"
+#include "BackupStoreException.h"
+#include "BackupStoreFile.h"
+#include "BackupStoreInfo.h"
+#include "BufferedStream.h"
+#include "CollectInBufferStream.h"
+#include "FileStream.h"
+#include "InvisibleTempFileStream.h"
+#include "RaidFileController.h"
+#include "StreamableMemBlock.h"
+
+#include "MemLeakFindOn.h"
+
+#define PROTOCOL_ERROR(code) \
+ std::auto_ptr<BackupProtocolMessage>(new BackupProtocolError( \
+ BackupProtocolError::ErrorType, \
+ BackupProtocolError::code));
+
+#define CHECK_PHASE(phase) \
+ if(rContext.GetPhase() != BackupStoreContext::phase) \
+ { \
+ BOX_ERROR("Received command " << ToString() << " " \
+ "in wrong protocol phase " << rContext.GetPhaseName() << ", " \
+ "expected in " #phase); \
+ return PROTOCOL_ERROR(Err_NotInRightProtocolPhase); \
+ }
+
+#define CHECK_WRITEABLE_SESSION \
+ if(rContext.SessionIsReadOnly()) \
+ { \
+ BOX_ERROR("Received command " << ToString() << " " \
+ "in a read-only session"); \
+ return PROTOCOL_ERROR(Err_SessionReadOnly); \
+ }
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolVersion::DoCommand(Protocol &, BackupStoreContext &)
+// Purpose: Return the current version, or an error if the requested version isn't allowed
+// Created: 2003/08/20
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolVersion::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Version)
+
+ // Correct version?
+ if(mVersion != BACKUP_STORE_SERVER_VERSION)
+ {
+ return PROTOCOL_ERROR(Err_WrongVersion);
+ }
+
+ // Mark the next phase
+ rContext.SetPhase(BackupStoreContext::Phase_Login);
+
+ // Return our version
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolVersion(BACKUP_STORE_SERVER_VERSION));
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolLogin::DoCommand(Protocol &, BackupStoreContext &)
+// Purpose: Return the current version, or an error if the requested version isn't allowed
+// Created: 2003/08/20
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolLogin::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Login)
+
+ // Check given client ID against the ID in the certificate certificate
+ // and that the client actually has an account on this machine
+ if(mClientID != rContext.GetClientID())
+ {
+ BOX_WARNING("Failed login from client ID " <<
+ BOX_FORMAT_ACCOUNT(mClientID) << ": "
+ "wrong certificate for this account");
+ return PROTOCOL_ERROR(Err_BadLogin);
+ }
+
+ if(!rContext.GetClientHasAccount())
+ {
+ BOX_WARNING("Failed login from client ID " <<
+ BOX_FORMAT_ACCOUNT(mClientID) << ": "
+ "no such account on this server");
+ return PROTOCOL_ERROR(Err_BadLogin);
+ }
+
+ // If we need to write, check that nothing else has got a write lock
+ if((mFlags & Flags_ReadOnly) != Flags_ReadOnly)
+ {
+ // See if the context will get the lock
+ if(!rContext.AttemptToGetWriteLock())
+ {
+ BOX_WARNING("Failed to get write lock for Client ID " <<
+ BOX_FORMAT_ACCOUNT(mClientID));
+ return PROTOCOL_ERROR(Err_CannotLockStoreForWriting);
+ }
+
+ // Debug: check we got the lock
+ ASSERT(!rContext.SessionIsReadOnly());
+ }
+
+ // Load the store info
+ rContext.LoadStoreInfo();
+
+ if(!rContext.GetBackupStoreInfo().IsAccountEnabled())
+ {
+ BOX_WARNING("Refused login from disabled client ID " <<
+ BOX_FORMAT_ACCOUNT(mClientID));
+ return PROTOCOL_ERROR(Err_DisabledAccount);
+ }
+
+ // Get the last client store marker
+ int64_t clientStoreMarker = rContext.GetClientStoreMarker();
+
+ // Mark the next phase
+ rContext.SetPhase(BackupStoreContext::Phase_Commands);
+
+ // Log login
+ BOX_NOTICE("Login from Client ID " <<
+ BOX_FORMAT_ACCOUNT(mClientID) << " "
+ "(name=" << rContext.GetAccountName() << "): " <<
+ (((mFlags & Flags_ReadOnly) != Flags_ReadOnly)
+ ?"Read/Write":"Read-only") << " from " <<
+ rContext.GetConnectionDetails());
+
+ // Get the usage info for reporting to the client
+ int64_t blocksUsed = 0, blocksSoftLimit = 0, blocksHardLimit = 0;
+ rContext.GetStoreDiscUsageInfo(blocksUsed, blocksSoftLimit, blocksHardLimit);
+
+ // Return success
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolLoginConfirmed(clientStoreMarker, blocksUsed, blocksSoftLimit, blocksHardLimit));
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolFinished::DoCommand(Protocol &, BackupStoreContext &)
+// Purpose: Marks end of conversation (Protocol framework handles this)
+// Created: 2003/08/20
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolFinished::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ BOX_NOTICE("Session finished for Client ID " <<
+ BOX_FORMAT_ACCOUNT(rContext.GetClientID()) << " "
+ "(name=" << rContext.GetAccountName() << ")");
+
+ // Let the context know about it
+ rContext.ReceivedFinishCommand();
+
+ // can be called in any phase
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolFinished);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolListDirectory::DoCommand(Protocol &, BackupStoreContext &)
+// Purpose: Command to list a directory
+// Created: 2003/09/02
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolListDirectory::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+
+ // Store the listing to a stream
+ std::auto_ptr<CollectInBufferStream> stream(new CollectInBufferStream);
+
+ try
+ {
+ // Ask the context for a directory
+ const BackupStoreDirectory &rdir(
+ rContext.GetDirectory(mObjectID));
+ rdir.WriteToStream(*stream, mFlagsMustBeSet,
+ mFlagsNotToBeSet, mSendAttributes,
+ false /* never send dependency info to the client */);
+ }
+ catch (RaidFileException &e)
+ {
+ if (e.GetSubType() == RaidFileException::RaidFileDoesntExist)
+ {
+ return PROTOCOL_ERROR(Err_DoesNotExist);
+ }
+ throw;
+ }
+
+ stream->SetForReading();
+
+ // Get the protocol to send the stream
+ rProtocol.SendStreamAfterCommand(static_cast< std::auto_ptr<IOStream> > (stream));
+
+ return std::auto_ptr<BackupProtocolMessage>(
+ new BackupProtocolSuccess(mObjectID));
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolStoreFile::DoCommand(Protocol &, BackupStoreContext &)
+// Purpose: Command to store a file on the server
+// Created: 2003/09/02
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolStoreFile::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+ CHECK_WRITEABLE_SESSION
+
+ std::auto_ptr<BackupProtocolMessage> hookResult =
+ rContext.StartCommandHook(*this);
+ if(hookResult.get())
+ {
+ return hookResult;
+ }
+
+ // Check that the diff from file actually exists, if it's specified
+ if(mDiffFromFileID != 0)
+ {
+ if(!rContext.ObjectExists(mDiffFromFileID,
+ BackupStoreContext::ObjectExists_File))
+ {
+ return PROTOCOL_ERROR(Err_DiffFromFileDoesNotExist);
+ }
+ }
+
+ // A stream follows, which contains the file
+ std::auto_ptr<IOStream> filestream(rProtocol.ReceiveStream());
+
+ // Ask the context to store it
+ int64_t id = 0;
+ try
+ {
+ id = rContext.AddFile(*filestream, mDirectoryObjectID,
+ mModificationTime, mAttributesHash, mDiffFromFileID,
+ mFilename,
+ true /* mark files with same name as old versions */);
+ }
+ catch(BackupStoreException &e)
+ {
+ if(e.GetSubType() == BackupStoreException::AddedFileDoesNotVerify)
+ {
+ return PROTOCOL_ERROR(Err_FileDoesNotVerify);
+ }
+ else if(e.GetSubType() == BackupStoreException::AddedFileExceedsStorageLimit)
+ {
+ return PROTOCOL_ERROR(Err_StorageLimitExceeded);
+ }
+ else
+ {
+ throw;
+ }
+ }
+
+ // Tell the caller what the file ID was
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(id));
+}
+
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolGetObject::DoCommand(Protocol &, BackupStoreContext &)
+// Purpose: Command to get an arbitary object from the server
+// Created: 2003/09/03
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolGetObject::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+
+ // Check the object exists
+ if(!rContext.ObjectExists(mObjectID))
+ {
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(NoObject));
+ }
+
+ // Open the object
+ std::auto_ptr<IOStream> object(rContext.OpenObject(mObjectID));
+
+ // Stream it to the peer
+ rProtocol.SendStreamAfterCommand(object);
+
+ // Tell the caller what the file was
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(mObjectID));
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolGetFile::DoCommand(Protocol &, BackupStoreContext &)
+// Purpose: Command to get an file object from the server -- may have to do a bit of
+// work to get the object.
+// Created: 2003/09/03
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolGetFile::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+
+ // Check the objects exist
+ if(!rContext.ObjectExists(mObjectID)
+ || !rContext.ObjectExists(mInDirectory))
+ {
+ return PROTOCOL_ERROR(Err_DoesNotExist);
+ }
+
+ // Get the directory it's in
+ const BackupStoreDirectory &rdir(rContext.GetDirectory(mInDirectory));
+
+ // Find the object within the directory
+ BackupStoreDirectory::Entry *pfileEntry = rdir.FindEntryByID(mObjectID);
+ if(pfileEntry == 0)
+ {
+ return PROTOCOL_ERROR(Err_DoesNotExistInDirectory);
+ }
+
+ // The result
+ std::auto_ptr<IOStream> stream;
+
+ // Does this depend on anything?
+ if(pfileEntry->GetDependsNewer() != 0)
+ {
+ // File exists, but is a patch from a new version. Generate the older version.
+ std::vector<int64_t> patchChain;
+ int64_t id = mObjectID;
+ BackupStoreDirectory::Entry *en = 0;
+ do
+ {
+ patchChain.push_back(id);
+ en = rdir.FindEntryByID(id);
+ if(en == 0)
+ {
+ BOX_ERROR("Object " <<
+ BOX_FORMAT_OBJECTID(mObjectID) <<
+ " in dir " <<
+ BOX_FORMAT_OBJECTID(mInDirectory) <<
+ " for account " <<
+ BOX_FORMAT_ACCOUNT(rContext.GetClientID()) <<
+ " references object " <<
+ BOX_FORMAT_OBJECTID(id) <<
+ " which does not exist in dir");
+ return PROTOCOL_ERROR(Err_PatchConsistencyError);
+ }
+ id = en->GetDependsNewer();
+ }
+ while(en != 0 && id != 0);
+
+ // OK! The last entry in the chain is the full file, the others are patches back from it.
+ // Open the last one, which is the current from file
+ std::auto_ptr<IOStream> from(rContext.OpenObject(patchChain[patchChain.size() - 1]));
+
+ // Then, for each patch in the chain, do a combine
+ for(int p = ((int)patchChain.size()) - 2; p >= 0; --p)
+ {
+ // ID of patch
+ int64_t patchID = patchChain[p];
+
+ // Open it a couple of times
+ std::auto_ptr<IOStream> diff(rContext.OpenObject(patchID));
+ std::auto_ptr<IOStream> diff2(rContext.OpenObject(patchID));
+
+ // Choose a temporary filename for the result of the combination
+ std::ostringstream fs;
+ fs << rContext.GetStoreRoot() << ".recombinetemp." << p;
+ std::string tempFn =
+ RaidFileController::DiscSetPathToFileSystemPath(
+ rContext.GetStoreDiscSet(), fs.str(),
+ p + 16);
+
+ // Open the temporary file
+ std::auto_ptr<IOStream> combined;
+ try
+ {
+ {
+ // Write nastily to allow this to work with gcc 2.x
+ std::auto_ptr<IOStream> t(
+ new InvisibleTempFileStream(
+ tempFn.c_str(),
+ O_RDWR | O_CREAT |
+ O_EXCL | O_BINARY |
+ O_TRUNC));
+ combined = t;
+ }
+ }
+ catch(...)
+ {
+ // Make sure it goes
+ ::unlink(tempFn.c_str());
+ throw;
+ }
+
+ // Do the combining
+ BackupStoreFile::CombineFile(*diff, *diff2, *from, *combined);
+
+ // Move to the beginning of the combined file
+ combined->Seek(0, IOStream::SeekType_Absolute);
+
+ // Then shuffle round for the next go
+ if (from.get()) from->Close();
+ from = combined;
+ }
+
+ // Now, from contains a nice file to send to the client. Reorder it
+ {
+ // Write nastily to allow this to work with gcc 2.x
+ std::auto_ptr<IOStream> t(BackupStoreFile::ReorderFileToStreamOrder(from.get(), true /* take ownership */));
+ stream = t;
+ }
+
+ // Release from file to avoid double deletion
+ from.release();
+ }
+ else
+ {
+ // Simple case: file already exists on disc ready to go
+
+ // Open the object
+ std::auto_ptr<IOStream> object(rContext.OpenObject(mObjectID));
+ BufferedStream buf(*object);
+
+ // Verify it
+ if(!BackupStoreFile::VerifyEncodedFileFormat(buf))
+ {
+ return PROTOCOL_ERROR(Err_FileDoesNotVerify);
+ }
+
+ // Reset stream -- seek to beginning
+ object->Seek(0, IOStream::SeekType_Absolute);
+
+ // Reorder the stream/file into stream order
+ {
+ // Write nastily to allow this to work with gcc 2.x
+ std::auto_ptr<IOStream> t(BackupStoreFile::ReorderFileToStreamOrder(object.get(), true /* take ownership */));
+ stream = t;
+ }
+
+ // Object will be deleted when the stream is deleted,
+ // so can release the object auto_ptr here to avoid
+ // premature deletion
+ object.release();
+ }
+
+ // Stream the reordered stream to the peer
+ rProtocol.SendStreamAfterCommand(stream);
+
+ // Tell the caller what the file was
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(mObjectID));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolCreateDirectory::DoCommand(Protocol &, BackupStoreContext &)
+// Purpose: Create directory command
+// Created: 2003/09/04
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolCreateDirectory::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+ CHECK_WRITEABLE_SESSION
+
+ // Get the stream containing the attributes
+ std::auto_ptr<IOStream> attrstream(rProtocol.ReceiveStream());
+ // Collect the attributes -- do this now so no matter what the outcome,
+ // the data has been absorbed.
+ StreamableMemBlock attr;
+ attr.Set(*attrstream, rProtocol.GetTimeout());
+
+ // Check to see if the hard limit has been exceeded
+ if(rContext.HardLimitExceeded())
+ {
+ // Won't allow creation if the limit has been exceeded
+ return PROTOCOL_ERROR(Err_StorageLimitExceeded);
+ }
+
+ bool alreadyExists = false;
+ int64_t id = rContext.AddDirectory(mContainingDirectoryID, mDirectoryName, attr, mAttributesModTime, alreadyExists);
+
+ if(alreadyExists)
+ {
+ return PROTOCOL_ERROR(Err_DirectoryAlreadyExists);
+ }
+
+ // Tell the caller what the file was
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(id));
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolChangeDirAttributes::DoCommand(Protocol &, BackupStoreContext &)
+// Purpose: Change attributes on directory
+// Created: 2003/09/06
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolChangeDirAttributes::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+ CHECK_WRITEABLE_SESSION
+
+ // Get the stream containing the attributes
+ std::auto_ptr<IOStream> attrstream(rProtocol.ReceiveStream());
+ // Collect the attributes -- do this now so no matter what the outcome,
+ // the data has been absorbed.
+ StreamableMemBlock attr;
+ attr.Set(*attrstream, rProtocol.GetTimeout());
+
+ // Get the context to do it's magic
+ rContext.ChangeDirAttributes(mObjectID, attr, mAttributesModTime);
+
+ // Tell the caller what the file was
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(mObjectID));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolSetReplacementFileAttributes::DoCommand(Protocol &, BackupStoreContext &)
+// Purpose: Change attributes on directory
+// Created: 2003/09/06
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolSetReplacementFileAttributes::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+ CHECK_WRITEABLE_SESSION
+
+ // Get the stream containing the attributes
+ std::auto_ptr<IOStream> attrstream(rProtocol.ReceiveStream());
+ // Collect the attributes -- do this now so no matter what the outcome,
+ // the data has been absorbed.
+ StreamableMemBlock attr;
+ attr.Set(*attrstream, rProtocol.GetTimeout());
+
+ // Get the context to do it's magic
+ int64_t objectID = 0;
+ if(!rContext.ChangeFileAttributes(mFilename, mInDirectory, attr, mAttributesHash, objectID))
+ {
+ // Didn't exist
+ return PROTOCOL_ERROR(Err_DoesNotExist);
+ }
+
+ // Tell the caller what the file was
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(objectID));
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolDeleteFile::DoCommand(BackupProtocolReplyable &, BackupStoreContext &)
+// Purpose: Delete a file
+// Created: 2003/10/21
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolDeleteFile::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+ CHECK_WRITEABLE_SESSION
+
+ // Context handles this
+ int64_t objectID = 0;
+ rContext.DeleteFile(mFilename, mInDirectory, objectID);
+
+ // return the object ID or zero for not found
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(objectID));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolUndeleteFile::DoCommand(
+// BackupProtocolBase &, BackupStoreContext &)
+// Purpose: Undelete a file
+// Created: 2008-09-12
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolUndeleteFile::DoCommand(
+ BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+ CHECK_WRITEABLE_SESSION
+
+ // Context handles this
+ bool result = rContext.UndeleteFile(mObjectID, mInDirectory);
+
+ // return the object ID or zero for not found
+ return std::auto_ptr<BackupProtocolMessage>(
+ new BackupProtocolSuccess(result ? mObjectID : 0));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolDeleteDirectory::DoCommand(BackupProtocolReplyable &, BackupStoreContext &)
+// Purpose: Delete a directory
+// Created: 2003/10/21
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolDeleteDirectory::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+ CHECK_WRITEABLE_SESSION
+
+ // Check it's not asking for the root directory to be deleted
+ if(mObjectID == BACKUPSTORE_ROOT_DIRECTORY_ID)
+ {
+ return PROTOCOL_ERROR(Err_CannotDeleteRoot);
+ }
+
+ // Context handles this
+ try
+ {
+ rContext.DeleteDirectory(mObjectID);
+ }
+ catch (BackupStoreException &e)
+ {
+ if(e.GetSubType() == BackupStoreException::MultiplyReferencedObject)
+ {
+ return PROTOCOL_ERROR(Err_MultiplyReferencedObject);
+ }
+
+ throw;
+ }
+
+ // return the object ID
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(mObjectID));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolUndeleteDirectory::DoCommand(BackupProtocolReplyable &, BackupStoreContext &)
+// Purpose: Undelete a directory
+// Created: 23/11/03
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolUndeleteDirectory::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+ CHECK_WRITEABLE_SESSION
+
+ // Check it's not asking for the root directory to be deleted
+ if(mObjectID == BACKUPSTORE_ROOT_DIRECTORY_ID)
+ {
+ return PROTOCOL_ERROR(Err_CannotDeleteRoot);
+ }
+
+ // Context handles this
+ rContext.DeleteDirectory(mObjectID, true /* undelete */);
+
+ // return the object ID
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(mObjectID));
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolSetClientStoreMarker::DoCommand(BackupProtocolReplyable &, BackupStoreContext &)
+// Purpose: Command to set the client's store marker
+// Created: 2003/10/29
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolSetClientStoreMarker::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+ CHECK_WRITEABLE_SESSION
+
+ // Set the marker
+ rContext.SetClientStoreMarker(mClientStoreMarker);
+
+ // return store marker set
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(mClientStoreMarker));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolMoveObject::DoCommand(BackupProtocolReplyable &, BackupStoreContext &)
+// Purpose: Command to move an object from one directory to another
+// Created: 2003/11/12
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolMoveObject::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+ CHECK_WRITEABLE_SESSION
+
+ // Let context do this, but modify error reporting on exceptions...
+ try
+ {
+ rContext.MoveObject(mObjectID, mMoveFromDirectory, mMoveToDirectory,
+ mNewFilename, (mFlags & Flags_MoveAllWithSameName) == Flags_MoveAllWithSameName,
+ (mFlags & Flags_AllowMoveOverDeletedObject) == Flags_AllowMoveOverDeletedObject);
+ }
+ catch(BackupStoreException &e)
+ {
+ if(e.GetSubType() == BackupStoreException::CouldNotFindEntryInDirectory)
+ {
+ return PROTOCOL_ERROR(Err_DoesNotExist);
+ }
+ else if(e.GetSubType() == BackupStoreException::NameAlreadyExistsInDirectory)
+ {
+ return PROTOCOL_ERROR(Err_TargetNameExists);
+ }
+ else
+ {
+ throw;
+ }
+ }
+
+ // Return the object ID
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(mObjectID));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolGetObjectName::DoCommand(BackupProtocolReplyable &, BackupStoreContext &)
+// Purpose: Command to find the name of an object
+// Created: 12/11/03
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolGetObjectName::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+
+ // Create a stream for the list of filenames
+ std::auto_ptr<CollectInBufferStream> stream(new CollectInBufferStream);
+
+ // Object and directory IDs
+ int64_t objectID = mObjectID;
+ int64_t dirID = mContainingDirectoryID;
+
+ // Data to return in the reply
+ int32_t numNameElements = 0;
+ int16_t objectFlags = 0;
+ int64_t modTime = 0;
+ uint64_t attrModHash = 0;
+ bool haveModTimes = false;
+
+ do
+ {
+ // Check the directory really exists
+ if(!rContext.ObjectExists(dirID, BackupStoreContext::ObjectExists_Directory))
+ {
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolObjectName(BackupProtocolObjectName::NumNameElements_ObjectDoesntExist, 0, 0, 0));
+ }
+
+ // Load up the directory
+ const BackupStoreDirectory &rdir(rContext.GetDirectory(dirID));
+
+ // Find the element in this directory and store it's name
+ if(objectID != ObjectID_DirectoryOnly)
+ {
+ const BackupStoreDirectory::Entry *en = rdir.FindEntryByID(objectID);
+
+ // If this can't be found, then there is a problem... tell the caller it can't be found
+ if(en == 0)
+ {
+ // Abort!
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolObjectName(BackupProtocolObjectName::NumNameElements_ObjectDoesntExist, 0, 0, 0));
+ }
+
+ // Store flags?
+ if(objectFlags == 0)
+ {
+ objectFlags = en->GetFlags();
+ }
+
+ // Store modification times?
+ if(!haveModTimes)
+ {
+ modTime = en->GetModificationTime();
+ attrModHash = en->GetAttributesHash();
+ haveModTimes = true;
+ }
+
+ // Store the name in the stream
+ en->GetName().WriteToStream(*stream);
+
+ // Count of name elements
+ ++numNameElements;
+ }
+
+ // Setup for next time round
+ objectID = dirID;
+ dirID = rdir.GetContainerID();
+
+ } while(objectID != 0 && objectID != BACKUPSTORE_ROOT_DIRECTORY_ID);
+
+ // Stream to send?
+ if(numNameElements > 0)
+ {
+ // Get the stream ready to go
+ stream->SetForReading();
+ // Tell the protocol to send the stream
+ rProtocol.SendStreamAfterCommand(static_cast< std::auto_ptr<IOStream> >(stream));
+ }
+
+ // Make reply
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolObjectName(numNameElements, modTime, attrModHash, objectFlags));
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolGetBlockIndexByID::DoCommand(BackupProtocolReplyable &, BackupStoreContext &)
+// Purpose: Get the block index from a file, by ID
+// Created: 19/1/04
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolGetBlockIndexByID::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+
+ // Open the file
+ std::auto_ptr<IOStream> stream(rContext.OpenObject(mObjectID));
+
+ // Move the file pointer to the block index
+ BackupStoreFile::MoveStreamPositionToBlockIndex(*stream);
+
+ // Return the stream to the client
+ rProtocol.SendStreamAfterCommand(stream);
+
+ // Return the object ID
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(mObjectID));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolGetBlockIndexByName::DoCommand(BackupProtocolReplyable &, BackupStoreContext &)
+// Purpose: Get the block index from a file, by name within a directory
+// Created: 19/1/04
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolGetBlockIndexByName::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+
+ // Get the directory
+ const BackupStoreDirectory &dir(rContext.GetDirectory(mInDirectory));
+
+ // Find the latest object ID within it which has the same name
+ int64_t objectID = 0;
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next(BackupStoreDirectory::Entry::Flags_File)) != 0)
+ {
+ if(en->GetName() == mFilename)
+ {
+ // Store the ID, if it's a newer ID than the last one
+ if(en->GetObjectID() > objectID)
+ {
+ objectID = en->GetObjectID();
+ }
+ }
+ }
+
+ // Found anything?
+ if(objectID == 0)
+ {
+ // No... return a zero object ID
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(0));
+ }
+
+ // Open the file
+ std::auto_ptr<IOStream> stream(rContext.OpenObject(objectID));
+
+ // Move the file pointer to the block index
+ BackupStoreFile::MoveStreamPositionToBlockIndex(*stream);
+
+ // Return the stream to the client
+ rProtocol.SendStreamAfterCommand(stream);
+
+ // Return the object ID
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolSuccess(objectID));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolGetAccountUsage::DoCommand(BackupProtocolReplyable &, BackupStoreContext &)
+// Purpose: Return the amount of disc space used
+// Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolGetAccountUsage::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+
+ // Get store info from context
+ const BackupStoreInfo &rinfo(rContext.GetBackupStoreInfo());
+
+ // Find block size
+ RaidFileController &rcontroller(RaidFileController::GetController());
+ RaidFileDiscSet &rdiscSet(rcontroller.GetDiscSet(rinfo.GetDiscSetNumber()));
+
+ // Return info
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolAccountUsage(
+ rinfo.GetBlocksUsed(),
+ rinfo.GetBlocksInOldFiles(),
+ rinfo.GetBlocksInDeletedFiles(),
+ rinfo.GetBlocksInDirectories(),
+ rinfo.GetBlocksSoftLimit(),
+ rinfo.GetBlocksHardLimit(),
+ rdiscSet.GetBlockSize()
+ ));
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupProtocolGetIsAlive::DoCommand(BackupProtocolReplyable &, BackupStoreContext &)
+// Purpose: Return the amount of disc space used
+// Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupProtocolMessage> BackupProtocolGetIsAlive::DoCommand(BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const
+{
+ CHECK_PHASE(Phase_Commands)
+
+ //
+ // NOOP
+ //
+ return std::auto_ptr<BackupProtocolMessage>(new BackupProtocolIsAlive());
+}
diff --git a/lib/backupstore/BackupConstants.h b/lib/backupstore/BackupConstants.h
new file mode 100644
index 00000000..19d06a15
--- /dev/null
+++ b/lib/backupstore/BackupConstants.h
@@ -0,0 +1,21 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupConstants.h
+// Purpose: Constants for the backup server and client
+// Created: 2003/08/20
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPCONSTANTS__H
+#define BACKUPCONSTANTS__H
+
+// 15 minutes to timeout (milliseconds)
+#define BACKUP_STORE_TIMEOUT (15*60*1000)
+
+// Should the store daemon convert files to Raid immediately?
+#define BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY true
+
+#endif // BACKUPCONSTANTS__H
+
+
diff --git a/lib/backupstore/BackupStoreAccountDatabase.cpp b/lib/backupstore/BackupStoreAccountDatabase.cpp
index 72a813d5..ec44de0e 100644
--- a/lib/backupstore/BackupStoreAccountDatabase.cpp
+++ b/lib/backupstore/BackupStoreAccountDatabase.cpp
@@ -40,7 +40,11 @@ public:
// Created: 2003/08/20
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
BackupStoreAccountDatabase::BackupStoreAccountDatabase(const char *Filename)
+=======
+BackupStoreAccountDatabase::BackupStoreAccountDatabase(const std::string& Filename)
+>>>>>>> 0.12
: pImpl(new _BackupStoreAccountDatabase)
{
pImpl->mFilename = Filename;
@@ -123,7 +127,11 @@ BackupStoreAccountDatabase::Entry::~Entry()
// Created: 2003/08/21
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
std::auto_ptr<BackupStoreAccountDatabase> BackupStoreAccountDatabase::Read(const char *Filename)
+=======
+std::auto_ptr<BackupStoreAccountDatabase> BackupStoreAccountDatabase::Read(const std::string& Filename)
+>>>>>>> 0.12
{
// Database object to use
std::auto_ptr<BackupStoreAccountDatabase> db(new BackupStoreAccountDatabase(Filename));
diff --git a/lib/backupstore/BackupStoreAccountDatabase.h b/lib/backupstore/BackupStoreAccountDatabase.h
index 79573242..cb19b01b 100644
--- a/lib/backupstore/BackupStoreAccountDatabase.h
+++ b/lib/backupstore/BackupStoreAccountDatabase.h
@@ -31,11 +31,19 @@ public:
friend class _BackupStoreAccountDatabase; // to stop compiler warnings
~BackupStoreAccountDatabase();
private:
+<<<<<<< HEAD
BackupStoreAccountDatabase(const char *Filename);
BackupStoreAccountDatabase(const BackupStoreAccountDatabase &);
public:
static std::auto_ptr<BackupStoreAccountDatabase> Read(const char *Filename);
+=======
+ BackupStoreAccountDatabase(const std::string& Filename);
+ BackupStoreAccountDatabase(const BackupStoreAccountDatabase &);
+public:
+
+ static std::auto_ptr<BackupStoreAccountDatabase> Read(const std::string& Filename);
+>>>>>>> 0.12
void Write();
class Entry
diff --git a/lib/backupstore/BackupStoreAccounts.cpp b/lib/backupstore/BackupStoreAccounts.cpp
index 5c7e4d38..8cb23a6a 100644
--- a/lib/backupstore/BackupStoreAccounts.cpp
+++ b/lib/backupstore/BackupStoreAccounts.cpp
@@ -11,6 +11,7 @@
#include <stdio.h>
+<<<<<<< HEAD
#include "BoxPortsAndFiles.h"
#include "BackupStoreAccounts.h"
#include "BackupStoreAccountDatabase.h"
@@ -19,6 +20,18 @@
#include "BackupStoreInfo.h"
#include "BackupStoreDirectory.h"
#include "BackupStoreConstants.h"
+=======
+#include "BackupStoreAccounts.h"
+#include "BackupStoreAccountDatabase.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreDirectory.h"
+#include "BackupStoreException.h"
+#include "BackupStoreInfo.h"
+#include "BackupStoreRefCountDatabase.h"
+#include "BoxPortsAndFiles.h"
+#include "RaidFileWrite.h"
+#include "StoreStructure.h"
+>>>>>>> 0.12
#include "UnixUser.h"
#include "MemLeakFindOn.h"
@@ -55,8 +68,13 @@ BackupStoreAccounts::~BackupStoreAccounts()
// Function
// Name: BackupStoreAccounts::Create(int32_t, int, int64_t, int64_t, const std::string &)
// Purpose: Create a new account on the specified disc set.
+<<<<<<< HEAD
// If rAsUsername is not empty, then the account information will be written under the
// username specified.
+=======
+// If rAsUsername is not empty, then the account information will be written under the
+// username specified.
+>>>>>>> 0.12
// Created: 2003/08/21
//
// --------------------------------------------------------------------------
@@ -102,6 +120,10 @@ void BackupStoreAccounts::Create(int32_t ID, int DiscSet, int64_t SizeSoftLimit,
std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID, dirName, DiscSet, false /* ReadWrite */));
info->ChangeBlocksUsed(rootDirSize);
info->ChangeBlocksInDirectories(rootDirSize);
+<<<<<<< HEAD
+=======
+ info->AdjustNumDirectories(1);
+>>>>>>> 0.12
// Save it back
info->Save();
@@ -167,4 +189,38 @@ bool BackupStoreAccounts::AccountExists(int32_t ID)
return mrDatabase.EntryExists(ID);
}
+<<<<<<< HEAD
+
+=======
+void BackupStoreAccounts::LockAccount(int32_t ID, NamedLock& rNamedLock)
+{
+ const BackupStoreAccountDatabase::Entry &en(mrDatabase.GetEntry(ID));
+ std::string rootDir = MakeAccountRootDir(ID, en.GetDiscSet());
+ int discSet = en.GetDiscSet();
+ std::string writeLockFilename;
+ StoreStructure::MakeWriteLockFilename(rootDir, discSet, writeLockFilename);
+
+ bool gotLock = false;
+ int triesLeft = 8;
+ do
+ {
+ gotLock = rNamedLock.TryAndGetLock(writeLockFilename,
+ 0600 /* restrictive file permissions */);
+
+ if(!gotLock)
+ {
+ --triesLeft;
+ ::sleep(1);
+ }
+ }
+ while (!gotLock && triesLeft > 0);
+
+ if (!gotLock)
+ {
+ THROW_EXCEPTION_MESSAGE(BackupStoreException,
+ CouldNotLockStoreAccount, "Failed to get exclusive "
+ "lock on account " << ID);
+ }
+}
+>>>>>>> 0.12
diff --git a/lib/backupstore/BackupStoreAccounts.h b/lib/backupstore/BackupStoreAccounts.h
index 224d7353..e0e420bb 100644
--- a/lib/backupstore/BackupStoreAccounts.h
+++ b/lib/backupstore/BackupStoreAccounts.h
@@ -13,6 +13,10 @@
#include <string>
#include "BackupStoreAccountDatabase.h"
+<<<<<<< HEAD
+=======
+#include "NamedLock.h"
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
@@ -31,7 +35,12 @@ private:
BackupStoreAccounts(const BackupStoreAccounts &rToCopy);
public:
+<<<<<<< HEAD
void Create(int32_t ID, int DiscSet, int64_t SizeSoftLimit, int64_t SizeHardLimit, const std::string &rAsUsername);
+=======
+ void Create(int32_t ID, int DiscSet, int64_t SizeSoftLimit,
+ int64_t SizeHardLimit, const std::string &rAsUsername);
+>>>>>>> 0.12
bool AccountExists(int32_t ID);
void GetAccountRoot(int32_t ID, std::string &rRootDirOut, int &rDiscSetOut) const;
@@ -40,6 +49,10 @@ public:
{
return MakeAccountRootDir(rEntry.GetID(), rEntry.GetDiscSet());
}
+<<<<<<< HEAD
+=======
+ void LockAccount(int32_t ID, NamedLock& rNamedLock);
+>>>>>>> 0.12
private:
static std::string MakeAccountRootDir(int32_t ID, int DiscSet);
diff --git a/lib/backupstore/BackupStoreCheck.cpp b/lib/backupstore/BackupStoreCheck.cpp
index 7598094e..79a61a77 100644
--- a/lib/backupstore/BackupStoreCheck.cpp
+++ b/lib/backupstore/BackupStoreCheck.cpp
@@ -11,6 +11,7 @@
#include <stdio.h>
#include <string.h>
+<<<<<<< HEAD
#include <unistd.h>
#include "BackupStoreCheck.h"
@@ -22,6 +23,26 @@
#include "BackupStoreFile.h"
#include "BackupStoreDirectory.h"
#include "BackupStoreConstants.h"
+=======
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include "autogen_BackupStoreException.h"
+#include "BackupStoreCheck.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreDirectory.h"
+#include "BackupStoreFile.h"
+#include "BackupStoreObjectMagic.h"
+#include "RaidFileController.h"
+#include "RaidFileException.h"
+#include "RaidFileRead.h"
+#include "RaidFileUtil.h"
+#include "RaidFileWrite.h"
+#include "StoreStructure.h"
+#include "Utils.h"
+>>>>>>> 0.12
#include "MemLeakFindOn.h"
@@ -47,9 +68,20 @@ BackupStoreCheck::BackupStoreCheck(const std::string &rStoreRoot, int DiscSetNum
mLostDirNameSerial(0),
mLostAndFoundDirectoryID(0),
mBlocksUsed(0),
+<<<<<<< HEAD
mBlocksInOldFiles(0),
mBlocksInDeletedFiles(0),
mBlocksInDirectories(0)
+=======
+ mBlocksInCurrentFiles(0),
+ mBlocksInOldFiles(0),
+ mBlocksInDeletedFiles(0),
+ mBlocksInDirectories(0),
+ mNumFiles(0),
+ mNumOldFiles(0),
+ mNumDeletedFiles(0),
+ mNumDirectories(0)
+>>>>>>> 0.12
{
}
@@ -73,12 +105,18 @@ BackupStoreCheck::~BackupStoreCheck()
//
// Function
// Name: BackupStoreCheck::Check()
+<<<<<<< HEAD
// Purpose: Perform the check on the given account
+=======
+// Purpose: Perform the check on the given account. You need to
+// hold a lock on the account before calling this!
+>>>>>>> 0.12
// Created: 21/4/04
//
// --------------------------------------------------------------------------
void BackupStoreCheck::Check()
{
+<<<<<<< HEAD
// Lock the account
{
std::string writeLockFilename;
@@ -107,6 +145,11 @@ void BackupStoreCheck::Check()
THROW_EXCEPTION(BackupStoreException, CouldNotLockStoreAccount)
}
}
+=======
+ std::string writeLockFilename;
+ StoreStructure::MakeWriteLockFilename(mStoreRoot, mDiscSetNumber, writeLockFilename);
+ ASSERT(FileExists(writeLockFilename));
+>>>>>>> 0.12
if(!mQuiet && mFixErrors)
{
@@ -296,6 +339,36 @@ int64_t BackupStoreCheck::CheckObjectsScanDir(int64_t StartID, int Level, const
// Read in all the directories, and recurse downwards
{
+<<<<<<< HEAD
+=======
+ // If any of the directories is missing, create it.
+ RaidFileController &rcontroller(RaidFileController::GetController());
+ RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mDiscSetNumber));
+
+ if(!rdiscSet.IsNonRaidSet())
+ {
+ unsigned int numDiscs = rdiscSet.size();
+
+ for(unsigned int l = 0; l < numDiscs; ++l)
+ {
+ // build name
+ std::string dn(rdiscSet[l] + DIRECTORY_SEPARATOR + rDirName);
+ struct stat st;
+
+ if(stat(dn.c_str(), &st) != 0 && errno == ENOENT)
+ {
+ if(mkdir(dn.c_str(), 0755) != 0)
+ {
+ THROW_SYS_FILE_ERROR("Failed to "
+ "create missing RaidFile "
+ "directory", dn,
+ RaidFileException, OSError);
+ }
+ }
+ }
+ }
+
+>>>>>>> 0.12
std::vector<std::string> dirs;
RaidFileRead::ReadDirectoryContents(mDiscSetNumber, rDirName,
RaidFileRead::DirReadType_DirsOnly, dirs);
@@ -318,7 +391,11 @@ int64_t BackupStoreCheck::CheckObjectsScanDir(int64_t StartID, int Level, const
}
else
{
+<<<<<<< HEAD
BOX_WARNING("Spurious or invalid directory " <<
+=======
+ BOX_ERROR("Spurious or invalid directory " <<
+>>>>>>> 0.12
rDirName << DIRECTORY_SEPARATOR <<
(*i) << " found, " <<
(mFixErrors?"deleting":"delete manually"));
@@ -335,7 +412,12 @@ int64_t BackupStoreCheck::CheckObjectsScanDir(int64_t StartID, int Level, const
//
// Function
// Name: BackupStoreCheck::CheckObjectsDir(int64_t)
+<<<<<<< HEAD
// Purpose: Check all the files within this directory which has the given starting ID.
+=======
+// Purpose: Check all the files within this directory which has
+// the given starting ID.
+>>>>>>> 0.12
// Created: 22/4/04
//
// --------------------------------------------------------------------------
@@ -383,6 +465,7 @@ void BackupStoreCheck::CheckObjectsDir(int64_t StartID)
// Filename is valid, mark as existing
idsPresent[n] = true;
}
+<<<<<<< HEAD
else
{
// info file in root dir is OK!
@@ -390,12 +473,31 @@ void BackupStoreCheck::CheckObjectsDir(int64_t StartID)
{
fileOK = false;
}
+=======
+ // No other files should be present in subdirectories
+ else if(StartID != 0)
+ {
+ fileOK = false;
+ }
+ // info and refcount databases are OK in the root directory
+ else if(*i == "info" || *i == "refcount.db")
+ {
+ fileOK = true;
+ }
+ else
+ {
+ fileOK = false;
+>>>>>>> 0.12
}
if(!fileOK)
{
// Unexpected or bad file, delete it
+<<<<<<< HEAD
BOX_WARNING("Spurious file " << dirName <<
+=======
+ BOX_ERROR("Spurious file " << dirName <<
+>>>>>>> 0.12
DIRECTORY_SEPARATOR << (*i) << " found" <<
(mFixErrors?", deleting":""));
++mNumberErrorsFound;
@@ -418,7 +520,11 @@ void BackupStoreCheck::CheckObjectsDir(int64_t StartID)
if(!CheckAndAddObject(StartID | i, dirName + leaf))
{
// File was bad, delete it
+<<<<<<< HEAD
BOX_WARNING("Corrupted file " << dirName <<
+=======
+ BOX_ERROR("Corrupted file " << dirName <<
+>>>>>>> 0.12
leaf << " found" <<
(mFixErrors?", deleting":""));
++mNumberErrorsFound;
@@ -436,6 +542,7 @@ void BackupStoreCheck::CheckObjectsDir(int64_t StartID)
// --------------------------------------------------------------------------
//
// Function
+<<<<<<< HEAD
// Name: BackupStoreCheck::CheckAndAddObject(int64_t, const std::string &)
// Purpose: Check a specific object and add it to the list if it's OK -- if
// there are any errors with the reading, return false and it'll be deleted.
@@ -443,6 +550,18 @@ void BackupStoreCheck::CheckObjectsDir(int64_t StartID)
//
// --------------------------------------------------------------------------
bool BackupStoreCheck::CheckAndAddObject(int64_t ObjectID, const std::string &rFilename)
+=======
+// Name: BackupStoreCheck::CheckAndAddObject(int64_t,
+// const std::string &)
+// Purpose: Check a specific object and add it to the list
+// if it's OK. If there are any errors with the
+// reading, return false and it'll be deleted.
+// Created: 21/4/04
+//
+// --------------------------------------------------------------------------
+bool BackupStoreCheck::CheckAndAddObject(int64_t ObjectID,
+ const std::string &rFilename)
+>>>>>>> 0.12
{
// Info on object...
bool isFile = true;
@@ -452,10 +571,19 @@ bool BackupStoreCheck::CheckAndAddObject(int64_t ObjectID, const std::string &rF
try
{
// Open file
+<<<<<<< HEAD
std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(mDiscSetNumber, rFilename));
size = file->GetDiscUsageInBlocks();
// Read in first four bytes -- don't have to worry about retrying if not all bytes read as is RaidFile
+=======
+ std::auto_ptr<RaidFileRead> file(
+ RaidFileRead::Open(mDiscSetNumber, rFilename));
+ size = file->GetDiscUsageInBlocks();
+
+ // Read in first four bytes -- don't have to worry about
+ // retrying if not all bytes read as is RaidFile
+>>>>>>> 0.12
uint32_t signature;
if(file->Read(&signature, sizeof(signature)) != sizeof(signature))
{
@@ -486,6 +614,7 @@ bool BackupStoreCheck::CheckAndAddObject(int64_t ObjectID, const std::string &rF
return false;
break;
}
+<<<<<<< HEAD
// Add to usage counts
mBlocksUsed += size;
@@ -493,6 +622,8 @@ bool BackupStoreCheck::CheckAndAddObject(int64_t ObjectID, const std::string &rF
{
mBlocksInDirectories += size;
}
+=======
+>>>>>>> 0.12
}
catch(...)
{
@@ -505,10 +636,64 @@ bool BackupStoreCheck::CheckAndAddObject(int64_t ObjectID, const std::string &rF
{
return false;
}
+<<<<<<< HEAD
// Add to list of IDs known about
AddID(ObjectID, containerID, size, isFile);
+=======
+
+ // Add to list of IDs known about
+ AddID(ObjectID, containerID, size, isFile);
+
+ // Add to usage counts
+ mBlocksUsed += size;
+ if(!isFile)
+ {
+ mBlocksInDirectories += size;
+ }
+
+ // If it looks like a good object, and it's non-RAID, and
+ // this is a RAID set, then convert it to RAID.
+
+ RaidFileController &rcontroller(RaidFileController::GetController());
+ RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mDiscSetNumber));
+ if(!rdiscSet.IsNonRaidSet())
+ {
+ // See if the file exists
+ RaidFileUtil::ExistType existance =
+ RaidFileUtil::RaidFileExists(rdiscSet, rFilename);
+ if(existance == RaidFileUtil::NonRaid)
+ {
+ BOX_WARNING("Found non-RAID write file in RAID set" <<
+ (mFixErrors?", transforming to RAID: ":"") <<
+ (mFixErrors?rFilename:""));
+ if(mFixErrors)
+ {
+ RaidFileWrite write(mDiscSetNumber, rFilename);
+ write.TransformToRaidStorage();
+ }
+ }
+ else if(existance == RaidFileUtil::AsRaidWithMissingReadable)
+ {
+ BOX_WARNING("Found damaged but repairable RAID file" <<
+ (mFixErrors?", repairing: ":"") <<
+ (mFixErrors?rFilename:""));
+ if(mFixErrors)
+ {
+ std::auto_ptr<RaidFileRead> read(
+ RaidFileRead::Open(mDiscSetNumber,
+ rFilename));
+ RaidFileWrite write(mDiscSetNumber, rFilename);
+ write.Open(true /* overwrite */);
+ read->CopyStreamTo(write);
+ read.reset();
+ write.Commit(true /* transform to RAID */);
+ }
+ }
+ }
+
+>>>>>>> 0.12
// Report success
return true;
}
@@ -518,13 +703,23 @@ bool BackupStoreCheck::CheckAndAddObject(int64_t ObjectID, const std::string &rF
//
// Function
// Name: BackupStoreCheck::CheckFile(int64_t, IOStream &)
+<<<<<<< HEAD
// Purpose: Do check on file, return original container ID if OK, or -1 on error
+=======
+// Purpose: Do check on file, return original container ID
+// if OK, or -1 on error
+>>>>>>> 0.12
// Created: 22/4/04
//
// --------------------------------------------------------------------------
int64_t BackupStoreCheck::CheckFile(int64_t ObjectID, IOStream &rStream)
{
+<<<<<<< HEAD
// Check that it's not the root directory ID. Having a file as the root directory would be bad.
+=======
+ // Check that it's not the root directory ID. Having a file as
+ // the root directory would be bad.
+>>>>>>> 0.12
if(ObjectID == BACKUPSTORE_ROOT_DIRECTORY_ID)
{
// Get that dodgy thing deleted!
@@ -534,7 +729,12 @@ int64_t BackupStoreCheck::CheckFile(int64_t ObjectID, IOStream &rStream)
// Check the format of the file, and obtain the container ID
int64_t originalContainerID = -1;
+<<<<<<< HEAD
if(!BackupStoreFile::VerifyEncodedFileFormat(rStream, 0 /* don't want diffing from ID */,
+=======
+ if(!BackupStoreFile::VerifyEncodedFileFormat(rStream,
+ 0 /* don't want diffing from ID */,
+>>>>>>> 0.12
&originalContainerID))
{
// Didn't verify
@@ -549,7 +749,12 @@ int64_t BackupStoreCheck::CheckFile(int64_t ObjectID, IOStream &rStream)
//
// Function
// Name: BackupStoreCheck::CheckDirInitial(int64_t, IOStream &)
+<<<<<<< HEAD
// Purpose: Do initial check on directory, return container ID if OK, or -1 on error
+=======
+// Purpose: Do initial check on directory, return container ID
+// if OK, or -1 on error
+>>>>>>> 0.12
// Created: 22/4/04
//
// --------------------------------------------------------------------------
@@ -588,7 +793,16 @@ void BackupStoreCheck::CheckDirectories()
// This phase will check all the files in the directories, make
// a note of all directories which are missing, and do initial fixing.
+<<<<<<< HEAD
// Scan all objects
+=======
+ // The root directory is not contained inside another directory, so
+ // it has no directory entry to scan, but we have to count it
+ // somewhere, so we'll count it here.
+ mNumDirectories++;
+
+ // Scan all objects.
+>>>>>>> 0.12
for(Info_t::const_iterator i(mInfo.begin()); i != mInfo.end(); ++i)
{
IDBlock *pblock = i->second;
@@ -609,6 +823,7 @@ void BackupStoreCheck::CheckDirectories()
}
// Flag for modifications
+<<<<<<< HEAD
bool isModified = false;
// Check for validity
@@ -625,10 +840,43 @@ void BackupStoreCheck::CheckDirectories()
// Go through, and check that everything in that directory exists and is valid
std::vector<int64_t> toDelete;
+=======
+ bool isModified = CheckDirectory(dir);
+
+ // Check the directory again, now that entries have been removed
+ if(dir.CheckAndFix())
+ {
+ // Wasn't quite right, and has been modified
+ BOX_ERROR("Directory ID " <<
+ BOX_FORMAT_OBJECTID(pblock->mID[e]) <<
+ " was still bad after all checks");
+ ++mNumberErrorsFound;
+ isModified = true;
+ }
+ else if(isModified)
+ {
+ BOX_INFO("Directory ID " <<
+ BOX_FORMAT_OBJECTID(pblock->mID[e]) <<
+ " was OK after fixing");
+ }
+
+ if(isModified && mFixErrors)
+ {
+ BOX_WARNING("Writing modified directory to disk: " <<
+ BOX_FORMAT_OBJECTID(pblock->mID[e]));
+ RaidFileWrite fixed(mDiscSetNumber, filename);
+ fixed.Open(true /* allow overwriting */);
+ dir.WriteToStream(fixed);
+ fixed.Commit(true /* convert to raid representation now */);
+ }
+
+ // Count valid entries
+>>>>>>> 0.12
BackupStoreDirectory::Iterator i(dir);
BackupStoreDirectory::Entry *en = 0;
while((en = i.Next()) != 0)
{
+<<<<<<< HEAD
// Lookup the item
int32_t iIndex;
IDBlock *piBlock = LookupID(en->GetObjectID(), iIndex);
@@ -773,4 +1021,238 @@ void BackupStoreCheck::CheckDirectories()
}
+=======
+ int32_t iIndex;
+ IDBlock *piBlock = LookupID(en->GetObjectID(), iIndex);
+
+ ASSERT(piBlock != 0 ||
+ mDirsWhichContainLostDirs.find(en->GetObjectID())
+ != mDirsWhichContainLostDirs.end());
+ if (piBlock)
+ {
+ // Normally it would exist and this
+ // check would not be necessary, but
+ // we might have missing directories
+ // that we will recreate later.
+ // cf mDirsWhichContainLostDirs.
+ uint8_t iflags = GetFlags(piBlock, iIndex);
+ SetFlags(piBlock, iIndex, iflags | Flags_IsContained);
+ }
+
+ if(en->IsDir())
+ {
+ mNumDirectories++;
+ }
+ else if(!en->IsFile())
+ {
+ BOX_TRACE("Not counting object " <<
+ BOX_FORMAT_OBJECTID(en->GetObjectID()) <<
+ " with flags " << en->GetFlags());
+ }
+ else // it's a good file, add to sizes
+ if(en->IsOld() && en->IsDeleted())
+ {
+ BOX_WARNING("File " <<
+ BOX_FORMAT_OBJECTID(en->GetObjectID()) <<
+ " is both old and deleted, "
+ "this should not happen!");
+ }
+ else if(en->IsOld())
+ {
+ mNumFiles++;
+ mNumOldFiles++;
+ mBlocksInOldFiles += en->GetSizeInBlocks();
+ }
+ else if(en->IsDeleted())
+ {
+ mNumFiles++;
+ mNumDeletedFiles++;
+ mBlocksInDeletedFiles += en->GetSizeInBlocks();
+ }
+ else
+ {
+ mNumFiles++;
+ mBlocksInCurrentFiles += en->GetSizeInBlocks();
+ }
+ }
+ }
+ }
+ }
+}
+
+bool BackupStoreCheck::CheckDirectory(BackupStoreDirectory& dir)
+{
+ bool restart = true;
+ bool isModified = false;
+
+ while(restart)
+ {
+ std::vector<int64_t> toDelete;
+ restart = false;
+
+ // Check for validity
+ if(dir.CheckAndFix())
+ {
+ // Wasn't quite right, and has been modified
+ BOX_ERROR("Directory ID " <<
+ BOX_FORMAT_OBJECTID(dir.GetObjectID()) <<
+ " had invalid entries" <<
+ (mFixErrors ? ", fixed" : ""));
+ ++mNumberErrorsFound;
+ isModified = true;
+ }
+
+ // Go through, and check that everything in that directory exists and is valid
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ // Lookup the item
+ int32_t iIndex;
+ IDBlock *piBlock = LookupID(en->GetObjectID(), iIndex);
+ bool badEntry = false;
+ if(piBlock != 0)
+ {
+ badEntry = !CheckDirectoryEntry(*en,
+ dir.GetObjectID(), isModified);
+ }
+ // Item can't be found. Is it a directory?
+ else if(en->IsDir())
+ {
+ // Store the directory for later attention
+ mDirsWhichContainLostDirs[en->GetObjectID()] =
+ dir.GetObjectID();
+ }
+ else
+ {
+ // Just remove the entry
+ badEntry = true;
+ BOX_ERROR("Directory ID " <<
+ BOX_FORMAT_OBJECTID(dir.GetObjectID()) <<
+ " references object " <<
+ BOX_FORMAT_OBJECTID(en->GetObjectID()) <<
+ " which does not exist.");
+ ++mNumberErrorsFound;
+ }
+
+ // Is this entry worth keeping?
+ if(badEntry)
+ {
+ toDelete.push_back(en->GetObjectID());
+ }
+ }
+
+ if(toDelete.size() > 0)
+ {
+ // Delete entries from directory
+ for(std::vector<int64_t>::const_iterator d(toDelete.begin()); d != toDelete.end(); ++d)
+ {
+ BOX_ERROR("Removing directory entry " <<
+ BOX_FORMAT_OBJECTID(*d) << " from "
+ "directory " <<
+ BOX_FORMAT_OBJECTID(dir.GetObjectID()));
+ ++mNumberErrorsFound;
+ dir.DeleteEntry(*d);
+ }
+
+ // Mark as modified
+ restart = true;
+ isModified = true;
+
+ // Errors found
+ }
+ }
+
+ return isModified;
+}
+
+bool BackupStoreCheck::CheckDirectoryEntry(BackupStoreDirectory::Entry& rEntry,
+ int64_t DirectoryID, bool& rIsModified)
+{
+ int32_t IndexInDirBlock;
+ IDBlock *piBlock = LookupID(rEntry.GetObjectID(), IndexInDirBlock);
+ ASSERT(piBlock != 0);
+
+ uint8_t iflags = GetFlags(piBlock, IndexInDirBlock);
+ bool badEntry = false;
+
+ // Is the type the same?
+ if(((iflags & Flags_IsDir) == Flags_IsDir) != rEntry.IsDir())
+ {
+ // Entry is of wrong type
+ BOX_ERROR("Directory ID " <<
+ BOX_FORMAT_OBJECTID(DirectoryID) <<
+ " references object " <<
+ BOX_FORMAT_OBJECTID(rEntry.GetObjectID()) <<
+ " which has a different type than expected.");
+ badEntry = true;
+ ++mNumberErrorsFound;
+ }
+ // Check that the entry is not already contained.
+ else if(iflags & Flags_IsContained)
+ {
+ BOX_ERROR("Directory ID " <<
+ BOX_FORMAT_OBJECTID(DirectoryID) <<
+ " references object " <<
+ BOX_FORMAT_OBJECTID(rEntry.GetObjectID()) <<
+ " which is already contained.");
+ badEntry = true;
+ ++mNumberErrorsFound;
+ }
+ else
+ {
+ // Not already contained by another directory.
+ // Don't set the flag until later, after we finish repairing
+ // the directory and removing all bad entries.
+
+ // Check that the container ID of the object is correct
+ if(piBlock->mContainer[IndexInDirBlock] != DirectoryID)
+ {
+ // Needs fixing...
+ if(iflags & Flags_IsDir)
+ {
+ // Add to will fix later list
+ BOX_ERROR("Directory ID " <<
+ BOX_FORMAT_OBJECTID(rEntry.GetObjectID())
+ << " has wrong container ID.");
+ mDirsWithWrongContainerID.push_back(rEntry.GetObjectID());
+ ++mNumberErrorsFound;
+ }
+ else
+ {
+ // This is OK for files, they might move
+ BOX_NOTICE("File ID " <<
+ BOX_FORMAT_OBJECTID(rEntry.GetObjectID())
+ << " has different container ID, "
+ "probably moved");
+ }
+
+ // Fix entry for now
+ piBlock->mContainer[IndexInDirBlock] = DirectoryID;
+ }
+ }
+
+ // Check the object size, if it's OK and a file
+ if(!badEntry && !rEntry.IsDir())
+ {
+ if(rEntry.GetSizeInBlocks() != piBlock->mObjectSizeInBlocks[IndexInDirBlock])
+ {
+ // Wrong size, correct it.
+ rEntry.SetSizeInBlocks(piBlock->mObjectSizeInBlocks[IndexInDirBlock]);
+
+ // Mark as changed
+ rIsModified = true;
+ ++mNumberErrorsFound;
+
+ // Tell user
+ BOX_ERROR("Directory ID " <<
+ BOX_FORMAT_OBJECTID(DirectoryID) <<
+ " has wrong size for object " <<
+ BOX_FORMAT_OBJECTID(rEntry.GetObjectID()));
+ }
+ }
+
+ return !badEntry;
+}
+>>>>>>> 0.12
diff --git a/lib/backupstore/BackupStoreCheck.h b/lib/backupstore/BackupStoreCheck.h
index 1d5c1b1e..cddd8a4d 100644
--- a/lib/backupstore/BackupStoreCheck.h
+++ b/lib/backupstore/BackupStoreCheck.h
@@ -16,6 +16,11 @@
#include <set>
#include "NamedLock.h"
+<<<<<<< HEAD
+=======
+#include "BackupStoreDirectory.h"
+
+>>>>>>> 0.12
class IOStream;
class BackupStoreFilename;
@@ -26,9 +31,14 @@ The following problems can be fixed:
* Spurious files deleted
* Corrupted files deleted
* Root ID as file, deleted
+<<<<<<< HEAD
* Dirs with wrong object id inside, deleted
* Direcetory entries pointing to non-existant files, deleted
* Doubly references files have second reference deleted
+=======
+ * Dirs with wrong object id in header, deleted
+ * Doubly referenced files have second reference deleted
+>>>>>>> 0.12
* Wrong directory container IDs fixed
* Missing root recreated
* Reattach files which exist, but aren't referenced
@@ -41,7 +51,13 @@ The following problems can be fixed:
* Inside directories,
- only one object per name has old version clear
- IDs aren't duplicated
+<<<<<<< HEAD
* Bad store info files regenerated
+=======
+ - entries pointing to non-existant files are deleted
+ - patches depending on non-existent objects are deleted
+ * Bad store info and refcount files regenerated
+>>>>>>> 0.12
* Bad sizes of files in directories fixed
*/
@@ -82,6 +98,13 @@ public:
void Check();
bool ErrorsFound() {return mNumberErrorsFound > 0;}
+<<<<<<< HEAD
+=======
+ inline int64_t GetNumErrorsFound()
+ {
+ return mNumberErrorsFound;
+ }
+>>>>>>> 0.12
private:
enum
@@ -120,6 +143,12 @@ private:
int64_t CheckObjectsScanDir(int64_t StartID, int Level, const std::string &rDirName);
void CheckObjectsDir(int64_t StartID);
bool CheckAndAddObject(int64_t ObjectID, const std::string &rFilename);
+<<<<<<< HEAD
+=======
+ bool CheckDirectory(BackupStoreDirectory& dir);
+ bool CheckDirectoryEntry(BackupStoreDirectory::Entry& rEntry,
+ int64_t DirectoryID, bool& rIsModified);
+>>>>>>> 0.12
int64_t CheckFile(int64_t ObjectID, IOStream &rStream);
int64_t CheckDirInitial(int64_t ObjectID, IOStream &rStream);
@@ -156,11 +185,19 @@ private:
#else
#define DUMP_OBJECT_INFO
#endif
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
private:
std::string mStoreRoot;
int mDiscSetNumber;
int32_t mAccountID;
+<<<<<<< HEAD
+=======
+ std::string mAccountName;
+>>>>>>> 0.12
bool mFixErrors;
bool mQuiet;
@@ -179,7 +216,12 @@ private:
// List of stuff to fix
std::vector<BackupStoreCheck_ID_t> mDirsWithWrongContainerID;
// This is a map of lost dir ID -> existing dir ID
+<<<<<<< HEAD
std::map<BackupStoreCheck_ID_t, BackupStoreCheck_ID_t> mDirsWhichContainLostDirs;
+=======
+ std::map<BackupStoreCheck_ID_t, BackupStoreCheck_ID_t>
+ mDirsWhichContainLostDirs;
+>>>>>>> 0.12
// Set of extra directories added
std::set<BackupStoreCheck_ID_t> mDirsAdded;
@@ -190,9 +232,20 @@ private:
// Usage
int64_t mBlocksUsed;
+<<<<<<< HEAD
+ int64_t mBlocksInOldFiles;
+ int64_t mBlocksInDeletedFiles;
+ int64_t mBlocksInDirectories;
+=======
+ int64_t mBlocksInCurrentFiles;
int64_t mBlocksInOldFiles;
int64_t mBlocksInDeletedFiles;
int64_t mBlocksInDirectories;
+ int64_t mNumFiles;
+ int64_t mNumOldFiles;
+ int64_t mNumDeletedFiles;
+ int64_t mNumDirectories;
+>>>>>>> 0.12
};
#endif // BACKUPSTORECHECK__H
diff --git a/lib/backupstore/BackupStoreCheck2.cpp b/lib/backupstore/BackupStoreCheck2.cpp
index bcb5c5e9..341ac524 100644
--- a/lib/backupstore/BackupStoreCheck2.cpp
+++ b/lib/backupstore/BackupStoreCheck2.cpp
@@ -12,6 +12,7 @@
#include <stdio.h>
#include <string.h>
+<<<<<<< HEAD
#include "BackupStoreCheck.h"
#include "StoreStructure.h"
#include "RaidFileRead.h"
@@ -23,6 +24,20 @@
#include "BackupStoreDirectory.h"
#include "BackupStoreConstants.h"
#include "BackupStoreInfo.h"
+=======
+#include "autogen_BackupStoreException.h"
+#include "BackupStoreCheck.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreDirectory.h"
+#include "BackupStoreFile.h"
+#include "BackupStoreFileWire.h"
+#include "BackupStoreInfo.h"
+#include "BackupStoreObjectMagic.h"
+#include "MemBlockStream.h"
+#include "RaidFileRead.h"
+#include "RaidFileWrite.h"
+#include "StoreStructure.h"
+>>>>>>> 0.12
#include "MemLeakFindOn.h"
@@ -137,7 +152,11 @@ void BackupStoreCheck::CheckUnattachedObjects()
if((flags & Flags_IsContained) == 0)
{
// Unattached object...
+<<<<<<< HEAD
BOX_WARNING("Object " <<
+=======
+ BOX_ERROR("Object " <<
+>>>>>>> 0.12
BOX_FORMAT_OBJECTID(pblock->mID[e]) <<
" is unattached.");
++mNumberErrorsFound;
@@ -157,6 +176,10 @@ void BackupStoreCheck::CheckUnattachedObjects()
int64_t diffFromObjectID = 0;
std::string filename;
StoreStructure::MakeObjectFilename(pblock->mID[e], mStoreRoot, mDiscSetNumber, filename, false /* don't attempt to make sure the dir exists */);
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
// The easiest way to do this is to verify it again. Not such a bad penalty, because
// this really shouldn't be done very often.
{
@@ -382,7 +405,11 @@ void BackupStoreDirectoryFixer::InsertObject(int64_t ObjectID, bool IsDirectory,
}
// Add a new entry in an appropriate place
+<<<<<<< HEAD
mDirectory.AddUnattactedObject(objectStoreFilename, modTime,
+=======
+ mDirectory.AddUnattachedObject(objectStoreFilename, modTime,
+>>>>>>> 0.12
ObjectID, sizeInBlocks,
IsDirectory?(BackupStoreDirectory::Entry::Flags_Dir):(BackupStoreDirectory::Entry::Flags_File));
}
@@ -573,6 +600,7 @@ void BackupStoreCheck::FixDirsWithLostDirs()
void BackupStoreCheck::WriteNewStoreInfo()
{
// Attempt to load the existing store info file
+<<<<<<< HEAD
std::auto_ptr<BackupStoreInfo> poldInfo;
try
{
@@ -584,12 +612,37 @@ void BackupStoreCheck::WriteNewStoreInfo()
++mNumberErrorsFound;
}
+=======
+ std::auto_ptr<BackupStoreInfo> pOldInfo;
+ try
+ {
+ pOldInfo.reset(BackupStoreInfo::Load(mAccountID, mStoreRoot, mDiscSetNumber, true /* read only */).release());
+ mAccountName = pOldInfo->GetAccountName();
+ }
+ catch(...)
+ {
+ BOX_ERROR("Load of existing store info failed, regenerating.");
+ ++mNumberErrorsFound;
+ }
+
+ BOX_NOTICE("Total files: " << mNumFiles << " (of which "
+ "old files: " << mNumOldFiles << ", "
+ "deleted files: " << mNumDeletedFiles << "), "
+ "directories: " << mNumDirectories);
+
+>>>>>>> 0.12
// Minimum soft and hard limits
int64_t minSoft = ((mBlocksUsed * 11) / 10) + 1024;
int64_t minHard = ((minSoft * 11) / 10) + 1024;
// Need to do anything?
+<<<<<<< HEAD
if(poldInfo.get() != 0 && mNumberErrorsFound == 0 && poldInfo->GetAccountID() == mAccountID)
+=======
+ if(pOldInfo.get() != 0 &&
+ mNumberErrorsFound == 0 &&
+ pOldInfo->GetAccountID() == mAccountID)
+>>>>>>> 0.12
{
// Leave the store info as it is, no need to alter it because nothing really changed,
// and the only essential thing was that the account ID was correct, which is was.
@@ -601,6 +654,7 @@ void BackupStoreCheck::WriteNewStoreInfo()
// Work out the new limits
int64_t softLimit = minSoft;
int64_t hardLimit = minHard;
+<<<<<<< HEAD
if(poldInfo.get() != 0 && poldInfo->GetBlocksSoftLimit() > minSoft)
{
softLimit = poldInfo->GetBlocksSoftLimit();
@@ -616,6 +670,25 @@ void BackupStoreCheck::WriteNewStoreInfo()
else
{
BOX_WARNING("Hard limit for account changed to ensure housekeeping doesn't delete files on next run.");
+=======
+ if(pOldInfo.get() != 0 && pOldInfo->GetBlocksSoftLimit() > minSoft)
+ {
+ softLimit = pOldInfo->GetBlocksSoftLimit();
+ }
+ else
+ {
+ BOX_WARNING("Soft limit for account changed to ensure "
+ "housekeeping doesn't delete files on next run.");
+ }
+ if(pOldInfo.get() != 0 && pOldInfo->GetBlocksHardLimit() > minHard)
+ {
+ hardLimit = pOldInfo->GetBlocksHardLimit();
+ }
+ else
+ {
+ BOX_WARNING("Hard limit for account changed to ensure "
+ "housekeeping doesn't delete files on next run.");
+>>>>>>> 0.12
}
// Object ID
@@ -626,17 +699,51 @@ void BackupStoreCheck::WriteNewStoreInfo()
}
// Build a new store info
+<<<<<<< HEAD
+ std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::CreateForRegeneration(
+ mAccountID,
+=======
+ std::auto_ptr<MemBlockStream> extra_data;
+ if(pOldInfo.get())
+ {
+ extra_data.reset(new MemBlockStream(pOldInfo->GetExtraData()));
+ }
+ else
+ {
+ extra_data.reset(new MemBlockStream(/* empty */));
+ }
std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::CreateForRegeneration(
mAccountID,
+ mAccountName,
+>>>>>>> 0.12
mStoreRoot,
mDiscSetNumber,
lastObjID,
mBlocksUsed,
+<<<<<<< HEAD
+=======
+ mBlocksInCurrentFiles,
+>>>>>>> 0.12
mBlocksInOldFiles,
mBlocksInDeletedFiles,
mBlocksInDirectories,
softLimit,
+<<<<<<< HEAD
hardLimit));
+=======
+ hardLimit,
+ (pOldInfo.get() ? pOldInfo->IsAccountEnabled() : true),
+ *extra_data));
+ info->AdjustNumFiles(mNumFiles);
+ info->AdjustNumOldFiles(mNumOldFiles);
+ info->AdjustNumDeletedFiles(mNumDeletedFiles);
+ info->AdjustNumDirectories(mNumDirectories);
+
+ if(pOldInfo.get())
+ {
+ mNumberErrorsFound += info->ReportChangesTo(*pOldInfo);
+ }
+>>>>>>> 0.12
// Save to disc?
if(mFixErrors)
@@ -663,7 +770,16 @@ bool BackupStoreDirectory::CheckAndFix()
bool changed = false;
// Check that if a file depends on a new version, that version is in this directory
+<<<<<<< HEAD
+ {
+=======
+ bool restart;
+
+ do
{
+ restart = false;
+
+>>>>>>> 0.12
std::vector<Entry*>::iterator i(mEntries.begin());
for(; i != mEntries.end(); ++i)
{
@@ -674,7 +790,11 @@ bool BackupStoreDirectory::CheckAndFix()
if(newerEn == 0)
{
// Depends on something, but it isn't there.
+<<<<<<< HEAD
BOX_TRACE("Entry id " << FMT_i <<
+=======
+ BOX_WARNING("Entry id " << FMT_i <<
+>>>>>>> 0.12
" removed because depends "
"on newer version " <<
FMT_OID(dependsNewer) <<
@@ -684,11 +804,20 @@ bool BackupStoreDirectory::CheckAndFix()
delete *i;
mEntries.erase(i);
+<<<<<<< HEAD
// Start again at the beginning of the vector, the iterator is now invalid
i = mEntries.begin();
// Mark as changed
changed = true;
+=======
+ // Mark as changed
+ changed = true;
+
+ // Start again at the beginning of the vector, the iterator is now invalid
+ restart = true;
+ break;
+>>>>>>> 0.12
}
else
{
@@ -710,6 +839,10 @@ bool BackupStoreDirectory::CheckAndFix()
}
}
}
+<<<<<<< HEAD
+=======
+ while(restart);
+>>>>>>> 0.12
// Check that if a file has a dependency marked, it exists, and remove it if it doesn't
{
@@ -834,7 +967,11 @@ bool BackupStoreDirectory::CheckAndFix()
// erase the thing from the list
Entry *pentry = (*i);
mEntries.erase(i);
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
// And delete the entry object
delete pentry;
@@ -852,12 +989,20 @@ bool BackupStoreDirectory::CheckAndFix()
// --------------------------------------------------------------------------
//
// Function
+<<<<<<< HEAD
// Name: BackupStoreDirectory::AddUnattactedObject(...)
+=======
+// Name: BackupStoreDirectory::AddUnattachedObject(...)
+>>>>>>> 0.12
// Purpose: Adds an object which is currently unattached. Assume that CheckAndFix() will be called afterwards.
// Created: 22/4/04
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
void BackupStoreDirectory::AddUnattactedObject(const BackupStoreFilename &rName,
+=======
+void BackupStoreDirectory::AddUnattachedObject(const BackupStoreFilename &rName,
+>>>>>>> 0.12
box_time_t ModificationTime, int64_t ObjectID, int64_t SizeInBlocks, int16_t Flags)
{
Entry *pnew = new Entry(rName, ModificationTime, ObjectID, SizeInBlocks, Flags,
diff --git a/lib/backupstore/BackupStoreCheckData.cpp b/lib/backupstore/BackupStoreCheckData.cpp
index fed0c3f1..c89b5082 100644
--- a/lib/backupstore/BackupStoreCheckData.cpp
+++ b/lib/backupstore/BackupStoreCheckData.cpp
@@ -65,16 +65,23 @@ void BackupStoreCheck::AddID(BackupStoreCheck_ID_t ID,
if(mpInfoLastBlock == 0 || mInfoLastBlockEntries >= BACKUPSTORECHECK_BLOCK_SIZE)
{
// No. Allocate a new one
+<<<<<<< HEAD
IDBlock *pblk = (IDBlock*)::malloc(sizeof(IDBlock));
+=======
+ IDBlock *pblk = (IDBlock*)calloc(1, sizeof(IDBlock));
+>>>>>>> 0.12
if(pblk == 0)
{
throw std::bad_alloc();
}
+<<<<<<< HEAD
// Zero all the flags entries
for(int z = 0; z < (BACKUPSTORECHECK_BLOCK_SIZE * Flags__NumFlags / Flags__NumItemsPerEntry); ++z)
{
pblk->mFlags[z] = 0;
}
+=======
+>>>>>>> 0.12
// Store in map
mInfo[ID] = pblk;
// Allocated and stored OK, setup for use
@@ -141,8 +148,13 @@ BackupStoreCheck::IDBlock *BackupStoreCheck::LookupID(BackupStoreCheck_ID_t ID,
pblock = ib->second;
}
+<<<<<<< HEAD
ASSERT(pblock != 0);
if(pblock == 0) return 0;
+=======
+ if(pblock == 0) return 0;
+ ASSERT(pblock != 0);
+>>>>>>> 0.12
// How many entries are there in the block
int32_t bentries = (pblock == mpInfoLastBlock)?mInfoLastBlockEntries:BACKUPSTORECHECK_BLOCK_SIZE;
diff --git a/lib/backupstore/BackupStoreConfigVerify.cpp b/lib/backupstore/BackupStoreConfigVerify.cpp
index cc6efcf5..c2344634 100644
--- a/lib/backupstore/BackupStoreConfigVerify.cpp
+++ b/lib/backupstore/BackupStoreConfigVerify.cpp
@@ -38,6 +38,7 @@ static const ConfigurationVerifyKey verifyrootkeys[] =
ConfigTest_Exists | ConfigTest_IsInt),
ConfigurationVerifyKey("ExtendedLogging", ConfigTest_IsBool, false),
// make value "yes" to enable in config file
+<<<<<<< HEAD
#ifdef WIN32
ConfigurationVerifyKey("RaidFileConf", ConfigTest_LastEntry)
@@ -45,6 +46,9 @@ static const ConfigurationVerifyKey verifyrootkeys[] =
ConfigurationVerifyKey("RaidFileConf", ConfigTest_LastEntry,
BOX_FILE_RAIDFILE_DEFAULT_CONFIG)
#endif
+=======
+ ConfigurationVerifyKey("RaidFileConf", ConfigTest_LastEntry)
+>>>>>>> 0.12
};
const ConfigurationVerify BackupConfigFileVerify =
diff --git a/lib/backupstore/BackupStoreConstants.h b/lib/backupstore/BackupStoreConstants.h
new file mode 100644
index 00000000..2c33fd8f
--- /dev/null
+++ b/lib/backupstore/BackupStoreConstants.h
@@ -0,0 +1,44 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreContants.h
+// Purpose: constants for the backup system
+// Created: 2003/08/28
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPSTORECONSTANTS__H
+#define BACKUPSTORECONSTANTS__H
+
+#define BACKUPSTORE_ROOT_DIRECTORY_ID 1
+
+#define BACKUP_STORE_SERVER_VERSION 1
+
+// Minimum size for a chunk to be compressed
+#define BACKUP_FILE_MIN_COMPRESSED_CHUNK_SIZE 256
+
+// min and max sizes for blocks
+#define BACKUP_FILE_MIN_BLOCK_SIZE 4096
+#define BACKUP_FILE_MAX_BLOCK_SIZE (512*1024)
+
+// Increase the block size if there are more than this number of blocks
+#define BACKUP_FILE_INCREASE_BLOCK_SIZE_AFTER 4096
+
+// Avoid creating blocks smaller than this
+#define BACKUP_FILE_AVOID_BLOCKS_LESS_THAN 128
+
+// Maximum number of sizes to do an rsync-like scan for
+#define BACKUP_FILE_DIFF_MAX_BLOCK_SIZES 64
+
+// When doing rsync scans, do not scan for blocks smaller than
+#define BACKUP_FILE_DIFF_MIN_BLOCK_SIZE 128
+
+// A limit to stop diffing running out of control: If more than this
+// times the number of blocks in the original index are found, stop
+// looking. This stops really bad cases of diffing files containing
+// all the same byte using huge amounts of memory and processor time.
+// This is a multiple of the number of blocks in the diff from file.
+#define BACKUP_FILE_DIFF_MAX_BLOCK_FIND_MULTIPLE 4096
+
+#endif // BACKUPSTORECONSTANTS__H
+
diff --git a/lib/backupstore/BackupStoreContext.cpp b/lib/backupstore/BackupStoreContext.cpp
new file mode 100644
index 00000000..2c98b1d7
--- /dev/null
+++ b/lib/backupstore/BackupStoreContext.cpp
@@ -0,0 +1,1808 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreContext.cpp
+// Purpose: Context for backup store server
+// Created: 2003/08/20
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+
+#include "BackupConstants.h"
+#include "BackupStoreContext.h"
+#include "BackupStoreDirectory.h"
+#include "BackupStoreException.h"
+#include "BackupStoreFile.h"
+#include "BackupStoreInfo.h"
+#include "BackupStoreObjectMagic.h"
+#include "BufferedStream.h"
+#include "BufferedWriteStream.h"
+#include "FileStream.h"
+#include "InvisibleTempFileStream.h"
+#include "RaidFileController.h"
+#include "RaidFileRead.h"
+#include "RaidFileWrite.h"
+#include "StoreStructure.h"
+
+#include "MemLeakFindOn.h"
+
+
+// Maximum number of directories to keep in the cache
+// When the cache is bigger than this, everything gets
+// deleted.
+#ifdef BOX_RELEASE_BUILD
+ #define MAX_CACHE_SIZE 32
+#else
+ #define MAX_CACHE_SIZE 2
+#endif
+
+// Allow the housekeeping process 4 seconds to release an account
+#define MAX_WAIT_FOR_HOUSEKEEPING_TO_RELEASE_ACCOUNT 4
+
+// Maximum amount of store info updates before it's actually saved to disc.
+#define STORE_INFO_SAVE_DELAY 96
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::BackupStoreContext()
+// Purpose: Constructor
+// Created: 2003/08/20
+//
+// --------------------------------------------------------------------------
+BackupStoreContext::BackupStoreContext(int32_t ClientID,
+ HousekeepingInterface &rDaemon, const std::string& rConnectionDetails)
+ : mConnectionDetails(rConnectionDetails),
+ mClientID(ClientID),
+ mrDaemon(rDaemon),
+ mProtocolPhase(Phase_START),
+ mClientHasAccount(false),
+ mStoreDiscSet(-1),
+ mReadOnly(true),
+ mSaveStoreInfoDelay(STORE_INFO_SAVE_DELAY),
+ mpTestHook(NULL)
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::~BackupStoreContext()
+// Purpose: Destructor
+// Created: 2003/08/20
+//
+// --------------------------------------------------------------------------
+BackupStoreContext::~BackupStoreContext()
+{
+ // Delete the objects in the cache
+ for(std::map<int64_t, BackupStoreDirectory*>::iterator i(mDirectoryCache.begin()); i != mDirectoryCache.end(); ++i)
+ {
+ delete (i->second);
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::CleanUp()
+// Purpose: Clean up after a connection
+// Created: 16/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreContext::CleanUp()
+{
+ // Make sure the store info is saved, if it has been loaded, isn't read only and has been modified
+ if(mapStoreInfo.get() && !(mapStoreInfo->IsReadOnly()) &&
+ mapStoreInfo->IsModified())
+ {
+ mapStoreInfo->Save();
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::ReceivedFinishCommand()
+// Purpose: Called when the finish command is received by the protocol
+// Created: 16/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreContext::ReceivedFinishCommand()
+{
+ if(!mReadOnly && mapStoreInfo.get())
+ {
+ // Save the store info, not delayed
+ SaveStoreInfo(false);
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::AttemptToGetWriteLock()
+// Purpose: Attempt to get a write lock for the store, and if so, unset the read only flags
+// Created: 2003/09/02
+//
+// --------------------------------------------------------------------------
+bool BackupStoreContext::AttemptToGetWriteLock()
+{
+ // Make the filename of the write lock file
+ std::string writeLockFile;
+ StoreStructure::MakeWriteLockFilename(mStoreRoot, mStoreDiscSet, writeLockFile);
+
+ // Request the lock
+ bool gotLock = mWriteLock.TryAndGetLock(writeLockFile.c_str(), 0600 /* restrictive file permissions */);
+
+ if(!gotLock)
+ {
+ // The housekeeping process might have the thing open -- ask it to stop
+ char msg[256];
+ int msgLen = sprintf(msg, "r%x\n", mClientID);
+ // Send message
+ mrDaemon.SendMessageToHousekeepingProcess(msg, msgLen);
+
+ // Then try again a few times
+ int tries = MAX_WAIT_FOR_HOUSEKEEPING_TO_RELEASE_ACCOUNT;
+ do
+ {
+ ::sleep(1 /* second */);
+ --tries;
+ gotLock = mWriteLock.TryAndGetLock(writeLockFile.c_str(), 0600 /* restrictive file permissions */);
+
+ } while(!gotLock && tries > 0);
+ }
+
+ if(gotLock)
+ {
+ // Got the lock, mark as not read only
+ mReadOnly = false;
+ }
+
+ return gotLock;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::LoadStoreInfo()
+// Purpose: Load the store info from disc
+// Created: 2003/09/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreContext::LoadStoreInfo()
+{
+ if(mapStoreInfo.get() != 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoAlreadyLoaded)
+ }
+
+ // Load it up!
+ std::auto_ptr<BackupStoreInfo> i(BackupStoreInfo::Load(mClientID, mStoreRoot, mStoreDiscSet, mReadOnly));
+
+ // Check it
+ if(i->GetAccountID() != mClientID)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoForWrongAccount)
+ }
+
+ // Keep the pointer to it
+ mapStoreInfo = i;
+
+ BackupStoreAccountDatabase::Entry account(mClientID, mStoreDiscSet);
+
+ // try to load the reference count database
+ try
+ {
+ mapRefCount = BackupStoreRefCountDatabase::Load(account, false);
+ }
+ catch(BoxException &e)
+ {
+ BOX_WARNING("Reference count database is missing or corrupted, "
+ "creating a new one, expect housekeeping to find and "
+ "fix problems with reference counts later.");
+
+ BackupStoreRefCountDatabase::CreateForRegeneration(account);
+ mapRefCount = BackupStoreRefCountDatabase::Load(account, false);
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::SaveStoreInfo(bool)
+// Purpose: Potentially delayed saving of the store info
+// Created: 16/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreContext::SaveStoreInfo(bool AllowDelay)
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+ if(mReadOnly)
+ {
+ THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly)
+ }
+
+ // Can delay saving it a little while?
+ if(AllowDelay)
+ {
+ --mSaveStoreInfoDelay;
+ if(mSaveStoreInfoDelay > 0)
+ {
+ return;
+ }
+ }
+
+ // Want to save now
+ mapStoreInfo->Save();
+
+ // Set count for next delay
+ mSaveStoreInfoDelay = STORE_INFO_SAVE_DELAY;
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::MakeObjectFilename(int64_t, std::string &, bool)
+// Purpose: Create the filename of an object in the store, optionally creating the
+// containing directory if it doesn't already exist.
+// Created: 2003/09/02
+//
+// --------------------------------------------------------------------------
+void BackupStoreContext::MakeObjectFilename(int64_t ObjectID, std::string &rOutput, bool EnsureDirectoryExists)
+{
+ // Delegate to utility function
+ StoreStructure::MakeObjectFilename(ObjectID, mStoreRoot, mStoreDiscSet, rOutput, EnsureDirectoryExists);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::GetDirectoryInternal(int64_t)
+// Purpose: Return a reference to a directory. Valid only until the
+// next time a function which affects directories is called.
+// Mainly this funciton, and creation of files.
+// Private version of this, which returns non-const directories.
+// Created: 2003/09/02
+//
+// --------------------------------------------------------------------------
+BackupStoreDirectory &BackupStoreContext::GetDirectoryInternal(int64_t ObjectID)
+{
+ // Get the filename
+ std::string filename;
+ MakeObjectFilename(ObjectID, filename);
+
+ // Already in cache?
+ std::map<int64_t, BackupStoreDirectory*>::iterator item(mDirectoryCache.find(ObjectID));
+ if(item != mDirectoryCache.end())
+ {
+ // Check the revision ID of the file -- does it need refreshing?
+ int64_t revID = 0;
+ if(!RaidFileRead::FileExists(mStoreDiscSet, filename, &revID))
+ {
+ THROW_EXCEPTION(BackupStoreException, DirectoryHasBeenDeleted)
+ }
+
+ if(revID == item->second->GetRevisionID())
+ {
+ // Looks good... return the cached object
+ BOX_TRACE("Returning object " <<
+ BOX_FORMAT_OBJECTID(ObjectID) <<
+ " from cache, modtime = " << revID);
+ return *(item->second);
+ }
+
+ BOX_TRACE("Refreshing object " <<
+ BOX_FORMAT_OBJECTID(ObjectID) <<
+ " in cache, modtime changed from " <<
+ item->second->GetRevisionID() << " to " << revID);
+
+ // Delete this cached object
+ delete item->second;
+ mDirectoryCache.erase(item);
+ }
+
+ // Need to load it up
+
+ // First check to see if the cache is too big
+ if(mDirectoryCache.size() > MAX_CACHE_SIZE)
+ {
+ // Very simple. Just delete everything!
+ for(std::map<int64_t, BackupStoreDirectory*>::iterator i(mDirectoryCache.begin()); i != mDirectoryCache.end(); ++i)
+ {
+ delete (i->second);
+ }
+ mDirectoryCache.clear();
+ }
+
+ // Get a RaidFileRead to read it
+ int64_t revID = 0;
+ std::auto_ptr<RaidFileRead> objectFile(RaidFileRead::Open(mStoreDiscSet, filename, &revID));
+ ASSERT(revID != 0);
+
+ // New directory object
+ std::auto_ptr<BackupStoreDirectory> dir(new BackupStoreDirectory);
+
+ // Read it from the stream, then set it's revision ID
+ BufferedStream buf(*objectFile);
+ dir->ReadFromStream(buf, IOStream::TimeOutInfinite);
+ dir->SetRevisionID(revID);
+
+ // Make sure the size of the directory is available for writing the dir back
+ int64_t dirSize = objectFile->GetDiscUsageInBlocks();
+ ASSERT(dirSize > 0);
+ dir->SetUserInfo1_SizeInBlocks(dirSize);
+
+ // Store in cache
+ BackupStoreDirectory *pdir = dir.release();
+ try
+ {
+ mDirectoryCache[ObjectID] = pdir;
+ }
+ catch(...)
+ {
+ delete pdir;
+ throw;
+ }
+
+ // Return it
+ return *pdir;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::AllocateObjectID()
+// Purpose: Allocate a new object ID, tolerant of failures to save store info
+// Created: 16/12/03
+//
+// --------------------------------------------------------------------------
+int64_t BackupStoreContext::AllocateObjectID()
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+
+ // Given that the store info may not be saved for STORE_INFO_SAVE_DELAY
+ // times after it has been updated, this is a reasonable number of times
+ // to try for finding an unused ID.
+ // (Sizes used in the store info are fixed by the housekeeping process)
+ int retryLimit = (STORE_INFO_SAVE_DELAY * 2);
+
+ while(retryLimit > 0)
+ {
+ // Attempt to allocate an ID from the store
+ int64_t id = mapStoreInfo->AllocateObjectID();
+
+ // Generate filename
+ std::string filename;
+ MakeObjectFilename(id, filename);
+ // Check it doesn't exist
+ if(!RaidFileRead::FileExists(mStoreDiscSet, filename))
+ {
+ // Success!
+ return id;
+ }
+
+ // Decrement retry count, and try again
+ --retryLimit;
+
+ // Mark that the store info should be saved as soon as possible
+ mSaveStoreInfoDelay = 0;
+
+ BOX_WARNING("When allocating object ID, found that " <<
+ BOX_FORMAT_OBJECTID(id) << " is already in use");
+ }
+
+ THROW_EXCEPTION(BackupStoreException, CouldNotFindUnusedIDDuringAllocation)
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::AddFile(IOStream &, int64_t,
+// int64_t, int64_t, const BackupStoreFilename &, bool)
+// Purpose: Add a file to the store, from a given stream, into
+// a specified directory. Returns object ID of the new
+// file.
+// Created: 2003/09/03
+//
+// --------------------------------------------------------------------------
+int64_t BackupStoreContext::AddFile(IOStream &rFile, int64_t InDirectory,
+ int64_t ModificationTime, int64_t AttributesHash,
+ int64_t DiffFromFileID, const BackupStoreFilename &rFilename,
+ bool MarkFileWithSameNameAsOldVersions)
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+ if(mReadOnly)
+ {
+ THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly)
+ }
+
+ // This is going to be a bit complex to make sure it copes OK
+ // with things going wrong.
+ // The only thing which isn't safe is incrementing the object ID
+ // and keeping the blocks used entirely accurate -- but these
+ // aren't big problems if they go horribly wrong. The sizes will
+ // be corrected the next time the account has a housekeeping run,
+ // and the object ID allocation code is tolerant of missed IDs.
+ // (the info is written lazily, so these are necessary)
+
+ // Get the directory we want to modify
+ BackupStoreDirectory &dir(GetDirectoryInternal(InDirectory));
+
+ // Allocate the next ID
+ int64_t id = AllocateObjectID();
+
+ // Stream the file to disc
+ std::string fn;
+ MakeObjectFilename(id, fn, true /* make sure the directory it's in exists */);
+ int64_t newObjectBlocksUsed = 0;
+ RaidFileWrite *ppreviousVerStoreFile = 0;
+ bool reversedDiffIsCompletelyDifferent = false;
+ int64_t oldVersionNewBlocksUsed = 0;
+ try
+ {
+ RaidFileWrite storeFile(mStoreDiscSet, fn);
+ storeFile.Open(false /* no overwriting */);
+
+ // size adjustment from use of patch in old file
+ int64_t spaceSavedByConversionToPatch = 0;
+
+ // Diff or full file?
+ if(DiffFromFileID == 0)
+ {
+ // A full file, just store to disc
+ if(!rFile.CopyStreamTo(storeFile, BACKUP_STORE_TIMEOUT))
+ {
+ THROW_EXCEPTION(BackupStoreException, ReadFileFromStreamTimedOut)
+ }
+ }
+ else
+ {
+ // Check that the diffed from ID actually exists in the directory
+ if(dir.FindEntryByID(DiffFromFileID) == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, DiffFromIDNotFoundInDirectory)
+ }
+
+ // Diff file, needs to be recreated.
+ // Choose a temporary filename.
+ std::string tempFn(RaidFileController::DiscSetPathToFileSystemPath(mStoreDiscSet, fn + ".difftemp",
+ 1 /* NOT the same disc as the write file, to avoid using lots of space on the same disc unnecessarily */));
+
+ try
+ {
+ // Open it twice
+#ifdef WIN32
+ InvisibleTempFileStream diff(tempFn.c_str(),
+ O_RDWR | O_CREAT | O_BINARY);
+ InvisibleTempFileStream diff2(tempFn.c_str(),
+ O_RDWR | O_BINARY);
+#else
+ FileStream diff(tempFn.c_str(), O_RDWR | O_CREAT | O_EXCL);
+ FileStream diff2(tempFn.c_str(), O_RDONLY);
+
+ // Unlink it immediately, so it definitely goes away
+ if(::unlink(tempFn.c_str()) != 0)
+ {
+ THROW_EXCEPTION(CommonException, OSFileError);
+ }
+#endif
+
+ // Stream the incoming diff to this temporary file
+ if(!rFile.CopyStreamTo(diff, BACKUP_STORE_TIMEOUT))
+ {
+ THROW_EXCEPTION(BackupStoreException, ReadFileFromStreamTimedOut)
+ }
+
+ // Verify the diff
+ diff.Seek(0, IOStream::SeekType_Absolute);
+ if(!BackupStoreFile::VerifyEncodedFileFormat(diff))
+ {
+ THROW_EXCEPTION(BackupStoreException, AddedFileDoesNotVerify)
+ }
+
+ // Seek to beginning of diff file
+ diff.Seek(0, IOStream::SeekType_Absolute);
+
+ // Filename of the old version
+ std::string oldVersionFilename;
+ MakeObjectFilename(DiffFromFileID, oldVersionFilename, false /* no need to make sure the directory it's in exists */);
+
+ // Reassemble that diff -- open previous file, and combine the patch and file
+ std::auto_ptr<RaidFileRead> from(RaidFileRead::Open(mStoreDiscSet, oldVersionFilename));
+ BackupStoreFile::CombineFile(diff, diff2, *from, storeFile);
+
+ // Then... reverse the patch back (open the from file again, and create a write file to overwrite it)
+ std::auto_ptr<RaidFileRead> from2(RaidFileRead::Open(mStoreDiscSet, oldVersionFilename));
+ ppreviousVerStoreFile = new RaidFileWrite(mStoreDiscSet, oldVersionFilename);
+ ppreviousVerStoreFile->Open(true /* allow overwriting */);
+ from->Seek(0, IOStream::SeekType_Absolute);
+ diff.Seek(0, IOStream::SeekType_Absolute);
+ BackupStoreFile::ReverseDiffFile(diff, *from, *from2, *ppreviousVerStoreFile,
+ DiffFromFileID, &reversedDiffIsCompletelyDifferent);
+
+ // Store disc space used
+ oldVersionNewBlocksUsed = ppreviousVerStoreFile->GetDiscUsageInBlocks();
+
+ // And make a space adjustment for the size calculation
+ spaceSavedByConversionToPatch =
+ from->GetDiscUsageInBlocks() -
+ oldVersionNewBlocksUsed;
+
+ // Everything cleans up here...
+ }
+ catch(...)
+ {
+ // Be very paranoid about deleting this temp file -- we could only leave a zero byte file anyway
+ ::unlink(tempFn.c_str());
+ throw;
+ }
+ }
+
+ // Get the blocks used
+ newObjectBlocksUsed = storeFile.GetDiscUsageInBlocks();
+
+ // Exceeds the hard limit?
+ int64_t newBlocksUsed = mapStoreInfo->GetBlocksUsed() +
+ newObjectBlocksUsed - spaceSavedByConversionToPatch;
+ if(newBlocksUsed > mapStoreInfo->GetBlocksHardLimit())
+ {
+ THROW_EXCEPTION(BackupStoreException, AddedFileExceedsStorageLimit)
+ // The store file will be deleted automatically by the RaidFile object
+ }
+
+ // Commit the file
+ storeFile.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY);
+ }
+ catch(...)
+ {
+ // Delete any previous version store file
+ if(ppreviousVerStoreFile != 0)
+ {
+ delete ppreviousVerStoreFile;
+ ppreviousVerStoreFile = 0;
+ }
+
+ throw;
+ }
+
+ // Verify the file -- only necessary for non-diffed versions
+ // NOTE: No need to catch exceptions and delete ppreviousVerStoreFile, because
+ // in the non-diffed code path it's never allocated.
+ if(DiffFromFileID == 0)
+ {
+ std::auto_ptr<RaidFileRead> checkFile(RaidFileRead::Open(mStoreDiscSet, fn));
+ if(!BackupStoreFile::VerifyEncodedFileFormat(*checkFile))
+ {
+ // Error! Delete the file
+ RaidFileWrite del(mStoreDiscSet, fn);
+ del.Delete();
+
+ // Exception
+ THROW_EXCEPTION(BackupStoreException, AddedFileDoesNotVerify)
+ }
+ }
+
+ // Modify the directory -- first make all files with the same name
+ // marked as an old version
+ int64_t blocksInOldFiles = 0;
+ try
+ {
+ if(MarkFileWithSameNameAsOldVersions)
+ {
+ BackupStoreDirectory::Iterator i(dir);
+
+ BackupStoreDirectory::Entry *e = 0;
+ while((e = i.Next()) != 0)
+ {
+ // First, check it's not an old version (cheaper comparison)
+ if(! e->IsOld())
+ {
+ // Compare name
+ if(e->GetName() == rFilename)
+ {
+ // Check that it's definately not an old version
+ ASSERT((e->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) == 0);
+ // Set old version flag
+ e->AddFlags(BackupStoreDirectory::Entry::Flags_OldVersion);
+ // Can safely do this, because we know we won't be here if it's already
+ // an old version
+ blocksInOldFiles += e->GetSizeInBlocks();
+ }
+ }
+ }
+ }
+
+ // Then the new entry
+ BackupStoreDirectory::Entry *pnewEntry = dir.AddEntry(rFilename,
+ ModificationTime, id, newObjectBlocksUsed,
+ BackupStoreDirectory::Entry::Flags_File,
+ AttributesHash);
+
+ // Adjust for the patch back stuff?
+ if(DiffFromFileID != 0)
+ {
+ // Get old version entry
+ BackupStoreDirectory::Entry *poldEntry = dir.FindEntryByID(DiffFromFileID);
+ ASSERT(poldEntry != 0);
+
+ // Adjust dependency info of file?
+ if(!reversedDiffIsCompletelyDifferent)
+ {
+ poldEntry->SetDependsNewer(id);
+ pnewEntry->SetDependsOlder(DiffFromFileID);
+ }
+
+ // Adjust size of old entry
+ int64_t oldSize = poldEntry->GetSizeInBlocks();
+ poldEntry->SetSizeInBlocks(oldVersionNewBlocksUsed);
+
+ // And adjust blocks used count, for later adjustment
+ newObjectBlocksUsed += (oldVersionNewBlocksUsed - oldSize);
+ blocksInOldFiles += (oldVersionNewBlocksUsed - oldSize);
+ }
+
+ // Write the directory back to disc
+ SaveDirectory(dir, InDirectory);
+
+ // Commit the old version's new patched version, now that the directory safely reflects
+ // the state of the files on disc.
+ if(ppreviousVerStoreFile != 0)
+ {
+ ppreviousVerStoreFile->Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY);
+ delete ppreviousVerStoreFile;
+ ppreviousVerStoreFile = 0;
+ }
+ }
+ catch(...)
+ {
+ // Back out on adding that file
+ RaidFileWrite del(mStoreDiscSet, fn);
+ del.Delete();
+
+ // Remove this entry from the cache
+ RemoveDirectoryFromCache(InDirectory);
+
+ // Delete any previous version store file
+ if(ppreviousVerStoreFile != 0)
+ {
+ delete ppreviousVerStoreFile;
+ ppreviousVerStoreFile = 0;
+ }
+
+ // Don't worry about the incremented number in the store info
+ throw;
+ }
+
+ // Check logic
+ ASSERT(ppreviousVerStoreFile == 0);
+
+ // Modify the store info
+
+ if(DiffFromFileID == 0)
+ {
+ mapStoreInfo->AdjustNumFiles(1);
+ }
+ else
+ {
+ mapStoreInfo->AdjustNumOldFiles(1);
+ }
+
+ mapStoreInfo->ChangeBlocksUsed(newObjectBlocksUsed);
+ mapStoreInfo->ChangeBlocksInCurrentFiles(newObjectBlocksUsed -
+ blocksInOldFiles);
+ mapStoreInfo->ChangeBlocksInOldFiles(blocksInOldFiles);
+
+ // Increment reference count on the new directory to one
+ mapRefCount->AddReference(id);
+
+ // Save the store info -- can cope if this exceptions because infomation
+ // will be rebuilt by housekeeping, and ID allocation can recover.
+ SaveStoreInfo(false);
+
+ // Return the ID to the caller
+ return id;
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::DeleteFile(const BackupStoreFilename &, int64_t, int64_t &)
+// Purpose: Deletes a file, returning true if the file existed. Object ID returned too, set to zero if not found.
+// Created: 2003/10/21
+//
+// --------------------------------------------------------------------------
+bool BackupStoreContext::DeleteFile(const BackupStoreFilename &rFilename, int64_t InDirectory, int64_t &rObjectIDOut)
+{
+ // Essential checks!
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+
+ if(mReadOnly)
+ {
+ THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly)
+ }
+
+ // Find the directory the file is in (will exception if it fails)
+ BackupStoreDirectory &dir(GetDirectoryInternal(InDirectory));
+
+ // Setup flags
+ bool fileExisted = false;
+ bool madeChanges = false;
+ rObjectIDOut = 0; // not found
+
+ // Count of deleted blocks
+ int64_t blocksDel = 0;
+
+ try
+ {
+ // Iterate through directory, only looking at files which haven't been deleted
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *e = 0;
+ while((e = i.Next(BackupStoreDirectory::Entry::Flags_File,
+ BackupStoreDirectory::Entry::Flags_Deleted)) != 0)
+ {
+ // Compare name
+ if(e->GetName() == rFilename)
+ {
+ // Check that it's definately not already deleted
+ ASSERT((e->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) == 0);
+ // Set deleted flag
+ e->AddFlags(BackupStoreDirectory::Entry::Flags_Deleted);
+ // Mark as made a change
+ madeChanges = true;
+ // Can safely do this, because we know we won't be here if it's already
+ // an old version
+ blocksDel += e->GetSizeInBlocks();
+ // Is this the last version?
+ if((e->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) == 0)
+ {
+ // Yes. It's been found.
+ rObjectIDOut = e->GetObjectID();
+ fileExisted = true;
+ }
+ }
+ }
+
+ // Save changes?
+ if(madeChanges)
+ {
+ // Save the directory back
+ SaveDirectory(dir, InDirectory);
+
+ // Modify the store info, and write
+ // It definitely wasn't an old or deleted version
+ mapStoreInfo->AdjustNumFiles(-1);
+ mapStoreInfo->AdjustNumDeletedFiles(1);
+ mapStoreInfo->ChangeBlocksInDeletedFiles(blocksDel);
+
+ SaveStoreInfo(false);
+ }
+ }
+ catch(...)
+ {
+ RemoveDirectoryFromCache(InDirectory);
+ throw;
+ }
+
+ return fileExisted;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::UndeleteFile(int64_t, int64_t)
+// Purpose: Undeletes a file, if it exists, returning true if
+// the file existed.
+// Created: 2003/10/21
+//
+// --------------------------------------------------------------------------
+bool BackupStoreContext::UndeleteFile(int64_t ObjectID, int64_t InDirectory)
+{
+ // Essential checks!
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+
+ if(mReadOnly)
+ {
+ THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly)
+ }
+
+ // Find the directory the file is in (will exception if it fails)
+ BackupStoreDirectory &dir(GetDirectoryInternal(InDirectory));
+
+ // Setup flags
+ bool fileExisted = false;
+ bool madeChanges = false;
+
+ // Count of deleted blocks
+ int64_t blocksDel = 0;
+
+ try
+ {
+ // Iterate through directory, only looking at files which have been deleted
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *e = 0;
+ while((e = i.Next(BackupStoreDirectory::Entry::Flags_File |
+ BackupStoreDirectory::Entry::Flags_Deleted, 0)) != 0)
+ {
+ // Compare name
+ if(e->GetObjectID() == ObjectID)
+ {
+ // Check that it's definitely already deleted
+ ASSERT((e->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) != 0);
+ // Clear deleted flag
+ e->RemoveFlags(BackupStoreDirectory::Entry::Flags_Deleted);
+ // Mark as made a change
+ madeChanges = true;
+ blocksDel -= e->GetSizeInBlocks();
+
+ // Is this the last version?
+ if((e->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) == 0)
+ {
+ // Yes. It's been found.
+ fileExisted = true;
+ }
+ }
+ }
+
+ // Save changes?
+ if(madeChanges)
+ {
+ // Save the directory back
+ SaveDirectory(dir, InDirectory);
+
+ // Modify the store info, and write
+ mapStoreInfo->ChangeBlocksInDeletedFiles(blocksDel);
+
+ // Maybe postponed save of store info
+ SaveStoreInfo();
+ }
+ }
+ catch(...)
+ {
+ RemoveDirectoryFromCache(InDirectory);
+ throw;
+ }
+
+ return fileExisted;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::RemoveDirectoryFromCache(int64_t)
+// Purpose: Remove directory from cache
+// Created: 2003/09/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreContext::RemoveDirectoryFromCache(int64_t ObjectID)
+{
+ std::map<int64_t, BackupStoreDirectory*>::iterator item(mDirectoryCache.find(ObjectID));
+ if(item != mDirectoryCache.end())
+ {
+ // Delete this cached object
+ delete item->second;
+ // Erase the entry form the map
+ mDirectoryCache.erase(item);
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::SaveDirectory(BackupStoreDirectory &, int64_t)
+// Purpose: Save directory back to disc, update time in cache
+// Created: 2003/09/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreContext::SaveDirectory(BackupStoreDirectory &rDir, int64_t ObjectID)
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+ if(rDir.GetObjectID() != ObjectID)
+ {
+ THROW_EXCEPTION(BackupStoreException, Internal)
+ }
+
+ try
+ {
+ // Write to disc, adjust size in store info
+ std::string dirfn;
+ MakeObjectFilename(ObjectID, dirfn);
+ {
+ RaidFileWrite writeDir(mStoreDiscSet, dirfn);
+ writeDir.Open(true /* allow overwriting */);
+
+ BufferedWriteStream buffer(writeDir);
+ rDir.WriteToStream(buffer);
+ buffer.Flush();
+
+ // get the disc usage (must do this before commiting it)
+ int64_t dirSize = writeDir.GetDiscUsageInBlocks();
+
+ // Commit directory
+ writeDir.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY);
+
+ // Make sure the size of the directory is available for writing the dir back
+ ASSERT(dirSize > 0);
+ int64_t sizeAdjustment = dirSize - rDir.GetUserInfo1_SizeInBlocks();
+ mapStoreInfo->ChangeBlocksUsed(sizeAdjustment);
+ mapStoreInfo->ChangeBlocksInDirectories(sizeAdjustment);
+ // Update size stored in directory
+ rDir.SetUserInfo1_SizeInBlocks(dirSize);
+ }
+ // Refresh revision ID in cache
+ {
+ int64_t revid = 0;
+ if(!RaidFileRead::FileExists(mStoreDiscSet, dirfn, &revid))
+ {
+ THROW_EXCEPTION(BackupStoreException, Internal)
+ }
+ rDir.SetRevisionID(revid);
+ }
+ }
+ catch(...)
+ {
+ // Remove it from the cache if anything went wrong
+ RemoveDirectoryFromCache(ObjectID);
+ throw;
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::AddDirectory(int64_t,
+// const BackupStoreFilename &, bool &)
+// Purpose: Creates a directory (or just returns the ID of an
+// existing one). rAlreadyExists set appropraitely.
+// Created: 2003/09/04
+//
+// --------------------------------------------------------------------------
+int64_t BackupStoreContext::AddDirectory(int64_t InDirectory, const BackupStoreFilename &rFilename, const StreamableMemBlock &Attributes, int64_t AttributesModTime, bool &rAlreadyExists)
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+ if(mReadOnly)
+ {
+ THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly)
+ }
+
+ // Flags as not already existing
+ rAlreadyExists = false;
+
+ // Get the directory we want to modify
+ BackupStoreDirectory &dir(GetDirectoryInternal(InDirectory));
+
+ // Scan the directory for the name (only looking for directories which already exist)
+ {
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next(BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING,
+ BackupStoreDirectory::Entry::Flags_Deleted | BackupStoreDirectory::Entry::Flags_OldVersion)) != 0) // Ignore deleted and old directories
+ {
+ if(en->GetName() == rFilename)
+ {
+ // Already exists
+ rAlreadyExists = true;
+ return en->GetObjectID();
+ }
+ }
+ }
+
+ // Allocate the next ID
+ int64_t id = AllocateObjectID();
+
+ // Create an empty directory with the given attributes on disc
+ std::string fn;
+ MakeObjectFilename(id, fn, true /* make sure the directory it's in exists */);
+ {
+ BackupStoreDirectory emptyDir(id, InDirectory);
+ // add the atttribues
+ emptyDir.SetAttributes(Attributes, AttributesModTime);
+
+ // Write...
+ RaidFileWrite dirFile(mStoreDiscSet, fn);
+ dirFile.Open(false /* no overwriting */);
+ emptyDir.WriteToStream(dirFile);
+ // Get disc usage, before it's commited
+ int64_t dirSize = dirFile.GetDiscUsageInBlocks();
+ // Commit the file
+ dirFile.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY);
+
+ // Make sure the size of the directory is added to the usage counts in the info
+ ASSERT(dirSize > 0);
+ mapStoreInfo->ChangeBlocksUsed(dirSize);
+ mapStoreInfo->ChangeBlocksInDirectories(dirSize);
+ // Not added to cache, so don't set the size in the directory
+ }
+
+ // Then add it into the parent directory
+ try
+ {
+ dir.AddEntry(rFilename, 0 /* modification time */, id, 0 /* blocks used */, BackupStoreDirectory::Entry::Flags_Dir, 0 /* attributes mod time */);
+ SaveDirectory(dir, InDirectory);
+
+ // Increment reference count on the new directory to one
+ mapRefCount->AddReference(id);
+ }
+ catch(...)
+ {
+ // Back out on adding that directory
+ RaidFileWrite del(mStoreDiscSet, fn);
+ del.Delete();
+
+ // Remove this entry from the cache
+ RemoveDirectoryFromCache(InDirectory);
+
+ // Don't worry about the incremented number in the store info
+ throw;
+ }
+
+ // Save the store info (may not be postponed)
+ mapStoreInfo->AdjustNumDirectories(1);
+ SaveStoreInfo(false);
+
+ // tell caller what the ID was
+ return id;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::DeleteFile(const BackupStoreFilename &, int64_t, int64_t &, bool)
+// Purpose: Recusively deletes a directory (or undeletes if Undelete = true)
+// Created: 2003/10/21
+//
+// --------------------------------------------------------------------------
+void BackupStoreContext::DeleteDirectory(int64_t ObjectID, bool Undelete)
+{
+ // Essential checks!
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+ if(mReadOnly)
+ {
+ THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly)
+ }
+
+ // Containing directory
+ int64_t InDirectory = 0;
+
+ // Count of blocks deleted
+ int64_t blocksDeleted = 0;
+
+ try
+ {
+ // Get the directory that's to be deleted
+ {
+ // In block, because dir may not be valid after the delete directory call
+ BackupStoreDirectory &dir(GetDirectoryInternal(ObjectID));
+
+ // Store the directory it's in for later
+ InDirectory = dir.GetContainerID();
+
+ // Depth first delete of contents
+ DeleteDirectoryRecurse(ObjectID, blocksDeleted, Undelete);
+ }
+
+ // Remove the entry from the directory it's in
+ ASSERT(InDirectory != 0);
+ BackupStoreDirectory &parentDir(GetDirectoryInternal(InDirectory));
+
+ BackupStoreDirectory::Iterator i(parentDir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next(Undelete?(BackupStoreDirectory::Entry::Flags_Deleted):(BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING),
+ Undelete?(0):(BackupStoreDirectory::Entry::Flags_Deleted))) != 0) // Ignore deleted directories (or not deleted if Undelete)
+ {
+ if(en->GetObjectID() == ObjectID)
+ {
+ // This is the one to delete
+ if(Undelete)
+ {
+ en->RemoveFlags(BackupStoreDirectory::Entry::Flags_Deleted);
+ }
+ else
+ {
+ en->AddFlags(BackupStoreDirectory::Entry::Flags_Deleted);
+ }
+
+ // Save it
+ SaveDirectory(parentDir, InDirectory);
+
+ // Done
+ break;
+ }
+ }
+
+ // Update blocks deleted count
+ mapStoreInfo->ChangeBlocksInDeletedFiles(Undelete?(0 - blocksDeleted):(blocksDeleted));
+ mapStoreInfo->AdjustNumDirectories(-1);
+ SaveStoreInfo(false);
+ }
+ catch(...)
+ {
+ RemoveDirectoryFromCache(InDirectory);
+ throw;
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::DeleteDirectoryRecurse(BackupStoreDirectory &, int64_t)
+// Purpose: Private. Deletes a directory depth-first recusively.
+// Created: 2003/10/21
+//
+// --------------------------------------------------------------------------
+void BackupStoreContext::DeleteDirectoryRecurse(int64_t ObjectID, int64_t &rBlocksDeletedOut, bool Undelete)
+{
+ try
+ {
+ // Does things carefully to avoid using a directory in the cache after recursive call
+ // because it may have been deleted.
+
+ // Do sub directories
+ {
+ // Get the directory...
+ BackupStoreDirectory &dir(GetDirectoryInternal(ObjectID));
+
+ // Then scan it for directories
+ std::vector<int64_t> subDirs;
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ if(Undelete)
+ {
+ while((en = i.Next(BackupStoreDirectory::Entry::Flags_Dir | BackupStoreDirectory::Entry::Flags_Deleted, // deleted dirs
+ BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING)) != 0)
+ {
+ // Store the directory ID.
+ subDirs.push_back(en->GetObjectID());
+ }
+ }
+ else
+ {
+ while((en = i.Next(BackupStoreDirectory::Entry::Flags_Dir, // dirs only
+ BackupStoreDirectory::Entry::Flags_Deleted)) != 0) // but not deleted ones
+ {
+ // Store the directory ID.
+ subDirs.push_back(en->GetObjectID());
+ }
+ }
+
+ // Done with the directory for now. Recurse to sub directories
+ for(std::vector<int64_t>::const_iterator i = subDirs.begin(); i != subDirs.end(); ++i)
+ {
+ DeleteDirectoryRecurse((*i), rBlocksDeletedOut, Undelete);
+ }
+ }
+
+ // Then, delete the files. Will need to load the directory again because it might have
+ // been removed from the cache.
+ {
+ // Get the directory...
+ BackupStoreDirectory &dir(GetDirectoryInternal(ObjectID));
+
+ // Changes made?
+ bool changesMade = false;
+
+ // Run through files
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+
+ while((en = i.Next(Undelete?(BackupStoreDirectory::Entry::Flags_Deleted):(BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING),
+ Undelete?(0):(BackupStoreDirectory::Entry::Flags_Deleted))) != 0) // Ignore deleted directories (or not deleted if Undelete)
+ {
+ // Add/remove the deleted flags
+ if(Undelete)
+ {
+ en->RemoveFlags(BackupStoreDirectory::Entry::Flags_Deleted);
+ }
+ else
+ {
+ en->AddFlags(BackupStoreDirectory::Entry::Flags_Deleted);
+ }
+
+ // Keep count of the deleted blocks
+ if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_File) != 0)
+ {
+ rBlocksDeletedOut += en->GetSizeInBlocks();
+ }
+
+ // Did something
+ changesMade = true;
+ }
+
+ // Save the directory
+ if(changesMade)
+ {
+ SaveDirectory(dir, ObjectID);
+ }
+ }
+ }
+ catch(...)
+ {
+ RemoveDirectoryFromCache(ObjectID);
+ throw;
+ }
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::ChangeDirAttributes(int64_t, const StreamableMemBlock &, int64_t)
+// Purpose: Change the attributes of a directory
+// Created: 2003/09/06
+//
+// --------------------------------------------------------------------------
+void BackupStoreContext::ChangeDirAttributes(int64_t Directory, const StreamableMemBlock &Attributes, int64_t AttributesModTime)
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+ if(mReadOnly)
+ {
+ THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly)
+ }
+
+ try
+ {
+ // Get the directory we want to modify
+ BackupStoreDirectory &dir(GetDirectoryInternal(Directory));
+
+ // Set attributes
+ dir.SetAttributes(Attributes, AttributesModTime);
+
+ // Save back
+ SaveDirectory(dir, Directory);
+ }
+ catch(...)
+ {
+ RemoveDirectoryFromCache(Directory);
+ throw;
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::ChangeFileAttributes(int64_t, int64_t, const StreamableMemBlock &, int64_t)
+// Purpose: Sets the attributes on a directory entry. Returns true if the object existed, false if it didn't.
+// Created: 2003/09/06
+//
+// --------------------------------------------------------------------------
+bool BackupStoreContext::ChangeFileAttributes(const BackupStoreFilename &rFilename, int64_t InDirectory, const StreamableMemBlock &Attributes, int64_t AttributesHash, int64_t &rObjectIDOut)
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+ if(mReadOnly)
+ {
+ THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly)
+ }
+
+ try
+ {
+ // Get the directory we want to modify
+ BackupStoreDirectory &dir(GetDirectoryInternal(InDirectory));
+
+ // Find the file entry
+ BackupStoreDirectory::Entry *en = 0;
+ // Iterate through current versions of files, only
+ BackupStoreDirectory::Iterator i(dir);
+ while((en = i.Next(
+ BackupStoreDirectory::Entry::Flags_File,
+ BackupStoreDirectory::Entry::Flags_Deleted | BackupStoreDirectory::Entry::Flags_OldVersion)
+ ) != 0)
+ {
+ if(en->GetName() == rFilename)
+ {
+ // Set attributes
+ en->SetAttributes(Attributes, AttributesHash);
+
+ // Tell caller the object ID
+ rObjectIDOut = en->GetObjectID();
+
+ // Done
+ break;
+ }
+ }
+ if(en == 0)
+ {
+ // Didn't find it
+ return false;
+ }
+
+ // Save back
+ SaveDirectory(dir, InDirectory);
+ }
+ catch(...)
+ {
+ RemoveDirectoryFromCache(InDirectory);
+ throw;
+ }
+
+ // Changed, everything OK
+ return true;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::ObjectExists(int64_t)
+// Purpose: Test to see if an object of this ID exists in the store
+// Created: 2003/09/03
+//
+// --------------------------------------------------------------------------
+bool BackupStoreContext::ObjectExists(int64_t ObjectID, int MustBe)
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+
+ // Note that we need to allow object IDs a little bit greater than the last one in the store info,
+ // because the store info may not have got saved in an error condition. Max greater ID is
+ // STORE_INFO_SAVE_DELAY in this case, *2 to be safe.
+ if(ObjectID <= 0 || ObjectID > (mapStoreInfo->GetLastObjectIDUsed() + (STORE_INFO_SAVE_DELAY * 2)))
+ {
+ // Obviously bad object ID
+ return false;
+ }
+
+ // Test to see if it exists on the disc
+ std::string filename;
+ MakeObjectFilename(ObjectID, filename);
+ if(!RaidFileRead::FileExists(mStoreDiscSet, filename))
+ {
+ // RaidFile reports no file there
+ return false;
+ }
+
+ // Do we need to be more specific?
+ if(MustBe != ObjectExists_Anything)
+ {
+ // Open the file
+ std::auto_ptr<RaidFileRead> objectFile(RaidFileRead::Open(mStoreDiscSet, filename));
+
+ // Read the first integer
+ u_int32_t magic;
+ if(!objectFile->ReadFullBuffer(&magic, sizeof(magic), 0 /* not interested in how many read if failure */))
+ {
+ // Failed to get any bytes, must have failed
+ return false;
+ }
+
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ if(MustBe == ObjectExists_File && ntohl(magic) == OBJECTMAGIC_FILE_MAGIC_VALUE_V0)
+ {
+ // Old version detected
+ return true;
+ }
+#endif
+
+ // Right one?
+ u_int32_t requiredMagic = (MustBe == ObjectExists_File)?OBJECTMAGIC_FILE_MAGIC_VALUE_V1:OBJECTMAGIC_DIR_MAGIC_VALUE;
+
+ // Check
+ if(ntohl(magic) != requiredMagic)
+ {
+ return false;
+ }
+
+ // File is implicitly closed
+ }
+
+ return true;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::OpenObject(int64_t)
+// Purpose: Opens an object
+// Created: 2003/09/03
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<IOStream> BackupStoreContext::OpenObject(int64_t ObjectID)
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+
+ // Attempt to open the file
+ std::string fn;
+ MakeObjectFilename(ObjectID, fn);
+ return std::auto_ptr<IOStream>(RaidFileRead::Open(mStoreDiscSet, fn).release());
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::GetClientStoreMarker()
+// Purpose: Retrieve the client store marker
+// Created: 2003/10/29
+//
+// --------------------------------------------------------------------------
+int64_t BackupStoreContext::GetClientStoreMarker()
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+
+ return mapStoreInfo->GetClientStoreMarker();
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::GetStoreDiscUsageInfo(int64_t &, int64_t &, int64_t &)
+// Purpose: Get disc usage info from store info
+// Created: 1/1/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreContext::GetStoreDiscUsageInfo(int64_t &rBlocksUsed, int64_t &rBlocksSoftLimit, int64_t &rBlocksHardLimit)
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+
+ rBlocksUsed = mapStoreInfo->GetBlocksUsed();
+ rBlocksSoftLimit = mapStoreInfo->GetBlocksSoftLimit();
+ rBlocksHardLimit = mapStoreInfo->GetBlocksHardLimit();
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::HardLimitExceeded()
+// Purpose: Returns true if the hard limit has been exceeded
+// Created: 1/1/04
+//
+// --------------------------------------------------------------------------
+bool BackupStoreContext::HardLimitExceeded()
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+
+ return mapStoreInfo->GetBlocksUsed() > mapStoreInfo->GetBlocksHardLimit();
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::SetClientStoreMarker(int64_t)
+// Purpose: Sets the client store marker, and commits it to disc
+// Created: 2003/10/29
+//
+// --------------------------------------------------------------------------
+void BackupStoreContext::SetClientStoreMarker(int64_t ClientStoreMarker)
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+ if(mReadOnly)
+ {
+ THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly)
+ }
+
+ mapStoreInfo->SetClientStoreMarker(ClientStoreMarker);
+ SaveStoreInfo(false /* don't delay saving this */);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::MoveObject(int64_t, int64_t, int64_t, const BackupStoreFilename &, bool)
+// Purpose: Move an object (and all objects with the same name) from one directory to another
+// Created: 12/11/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreContext::MoveObject(int64_t ObjectID, int64_t MoveFromDirectory, int64_t MoveToDirectory, const BackupStoreFilename &rNewFilename, bool MoveAllWithSameName, bool AllowMoveOverDeletedObject)
+{
+ if(mReadOnly)
+ {
+ THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly)
+ }
+
+ // Should deleted files be excluded when checking for the existance of objects with the target name?
+ int64_t targetSearchExcludeFlags = (AllowMoveOverDeletedObject)
+ ?(BackupStoreDirectory::Entry::Flags_Deleted)
+ :(BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING);
+
+ // Special case if the directories are the same...
+ if(MoveFromDirectory == MoveToDirectory)
+ {
+ try
+ {
+ // Get the first directory
+ BackupStoreDirectory &dir(GetDirectoryInternal(MoveFromDirectory));
+
+ // Find the file entry
+ BackupStoreDirectory::Entry *en = dir.FindEntryByID(ObjectID);
+
+ // Error if not found
+ if(en == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldNotFindEntryInDirectory)
+ }
+
+ // Check the new name doens't already exist (optionally ignoring deleted files)
+ {
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *c = 0;
+ while((c = i.Next(BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING, targetSearchExcludeFlags)) != 0)
+ {
+ if(c->GetName() == rNewFilename)
+ {
+ THROW_EXCEPTION(BackupStoreException, NameAlreadyExistsInDirectory)
+ }
+ }
+ }
+
+ // Need to get all the entries with the same name?
+ if(MoveAllWithSameName)
+ {
+ // Iterate through the directory, copying all with matching names
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *c = 0;
+ while((c = i.Next()) != 0)
+ {
+ if(c->GetName() == en->GetName())
+ {
+ // Rename this one
+ c->SetName(rNewFilename);
+ }
+ }
+ }
+ else
+ {
+ // Just copy this one
+ en->SetName(rNewFilename);
+ }
+
+ // Save the directory back
+ SaveDirectory(dir, MoveFromDirectory);
+ }
+ catch(...)
+ {
+ RemoveDirectoryFromCache(MoveToDirectory); // either will do, as they're the same
+ throw;
+ }
+
+ return;
+ }
+
+ // Got to be careful how this is written, as we can't guarentte that if we have two
+ // directories open, the first won't be deleted as the second is opened. (cache)
+
+ // List of entries to move
+ std::vector<BackupStoreDirectory::Entry *> moving;
+
+ // list of directory IDs which need to have containing dir id changed
+ std::vector<int64_t> dirsToChangeContainingID;
+
+ try
+ {
+ // First of all, get copies of the entries to move to the to directory.
+
+ {
+ // Get the first directory
+ BackupStoreDirectory &from(GetDirectoryInternal(MoveFromDirectory));
+
+ // Find the file entry
+ BackupStoreDirectory::Entry *en = from.FindEntryByID(ObjectID);
+
+ // Error if not found
+ if(en == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldNotFindEntryInDirectory)
+ }
+
+ // Need to get all the entries with the same name?
+ if(MoveAllWithSameName)
+ {
+ // Iterate through the directory, copying all with matching names
+ BackupStoreDirectory::Iterator i(from);
+ BackupStoreDirectory::Entry *c = 0;
+ while((c = i.Next()) != 0)
+ {
+ if(c->GetName() == en->GetName())
+ {
+ // Copy
+ moving.push_back(new BackupStoreDirectory::Entry(*c));
+
+ // Check for containing directory correction
+ if(c->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) dirsToChangeContainingID.push_back(c->GetObjectID());
+ }
+ }
+ ASSERT(!moving.empty());
+ }
+ else
+ {
+ // Just copy this one
+ moving.push_back(new BackupStoreDirectory::Entry(*en));
+
+ // Check for containing directory correction
+ if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) dirsToChangeContainingID.push_back(en->GetObjectID());
+ }
+ }
+
+ // Secondly, insert them into the to directory, and save it
+
+ {
+ // To directory
+ BackupStoreDirectory &to(GetDirectoryInternal(MoveToDirectory));
+
+ // Check the new name doens't already exist
+ {
+ BackupStoreDirectory::Iterator i(to);
+ BackupStoreDirectory::Entry *c = 0;
+ while((c = i.Next(BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING, targetSearchExcludeFlags)) != 0)
+ {
+ if(c->GetName() == rNewFilename)
+ {
+ THROW_EXCEPTION(BackupStoreException, NameAlreadyExistsInDirectory)
+ }
+ }
+ }
+
+ // Copy the entries into it, changing the name as we go
+ for(std::vector<BackupStoreDirectory::Entry *>::iterator i(moving.begin()); i != moving.end(); ++i)
+ {
+ BackupStoreDirectory::Entry *en = (*i);
+ en->SetName(rNewFilename);
+ to.AddEntry(*en); // adds copy
+ }
+
+ // Save back
+ SaveDirectory(to, MoveToDirectory);
+ }
+
+ // Thirdly... remove them from the first directory -- but if it fails, attempt to delete them from the to directory
+ try
+ {
+ // Get directory
+ BackupStoreDirectory &from(GetDirectoryInternal(MoveFromDirectory));
+
+ // Delete each one
+ for(std::vector<BackupStoreDirectory::Entry *>::iterator i(moving.begin()); i != moving.end(); ++i)
+ {
+ from.DeleteEntry((*i)->GetObjectID());
+ }
+
+ // Save back
+ SaveDirectory(from, MoveFromDirectory);
+ }
+ catch(...)
+ {
+ // UNDO modification to To directory
+
+ // Get directory
+ BackupStoreDirectory &to(GetDirectoryInternal(MoveToDirectory));
+
+ // Delete each one
+ for(std::vector<BackupStoreDirectory::Entry *>::iterator i(moving.begin()); i != moving.end(); ++i)
+ {
+ to.DeleteEntry((*i)->GetObjectID());
+ }
+
+ // Save back
+ SaveDirectory(to, MoveToDirectory);
+
+ // Throw the error
+ throw;
+ }
+
+ // Finally... for all the directories we moved, modify their containing directory ID
+ for(std::vector<int64_t>::iterator i(dirsToChangeContainingID.begin()); i != dirsToChangeContainingID.end(); ++i)
+ {
+ // Load the directory
+ BackupStoreDirectory &change(GetDirectoryInternal(*i));
+
+ // Modify containing dir ID
+ change.SetContainerID(MoveToDirectory);
+
+ // Save it back
+ SaveDirectory(change, *i);
+ }
+ }
+ catch(...)
+ {
+ // Make sure directories aren't in the cache, as they may have been modified
+ RemoveDirectoryFromCache(MoveToDirectory);
+ RemoveDirectoryFromCache(MoveFromDirectory);
+ for(std::vector<int64_t>::iterator i(dirsToChangeContainingID.begin()); i != dirsToChangeContainingID.end(); ++i)
+ {
+ RemoveDirectoryFromCache(*i);
+ }
+
+ while(!moving.empty())
+ {
+ delete moving.back();
+ moving.pop_back();
+ }
+ throw;
+ }
+
+ // Clean up
+ while(!moving.empty())
+ {
+ delete moving.back();
+ moving.pop_back();
+ }
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreContext::GetBackupStoreInfo()
+// Purpose: Return the backup store info object, exception if it isn't loaded
+// Created: 19/4/04
+//
+// --------------------------------------------------------------------------
+const BackupStoreInfo &BackupStoreContext::GetBackupStoreInfo() const
+{
+ if(mapStoreInfo.get() == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded)
+ }
+
+ return *(mapStoreInfo.get());
+}
+
+
diff --git a/lib/backupstore/BackupStoreContext.h b/lib/backupstore/BackupStoreContext.h
new file mode 100644
index 00000000..c33e7d50
--- /dev/null
+++ b/lib/backupstore/BackupStoreContext.h
@@ -0,0 +1,211 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreContext.h
+// Purpose: Context for backup store server
+// Created: 2003/08/20
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPCONTEXT__H
+#define BACKUPCONTEXT__H
+
+#include <string>
+#include <map>
+#include <memory>
+
+#include "autogen_BackupProtocol.h"
+#include "BackupStoreInfo.h"
+#include "BackupStoreRefCountDatabase.h"
+#include "NamedLock.h"
+#include "Message.h"
+#include "Utils.h"
+
+class BackupStoreDirectory;
+class BackupStoreFilename;
+class IOStream;
+class BackupProtocolMessage;
+class StreamableMemBlock;
+
+class HousekeepingInterface
+{
+ public:
+ virtual ~HousekeepingInterface() { }
+ virtual void SendMessageToHousekeepingProcess(const void *Msg, int MsgLen) = 0;
+};
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: BackupStoreContext
+// Purpose: Context for backup store server
+// Created: 2003/08/20
+//
+// --------------------------------------------------------------------------
+class BackupStoreContext
+{
+public:
+ BackupStoreContext(int32_t ClientID, HousekeepingInterface &rDaemon,
+ const std::string& rConnectionDetails);
+ ~BackupStoreContext();
+private:
+ BackupStoreContext(const BackupStoreContext &rToCopy);
+public:
+
+ void ReceivedFinishCommand();
+ void CleanUp();
+
+ int32_t GetClientID() {return mClientID;}
+
+ enum
+ {
+ Phase_START = 0,
+ Phase_Version = 0,
+ Phase_Login = 1,
+ Phase_Commands = 2
+ };
+
+ int GetPhase() const {return mProtocolPhase;}
+ std::string GetPhaseName() const
+ {
+ switch(mProtocolPhase)
+ {
+ case Phase_Version: return "Phase_Version";
+ case Phase_Login: return "Phase_Login";
+ case Phase_Commands: return "Phase_Commands";
+ default:
+ std::ostringstream oss;
+ oss << "Unknown phase " << mProtocolPhase;
+ return oss.str();
+ }
+ }
+ void SetPhase(int NewPhase) {mProtocolPhase = NewPhase;}
+
+ // Read only locking
+ bool SessionIsReadOnly() {return mReadOnly;}
+ bool AttemptToGetWriteLock();
+
+ void SetClientHasAccount(const std::string &rStoreRoot, int StoreDiscSet) {mClientHasAccount = true; mStoreRoot = rStoreRoot; mStoreDiscSet = StoreDiscSet;}
+ bool GetClientHasAccount() const {return mClientHasAccount;}
+ const std::string &GetStoreRoot() const {return mStoreRoot;}
+ int GetStoreDiscSet() const {return mStoreDiscSet;}
+
+ // Store info
+ void LoadStoreInfo();
+ void SaveStoreInfo(bool AllowDelay = true);
+ const BackupStoreInfo &GetBackupStoreInfo() const;
+ const std::string GetAccountName()
+ {
+ if(!mapStoreInfo.get())
+ {
+ return "Unknown";
+ }
+ return mapStoreInfo->GetAccountName();
+ }
+
+ // Client marker
+ int64_t GetClientStoreMarker();
+ void SetClientStoreMarker(int64_t ClientStoreMarker);
+
+ // Usage information
+ void GetStoreDiscUsageInfo(int64_t &rBlocksUsed, int64_t &rBlocksSoftLimit, int64_t &rBlocksHardLimit);
+ bool HardLimitExceeded();
+
+ // Reading directories
+ // --------------------------------------------------------------------------
+ //
+ // Function
+ // Name: BackupStoreContext::GetDirectory(int64_t)
+ // Purpose: Return a reference to a directory. Valid only until the
+ // next time a function which affects directories is called.
+ // Mainly this funciton, and creation of files.
+ // Created: 2003/09/02
+ //
+ // --------------------------------------------------------------------------
+ const BackupStoreDirectory &GetDirectory(int64_t ObjectID)
+ {
+ // External callers aren't allowed to change it -- this function
+ // merely turns the the returned directory const.
+ return GetDirectoryInternal(ObjectID);
+ }
+
+ // Manipulating files/directories
+ int64_t AddFile(IOStream &rFile, int64_t InDirectory, int64_t ModificationTime, int64_t AttributesHash, int64_t DiffFromFileID, const BackupStoreFilename &rFilename, bool MarkFileWithSameNameAsOldVersions);
+ int64_t AddDirectory(int64_t InDirectory, const BackupStoreFilename &rFilename, const StreamableMemBlock &Attributes, int64_t AttributesModTime, bool &rAlreadyExists);
+ void ChangeDirAttributes(int64_t Directory, const StreamableMemBlock &Attributes, int64_t AttributesModTime);
+ bool ChangeFileAttributes(const BackupStoreFilename &rFilename, int64_t InDirectory, const StreamableMemBlock &Attributes, int64_t AttributesHash, int64_t &rObjectIDOut);
+ bool DeleteFile(const BackupStoreFilename &rFilename, int64_t InDirectory, int64_t &rObjectIDOut);
+ bool UndeleteFile(int64_t ObjectID, int64_t InDirectory);
+ void DeleteDirectory(int64_t ObjectID, bool Undelete = false);
+ void MoveObject(int64_t ObjectID, int64_t MoveFromDirectory, int64_t MoveToDirectory, const BackupStoreFilename &rNewFilename, bool MoveAllWithSameName, bool AllowMoveOverDeletedObject);
+
+ // Manipulating objects
+ enum
+ {
+ ObjectExists_Anything = 0,
+ ObjectExists_File = 1,
+ ObjectExists_Directory = 2
+ };
+ bool ObjectExists(int64_t ObjectID, int MustBe = ObjectExists_Anything);
+ std::auto_ptr<IOStream> OpenObject(int64_t ObjectID);
+
+ // Info
+ int32_t GetClientID() const {return mClientID;}
+ const std::string& GetConnectionDetails() { return mConnectionDetails; }
+
+private:
+ void MakeObjectFilename(int64_t ObjectID, std::string &rOutput, bool EnsureDirectoryExists = false);
+ BackupStoreDirectory &GetDirectoryInternal(int64_t ObjectID);
+ void SaveDirectory(BackupStoreDirectory &rDir, int64_t ObjectID);
+ void RemoveDirectoryFromCache(int64_t ObjectID);
+ void DeleteDirectoryRecurse(int64_t ObjectID, int64_t &rBlocksDeletedOut, bool Undelete);
+ int64_t AllocateObjectID();
+
+ std::string mConnectionDetails;
+ int32_t mClientID;
+ HousekeepingInterface &mrDaemon;
+ int mProtocolPhase;
+ bool mClientHasAccount;
+ std::string mStoreRoot; // has final directory separator
+ int mStoreDiscSet;
+ bool mReadOnly;
+ NamedLock mWriteLock;
+ int mSaveStoreInfoDelay; // how many times to delay saving the store info
+
+ // Store info
+ std::auto_ptr<BackupStoreInfo> mapStoreInfo;
+
+ // Refcount database
+ std::auto_ptr<BackupStoreRefCountDatabase> mapRefCount;
+
+ // Directory cache
+ std::map<int64_t, BackupStoreDirectory*> mDirectoryCache;
+
+public:
+ class TestHook
+ {
+ public:
+ virtual std::auto_ptr<BackupProtocolMessage>
+ StartCommand(const BackupProtocolMessage& rCommand) = 0;
+ virtual ~TestHook() { }
+ };
+ void SetTestHook(TestHook& rTestHook)
+ {
+ mpTestHook = &rTestHook;
+ }
+ std::auto_ptr<BackupProtocolMessage>
+ StartCommandHook(const BackupProtocolMessage& rCommand)
+ {
+ if(mpTestHook)
+ {
+ return mpTestHook->StartCommand(rCommand);
+ }
+ return std::auto_ptr<BackupProtocolMessage>();
+ }
+
+private:
+ TestHook* mpTestHook;
+};
+
+#endif // BACKUPCONTEXT__H
+
diff --git a/lib/backupstore/BackupStoreDirectory.cpp b/lib/backupstore/BackupStoreDirectory.cpp
new file mode 100644
index 00000000..81126ede
--- /dev/null
+++ b/lib/backupstore/BackupStoreDirectory.cpp
@@ -0,0 +1,578 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreDirectory.h
+// Purpose: Representation of a backup directory
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <sys/types.h>
+
+#include "BackupStoreDirectory.h"
+#include "IOStream.h"
+#include "BackupStoreException.h"
+#include "BackupStoreObjectMagic.h"
+
+#include "MemLeakFindOn.h"
+
+// set packing to one byte
+#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS
+#include "BeginStructPackForWire.h"
+#else
+BEGIN_STRUCTURE_PACKING_FOR_WIRE
+#endif
+
+typedef struct
+{
+ int32_t mMagicValue; // also the version number
+ int32_t mNumEntries;
+ int64_t mObjectID; // this object ID
+ int64_t mContainerID; // ID of container
+ uint64_t mAttributesModTime;
+ int32_t mOptionsPresent; // bit mask of optional sections / features present
+ // Then a StreamableMemBlock for attributes
+} dir_StreamFormat;
+
+typedef enum
+{
+ Option_DependencyInfoPresent = 1
+} dir_StreamFormatOptions;
+
+typedef struct
+{
+ uint64_t mModificationTime;
+ int64_t mObjectID;
+ int64_t mSizeInBlocks;
+ uint64_t mAttributesHash;
+ int16_t mFlags; // order smaller items after bigger ones (for alignment)
+ // Then a BackupStoreFilename
+ // Then a StreamableMemBlock for attributes
+} en_StreamFormat;
+
+typedef struct
+{
+ int64_t mDependsNewer;
+ int64_t mDependsOlder;
+} en_StreamFormatDepends;
+
+// Use default packing
+#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS
+#include "EndStructPackForWire.h"
+#else
+END_STRUCTURE_PACKING_FOR_WIRE
+#endif
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::BackupStoreDirectory()
+// Purpose: Constructor
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+BackupStoreDirectory::BackupStoreDirectory()
+ : mRevisionID(0), mObjectID(0), mContainerID(0), mAttributesModTime(0), mUserInfo1(0)
+{
+ ASSERT(sizeof(u_int64_t) == sizeof(box_time_t));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreDirectory::BackupStoreDirectory(int64_t, int64_t)
+// Purpose: Constructor giving object and container IDs
+// Created: 2003/08/28
+//
+// --------------------------------------------------------------------------
+BackupStoreDirectory::BackupStoreDirectory(int64_t ObjectID, int64_t ContainerID)
+ : mRevisionID(0), mObjectID(ObjectID), mContainerID(ContainerID), mAttributesModTime(0), mUserInfo1(0)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::~BackupStoreDirectory()
+// Purpose: Destructor
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+BackupStoreDirectory::~BackupStoreDirectory()
+{
+ for(std::vector<Entry*>::iterator i(mEntries.begin()); i != mEntries.end(); ++i)
+ {
+ delete (*i);
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::ReadFromStream(IOStream &, int)
+// Purpose: Reads the directory contents from a stream.
+// Exceptions will result in incomplete reads.
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+void BackupStoreDirectory::ReadFromStream(IOStream &rStream, int Timeout)
+{
+ // Get the header
+ dir_StreamFormat hdr;
+ if(!rStream.ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */, Timeout))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Check magic value...
+ if(OBJECTMAGIC_DIR_MAGIC_VALUE != ntohl(hdr.mMagicValue))
+ {
+ THROW_EXCEPTION_MESSAGE(BackupStoreException, BadDirectoryFormat,
+ "Wrong magic number in directory object " <<
+ BOX_FORMAT_OBJECTID(mObjectID) << ": expected " <<
+ BOX_FORMAT_HEX32(OBJECTMAGIC_DIR_MAGIC_VALUE) <<
+ " but found " <<
+ BOX_FORMAT_HEX32(ntohl(hdr.mMagicValue)));
+ }
+
+ // Get data
+ mObjectID = box_ntoh64(hdr.mObjectID);
+ mContainerID = box_ntoh64(hdr.mContainerID);
+ mAttributesModTime = box_ntoh64(hdr.mAttributesModTime);
+
+ // Options
+ int32_t options = ntohl(hdr.mOptionsPresent);
+
+ // Get attributes
+ mAttributes.ReadFromStream(rStream, Timeout);
+
+ // Decode count
+ int count = ntohl(hdr.mNumEntries);
+
+ // Clear existing list
+ for(std::vector<Entry*>::iterator i = mEntries.begin();
+ i != mEntries.end(); i++)
+ {
+ delete (*i);
+ }
+ mEntries.clear();
+
+ // Read them in!
+ for(int c = 0; c < count; ++c)
+ {
+ Entry *pen = new Entry;
+ try
+ {
+ // Read from stream
+ pen->ReadFromStream(rStream, Timeout);
+
+ // Add to list
+ mEntries.push_back(pen);
+ }
+ catch(...)
+ {
+ delete pen;
+ throw;
+ }
+ }
+
+ // Read in dependency info?
+ if(options & Option_DependencyInfoPresent)
+ {
+ // Read in extra dependency data
+ for(int c = 0; c < count; ++c)
+ {
+ mEntries[c]->ReadFromStreamDependencyInfo(rStream, Timeout);
+ }
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::WriteToStream(IOStream &, int16_t, int16_t, bool, bool)
+// Purpose: Writes a selection of entries to a stream
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+void BackupStoreDirectory::WriteToStream(IOStream &rStream, int16_t FlagsMustBeSet, int16_t FlagsNotToBeSet, bool StreamAttributes, bool StreamDependencyInfo) const
+{
+ // Get count of entries
+ int32_t count = mEntries.size();
+ if(FlagsMustBeSet != Entry::Flags_INCLUDE_EVERYTHING || FlagsNotToBeSet != Entry::Flags_EXCLUDE_NOTHING)
+ {
+ // Need to count the entries
+ count = 0;
+ Iterator i(*this);
+ while(i.Next(FlagsMustBeSet, FlagsNotToBeSet) != 0)
+ {
+ count++;
+ }
+ }
+
+ // Check that sensible IDs have been set
+ ASSERT(mObjectID != 0);
+ ASSERT(mContainerID != 0);
+
+ // Need dependency info?
+ bool dependencyInfoRequired = false;
+ if(StreamDependencyInfo)
+ {
+ Iterator i(*this);
+ Entry *pen = 0;
+ while((pen = i.Next(FlagsMustBeSet, FlagsNotToBeSet)) != 0)
+ {
+ if(pen->HasDependencies())
+ {
+ dependencyInfoRequired = true;
+ }
+ }
+ }
+
+ // Options
+ int32_t options = 0;
+ if(dependencyInfoRequired) options |= Option_DependencyInfoPresent;
+
+ // Build header
+ dir_StreamFormat hdr;
+ hdr.mMagicValue = htonl(OBJECTMAGIC_DIR_MAGIC_VALUE);
+ hdr.mNumEntries = htonl(count);
+ hdr.mObjectID = box_hton64(mObjectID);
+ hdr.mContainerID = box_hton64(mContainerID);
+ hdr.mAttributesModTime = box_hton64(mAttributesModTime);
+ hdr.mOptionsPresent = htonl(options);
+
+ // Write header
+ rStream.Write(&hdr, sizeof(hdr));
+
+ // Write the attributes?
+ if(StreamAttributes)
+ {
+ mAttributes.WriteToStream(rStream);
+ }
+ else
+ {
+ // Write a blank header instead
+ StreamableMemBlock::WriteEmptyBlockToStream(rStream);
+ }
+
+ // Then write all the entries
+ Iterator i(*this);
+ Entry *pen = 0;
+ while((pen = i.Next(FlagsMustBeSet, FlagsNotToBeSet)) != 0)
+ {
+ pen->WriteToStream(rStream);
+ }
+
+ // Write dependency info?
+ if(dependencyInfoRequired)
+ {
+ Iterator i(*this);
+ Entry *pen = 0;
+ while((pen = i.Next(FlagsMustBeSet, FlagsNotToBeSet)) != 0)
+ {
+ pen->WriteToStreamDependencyInfo(rStream);
+ }
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::AddEntry(const Entry &)
+// Purpose: Adds entry to directory (no checking)
+// Created: 2003/08/27
+//
+// --------------------------------------------------------------------------
+BackupStoreDirectory::Entry *BackupStoreDirectory::AddEntry(const Entry &rEntryToCopy)
+{
+ Entry *pnew = new Entry(rEntryToCopy);
+ try
+ {
+ mEntries.push_back(pnew);
+ }
+ catch(...)
+ {
+ delete pnew;
+ throw;
+ }
+
+ return pnew;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::AddEntry(const BackupStoreFilename &, int64_t, int64_t, int16_t)
+// Purpose: Adds entry to directory (no checking)
+// Created: 2003/08/27
+//
+// --------------------------------------------------------------------------
+BackupStoreDirectory::Entry *
+BackupStoreDirectory::AddEntry(const BackupStoreFilename &rName,
+ box_time_t ModificationTime, int64_t ObjectID, int64_t SizeInBlocks,
+ int16_t Flags, uint64_t AttributesHash)
+{
+ Entry *pnew = new Entry(rName, ModificationTime, ObjectID,
+ SizeInBlocks, Flags, AttributesHash);
+ try
+ {
+ mEntries.push_back(pnew);
+ }
+ catch(...)
+ {
+ delete pnew;
+ throw;
+ }
+
+ return pnew;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::DeleteEntry(int64_t)
+// Purpose: Deletes entry with given object ID (uses linear search, maybe a little inefficient)
+// Created: 2003/08/27
+//
+// --------------------------------------------------------------------------
+void BackupStoreDirectory::DeleteEntry(int64_t ObjectID)
+{
+ for(std::vector<Entry*>::iterator i(mEntries.begin());
+ i != mEntries.end(); ++i)
+ {
+ if((*i)->mObjectID == ObjectID)
+ {
+ // Delete
+ delete (*i);
+ // Remove from list
+ mEntries.erase(i);
+ // Done
+ return;
+ }
+ }
+
+ // Not found
+ THROW_EXCEPTION(BackupStoreException, CouldNotFindEntryInDirectory)
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::FindEntryByID(int64_t)
+// Purpose: Finds a specific entry. Returns 0 if the entry doesn't exist.
+// Created: 12/11/03
+//
+// --------------------------------------------------------------------------
+BackupStoreDirectory::Entry *BackupStoreDirectory::FindEntryByID(int64_t ObjectID) const
+{
+ for(std::vector<Entry*>::const_iterator i(mEntries.begin());
+ i != mEntries.end(); ++i)
+ {
+ if((*i)->mObjectID == ObjectID)
+ {
+ // Found
+ return (*i);
+ }
+ }
+
+ // Not found
+ return 0;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::Entry::Entry()
+// Purpose: Constructor
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+BackupStoreDirectory::Entry::Entry()
+ : mModificationTime(0),
+ mObjectID(0),
+ mSizeInBlocks(0),
+ mFlags(0),
+ mAttributesHash(0),
+ mMinMarkNumber(0),
+ mMarkNumber(0),
+ mDependsNewer(0),
+ mDependsOlder(0)
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::Entry::~Entry()
+// Purpose: Destructor
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+BackupStoreDirectory::Entry::~Entry()
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::Entry::Entry(const Entry &)
+// Purpose: Copy constructor
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+BackupStoreDirectory::Entry::Entry(const Entry &rToCopy)
+ : mName(rToCopy.mName),
+ mModificationTime(rToCopy.mModificationTime),
+ mObjectID(rToCopy.mObjectID),
+ mSizeInBlocks(rToCopy.mSizeInBlocks),
+ mFlags(rToCopy.mFlags),
+ mAttributesHash(rToCopy.mAttributesHash),
+ mAttributes(rToCopy.mAttributes),
+ mMinMarkNumber(rToCopy.mMinMarkNumber),
+ mMarkNumber(rToCopy.mMarkNumber),
+ mDependsNewer(rToCopy.mDependsNewer),
+ mDependsOlder(rToCopy.mDependsOlder)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::Entry::Entry(const BackupStoreFilename &, int64_t, int64_t, int16_t)
+// Purpose: Constructor from values
+// Created: 2003/08/27
+//
+// --------------------------------------------------------------------------
+BackupStoreDirectory::Entry::Entry(const BackupStoreFilename &rName, box_time_t ModificationTime, int64_t ObjectID, int64_t SizeInBlocks, int16_t Flags, uint64_t AttributesHash)
+ : mName(rName),
+ mModificationTime(ModificationTime),
+ mObjectID(ObjectID),
+ mSizeInBlocks(SizeInBlocks),
+ mFlags(Flags),
+ mAttributesHash(AttributesHash),
+ mMinMarkNumber(0),
+ mMarkNumber(0),
+ mDependsNewer(0),
+ mDependsOlder(0)
+{
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::Entry::TryReading(IOStream &, int)
+// Purpose: Read an entry from a stream
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+void BackupStoreDirectory::Entry::ReadFromStream(IOStream &rStream, int Timeout)
+{
+ // Grab the raw bytes from the stream which compose the header
+ en_StreamFormat entry;
+ if(!rStream.ReadFullBuffer(&entry, sizeof(entry), 0 /* not interested in bytes read if this fails */, Timeout))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Do reading first before modifying the variables, to be more exception safe
+
+ // Get the filename
+ BackupStoreFilename name;
+ name.ReadFromStream(rStream, Timeout);
+
+ // Get the attributes
+ mAttributes.ReadFromStream(rStream, Timeout);
+
+ // Store the rest of the bits
+ mModificationTime = box_ntoh64(entry.mModificationTime);
+ mObjectID = box_ntoh64(entry.mObjectID);
+ mSizeInBlocks = box_ntoh64(entry.mSizeInBlocks);
+ mAttributesHash = box_ntoh64(entry.mAttributesHash);
+ mFlags = ntohs(entry.mFlags);
+ mName = name;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::Entry::WriteToStream(IOStream &)
+// Purpose: Writes the entry to a stream
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+void BackupStoreDirectory::Entry::WriteToStream(IOStream &rStream) const
+{
+ // Build a structure
+ en_StreamFormat entry;
+ entry.mModificationTime = box_hton64(mModificationTime);
+ entry.mObjectID = box_hton64(mObjectID);
+ entry.mSizeInBlocks = box_hton64(mSizeInBlocks);
+ entry.mAttributesHash = box_hton64(mAttributesHash);
+ entry.mFlags = htons(mFlags);
+
+ // Write it
+ rStream.Write(&entry, sizeof(entry));
+
+ // Write the filename
+ mName.WriteToStream(rStream);
+
+ // Write any attributes
+ mAttributes.WriteToStream(rStream);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::Entry::ReadFromStreamDependencyInfo(IOStream &, int)
+// Purpose: Read the optional dependency info from a stream
+// Created: 13/7/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreDirectory::Entry::ReadFromStreamDependencyInfo(IOStream &rStream, int Timeout)
+{
+ // Grab the raw bytes from the stream which compose the header
+ en_StreamFormatDepends depends;
+ if(!rStream.ReadFullBuffer(&depends, sizeof(depends), 0 /* not interested in bytes read if this fails */, Timeout))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Store the data
+ mDependsNewer = box_ntoh64(depends.mDependsNewer);
+ mDependsOlder = box_ntoh64(depends.mDependsOlder);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreDirectory::Entry::WriteToStreamDependencyInfo(IOStream &)
+// Purpose: Write the optional dependency info to a stream
+// Created: 13/7/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreDirectory::Entry::WriteToStreamDependencyInfo(IOStream &rStream) const
+{
+ // Build structure
+ en_StreamFormatDepends depends;
+ depends.mDependsNewer = box_hton64(mDependsNewer);
+ depends.mDependsOlder = box_hton64(mDependsOlder);
+ // Write
+ rStream.Write(&depends, sizeof(depends));
+}
+
+
+
diff --git a/lib/backupstore/BackupStoreDirectory.h b/lib/backupstore/BackupStoreDirectory.h
new file mode 100644
index 00000000..1348f4e6
--- /dev/null
+++ b/lib/backupstore/BackupStoreDirectory.h
@@ -0,0 +1,289 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreDirectory.h
+// Purpose: Representation of a backup directory
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPSTOREDIRECTORY__H
+#define BACKUPSTOREDIRECTORY__H
+
+#include <string>
+#include <vector>
+
+#include "BackupStoreFilenameClear.h"
+#include "StreamableMemBlock.h"
+#include "BoxTime.h"
+
+class IOStream;
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: BackupStoreDirectory
+// Purpose: In memory representation of a directory
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+class BackupStoreDirectory
+{
+public:
+ BackupStoreDirectory();
+ BackupStoreDirectory(int64_t ObjectID, int64_t ContainerID);
+private:
+ // Copying not allowed
+ BackupStoreDirectory(const BackupStoreDirectory &rToCopy);
+public:
+ ~BackupStoreDirectory();
+
+ class Entry
+ {
+ public:
+ friend class BackupStoreDirectory;
+
+ Entry();
+ ~Entry();
+ Entry(const Entry &rToCopy);
+ Entry(const BackupStoreFilename &rName, box_time_t ModificationTime, int64_t ObjectID, int64_t SizeInBlocks, int16_t Flags, uint64_t AttributesHash);
+
+ void ReadFromStream(IOStream &rStream, int Timeout);
+ void WriteToStream(IOStream &rStream) const;
+
+ const BackupStoreFilename &GetName() const {return mName;}
+ box_time_t GetModificationTime() const {return mModificationTime;}
+ int64_t GetObjectID() const {return mObjectID;}
+ int64_t GetSizeInBlocks() const {return mSizeInBlocks;}
+ int16_t GetFlags() const {return mFlags;}
+ void AddFlags(int16_t Flags) {mFlags |= Flags;}
+ void RemoveFlags(int16_t Flags) {mFlags &= ~Flags;}
+
+ // Some things can be changed
+ void SetName(const BackupStoreFilename &rNewName) {mName = rNewName;}
+ void SetSizeInBlocks(int64_t SizeInBlocks) {mSizeInBlocks = SizeInBlocks;}
+
+ // Attributes
+ bool HasAttributes() const {return !mAttributes.IsEmpty();}
+ void SetAttributes(const StreamableMemBlock &rAttr, uint64_t AttributesHash) {mAttributes.Set(rAttr); mAttributesHash = AttributesHash;}
+ const StreamableMemBlock &GetAttributes() const {return mAttributes;}
+ uint64_t GetAttributesHash() const {return mAttributesHash;}
+
+ // Marks
+ // The lowest mark number a version of a file of this name has ever had
+ uint32_t GetMinMarkNumber() const {return mMinMarkNumber;}
+ // The mark number on this file
+ uint32_t GetMarkNumber() const {return mMarkNumber;}
+
+ // Make sure these flags are synced with those in backupprocotol.txt
+ // ListDirectory command
+ enum
+ {
+ Flags_INCLUDE_EVERYTHING = -1,
+ Flags_EXCLUDE_NOTHING = 0,
+ Flags_EXCLUDE_EVERYTHING = 31, // make sure this is kept as sum of ones below!
+ Flags_File = 1,
+ Flags_Dir = 2,
+ Flags_Deleted = 4,
+ Flags_OldVersion = 8,
+ Flags_RemoveASAP = 16 // if this flag is set, housekeeping will remove it as it is marked Deleted or OldVersion
+ };
+ // characters for textual listing of files -- see bbackupquery/BackupQueries
+ #define BACKUPSTOREDIRECTORY_ENTRY_FLAGS_DISPLAY_NAMES "fdXoR"
+
+ // convenience methods
+ bool inline IsDir()
+ {
+ return GetFlags() & Flags_Dir;
+ }
+ bool inline IsFile()
+ {
+ return GetFlags() & Flags_File;
+ }
+ bool inline IsOld()
+ {
+ return GetFlags() & Flags_OldVersion;
+ }
+ bool inline IsDeleted()
+ {
+ return GetFlags() & Flags_Deleted;
+ }
+ bool inline MatchesFlags(int16_t FlagsMustBeSet, int16_t FlagsNotToBeSet)
+ {
+ return ((FlagsMustBeSet == Flags_INCLUDE_EVERYTHING) || ((mFlags & FlagsMustBeSet) == FlagsMustBeSet))
+ && ((mFlags & FlagsNotToBeSet) == 0);
+ };
+
+ // Get dependency info
+ // new version this depends on
+ int64_t GetDependsNewer() const {return mDependsNewer;}
+ void SetDependsNewer(int64_t ObjectID) {mDependsNewer = ObjectID;}
+ // older version which depends on this
+ int64_t GetDependsOlder() const {return mDependsOlder;}
+ void SetDependsOlder(int64_t ObjectID) {mDependsOlder = ObjectID;}
+
+ // Dependency info saving
+ bool HasDependencies() {return mDependsNewer != 0 || mDependsOlder != 0;}
+ void ReadFromStreamDependencyInfo(IOStream &rStream, int Timeout);
+ void WriteToStreamDependencyInfo(IOStream &rStream) const;
+
+ private:
+ BackupStoreFilename mName;
+ box_time_t mModificationTime;
+ int64_t mObjectID;
+ int64_t mSizeInBlocks;
+ int16_t mFlags;
+ uint64_t mAttributesHash;
+ StreamableMemBlock mAttributes;
+ uint32_t mMinMarkNumber;
+ uint32_t mMarkNumber;
+
+ uint64_t mDependsNewer; // new version this depends on
+ uint64_t mDependsOlder; // older version which depends on this
+ };
+
+ void ReadFromStream(IOStream &rStream, int Timeout);
+ void WriteToStream(IOStream &rStream,
+ int16_t FlagsMustBeSet = Entry::Flags_INCLUDE_EVERYTHING,
+ int16_t FlagsNotToBeSet = Entry::Flags_EXCLUDE_NOTHING,
+ bool StreamAttributes = true, bool StreamDependencyInfo = true) const;
+
+ Entry *AddEntry(const Entry &rEntryToCopy);
+ Entry *AddEntry(const BackupStoreFilename &rName,
+ box_time_t ModificationTime, int64_t ObjectID,
+ int64_t SizeInBlocks, int16_t Flags,
+ uint64_t AttributesHash);
+ void DeleteEntry(int64_t ObjectID);
+ Entry *FindEntryByID(int64_t ObjectID) const;
+
+ int64_t GetObjectID() const {return mObjectID;}
+ int64_t GetContainerID() const {return mContainerID;}
+
+ // Need to be able to update the container ID when moving objects
+ void SetContainerID(int64_t ContainerID) {mContainerID = ContainerID;}
+
+ // Purely for use of server -- not serialised into streams
+ int64_t GetRevisionID() const {return mRevisionID;}
+ void SetRevisionID(int64_t RevisionID) {mRevisionID = RevisionID;}
+
+ unsigned int GetNumberOfEntries() const {return mEntries.size();}
+
+ // User info -- not serialised into streams
+ int64_t GetUserInfo1_SizeInBlocks() const {return mUserInfo1;}
+ void SetUserInfo1_SizeInBlocks(int64_t UserInfo1) {mUserInfo1 = UserInfo1;}
+
+ // Attributes
+ bool HasAttributes() const {return !mAttributes.IsEmpty();}
+ void SetAttributes(const StreamableMemBlock &rAttr, box_time_t AttributesModTime) {mAttributes.Set(rAttr); mAttributesModTime = AttributesModTime;}
+ const StreamableMemBlock &GetAttributes() const {return mAttributes;}
+ box_time_t GetAttributesModTime() const {return mAttributesModTime;}
+
+ class Iterator
+ {
+ public:
+ Iterator(const BackupStoreDirectory &rDir)
+ : mrDir(rDir), i(rDir.mEntries.begin())
+ {
+ }
+
+ BackupStoreDirectory::Entry *Next(int16_t FlagsMustBeSet = Entry::Flags_INCLUDE_EVERYTHING, int16_t FlagsNotToBeSet = Entry::Flags_EXCLUDE_NOTHING)
+ {
+ // Skip over things which don't match the required flags
+ while(i != mrDir.mEntries.end() && !(*i)->MatchesFlags(FlagsMustBeSet, FlagsNotToBeSet))
+ {
+ ++i;
+ }
+ // Not the last one?
+ if(i == mrDir.mEntries.end())
+ {
+ return 0;
+ }
+ // Return entry, and increment
+ return (*(i++));
+ }
+
+ // WARNING: This function is really very inefficient.
+ // Only use when you want to look up ONE filename, not in a loop looking up lots.
+ // In a looping situation, cache the decrypted filenames in another memory structure.
+ BackupStoreDirectory::Entry *FindMatchingClearName(const BackupStoreFilenameClear &rFilename, int16_t FlagsMustBeSet = Entry::Flags_INCLUDE_EVERYTHING, int16_t FlagsNotToBeSet = Entry::Flags_EXCLUDE_NOTHING)
+ {
+ // Skip over things which don't match the required flags or filename
+ while( (i != mrDir.mEntries.end())
+ && ( (!(*i)->MatchesFlags(FlagsMustBeSet, FlagsNotToBeSet))
+ || (BackupStoreFilenameClear((*i)->GetName()).GetClearFilename() != rFilename.GetClearFilename()) ) )
+ {
+ ++i;
+ }
+ // Not the last one?
+ if(i == mrDir.mEntries.end())
+ {
+ return 0;
+ }
+ // Return entry, and increment
+ return (*(i++));
+ }
+
+ private:
+ const BackupStoreDirectory &mrDir;
+ std::vector<Entry*>::const_iterator i;
+ };
+
+ friend class Iterator;
+
+ class ReverseIterator
+ {
+ public:
+ ReverseIterator(const BackupStoreDirectory &rDir)
+ : mrDir(rDir), i(rDir.mEntries.rbegin())
+ {
+ }
+
+ BackupStoreDirectory::Entry *Next(int16_t FlagsMustBeSet = Entry::Flags_INCLUDE_EVERYTHING, int16_t FlagsNotToBeSet = Entry::Flags_EXCLUDE_NOTHING)
+ {
+ // Skip over things which don't match the required flags
+ while(i != mrDir.mEntries.rend() && !(*i)->MatchesFlags(FlagsMustBeSet, FlagsNotToBeSet))
+ {
+ ++i;
+ }
+ // Not the last one?
+ if(i == mrDir.mEntries.rend())
+ {
+ return 0;
+ }
+ // Return entry, and increment
+ return (*(i++));
+ }
+
+ private:
+ const BackupStoreDirectory &mrDir;
+ std::vector<Entry*>::const_reverse_iterator i;
+ };
+
+ friend class ReverseIterator;
+
+ // For recovery of the store
+ // Implemented in BackupStoreCheck2.cpp
+ bool CheckAndFix();
+ void AddUnattachedObject(const BackupStoreFilename &rName,
+ box_time_t ModificationTime, int64_t ObjectID,
+ int64_t SizeInBlocks, int16_t Flags);
+ bool NameInUse(const BackupStoreFilename &rName);
+
+ // For testing
+ // Don't use these functions in normal code!
+ void TESTONLY_SetObjectID(int64_t ObjectID) {mObjectID = ObjectID;}
+ // Debug and diagnostics
+ void Dump(void *clibFileHandle, bool ToTrace); // first arg is FILE *, but avoid including stdio.h everywhere
+
+private:
+ int64_t mRevisionID;
+ int64_t mObjectID;
+ int64_t mContainerID;
+ std::vector<Entry*> mEntries;
+ box_time_t mAttributesModTime;
+ StreamableMemBlock mAttributes;
+ int64_t mUserInfo1;
+};
+
+#endif // BACKUPSTOREDIRECTORY__H
+
diff --git a/lib/backupstore/BackupStoreException.h b/lib/backupstore/BackupStoreException.h
new file mode 100644
index 00000000..981dfa60
--- /dev/null
+++ b/lib/backupstore/BackupStoreException.h
@@ -0,0 +1,17 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreException.h
+// Purpose: Exception
+// Created: 2003/07/08
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPSTOREEXCEPTION__H
+#define BACKUPSTOREEXCEPTION__H
+
+// Compatibility
+#include "autogen_BackupStoreException.h"
+
+#endif // BACKUPSTOREEXCEPTION__H
+
diff --git a/lib/backupstore/BackupStoreException.txt b/lib/backupstore/BackupStoreException.txt
new file mode 100644
index 00000000..ece772c0
--- /dev/null
+++ b/lib/backupstore/BackupStoreException.txt
@@ -0,0 +1,72 @@
+EXCEPTION BackupStore 4
+
+Internal 0
+BadAccountDatabaseFile 1
+AccountDatabaseNoSuchEntry 2
+InvalidBackupStoreFilename 3
+UnknownFilenameEncoding 4
+CouldntReadEntireStructureFromStream 5
+BadDirectoryFormat 6
+CouldNotFindEntryInDirectory 7
+OutputFileAlreadyExists 8
+OSFileError 9
+StreamDoesntHaveRequiredFeatures 10
+BadBackupStoreFile 11
+CouldNotLoadStoreInfo 12
+BadStoreInfoOnLoad 13
+StoreInfoIsReadOnly 14
+StoreInfoDirNotInList 15
+StoreInfoBlockDeltaMakesValueNegative 16
+DirectoryHasBeenDeleted 17
+StoreInfoNotInitialised 18
+StoreInfoAlreadyLoaded 19
+StoreInfoNotLoaded 20
+ReadFileFromStreamTimedOut 21
+FileWrongSizeAfterBeingStored 22
+AddedFileDoesNotVerify 23
+StoreInfoForWrongAccount 24
+ContextIsReadOnly 25
+AttributesNotLoaded 26
+AttributesNotUnderstood 27
+WrongServerVersion 28 # client side
+ClientMarkerNotAsExpected 29 Another process logged into the store and modified it while this process was running. Check you're not running two or more clients on the same account.
+NameAlreadyExistsInDirectory 30
+BerkelyDBFailure 31 # client side
+InodeMapIsReadOnly 32 # client side
+InodeMapNotOpen 33 # client side
+FilenameEncryptionKeyNotKnown 34
+FilenameEncryptionNoKeyForSpecifiedMethod 35
+FilenameEncryptionNotSetup 36
+CouldntLoadClientKeyMaterial 37
+BadEncryptedAttributes 38
+EncryptedAttributesHaveUnknownEncoding 39
+OutputSizeTooSmallForChunk 40
+BadEncodedChunk 41
+NotEnoughSpaceToDecodeChunk 42
+ChunkHasUnknownEncoding 43
+ChunkContainsBadCompressedData 44
+CantWriteToEncodedFileStream 45
+Temp_FileEncodeStreamDidntReadBuffer 46
+CantWriteToDecodedFileStream 47
+WhenDecodingExpectedToReadButCouldnt 48
+BackupStoreFileFailedIntegrityCheck 49
+ThereIsNoDataInASymLink 50
+IVLengthForEncodedBlockSizeDoesntMeetLengthRequirements 51
+BlockEntryEncodingDidntGiveExpectedLength 52
+CouldNotFindUnusedIDDuringAllocation 53
+AddedFileExceedsStorageLimit 54
+CannotDiffAnIncompleteStoreFile 55
+CannotDecodeDiffedFilesWithoutCombining 56
+FailedToReadBlockOnCombine 57
+OnCombineFromFileIsIncomplete 58
+BadNotifySysadminEventCode 59
+InternalAlgorithmErrorCheckIDNotMonotonicallyIncreasing 60
+CouldNotLockStoreAccount 61 Another process is accessing this account -- is a client connected to the server?
+AttributeHashSecretNotSet 62
+AEScipherNotSupportedByInstalledOpenSSL 63 The system needs to be compiled with support for OpenSSL 0.9.7 or later to be able to decode files encrypted with AES
+SignalReceived 64 A signal was received by the process, restart or terminate needed. Exception thrown to abort connection.
+IncompatibleFromAndDiffFiles 65 Attempt to use a diff and a from file together, when they're not related
+DiffFromIDNotFoundInDirectory 66 When uploading via a diff, the diff from file must be in the same directory
+PatchChainInfoBadInDirectory 67 A directory contains inconsistent information. Run bbstoreaccounts check to fix it.
+UnknownObjectRefCountRequested 68 A reference count was requested for an object whose reference count is not known.
+MultiplyReferencedObject 69 Attempted to modify an object with multiple references, should be uncloned first
diff --git a/lib/backupstore/BackupStoreFile.cpp b/lib/backupstore/BackupStoreFile.cpp
new file mode 100644
index 00000000..519305ff
--- /dev/null
+++ b/lib/backupstore/BackupStoreFile.cpp
@@ -0,0 +1,1558 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFile.cpp
+// Purpose: Utils for manipulating files
+// Created: 2003/08/28
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#ifdef HAVE_UNISTD_H
+ #include <unistd.h>
+#endif
+
+#include <sys/stat.h>
+#include <string.h>
+#include <new>
+#include <string.h>
+
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ #include <stdio.h>
+#endif
+
+#include "BackupStoreFile.h"
+#include "BackupStoreFileWire.h"
+#include "BackupStoreFileCryptVar.h"
+#include "BackupStoreFilename.h"
+#include "BackupStoreException.h"
+#include "IOStream.h"
+#include "Guards.h"
+#include "FileModificationTime.h"
+#include "FileStream.h"
+#include "BackupClientFileAttributes.h"
+#include "BackupStoreObjectMagic.h"
+#include "Compress.h"
+#include "CipherContext.h"
+#include "CipherBlowfish.h"
+#include "CipherAES.h"
+#include "BackupStoreConstants.h"
+#include "CollectInBufferStream.h"
+#include "RollingChecksum.h"
+#include "MD5Digest.h"
+#include "ReadGatherStream.h"
+#include "Random.h"
+#include "BackupStoreFileEncodeStream.h"
+#include "Logging.h"
+
+#include "MemLeakFindOn.h"
+
+using namespace BackupStoreFileCryptVar;
+
+// How big a buffer to use for copying files
+#define COPY_BUFFER_SIZE (8*1024)
+
+// Statistics
+BackupStoreFileStats BackupStoreFile::msStats = {0,0,0};
+
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ bool sWarnedAboutBackwardsCompatiblity = false;
+#endif
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::EncodeFile(IOStream &, IOStream &)
+// Purpose: Encode a file into something for storing on file server.
+// Requires a real filename so full info can be stored.
+//
+// Returns a stream. Most of the work is done by the stream
+// when data is actually requested -- the file will be held
+// open until the stream is deleted or the file finished.
+// Created: 2003/08/28
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupStoreFileEncodeStream> BackupStoreFile::EncodeFile(
+ const std::string& Filename, int64_t ContainerID,
+ const BackupStoreFilename &rStoreFilename,
+ int64_t *pModificationTime,
+ ReadLoggingStream::Logger* pLogger,
+ RunStatusProvider* pRunStatusProvider)
+{
+ // Create the stream
+ std::auto_ptr<BackupStoreFileEncodeStream> stream(
+ new BackupStoreFileEncodeStream);
+
+ // Do the initial setup
+ stream->Setup(Filename, 0 /* no recipe, just encode */, ContainerID,
+ rStoreFilename, pModificationTime, pLogger, pRunStatusProvider);
+
+ // Return the stream for the caller
+ return stream;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::VerifyEncodedFileFormat(IOStream &)
+// Purpose: Verify that an encoded file meets the format
+// requirements. Doesn't verify that the data is intact
+// and can be decoded. Optionally returns the ID of the
+// file which it is diffed from, and the (original)
+// container ID.
+// Created: 2003/08/28
+//
+// --------------------------------------------------------------------------
+bool BackupStoreFile::VerifyEncodedFileFormat(IOStream &rFile, int64_t *pDiffFromObjectIDOut, int64_t *pContainerIDOut)
+{
+ // Get the size of the file
+ int64_t fileSize = rFile.BytesLeftToRead();
+ if(fileSize == IOStream::SizeOfStreamUnknown)
+ {
+ THROW_EXCEPTION(BackupStoreException, StreamDoesntHaveRequiredFeatures)
+ }
+
+ // Get the header...
+ file_StreamFormat hdr;
+ if(!rFile.ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */))
+ {
+ // Couldn't read header
+ return false;
+ }
+
+ // Check magic number
+ if(ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ && ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V0
+#endif
+ )
+ {
+ return false;
+ }
+
+ // Get a filename, see if it loads OK
+ try
+ {
+ BackupStoreFilename fn;
+ fn.ReadFromStream(rFile, IOStream::TimeOutInfinite);
+ }
+ catch(...)
+ {
+ // an error occured while reading it, so that's not good
+ return false;
+ }
+
+ // Skip the attributes -- because they're encrypted, the server can't tell whether they're OK or not
+ try
+ {
+ int32_t size_s;
+ if(!rFile.ReadFullBuffer(&size_s, sizeof(size_s), 0 /* not interested in bytes read if this fails */))
+ {
+ THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead)
+ }
+ int size = ntohl(size_s);
+ // Skip forward the size
+ rFile.Seek(size, IOStream::SeekType_Relative);
+ }
+ catch(...)
+ {
+ // an error occured while reading it, so that's not good
+ return false;
+ }
+
+ // Get current position in file -- the end of the header
+ int64_t headerEnd = rFile.GetPosition();
+
+ // Get number of blocks
+ int64_t numBlocks = box_ntoh64(hdr.mNumBlocks);
+
+ // Calculate where the block index will be, check it's reasonable
+ int64_t blockIndexLoc = fileSize - ((numBlocks * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader));
+ if(blockIndexLoc < headerEnd)
+ {
+ // Not enough space left for the block index, let alone the blocks themselves
+ return false;
+ }
+
+ // Load the block index header
+ rFile.Seek(blockIndexLoc, IOStream::SeekType_Absolute);
+ file_BlockIndexHeader blkhdr;
+ if(!rFile.ReadFullBuffer(&blkhdr, sizeof(blkhdr), 0 /* not interested in bytes read if this fails */))
+ {
+ // Couldn't read block index header -- assume bad file
+ return false;
+ }
+
+ // Check header
+ if((ntohl(blkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ && ntohl(blkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0
+#endif
+ )
+ || (int64_t)box_ntoh64(blkhdr.mNumBlocks) != numBlocks)
+ {
+ // Bad header -- either magic value or number of blocks is wrong
+ return false;
+ }
+
+ // Flag for recording whether a block is referenced from another file
+ bool blockFromOtherFileReferenced = false;
+
+ // Read the index, checking that the length values all make sense
+ int64_t currentBlockStart = headerEnd;
+ for(int64_t b = 0; b < numBlocks; ++b)
+ {
+ // Read block entry
+ file_BlockIndexEntry blk;
+ if(!rFile.ReadFullBuffer(&blk, sizeof(blk), 0 /* not interested in bytes read if this fails */))
+ {
+ // Couldn't read block index entry -- assume bad file
+ return false;
+ }
+
+ // Check size and location
+ int64_t blkSize = box_ntoh64(blk.mEncodedSize);
+ if(blkSize <= 0)
+ {
+ // Mark that this file references another file
+ blockFromOtherFileReferenced = true;
+ }
+ else
+ {
+ // This block is actually in this file
+ if((currentBlockStart + blkSize) > blockIndexLoc)
+ {
+ // Encoded size makes the block run over the index
+ return false;
+ }
+
+ // Move the current block start ot the end of this block
+ currentBlockStart += blkSize;
+ }
+ }
+
+ // Check that there's no empty space
+ if(currentBlockStart != blockIndexLoc)
+ {
+ return false;
+ }
+
+ // Check that if another block is references, then the ID is there, and if one isn't there is no ID.
+ int64_t otherID = box_ntoh64(blkhdr.mOtherFileID);
+ if((otherID != 0 && blockFromOtherFileReferenced == false)
+ || (otherID == 0 && blockFromOtherFileReferenced == true))
+ {
+ // Doesn't look good!
+ return false;
+ }
+
+ // Does the caller want the other ID?
+ if(pDiffFromObjectIDOut)
+ {
+ *pDiffFromObjectIDOut = otherID;
+ }
+
+ // Does the caller want the container ID?
+ if(pContainerIDOut)
+ {
+ *pContainerIDOut = box_ntoh64(hdr.mContainerID);
+ }
+
+ // Passes all tests
+ return true;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::DecodeFile(IOStream &, const char *)
+// Purpose: Decode a file. Will set file attributes. File must not exist.
+// Created: 2003/08/28
+//
+// --------------------------------------------------------------------------
+void BackupStoreFile::DecodeFile(IOStream &rEncodedFile, const char *DecodedFilename, int Timeout, const BackupClientFileAttributes *pAlterativeAttr)
+{
+ // Does file exist?
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(DecodedFilename, &st) == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, OutputFileAlreadyExists)
+ }
+
+ // Try, delete output file if error
+ try
+ {
+ // Make a stream for outputting this file
+ FileStream out(DecodedFilename, O_WRONLY | O_CREAT | O_EXCL);
+
+ // Get the decoding stream
+ std::auto_ptr<DecodedStream> stream(DecodeFileStream(rEncodedFile, Timeout, pAlterativeAttr));
+
+ // Is it a symlink?
+ if(!stream->IsSymLink())
+ {
+ // Copy it out to the file
+ stream->CopyStreamTo(out);
+ }
+
+ out.Close();
+
+ // The stream might have uncertain size, in which case
+ // we need to drain it to get the
+ // Protocol::ProtocolStreamHeader_EndOfStream byte
+ // out of our connection stream.
+ char buffer[1];
+ int drained = rEncodedFile.Read(buffer, 1);
+
+ // The Read will return 0 if we are actually at the end
+ // of the stream, but some tests decode files directly,
+ // in which case we are actually positioned at the start
+ // of the block index. I hope that reading an extra byte
+ // doesn't hurt!
+ // ASSERT(drained == 0);
+
+ // Write the attributes
+ try
+ {
+ stream->GetAttributes().WriteAttributes(DecodedFilename);
+ }
+ catch (std::exception& e)
+ {
+ BOX_WARNING("Failed to restore attributes on " <<
+ DecodedFilename << ": " << e.what());
+ }
+ }
+ catch(...)
+ {
+ ::unlink(DecodedFilename);
+ throw;
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::DecodeFileStream(IOStream &, int, const BackupClientFileAttributes *)
+// Purpose: Return a stream which will decode the encrypted file data on the fly.
+// Accepts streams in block index first, or main header first, order. In the latter case,
+// the stream must be Seek()able.
+//
+// Before you use the returned stream, call IsSymLink() -- symlink streams won't allow
+// you to read any data to enforce correct logic. See BackupStoreFile::DecodeFile() implementation.
+// Created: 9/12/03
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupStoreFile::DecodedStream> BackupStoreFile::DecodeFileStream(IOStream &rEncodedFile, int Timeout, const BackupClientFileAttributes *pAlterativeAttr)
+{
+ // Create stream
+ std::auto_ptr<DecodedStream> stream(new DecodedStream(rEncodedFile, Timeout));
+
+ // Get it ready
+ stream->Setup(pAlterativeAttr);
+
+ // Return to caller
+ return stream;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::DecodedStream::DecodedStream(IOStream &, int)
+// Purpose: Constructor
+// Created: 9/12/03
+//
+// --------------------------------------------------------------------------
+BackupStoreFile::DecodedStream::DecodedStream(IOStream &rEncodedFile, int Timeout)
+ : mrEncodedFile(rEncodedFile),
+ mTimeout(Timeout),
+ mNumBlocks(0),
+ mpBlockIndex(0),
+ mpEncodedData(0),
+ mpClearData(0),
+ mClearDataSize(0),
+ mCurrentBlock(-1),
+ mCurrentBlockClearSize(0),
+ mPositionInCurrentBlock(0),
+ mEntryIVBase(42) // different to default value in the encoded stream!
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ , mIsOldVersion(false)
+#endif
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::DecodedStream::~DecodedStream()
+// Purpose: Desctructor
+// Created: 9/12/03
+//
+// --------------------------------------------------------------------------
+BackupStoreFile::DecodedStream::~DecodedStream()
+{
+ // Free any allocated memory
+ if(mpBlockIndex)
+ {
+ ::free(mpBlockIndex);
+ }
+ if(mpEncodedData)
+ {
+ BackupStoreFile::CodingChunkFree(mpEncodedData);
+ }
+ if(mpClearData)
+ {
+ ::free(mpClearData);
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::DecodedStream::Setup(const BackupClientFileAttributes *)
+// Purpose: Get the stream ready to decode -- reads in headers
+// Created: 9/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreFile::DecodedStream::Setup(const BackupClientFileAttributes *pAlterativeAttr)
+{
+ // Get the size of the file
+ int64_t fileSize = mrEncodedFile.BytesLeftToRead();
+
+ // Get the magic number to work out which order the stream is in
+ int32_t magic;
+ if(!mrEncodedFile.ReadFullBuffer(&magic, sizeof(magic), 0 /* not interested in bytes read if this fails */, mTimeout))
+ {
+ // Couldn't read magic value
+ THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt)
+ }
+
+ bool inFileOrder = true;
+ switch(ntohl(magic))
+ {
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ case OBJECTMAGIC_FILE_MAGIC_VALUE_V0:
+ mIsOldVersion = true;
+ // control flows on
+#endif
+ case OBJECTMAGIC_FILE_MAGIC_VALUE_V1:
+ inFileOrder = true;
+ break;
+
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ case OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0:
+ mIsOldVersion = true;
+ // control flows on
+#endif
+ case OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1:
+ inFileOrder = false;
+ break;
+
+ default:
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // If not in file order, then the index list must be read now
+ if(!inFileOrder)
+ {
+ ReadBlockIndex(true /* have already read and verified the magic number */);
+ }
+
+ // Get header
+ file_StreamFormat hdr;
+ if(inFileOrder)
+ {
+ // Read the header, without the magic number
+ if(!mrEncodedFile.ReadFullBuffer(((uint8_t*)&hdr) + sizeof(magic), sizeof(hdr) - sizeof(magic),
+ 0 /* not interested in bytes read if this fails */, mTimeout))
+ {
+ // Couldn't read header
+ THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt)
+ }
+ // Put in magic number
+ hdr.mMagicValue = magic;
+ }
+ else
+ {
+ // Not in file order, so need to read the full header
+ if(!mrEncodedFile.ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */, mTimeout))
+ {
+ // Couldn't read header
+ THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt)
+ }
+ }
+
+ // Check magic number
+ if(ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ && ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V0
+#endif
+ )
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // Get the filename
+ mFilename.ReadFromStream(mrEncodedFile, mTimeout);
+
+ // Get the attributes (either from stream, or supplied attributes)
+ if(pAlterativeAttr != 0)
+ {
+ // Read dummy attributes
+ BackupClientFileAttributes attr;
+ attr.ReadFromStream(mrEncodedFile, mTimeout);
+
+ // Set to supplied attributes
+ mAttributes = *pAlterativeAttr;
+ }
+ else
+ {
+ // Read the attributes from the stream
+ mAttributes.ReadFromStream(mrEncodedFile, mTimeout);
+ }
+
+ // If it is in file order, go and read the file attributes
+ // Requires that the stream can seek
+ if(inFileOrder)
+ {
+ // Make sure the file size is known
+ if(fileSize == IOStream::SizeOfStreamUnknown)
+ {
+ THROW_EXCEPTION(BackupStoreException, StreamDoesntHaveRequiredFeatures)
+ }
+
+ // Store current location (beginning of encoded blocks)
+ int64_t endOfHeaderPos = mrEncodedFile.GetPosition();
+
+ // Work out where the index is
+ int64_t numBlocks = box_ntoh64(hdr.mNumBlocks);
+ int64_t blockHeaderPos = fileSize - ((numBlocks * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader));
+
+ // Seek to that position
+ mrEncodedFile.Seek(blockHeaderPos, IOStream::SeekType_Absolute);
+
+ // Read the block index
+ ReadBlockIndex(false /* magic number still to be read */);
+
+ // Seek back to the end of header position, ready for reading the chunks
+ mrEncodedFile.Seek(endOfHeaderPos, IOStream::SeekType_Absolute);
+ }
+
+ // Check view of blocks from block header and file header match
+ if(mNumBlocks != (int64_t)box_ntoh64(hdr.mNumBlocks))
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // Need to allocate some memory for the two blocks for reading encoded data, and clear data
+ if(mNumBlocks > 0)
+ {
+ // Find the maximum encoded data size
+ int32_t maxEncodedDataSize = 0;
+ const file_BlockIndexEntry *entry = (file_BlockIndexEntry *)mpBlockIndex;
+ ASSERT(entry != 0);
+ for(int64_t e = 0; e < mNumBlocks; e++)
+ {
+ // Get the clear and encoded size
+ int32_t encodedSize = box_ntoh64(entry[e].mEncodedSize);
+ ASSERT(encodedSize > 0);
+
+ // Larger?
+ if(encodedSize > maxEncodedDataSize) maxEncodedDataSize = encodedSize;
+ }
+
+ // Allocate those blocks!
+ mpEncodedData = (uint8_t*)BackupStoreFile::CodingChunkAlloc(maxEncodedDataSize + 32);
+
+ // Allocate the block for the clear data, using the hint from the header.
+ // If this is wrong, things will exception neatly later on, so it can't be used
+ // to do anything more than cause an error on downloading.
+ mClearDataSize = OutputBufferSizeForKnownOutputSize(ntohl(hdr.mMaxBlockClearSize)) + 32;
+ mpClearData = (uint8_t*)::malloc(mClearDataSize);
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::DecodedStream::ReadBlockIndex(bool)
+// Purpose: Read the block index from the stream, and store in internal buffer (minus header)
+// Created: 9/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreFile::DecodedStream::ReadBlockIndex(bool MagicAlreadyRead)
+{
+ // Header
+ file_BlockIndexHeader blkhdr;
+
+ // Read it in -- way depends on how whether the magic number has already been read
+ if(MagicAlreadyRead)
+ {
+ // Read the header, without the magic number
+ if(!mrEncodedFile.ReadFullBuffer(((uint8_t*)&blkhdr) + sizeof(blkhdr.mMagicValue), sizeof(blkhdr) - sizeof(blkhdr.mMagicValue),
+ 0 /* not interested in bytes read if this fails */, mTimeout))
+ {
+ // Couldn't read header
+ THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt)
+ }
+ }
+ else
+ {
+ // Magic not already read, so need to read the full header
+ if(!mrEncodedFile.ReadFullBuffer(&blkhdr, sizeof(blkhdr), 0 /* not interested in bytes read if this fails */, mTimeout))
+ {
+ // Couldn't read header
+ THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt)
+ }
+
+ // Check magic value
+ if(ntohl(blkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ && ntohl(blkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0
+#endif
+ )
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ }
+
+ // Get the number of blocks out of the header
+ mNumBlocks = box_ntoh64(blkhdr.mNumBlocks);
+
+ // Read the IV base
+ mEntryIVBase = box_ntoh64(blkhdr.mEntryIVBase);
+
+ // Load the block entries in?
+ if(mNumBlocks > 0)
+ {
+ // How big is the index?
+ int64_t indexSize = sizeof(file_BlockIndexEntry) * mNumBlocks;
+
+ // Allocate some memory
+ mpBlockIndex = ::malloc(indexSize);
+ if(mpBlockIndex == 0)
+ {
+ throw std::bad_alloc();
+ }
+
+ // Read it in
+ if(!mrEncodedFile.ReadFullBuffer(mpBlockIndex, indexSize, 0 /* not interested in bytes read if this fails */, mTimeout))
+ {
+ // Couldn't read header
+ THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt)
+ }
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::DecodedStream::Read(void *, int, int)
+// Purpose: As interface. Reads decrpyted data.
+// Created: 9/12/03
+//
+// --------------------------------------------------------------------------
+int BackupStoreFile::DecodedStream::Read(void *pBuffer, int NBytes, int Timeout)
+{
+ // Symlinks don't have data. So can't read it. Not even zero bytes.
+ if(IsSymLink())
+ {
+ // Don't allow reading in this case
+ THROW_EXCEPTION(BackupStoreException, ThereIsNoDataInASymLink);
+ }
+
+ // Already finished?
+ if(mCurrentBlock >= mNumBlocks)
+ {
+ // At end of stream, nothing to do
+ return 0;
+ }
+
+ int bytesToRead = NBytes;
+ uint8_t *output = (uint8_t*)pBuffer;
+
+ while(bytesToRead > 0 && mCurrentBlock < mNumBlocks)
+ {
+ // Anything left in the current block?
+ if(mPositionInCurrentBlock < mCurrentBlockClearSize)
+ {
+ // Copy data out of this buffer
+ int s = mCurrentBlockClearSize - mPositionInCurrentBlock;
+ if(s > bytesToRead) s = bytesToRead; // limit to requested data
+
+ // Copy
+ ::memcpy(output, mpClearData + mPositionInCurrentBlock, s);
+
+ // Update positions
+ output += s;
+ mPositionInCurrentBlock += s;
+ bytesToRead -= s;
+ }
+
+ // Need to get some more data?
+ if(bytesToRead > 0 && mPositionInCurrentBlock >= mCurrentBlockClearSize)
+ {
+ // Number of next block
+ ++mCurrentBlock;
+ if(mCurrentBlock >= mNumBlocks)
+ {
+ // Stop now!
+ break;
+ }
+
+ // Get the size from the block index
+ const file_BlockIndexEntry *entry = (file_BlockIndexEntry *)mpBlockIndex;
+ int32_t encodedSize = box_ntoh64(entry[mCurrentBlock].mEncodedSize);
+ if(encodedSize <= 0)
+ {
+ // The caller is attempting to decode a file which is the direct result of a diff
+ // operation, and so does not contain all the data.
+ // It needs to be combined with the previous version first.
+ THROW_EXCEPTION(BackupStoreException, CannotDecodeDiffedFilesWithoutCombining)
+ }
+
+ // Load in next block
+ if(!mrEncodedFile.ReadFullBuffer(mpEncodedData, encodedSize, 0 /* not interested in bytes read if this fails */, mTimeout))
+ {
+ // Couldn't read header
+ THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt)
+ }
+
+ // Decode the data
+ mCurrentBlockClearSize = BackupStoreFile::DecodeChunk(mpEncodedData, encodedSize, mpClearData, mClearDataSize);
+
+ // Calculate IV for this entry
+ uint64_t iv = mEntryIVBase;
+ iv += mCurrentBlock;
+ // Convert to network byte order before encrypting with it, so that restores work on
+ // platforms with different endiannesses.
+ iv = box_hton64(iv);
+ sBlowfishDecryptBlockEntry.SetIV(&iv);
+
+ // Decrypt the encrypted section
+ file_BlockIndexEntryEnc entryEnc;
+ int sectionSize = sBlowfishDecryptBlockEntry.TransformBlock(&entryEnc, sizeof(entryEnc),
+ entry[mCurrentBlock].mEnEnc, sizeof(entry[mCurrentBlock].mEnEnc));
+ if(sectionSize != sizeof(entryEnc))
+ {
+ THROW_EXCEPTION(BackupStoreException, BlockEntryEncodingDidntGiveExpectedLength)
+ }
+
+ // Make sure this is the right size
+ if(mCurrentBlockClearSize != (int32_t)ntohl(entryEnc.mSize))
+ {
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ if(!mIsOldVersion)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ // Versions 0.05 and previous of Box Backup didn't properly handle endianess of the
+ // IV for the encrypted section. Try again, with the thing the other way round
+ iv = box_swap64(iv);
+ sBlowfishDecryptBlockEntry.SetIV(&iv);
+ int sectionSize = sBlowfishDecryptBlockEntry.TransformBlock(&entryEnc, sizeof(entryEnc),
+ entry[mCurrentBlock].mEnEnc, sizeof(entry[mCurrentBlock].mEnEnc));
+ if(sectionSize != sizeof(entryEnc))
+ {
+ THROW_EXCEPTION(BackupStoreException, BlockEntryEncodingDidntGiveExpectedLength)
+ }
+ if(mCurrentBlockClearSize != (int32_t)ntohl(entryEnc.mSize))
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ else
+ {
+ // Warn and log this issue
+ if(!sWarnedAboutBackwardsCompatiblity)
+ {
+ BOX_WARNING("WARNING: Decoded one or more files using backwards compatibility mode for block index.");
+ sWarnedAboutBackwardsCompatiblity = true;
+ }
+ }
+#else
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+#endif
+ }
+
+ // Check the digest
+ MD5Digest md5;
+ md5.Add(mpClearData, mCurrentBlockClearSize);
+ md5.Finish();
+ if(!md5.DigestMatches((uint8_t*)entryEnc.mStrongChecksum))
+ {
+ THROW_EXCEPTION(BackupStoreException, BackupStoreFileFailedIntegrityCheck)
+ }
+
+ // Set vars to say what's happening
+ mPositionInCurrentBlock = 0;
+ }
+ }
+
+ ASSERT(bytesToRead >= 0);
+ ASSERT(bytesToRead <= NBytes);
+
+ return NBytes - bytesToRead;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::DecodedStream::IsSymLink()
+// Purpose: Is the unencoded file actually a symlink?
+// Created: 10/12/03
+//
+// --------------------------------------------------------------------------
+bool BackupStoreFile::DecodedStream::IsSymLink()
+{
+ // First, check in with the attributes
+ if(!mAttributes.IsSymLink())
+ {
+ return false;
+ }
+
+ // So the attributes think it is a symlink.
+ // Consistency check...
+ if(mNumBlocks != 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ return true;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::DecodedStream::Write(const void *, int)
+// Purpose: As interface. Throws exception, as you can't write to this stream.
+// Created: 9/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreFile::DecodedStream::Write(const void *pBuffer, int NBytes)
+{
+ THROW_EXCEPTION(BackupStoreException, CantWriteToDecodedFileStream)
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::DecodedStream::StreamDataLeft()
+// Purpose: As interface. Any data left?
+// Created: 9/12/03
+//
+// --------------------------------------------------------------------------
+bool BackupStoreFile::DecodedStream::StreamDataLeft()
+{
+ return mCurrentBlock < mNumBlocks;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::DecodedStream::StreamClosed()
+// Purpose: As interface. Always returns true, no writing allowed.
+// Created: 9/12/03
+//
+// --------------------------------------------------------------------------
+bool BackupStoreFile::DecodedStream::StreamClosed()
+{
+ // Can't write to this stream!
+ return true;
+}
+
+
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::SetBlowfishKey(const void *, int)
+// Purpose: Static. Sets the key to use for encryption and decryption.
+// Created: 7/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreFile::SetBlowfishKeys(const void *pKey, int KeyLength, const void *pBlockEntryKey, int BlockEntryKeyLength)
+{
+ // IVs set later
+ sBlowfishEncrypt.Reset();
+ sBlowfishEncrypt.Init(CipherContext::Encrypt, CipherBlowfish(CipherDescription::Mode_CBC, pKey, KeyLength));
+ sBlowfishDecrypt.Reset();
+ sBlowfishDecrypt.Init(CipherContext::Decrypt, CipherBlowfish(CipherDescription::Mode_CBC, pKey, KeyLength));
+
+ sBlowfishEncryptBlockEntry.Reset();
+ sBlowfishEncryptBlockEntry.Init(CipherContext::Encrypt, CipherBlowfish(CipherDescription::Mode_CBC, pBlockEntryKey, BlockEntryKeyLength));
+ sBlowfishEncryptBlockEntry.UsePadding(false);
+ sBlowfishDecryptBlockEntry.Reset();
+ sBlowfishDecryptBlockEntry.Init(CipherContext::Decrypt, CipherBlowfish(CipherDescription::Mode_CBC, pBlockEntryKey, BlockEntryKeyLength));
+ sBlowfishDecryptBlockEntry.UsePadding(false);
+}
+
+
+#ifndef HAVE_OLD_SSL
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::SetAESKey(const void *, int)
+// Purpose: Sets the AES key to use for file data encryption. Will select AES as
+// the cipher to use when encrypting.
+// Created: 27/4/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreFile::SetAESKey(const void *pKey, int KeyLength)
+{
+ // Setup context
+ sAESEncrypt.Reset();
+ sAESEncrypt.Init(CipherContext::Encrypt, CipherAES(CipherDescription::Mode_CBC, pKey, KeyLength));
+ sAESDecrypt.Reset();
+ sAESDecrypt.Init(CipherContext::Decrypt, CipherAES(CipherDescription::Mode_CBC, pKey, KeyLength));
+
+ // Set encryption to use this key, instead of the "default" blowfish key
+ spEncrypt = &sAESEncrypt;
+ sEncryptCipherType = HEADER_AES_ENCODING;
+}
+#endif
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::MaxBlockSizeForChunkSize(int)
+// Purpose: The maximum output size of a block, given the chunk size
+// Created: 7/12/03
+//
+// --------------------------------------------------------------------------
+int BackupStoreFile::MaxBlockSizeForChunkSize(int ChunkSize)
+{
+ // Calculate... the maximum size of output by first the largest it could be after compression,
+ // which is encrypted, and has a 1 bytes header and the IV added, plus 1 byte for luck
+ // And then on top, add 128 bytes just to make sure. (Belts and braces approach to fixing
+ // an problem where a rather non-compressable file didn't fit in a block buffer.)
+ return sBlowfishEncrypt.MaxOutSizeForInBufferSize(Compress_MaxSizeForCompressedData(ChunkSize)) + 1 + 1
+ + sBlowfishEncrypt.GetIVLength() + 128;
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::EncodeChunk(const void *, int, BackupStoreFile::EncodingBuffer &)
+// Purpose: Encodes a chunk (encryption, possible compressed beforehand)
+// Created: 8/12/03
+//
+// --------------------------------------------------------------------------
+int BackupStoreFile::EncodeChunk(const void *Chunk, int ChunkSize, BackupStoreFile::EncodingBuffer &rOutput)
+{
+ ASSERT(spEncrypt != 0);
+
+ // Check there's some space in the output block
+ if(rOutput.mBufferSize < 256)
+ {
+ rOutput.Reallocate(256);
+ }
+
+ // Check alignment of the block
+ ASSERT((((uint32_t)(long)rOutput.mpBuffer) % BACKUPSTOREFILE_CODING_BLOCKSIZE) == BACKUPSTOREFILE_CODING_OFFSET);
+
+ // Want to compress it?
+ bool compressChunk = (ChunkSize >= BACKUP_FILE_MIN_COMPRESSED_CHUNK_SIZE);
+
+ // Build header
+ uint8_t header = sEncryptCipherType << HEADER_ENCODING_SHIFT;
+ if(compressChunk) header |= HEADER_CHUNK_IS_COMPRESSED;
+
+ // Store header
+ rOutput.mpBuffer[0] = header;
+ int outOffset = 1;
+
+ // Setup cipher, and store the IV
+ int ivLen = 0;
+ const void *iv = spEncrypt->SetRandomIV(ivLen);
+ ::memcpy(rOutput.mpBuffer + outOffset, iv, ivLen);
+ outOffset += ivLen;
+
+ // Start encryption process
+ spEncrypt->Begin();
+
+ #define ENCODECHUNK_CHECK_SPACE(ToEncryptSize) \
+ { \
+ if((rOutput.mBufferSize - outOffset) < ((ToEncryptSize) + 128)) \
+ { \
+ rOutput.Reallocate(rOutput.mBufferSize + (ToEncryptSize) + 128); \
+ } \
+ }
+
+ // Encode the chunk
+ if(compressChunk)
+ {
+ // buffer to compress into
+ uint8_t buffer[2048];
+
+ // Set compressor with all the chunk as an input
+ Compress<true> compress;
+ compress.Input(Chunk, ChunkSize);
+ compress.FinishInput();
+
+ // Get and encrypt output
+ while(!compress.OutputHasFinished())
+ {
+ int s = compress.Output(buffer, sizeof(buffer));
+ if(s > 0)
+ {
+ ENCODECHUNK_CHECK_SPACE(s)
+ outOffset += spEncrypt->Transform(rOutput.mpBuffer + outOffset, rOutput.mBufferSize - outOffset, buffer, s);
+ }
+ else
+ {
+ // Should never happen, as we put all the input in in one go.
+ // So if this happens, it means there's a logical problem somewhere
+ THROW_EXCEPTION(BackupStoreException, Internal)
+ }
+ }
+ ENCODECHUNK_CHECK_SPACE(16)
+ outOffset += spEncrypt->Final(rOutput.mpBuffer + outOffset, rOutput.mBufferSize - outOffset);
+ }
+ else
+ {
+ // Straight encryption
+ ENCODECHUNK_CHECK_SPACE(ChunkSize)
+ outOffset += spEncrypt->Transform(rOutput.mpBuffer + outOffset, rOutput.mBufferSize - outOffset, Chunk, ChunkSize);
+ ENCODECHUNK_CHECK_SPACE(16)
+ outOffset += spEncrypt->Final(rOutput.mpBuffer + outOffset, rOutput.mBufferSize - outOffset);
+ }
+
+ ASSERT(outOffset < rOutput.mBufferSize); // first check should have sorted this -- merely logic check
+
+ return outOffset;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::DecodeChunk(const void *, int, void *, int)
+// Purpose: Decode an encoded chunk -- use OutputBufferSizeForKnownOutputSize() to find
+// the extra output buffer size needed before calling.
+// See notes in EncodeChunk() for notes re alignment of the
+// encoded data.
+// Created: 8/12/03
+//
+// --------------------------------------------------------------------------
+int BackupStoreFile::DecodeChunk(const void *Encoded, int EncodedSize, void *Output, int OutputSize)
+{
+ // Check alignment of the encoded block
+ ASSERT((((uint32_t)(long)Encoded) % BACKUPSTOREFILE_CODING_BLOCKSIZE) == BACKUPSTOREFILE_CODING_OFFSET);
+
+ // First check
+ if(EncodedSize < 1)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadEncodedChunk)
+ }
+
+ const uint8_t *input = (uint8_t*)Encoded;
+
+ // Get header, make checks, etc
+ uint8_t header = input[0];
+ bool chunkCompressed = (header & HEADER_CHUNK_IS_COMPRESSED) == HEADER_CHUNK_IS_COMPRESSED;
+ uint8_t encodingType = (header >> HEADER_ENCODING_SHIFT);
+ if(encodingType != HEADER_BLOWFISH_ENCODING && encodingType != HEADER_AES_ENCODING)
+ {
+ THROW_EXCEPTION(BackupStoreException, ChunkHasUnknownEncoding)
+ }
+
+#ifndef HAVE_OLD_SSL
+ // Choose cipher
+ CipherContext &cipher((encodingType == HEADER_AES_ENCODING)?sAESDecrypt:sBlowfishDecrypt);
+#else
+ // AES not supported with this version of OpenSSL
+ if(encodingType == HEADER_AES_ENCODING)
+ {
+ THROW_EXCEPTION(BackupStoreException, AEScipherNotSupportedByInstalledOpenSSL)
+ }
+ CipherContext &cipher(sBlowfishDecrypt);
+#endif
+
+ // Check enough space for header, an IV and one byte of input
+ int ivLen = cipher.GetIVLength();
+ if(EncodedSize < (1 + ivLen + 1))
+ {
+ THROW_EXCEPTION(BackupStoreException, BadEncodedChunk)
+ }
+
+ // Set IV in decrypt context, and start
+ cipher.SetIV(input + 1);
+ cipher.Begin();
+
+ // Setup vars for code
+ int inOffset = 1 + ivLen;
+ uint8_t *output = (uint8_t*)Output;
+ int outOffset = 0;
+
+ // Do action
+ if(chunkCompressed)
+ {
+ // Do things in chunks
+ uint8_t buffer[2048];
+ int inputBlockLen = cipher.InSizeForOutBufferSize(sizeof(buffer));
+
+ // Decompressor
+ Compress<false> decompress;
+
+ while(inOffset < EncodedSize)
+ {
+ // Decrypt a block
+ int bl = inputBlockLen;
+ if(bl > (EncodedSize - inOffset)) bl = EncodedSize - inOffset; // not too long
+ int s = cipher.Transform(buffer, sizeof(buffer), input + inOffset, bl);
+ inOffset += bl;
+
+ // Decompress the decrypted data
+ if(s > 0)
+ {
+ decompress.Input(buffer, s);
+ int os = 0;
+ do
+ {
+ os = decompress.Output(output + outOffset, OutputSize - outOffset);
+ outOffset += os;
+ } while(os > 0);
+
+ // Check that there's space left in the output buffer -- there always should be
+ if(outOffset >= OutputSize)
+ {
+ THROW_EXCEPTION(BackupStoreException, NotEnoughSpaceToDecodeChunk)
+ }
+ }
+ }
+
+ // Get any compressed data remaining in the cipher context and compression
+ int s = cipher.Final(buffer, sizeof(buffer));
+ decompress.Input(buffer, s);
+ decompress.FinishInput();
+ while(!decompress.OutputHasFinished())
+ {
+ int os = decompress.Output(output + outOffset, OutputSize - outOffset);
+ outOffset += os;
+
+ // Check that there's space left in the output buffer -- there always should be
+ if(outOffset >= OutputSize)
+ {
+ THROW_EXCEPTION(BackupStoreException, NotEnoughSpaceToDecodeChunk)
+ }
+ }
+ }
+ else
+ {
+ // Easy decryption
+ outOffset += cipher.Transform(output + outOffset, OutputSize - outOffset, input + inOffset, EncodedSize - inOffset);
+ outOffset += cipher.Final(output + outOffset, OutputSize - outOffset);
+ }
+
+ return outOffset;
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::ReorderFileToStreamOrder(IOStream *, bool)
+// Purpose: Returns a stream which gives a Stream order version of the encoded file.
+// If TakeOwnership == true, then the input stream will be deleted when the
+// returned stream is deleted.
+// The input stream must be seekable.
+// Created: 10/12/03
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<IOStream> BackupStoreFile::ReorderFileToStreamOrder(IOStream *pStream, bool TakeOwnership)
+{
+ ASSERT(pStream != 0);
+
+ // Get the size of the file
+ int64_t fileSize = pStream->BytesLeftToRead();
+ if(fileSize == IOStream::SizeOfStreamUnknown)
+ {
+ THROW_EXCEPTION(BackupStoreException, StreamDoesntHaveRequiredFeatures)
+ }
+
+ // Read the header
+ int bytesRead = 0;
+ file_StreamFormat hdr;
+ bool readBlock = pStream->ReadFullBuffer(&hdr, sizeof(hdr), &bytesRead);
+
+ // Seek backwards to put the file pointer back where it was before we started this
+ pStream->Seek(0 - bytesRead, IOStream::SeekType_Relative);
+
+ // Check we got a block
+ if(!readBlock)
+ {
+ // Couldn't read header -- assume file bad
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // Check magic number
+ if(ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ && ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V0
+#endif
+ )
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // Get number of blocks
+ int64_t numBlocks = box_ntoh64(hdr.mNumBlocks);
+
+ // Calculate where the block index will be, check it's reasonable
+ int64_t blockIndexSize = ((numBlocks * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader));
+ int64_t blockIndexLoc = fileSize - blockIndexSize;
+ if(blockIndexLoc < 0)
+ {
+ // Doesn't look good!
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // Build a reordered stream
+ std::auto_ptr<IOStream> reordered(new ReadGatherStream(TakeOwnership));
+
+ // Set it up...
+ ReadGatherStream &rreordered(*((ReadGatherStream*)reordered.get()));
+ int component = rreordered.AddComponent(pStream);
+ // Send out the block index
+ rreordered.AddBlock(component, blockIndexSize, true, blockIndexLoc);
+ // And then the rest of the file
+ rreordered.AddBlock(component, blockIndexLoc, true, 0);
+
+ return reordered;
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::ResetStats()
+// Purpose: Reset the gathered statistics
+// Created: 20/1/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreFile::ResetStats()
+{
+ msStats.mBytesInEncodedFiles = 0;
+ msStats.mBytesAlreadyOnServer = 0;
+ msStats.mTotalFileStreamSize = 0;
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::CompareFileContentsAgainstBlockIndex(const char *, IOStream &)
+// Purpose: Compares the contents of a file against the checksums contained in the
+// block index. Returns true if the checksums match, meaning the file is
+// extremely likely to match the original. Will always consume the entire index.
+// Created: 21/1/04
+//
+// --------------------------------------------------------------------------
+bool BackupStoreFile::CompareFileContentsAgainstBlockIndex(const char *Filename, IOStream &rBlockIndex, int Timeout)
+{
+ // is it a symlink?
+ bool sourceIsSymlink = false;
+ {
+ EMU_STRUCT_STAT st;
+ if(EMU_LSTAT(Filename, &st) == -1)
+ {
+ THROW_EXCEPTION(CommonException, OSFileError)
+ }
+ if((st.st_mode & S_IFMT) == S_IFLNK)
+ {
+ sourceIsSymlink = true;
+ }
+ }
+
+ // Open file, if it's not a symlink
+ std::auto_ptr<FileStream> in;
+ if(!sourceIsSymlink)
+ {
+ in.reset(new FileStream(Filename));
+ }
+
+ // Read header
+ file_BlockIndexHeader hdr;
+ if(!rBlockIndex.ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */, Timeout))
+ {
+ // Couldn't read header
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Check magic
+ if(hdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1)
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ && hdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0)
+#endif
+ )
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ bool isOldVersion = hdr.mMagicValue == (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0);
+#endif
+
+ // Get basic information
+ int64_t numBlocks = box_ntoh64(hdr.mNumBlocks);
+ uint64_t entryIVBase = box_ntoh64(hdr.mEntryIVBase);
+
+ //TODO: Verify that these sizes look reasonable
+
+ // setup
+ void *data = 0;
+ int32_t dataSize = -1;
+ bool matches = true;
+ int64_t totalSizeInBlockIndex = 0;
+
+ try
+ {
+ for(int64_t b = 0; b < numBlocks; ++b)
+ {
+ // Read an entry from the stream
+ file_BlockIndexEntry entry;
+ if(!rBlockIndex.ReadFullBuffer(&entry, sizeof(entry), 0 /* not interested in bytes read if this fails */, Timeout))
+ {
+ // Couldn't read entry
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Calculate IV for this entry
+ uint64_t iv = entryIVBase;
+ iv += b;
+ iv = box_hton64(iv);
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ if(isOldVersion)
+ {
+ // Reverse the IV for compatibility
+ iv = box_swap64(iv);
+ }
+#endif
+ sBlowfishDecryptBlockEntry.SetIV(&iv);
+
+ // Decrypt the encrypted section
+ file_BlockIndexEntryEnc entryEnc;
+ int sectionSize = sBlowfishDecryptBlockEntry.TransformBlock(&entryEnc, sizeof(entryEnc),
+ entry.mEnEnc, sizeof(entry.mEnEnc));
+ if(sectionSize != sizeof(entryEnc))
+ {
+ THROW_EXCEPTION(BackupStoreException, BlockEntryEncodingDidntGiveExpectedLength)
+ }
+
+ // Size of block
+ int32_t blockClearSize = ntohl(entryEnc.mSize);
+ if(blockClearSize < 0 || blockClearSize > (BACKUP_FILE_MAX_BLOCK_SIZE + 1024))
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ totalSizeInBlockIndex += blockClearSize;
+
+ // Make sure there's enough memory allocated to load the block in
+ if(dataSize < blockClearSize)
+ {
+ // Too small, free the block if it's already allocated
+ if(data != 0)
+ {
+ ::free(data);
+ data = 0;
+ }
+ // Allocate a block
+ data = ::malloc(blockClearSize + 128);
+ if(data == 0)
+ {
+ throw std::bad_alloc();
+ }
+ dataSize = blockClearSize + 128;
+ }
+
+ // Load in the block from the file, if it's not a symlink
+ if(!sourceIsSymlink)
+ {
+ if(in->Read(data, blockClearSize) != blockClearSize)
+ {
+ // Not enough data left in the file, can't possibly match
+ matches = false;
+ }
+ else
+ {
+ // Check the checksum
+ MD5Digest md5;
+ md5.Add(data, blockClearSize);
+ md5.Finish();
+ if(!md5.DigestMatches(entryEnc.mStrongChecksum))
+ {
+ // Checksum didn't match
+ matches = false;
+ }
+ }
+ }
+
+ // Keep on going regardless, to make sure the entire block index stream is read
+ // -- must always be consistent about what happens with the stream.
+ }
+ }
+ catch(...)
+ {
+ // clean up in case of errors
+ if(data != 0)
+ {
+ ::free(data);
+ data = 0;
+ }
+ throw;
+ }
+
+ // free block
+ if(data != 0)
+ {
+ ::free(data);
+ data = 0;
+ }
+
+ // Check for data left over if it's not a symlink
+ if(!sourceIsSymlink)
+ {
+ // Anything left to read in the file?
+ if(in->BytesLeftToRead() != 0)
+ {
+ // File has extra data at the end
+ matches = false;
+ }
+ }
+
+ // Symlinks must have zero size on server
+ if(sourceIsSymlink)
+ {
+ matches = (totalSizeInBlockIndex == 0);
+ }
+
+ return matches;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::EncodingBuffer::EncodingBuffer()
+// Purpose: Constructor
+// Created: 25/11/04
+//
+// --------------------------------------------------------------------------
+BackupStoreFile::EncodingBuffer::EncodingBuffer()
+ : mpBuffer(0),
+ mBufferSize(0)
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::EncodingBuffer::~EncodingBuffer()
+// Purpose: Destructor
+// Created: 25/11/04
+//
+// --------------------------------------------------------------------------
+BackupStoreFile::EncodingBuffer::~EncodingBuffer()
+{
+ if(mpBuffer != 0)
+ {
+ BackupStoreFile::CodingChunkFree(mpBuffer);
+ mpBuffer = 0;
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::EncodingBuffer::Allocate(int)
+// Purpose: Do initial allocation of block
+// Created: 25/11/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreFile::EncodingBuffer::Allocate(int Size)
+{
+ ASSERT(mpBuffer == 0);
+ uint8_t *buffer = (uint8_t*)BackupStoreFile::CodingChunkAlloc(Size);
+ if(buffer == 0)
+ {
+ throw std::bad_alloc();
+ }
+ mpBuffer = buffer;
+ mBufferSize = Size;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::EncodingBuffer::Reallocate(int)
+// Purpose: Reallocate the block. Try not to call this, it has to copy
+// the entire contents as the block can't be reallocated straight.
+// Created: 25/11/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreFile::EncodingBuffer::Reallocate(int NewSize)
+{
+ BOX_TRACE("Reallocating EncodingBuffer from " << mBufferSize <<
+ " to " << NewSize);
+ ASSERT(mpBuffer != 0);
+ uint8_t *buffer = (uint8_t*)BackupStoreFile::CodingChunkAlloc(NewSize);
+ if(buffer == 0)
+ {
+ throw std::bad_alloc();
+ }
+ // Copy data
+ ::memcpy(buffer, mpBuffer, (NewSize > mBufferSize)?mBufferSize:NewSize);
+
+ // Free old
+ BackupStoreFile::CodingChunkFree(mpBuffer);
+
+ // Store new buffer
+ mpBuffer = buffer;
+ mBufferSize = NewSize;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: DiffTimer::DiffTimer();
+// Purpose: Constructor
+// Created: 2005/02/01
+//
+// --------------------------------------------------------------------------
+DiffTimer::DiffTimer()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: DiffTimer::DiffTimer();
+// Purpose: Destructor
+// Created: 2005/02/01
+//
+// --------------------------------------------------------------------------
+DiffTimer::~DiffTimer()
+{
+}
diff --git a/lib/backupstore/BackupStoreFile.h b/lib/backupstore/BackupStoreFile.h
new file mode 100644
index 00000000..7c72e010
--- /dev/null
+++ b/lib/backupstore/BackupStoreFile.h
@@ -0,0 +1,234 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFile.h
+// Purpose: Utils for manipulating files
+// Created: 2003/08/28
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPSTOREFILE__H
+#define BACKUPSTOREFILE__H
+
+#include <cstdlib>
+#include <memory>
+#include <cstdlib>
+
+#include "BackupClientFileAttributes.h"
+#include "BackupStoreFilename.h"
+#include "IOStream.h"
+#include "ReadLoggingStream.h"
+
+typedef struct
+{
+ int64_t mBytesInEncodedFiles;
+ int64_t mBytesAlreadyOnServer;
+ int64_t mTotalFileStreamSize;
+} BackupStoreFileStats;
+
+class RunStatusProvider;
+
+// Uncomment to disable backwards compatibility
+//#define BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+
+
+// Output buffer to EncodeChunk and input data to DecodeChunk must
+// have specific alignment, see function comments.
+#define BACKUPSTOREFILE_CODING_BLOCKSIZE 16
+#define BACKUPSTOREFILE_CODING_OFFSET 15
+
+// Have some memory allocation commands, note closing "Off" at end of file.
+#include "MemLeakFindOn.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: DiffTimer
+// Purpose: Interface for classes that can keep track of diffing time,
+// and send SSL keepalive messages
+// Created: 2006/01/19
+//
+// --------------------------------------------------------------------------
+class DiffTimer
+{
+public:
+ DiffTimer();
+ virtual ~DiffTimer();
+public:
+ virtual void DoKeepAlive() = 0;
+ virtual int GetMaximumDiffingTime() = 0;
+ virtual bool IsManaged() = 0;
+};
+
+class BackupStoreFileEncodeStream;
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: BackupStoreFile
+// Purpose: Class to hold together utils for manipulating files.
+// Created: 2003/08/28
+//
+// --------------------------------------------------------------------------
+class BackupStoreFile
+{
+public:
+ class DecodedStream : public IOStream
+ {
+ friend class BackupStoreFile;
+ private:
+ DecodedStream(IOStream &rEncodedFile, int Timeout);
+ DecodedStream(const DecodedStream &); // not allowed
+ DecodedStream &operator=(const DecodedStream &); // not allowed
+ public:
+ ~DecodedStream();
+
+ // Stream functions
+ virtual int Read(void *pBuffer, int NBytes, int Timeout);
+ virtual void Write(const void *pBuffer, int NBytes);
+ virtual bool StreamDataLeft();
+ virtual bool StreamClosed();
+
+ // Accessor functions
+ const BackupClientFileAttributes &GetAttributes() {return mAttributes;}
+ const BackupStoreFilename &GetFilename() {return mFilename;}
+ int64_t GetNumBlocks() {return mNumBlocks;} // primarily for tests
+
+ bool IsSymLink();
+
+ private:
+ void Setup(const BackupClientFileAttributes *pAlterativeAttr);
+ void ReadBlockIndex(bool MagicAlreadyRead);
+
+ private:
+ IOStream &mrEncodedFile;
+ int mTimeout;
+ BackupClientFileAttributes mAttributes;
+ BackupStoreFilename mFilename;
+ int64_t mNumBlocks;
+ void *mpBlockIndex;
+ uint8_t *mpEncodedData;
+ uint8_t *mpClearData;
+ int mClearDataSize;
+ int mCurrentBlock;
+ int mCurrentBlockClearSize;
+ int mPositionInCurrentBlock;
+ uint64_t mEntryIVBase;
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ bool mIsOldVersion;
+#endif
+ };
+
+
+ // Main interface
+ static std::auto_ptr<BackupStoreFileEncodeStream> EncodeFile
+ (
+ const std::string& Filename,
+ int64_t ContainerID, const BackupStoreFilename &rStoreFilename,
+ int64_t *pModificationTime = 0,
+ ReadLoggingStream::Logger* pLogger = NULL,
+ RunStatusProvider* pRunStatusProvider = NULL
+ );
+ static std::auto_ptr<BackupStoreFileEncodeStream> EncodeFileDiff
+ (
+ const std::string& Filename, int64_t ContainerID,
+ const BackupStoreFilename &rStoreFilename,
+ int64_t DiffFromObjectID, IOStream &rDiffFromBlockIndex,
+ int Timeout,
+ DiffTimer *pDiffTimer,
+ int64_t *pModificationTime = 0,
+ bool *pIsCompletelyDifferent = 0
+ );
+ static bool VerifyEncodedFileFormat(IOStream &rFile, int64_t *pDiffFromObjectIDOut = 0, int64_t *pContainerIDOut = 0);
+ static void CombineFile(IOStream &rDiff, IOStream &rDiff2, IOStream &rFrom, IOStream &rOut);
+ static void CombineDiffs(IOStream &rDiff1, IOStream &rDiff2, IOStream &rDiff2b, IOStream &rOut);
+ static void ReverseDiffFile(IOStream &rDiff, IOStream &rFrom, IOStream &rFrom2, IOStream &rOut, int64_t ObjectIDOfFrom, bool *pIsCompletelyDifferent = 0);
+ static void DecodeFile(IOStream &rEncodedFile, const char *DecodedFilename, int Timeout, const BackupClientFileAttributes *pAlterativeAttr = 0);
+ static std::auto_ptr<BackupStoreFile::DecodedStream> DecodeFileStream(IOStream &rEncodedFile, int Timeout, const BackupClientFileAttributes *pAlterativeAttr = 0);
+ static bool CompareFileContentsAgainstBlockIndex(const char *Filename, IOStream &rBlockIndex, int Timeout);
+ static std::auto_ptr<IOStream> CombineFileIndices(IOStream &rDiff, IOStream &rFrom, bool DiffIsIndexOnly = false, bool FromIsIndexOnly = false);
+
+ // Stream manipulation
+ static std::auto_ptr<IOStream> ReorderFileToStreamOrder(IOStream *pStream, bool TakeOwnership);
+ static void MoveStreamPositionToBlockIndex(IOStream &rStream);
+
+ // Crypto setup
+ static void SetBlowfishKeys(const void *pKey, int KeyLength, const void *pBlockEntryKey, int BlockEntryKeyLength);
+#ifndef HAVE_OLD_SSL
+ static void SetAESKey(const void *pKey, int KeyLength);
+#endif
+
+ // Allocation of properly aligning chunks for decoding and encoding chunks
+ inline static void *CodingChunkAlloc(int Size)
+ {
+ uint8_t *a = (uint8_t*)malloc((Size) + (BACKUPSTOREFILE_CODING_BLOCKSIZE * 3));
+ if(a == 0) return 0;
+ // Align to main block size
+ ASSERT(sizeof(uint64_t) >= sizeof(void*)); // make sure casting the right pointer size
+ uint8_t adjustment = BACKUPSTOREFILE_CODING_BLOCKSIZE
+ - (uint8_t)(((uint64_t)a) % BACKUPSTOREFILE_CODING_BLOCKSIZE);
+ uint8_t *b = (a + adjustment);
+ // Store adjustment
+ *b = adjustment;
+ // Return offset
+ return b + BACKUPSTOREFILE_CODING_OFFSET;
+ }
+ inline static void CodingChunkFree(void *Block)
+ {
+ // Check alignment is as expected
+ ASSERT(sizeof(uint64_t) >= sizeof(void*)); // make sure casting the right pointer size
+ ASSERT((uint8_t)(((uint64_t)Block) % BACKUPSTOREFILE_CODING_BLOCKSIZE) == BACKUPSTOREFILE_CODING_OFFSET);
+ uint8_t *a = (uint8_t*)Block;
+ a -= BACKUPSTOREFILE_CODING_OFFSET;
+ // Adjust downwards...
+ a -= *a;
+ free(a);
+ }
+
+ static void DiffTimerExpired();
+
+ // Building blocks
+ class EncodingBuffer
+ {
+ public:
+ EncodingBuffer();
+ ~EncodingBuffer();
+ private:
+ // No copying
+ EncodingBuffer(const EncodingBuffer &);
+ EncodingBuffer &operator=(const EncodingBuffer &);
+ public:
+ void Allocate(int Size);
+ void Reallocate(int NewSize);
+
+ uint8_t *mpBuffer;
+ int mBufferSize;
+ };
+ static int MaxBlockSizeForChunkSize(int ChunkSize);
+ static int EncodeChunk(const void *Chunk, int ChunkSize, BackupStoreFile::EncodingBuffer &rOutput);
+
+ // Caller should know how big the output size is, but also allocate a bit more memory to cover various
+ // overheads allowed for in checks
+ static inline int OutputBufferSizeForKnownOutputSize(int KnownChunkSize)
+ {
+ // Plenty big enough
+ return KnownChunkSize + 256;
+ }
+ static int DecodeChunk(const void *Encoded, int EncodedSize, void *Output, int OutputSize);
+
+ // Statisitics, not designed to be completely reliable
+ static void ResetStats();
+ static BackupStoreFileStats msStats;
+
+ // For debug
+#ifndef BOX_RELEASE_BUILD
+ static bool TraceDetailsOfDiffProcess;
+#endif
+
+ // For decoding encoded files
+ static void DumpFile(void *clibFileHandle, bool ToTrace, IOStream &rFile);
+};
+
+#include "MemLeakFindOff.h"
+
+#endif // BACKUPSTOREFILE__H
diff --git a/lib/backupstore/BackupStoreFileCmbDiff.cpp b/lib/backupstore/BackupStoreFileCmbDiff.cpp
new file mode 100644
index 00000000..1a88fa3f
--- /dev/null
+++ b/lib/backupstore/BackupStoreFileCmbDiff.cpp
@@ -0,0 +1,326 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFileCmbDiff.cpp
+// Purpose: Combine two diffs together
+// Created: 12/7/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <new>
+#include <stdlib.h>
+
+#include "BackupStoreFile.h"
+#include "BackupStoreFileWire.h"
+#include "BackupStoreObjectMagic.h"
+#include "BackupStoreException.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreFilename.h"
+
+#include "MemLeakFindOn.h"
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::CombineDiffs(IOStream &, IOStream &, IOStream &rOut)
+// Purpose: Given two diffs, combine them into a single diff, to produce a diff
+// which, combined with the original file, creates the result of applying
+// rDiff, then rDiff2. Two opens of rDiff2 are required
+// Created: 12/7/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreFile::CombineDiffs(IOStream &rDiff1, IOStream &rDiff2, IOStream &rDiff2b, IOStream &rOut)
+{
+ // Skip header of first diff, record where the data starts, and skip to the index
+ int64_t diff1DataStarts = 0;
+ {
+ // Read the header for the From file
+ file_StreamFormat diff1Hdr;
+ if(!rDiff1.ReadFullBuffer(&diff1Hdr, sizeof(diff1Hdr), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+ if(ntohl(diff1Hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ // Skip over the filename and attributes of the From file
+ // BLOCK
+ {
+ BackupStoreFilename filename2;
+ filename2.ReadFromStream(rDiff1, IOStream::TimeOutInfinite);
+ int32_t size_s;
+ if(!rDiff1.ReadFullBuffer(&size_s, sizeof(size_s), 0 /* not interested in bytes read if this fails */))
+ {
+ THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead)
+ }
+ int size = ntohl(size_s);
+ // Skip forward the size
+ rDiff1.Seek(size, IOStream::SeekType_Relative);
+ }
+ // Record position
+ diff1DataStarts = rDiff1.GetPosition();
+ // Skip to index
+ rDiff1.Seek(0 - (((box_ntoh64(diff1Hdr.mNumBlocks)) * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader)), IOStream::SeekType_End);
+ }
+
+ // Read the index of the first diff
+ // Header first
+ file_BlockIndexHeader diff1IdxHdr;
+ if(!rDiff1.ReadFullBuffer(&diff1IdxHdr, sizeof(diff1IdxHdr), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+ if(ntohl(diff1IdxHdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ int64_t diff1NumBlocks = box_ntoh64(diff1IdxHdr.mNumBlocks);
+ // Allocate some memory
+ int64_t *diff1BlockStartPositions = (int64_t*)::malloc((diff1NumBlocks + 1) * sizeof(int64_t));
+ if(diff1BlockStartPositions == 0)
+ {
+ throw std::bad_alloc();
+ }
+
+ // Buffer data
+ void *buffer = 0;
+ int bufferSize = 0;
+
+ try
+ {
+ // Then the entries:
+ // For each entry, want to know if it's in the file, and if so, how big it is.
+ // We'll store this as an array of file positions in the file, with an additioal
+ // entry on the end so that we can work out the length of the last block.
+ // If an entry isn't in the file, then store 0 - (position in other file).
+ int64_t diff1Position = diff1DataStarts;
+ for(int64_t b = 0; b < diff1NumBlocks; ++b)
+ {
+ file_BlockIndexEntry e;
+ if(!rDiff1.ReadFullBuffer(&e, sizeof(e), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Where's the block?
+ int64_t blockEn = box_ntoh64(e.mEncodedSize);
+ if(blockEn <= 0)
+ {
+ // Just store the negated block number
+ diff1BlockStartPositions[b] = blockEn;
+ }
+ else
+ {
+ // Block is present in this file
+ diff1BlockStartPositions[b] = diff1Position;
+ diff1Position += blockEn;
+ }
+ }
+
+ // Finish off the list, so the last entry can have it's size calcuated.
+ diff1BlockStartPositions[diff1NumBlocks] = diff1Position;
+
+ // Now read the second diff's header, copying it to the out file
+ file_StreamFormat diff2Hdr;
+ if(!rDiff2.ReadFullBuffer(&diff2Hdr, sizeof(diff2Hdr), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+ if(ntohl(diff2Hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ // Copy
+ rOut.Write(&diff2Hdr, sizeof(diff2Hdr));
+ // Copy over filename and attributes
+ // BLOCK
+ {
+ BackupStoreFilename filename;
+ filename.ReadFromStream(rDiff2, IOStream::TimeOutInfinite);
+ filename.WriteToStream(rOut);
+ StreamableMemBlock attr;
+ attr.ReadFromStream(rDiff2, IOStream::TimeOutInfinite);
+ attr.WriteToStream(rOut);
+ }
+
+ // Get to the index of rDiff2b, and read the header
+ MoveStreamPositionToBlockIndex(rDiff2b);
+ file_BlockIndexHeader diff2IdxHdr;
+ if(!rDiff2b.ReadFullBuffer(&diff2IdxHdr, sizeof(diff2IdxHdr), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+ if(ntohl(diff2IdxHdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ int64_t diff2NumBlocks = box_ntoh64(diff2IdxHdr.mNumBlocks);
+ int64_t diff2IndexEntriesStart = rDiff2b.GetPosition();
+
+ // Then read all the entries
+ int64_t diff2FilePosition = rDiff2.GetPosition();
+ for(int64_t b = 0; b < diff2NumBlocks; ++b)
+ {
+ file_BlockIndexEntry e;
+ if(!rDiff2b.ReadFullBuffer(&e, sizeof(e), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // What do to next about copying data
+ bool copyBlock = false;
+ int copySize = 0;
+ int64_t copyFrom = 0;
+ bool fromFileDiff1 = false;
+
+ // Where's the block?
+ int64_t blockEn = box_ntoh64(e.mEncodedSize);
+ if(blockEn > 0)
+ {
+ // Block is present in this file -- copy to out
+ copyBlock = true;
+ copyFrom = diff2FilePosition;
+ copySize = (int)blockEn;
+
+ // Move pointer onwards
+ diff2FilePosition += blockEn;
+ }
+ else
+ {
+ // Block isn't present here -- is it present in the old one?
+ int64_t blockIndex = 0 - blockEn;
+ if(blockIndex < 0 || blockIndex > diff1NumBlocks)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ if(diff1BlockStartPositions[blockIndex] > 0)
+ {
+ // Block is in the old diff file, copy it across
+ copyBlock = true;
+ copyFrom = diff1BlockStartPositions[blockIndex];
+ int nb = blockIndex + 1;
+ while(diff1BlockStartPositions[nb] <= 0)
+ {
+ // This is safe, because the last entry will terminate it properly!
+ ++nb;
+ ASSERT(nb <= diff1NumBlocks);
+ }
+ copySize = diff1BlockStartPositions[nb] - copyFrom;
+ fromFileDiff1 = true;
+ }
+ }
+ //TRACE4("%d %d %lld %d\n", copyBlock, copySize, copyFrom, fromFileDiff1);
+
+ // Copy data to the output file?
+ if(copyBlock)
+ {
+ // Allocate enough space
+ if(bufferSize < copySize || buffer == 0)
+ {
+ // Free old block
+ if(buffer != 0)
+ {
+ ::free(buffer);
+ buffer = 0;
+ bufferSize = 0;
+ }
+ // Allocate new block
+ buffer = ::malloc(copySize);
+ if(buffer == 0)
+ {
+ throw std::bad_alloc();
+ }
+ bufferSize = copySize;
+ }
+ ASSERT(bufferSize >= copySize);
+
+ // Load in the data
+ if(fromFileDiff1)
+ {
+ rDiff1.Seek(copyFrom, IOStream::SeekType_Absolute);
+ if(!rDiff1.ReadFullBuffer(buffer, copySize, 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+ }
+ else
+ {
+ rDiff2.Seek(copyFrom, IOStream::SeekType_Absolute);
+ if(!rDiff2.ReadFullBuffer(buffer, copySize, 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+ }
+ // Write out data
+ rOut.Write(buffer, copySize);
+ }
+ }
+
+ // Write the modified header
+ diff2IdxHdr.mOtherFileID = diff1IdxHdr.mOtherFileID;
+ rOut.Write(&diff2IdxHdr, sizeof(diff2IdxHdr));
+
+ // Then we'll write out the index, reading the data again
+ rDiff2b.Seek(diff2IndexEntriesStart, IOStream::SeekType_Absolute);
+ for(int64_t b = 0; b < diff2NumBlocks; ++b)
+ {
+ file_BlockIndexEntry e;
+ if(!rDiff2b.ReadFullBuffer(&e, sizeof(e), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Where's the block?
+ int64_t blockEn = box_ntoh64(e.mEncodedSize);
+
+ // If it's not in this file, it needs modification...
+ if(blockEn <= 0)
+ {
+ int64_t blockIndex = 0 - blockEn;
+ // In another file. Need to translate this against the other diff
+ if(diff1BlockStartPositions[blockIndex] > 0)
+ {
+ // Block is in the first diff file, stick in size
+ int nb = blockIndex + 1;
+ while(diff1BlockStartPositions[nb] <= 0)
+ {
+ // This is safe, because the last entry will terminate it properly!
+ ++nb;
+ ASSERT(nb <= diff1NumBlocks);
+ }
+ int64_t size = diff1BlockStartPositions[nb] - diff1BlockStartPositions[blockIndex];
+ e.mEncodedSize = box_hton64(size);
+ }
+ else
+ {
+ // Block in the original file, use translated value
+ e.mEncodedSize = box_hton64(diff1BlockStartPositions[blockIndex]);
+ }
+ }
+
+ // Write entry
+ rOut.Write(&e, sizeof(e));
+ }
+ }
+ catch(...)
+ {
+ // clean up
+ ::free(diff1BlockStartPositions);
+ if(buffer != 0)
+ {
+ ::free(buffer);
+ }
+ throw;
+ }
+
+ // Clean up allocated memory
+ ::free(diff1BlockStartPositions);
+ if(buffer != 0)
+ {
+ ::free(buffer);
+ }
+}
+
diff --git a/lib/backupstore/BackupStoreFileCmbIdx.cpp b/lib/backupstore/BackupStoreFileCmbIdx.cpp
new file mode 100644
index 00000000..c8bcc3b9
--- /dev/null
+++ b/lib/backupstore/BackupStoreFileCmbIdx.cpp
@@ -0,0 +1,324 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFileCmbIdx.cpp
+// Purpose: Combine indicies of a delta file and the file it's a diff from.
+// Created: 8/7/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <new>
+#include <string.h>
+
+#include "BackupStoreFile.h"
+#include "BackupStoreFileWire.h"
+#include "BackupStoreObjectMagic.h"
+#include "BackupStoreException.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreFilename.h"
+
+#include "MemLeakFindOn.h"
+
+// Hide from outside world
+namespace
+{
+
+class BSFCombinedIndexStream : public IOStream
+{
+public:
+ BSFCombinedIndexStream(IOStream *pDiff);
+ ~BSFCombinedIndexStream();
+
+ virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite);
+ virtual void Write(const void *pBuffer, int NBytes);
+ virtual bool StreamDataLeft();
+ virtual bool StreamClosed();
+ virtual void Initialise(IOStream &rFrom);
+
+private:
+ IOStream *mpDiff;
+ bool mIsInitialised;
+ bool mHeaderWritten;
+ file_BlockIndexHeader mHeader;
+ int64_t mNumEntriesToGo;
+ int64_t mNumEntriesInFromFile;
+ int64_t *mFromBlockSizes; // NOTE: Entries in network byte order
+};
+
+};
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::CombineFileIndices(IOStream &, IOStream &, bool)
+// Purpose: Given a diff file and the file it's a diff from, return a stream from which
+// can be read the index of the combined file, without actually combining them.
+// The stream of the diff must have a lifetime greater than or equal to the
+// lifetime of the returned stream object. The full "from" file stream
+// only needs to exist during the actual function call.
+// If you pass in dodgy files which aren't related, then you will either
+// get an error or bad results. So don't do that.
+// If DiffIsIndexOnly is true, then rDiff is assumed to be a stream positioned
+// at the beginning of the block index. Similarly for FromIsIndexOnly.
+// WARNING: Reads of the returned streams with buffer sizes less than 64 bytes
+// will not return any data.
+// Created: 8/7/04
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<IOStream> BackupStoreFile::CombineFileIndices(IOStream &rDiff, IOStream &rFrom, bool DiffIsIndexOnly, bool FromIsIndexOnly)
+{
+ // Reposition file pointers?
+ if(!DiffIsIndexOnly)
+ {
+ MoveStreamPositionToBlockIndex(rDiff);
+ }
+ if(!FromIsIndexOnly)
+ {
+ MoveStreamPositionToBlockIndex(rFrom);
+ }
+
+ // Create object
+ std::auto_ptr<IOStream> stream(new BSFCombinedIndexStream(&rDiff));
+
+ // Initialise it
+ ((BSFCombinedIndexStream *)stream.get())->Initialise(rFrom);
+
+ // And return the stream
+ return stream;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BSFCombinedIndexStream::BSFCombinedIndexStream()
+// Purpose: Private class. Constructor.
+// Created: 8/7/04
+//
+// --------------------------------------------------------------------------
+BSFCombinedIndexStream::BSFCombinedIndexStream(IOStream *pDiff)
+ : mpDiff(pDiff),
+ mIsInitialised(false),
+ mHeaderWritten(false),
+ mNumEntriesToGo(0),
+ mNumEntriesInFromFile(0),
+ mFromBlockSizes(0)
+{
+ ASSERT(mpDiff != 0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BSFCombinedIndexStream::~BSFCombinedIndexStream()
+// Purpose: Private class. Destructor.
+// Created: 8/7/04
+//
+// --------------------------------------------------------------------------
+BSFCombinedIndexStream::~BSFCombinedIndexStream()
+{
+ if(mFromBlockSizes != 0)
+ {
+ ::free(mFromBlockSizes);
+ mFromBlockSizes = 0;
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BSFCombinedIndexStream::Initialise(IOStream &)
+// Purpose: Private class. Initalise from the streams (diff passed in constructor).
+// Both streams must have file pointer positioned at the block index.
+// Created: 8/7/04
+//
+// --------------------------------------------------------------------------
+void BSFCombinedIndexStream::Initialise(IOStream &rFrom)
+{
+ // Paranoia is good.
+ if(mIsInitialised)
+ {
+ THROW_EXCEPTION(BackupStoreException, Internal)
+ }
+
+ // Look at the diff file: Read in the header
+ if(!mpDiff->ReadFullBuffer(&mHeader, sizeof(mHeader), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+ if(ntohl(mHeader.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // Read relevant data.
+ mNumEntriesToGo = box_ntoh64(mHeader.mNumBlocks);
+
+ // Adjust a bit to reflect the fact it's no longer a diff
+ mHeader.mOtherFileID = box_hton64(0);
+
+ // Now look at the from file: Read header
+ file_BlockIndexHeader fromHdr;
+ if(!rFrom.ReadFullBuffer(&fromHdr, sizeof(fromHdr), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+ if(ntohl(fromHdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // Then... allocate memory for the list of sizes
+ mNumEntriesInFromFile = box_ntoh64(fromHdr.mNumBlocks);
+ mFromBlockSizes = (int64_t*)::malloc(mNumEntriesInFromFile * sizeof(int64_t));
+ if(mFromBlockSizes == 0)
+ {
+ throw std::bad_alloc();
+ }
+
+ // And read them all in!
+ for(int64_t b = 0; b < mNumEntriesInFromFile; ++b)
+ {
+ file_BlockIndexEntry e;
+ if(!rFrom.ReadFullBuffer(&e, sizeof(e), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Check that the from file isn't a delta in itself
+ if(box_ntoh64(e.mEncodedSize) <= 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, OnCombineFromFileIsIncomplete)
+ }
+
+ // Store size (in network byte order)
+ mFromBlockSizes[b] = e.mEncodedSize;
+ }
+
+ // Flag as initialised
+ mIsInitialised = true;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BSFCombinedIndexStream::Read(void *, int, int)
+// Purpose: Private class. As interface.
+// Created: 8/7/04
+//
+// --------------------------------------------------------------------------
+int BSFCombinedIndexStream::Read(void *pBuffer, int NBytes, int Timeout)
+{
+ // Paranoia is good.
+ if(!mIsInitialised || mFromBlockSizes == 0 || mpDiff == 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, Internal)
+ }
+
+ int written = 0;
+
+ // Header output yet?
+ if(!mHeaderWritten)
+ {
+ // Enough space?
+ if(NBytes < (int)sizeof(mHeader)) return 0;
+
+ // Copy in
+ ::memcpy(pBuffer, &mHeader, sizeof(mHeader));
+ NBytes -= sizeof(mHeader);
+ written += sizeof(mHeader);
+
+ // Flag it's done
+ mHeaderWritten = true;
+ }
+
+ // How many entries can be written?
+ int entriesToWrite = NBytes / sizeof(file_BlockIndexEntry);
+ if(entriesToWrite > mNumEntriesToGo)
+ {
+ entriesToWrite = mNumEntriesToGo;
+ }
+
+ // Setup ready to go
+ file_BlockIndexEntry *poutput = (file_BlockIndexEntry*)(((uint8_t*)pBuffer) + written);
+
+ // Write entries
+ for(int b = 0; b < entriesToWrite; ++b)
+ {
+ if(!mpDiff->ReadFullBuffer(&(poutput[b]), sizeof(file_BlockIndexEntry), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Does this need adjusting?
+ int s = box_ntoh64(poutput[b].mEncodedSize);
+ if(s <= 0)
+ {
+ // A reference to a block in the from file
+ int block = 0 - s;
+ ASSERT(block >= 0);
+ if(block >= mNumEntriesInFromFile)
+ {
+ // That's not good, the block doesn't exist
+ THROW_EXCEPTION(BackupStoreException, OnCombineFromFileIsIncomplete)
+ }
+
+ // Adjust the entry in the buffer
+ poutput[b].mEncodedSize = mFromBlockSizes[block]; // stored in network byte order, no translation necessary
+ }
+ }
+
+ // Update written count
+ written += entriesToWrite * sizeof(file_BlockIndexEntry);
+ mNumEntriesToGo -= entriesToWrite;
+
+ return written;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BSFCombinedIndexStream::Write(const void *, int)
+// Purpose: Private class. As interface.
+// Created: 8/7/04
+//
+// --------------------------------------------------------------------------
+void BSFCombinedIndexStream::Write(const void *pBuffer, int NBytes)
+{
+ THROW_EXCEPTION(BackupStoreException, StreamDoesntHaveRequiredFeatures)
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BSFCombinedIndexStream::StreamDataLeft()
+// Purpose: Private class. As interface
+// Created: 8/7/04
+//
+// --------------------------------------------------------------------------
+bool BSFCombinedIndexStream::StreamDataLeft()
+{
+ return (!mHeaderWritten) || (mNumEntriesToGo > 0);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BSFCombinedIndexStream::StreamClosed()
+// Purpose: Private class. As interface.
+// Created: 8/7/04
+//
+// --------------------------------------------------------------------------
+bool BSFCombinedIndexStream::StreamClosed()
+{
+ return true; // doesn't do writing
+}
+
diff --git a/lib/backupstore/BackupStoreFileCombine.cpp b/lib/backupstore/BackupStoreFileCombine.cpp
new file mode 100644
index 00000000..baa331f0
--- /dev/null
+++ b/lib/backupstore/BackupStoreFileCombine.cpp
@@ -0,0 +1,410 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFileCombine.cpp
+// Purpose: File combining for BackupStoreFile
+// Created: 16/1/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <new>
+
+#include "BackupStoreFile.h"
+#include "BackupStoreFileWire.h"
+#include "BackupStoreObjectMagic.h"
+#include "BackupStoreException.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreFilename.h"
+#include "FileStream.h"
+
+#include "MemLeakFindOn.h"
+
+typedef struct
+{
+ int64_t mFilePosition;
+} FromIndexEntry;
+
+static void LoadFromIndex(IOStream &rFrom, FromIndexEntry *pIndex, int64_t NumEntries);
+static void CopyData(IOStream &rDiffData, IOStream &rDiffIndex, int64_t DiffNumBlocks, IOStream &rFrom, FromIndexEntry *pFromIndex, int64_t FromNumBlocks, IOStream &rOut);
+static void WriteNewIndex(IOStream &rDiff, int64_t DiffNumBlocks, FromIndexEntry *pFromIndex, int64_t FromNumBlocks, IOStream &rOut);
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::CombineFile(IOStream &, IOStream &, IOStream &)
+// Purpose: Where rDiff is a store file which is incomplete as a result of a
+// diffing operation, rFrom is the file it is diffed from, and
+// rOut is the stream in which to place the result, the old file
+// and new file are combined into a file containing all the data.
+// rDiff2 is the same file as rDiff, opened again to get two
+// independent streams to the same file.
+// Created: 16/1/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreFile::CombineFile(IOStream &rDiff, IOStream &rDiff2, IOStream &rFrom, IOStream &rOut)
+{
+ // Read and copy the header.
+ file_StreamFormat hdr;
+ if(!rDiff.ReadFullBuffer(&hdr, sizeof(hdr), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+ if(ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ // Copy
+ rOut.Write(&hdr, sizeof(hdr));
+ // Copy over filename and attributes
+ // BLOCK
+ {
+ BackupStoreFilename filename;
+ filename.ReadFromStream(rDiff, IOStream::TimeOutInfinite);
+ filename.WriteToStream(rOut);
+ StreamableMemBlock attr;
+ attr.ReadFromStream(rDiff, IOStream::TimeOutInfinite);
+ attr.WriteToStream(rOut);
+ }
+
+ // Read the header for the From file
+ file_StreamFormat fromHdr;
+ if(!rFrom.ReadFullBuffer(&fromHdr, sizeof(fromHdr), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+ if(ntohl(fromHdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ // Skip over the filename and attributes of the From file
+ // BLOCK
+ {
+ BackupStoreFilename filename2;
+ filename2.ReadFromStream(rFrom, IOStream::TimeOutInfinite);
+ int32_t size_s;
+ if(!rFrom.ReadFullBuffer(&size_s, sizeof(size_s), 0 /* not interested in bytes read if this fails */))
+ {
+ THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead)
+ }
+ int size = ntohl(size_s);
+ // Skip forward the size
+ rFrom.Seek(size, IOStream::SeekType_Relative);
+ }
+
+ // Allocate memory for the block index of the From file
+ int64_t fromNumBlocks = box_ntoh64(fromHdr.mNumBlocks);
+ // NOTE: An extra entry is required so that the length of the last block can be calculated
+ FromIndexEntry *pFromIndex = (FromIndexEntry*)::malloc((fromNumBlocks+1) * sizeof(FromIndexEntry));
+ if(pFromIndex == 0)
+ {
+ throw std::bad_alloc();
+ }
+
+ try
+ {
+ // Load the index from the From file, calculating the offsets in the
+ // file as we go along, and enforce that everything should be present.
+ LoadFromIndex(rFrom, pFromIndex, fromNumBlocks);
+
+ // Read in the block index of the Diff file in small chunks, and output data
+ // for each block, either from this file, or the other file.
+ int64_t diffNumBlocks = box_ntoh64(hdr.mNumBlocks);
+ CopyData(rDiff /* positioned at start of data */, rDiff2, diffNumBlocks, rFrom, pFromIndex, fromNumBlocks, rOut);
+
+ // Read in the block index again, and output the new block index, simply
+ // filling in the sizes of blocks from the old file.
+ WriteNewIndex(rDiff, diffNumBlocks, pFromIndex, fromNumBlocks, rOut);
+
+ // Free buffers
+ ::free(pFromIndex);
+ pFromIndex = 0;
+ }
+ catch(...)
+ {
+ // Clean up
+ if(pFromIndex != 0)
+ {
+ ::free(pFromIndex);
+ pFromIndex = 0;
+ }
+ throw;
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: static LoadFromIndex(IOStream &, FromIndexEntry *, int64_t)
+// Purpose: Static. Load the index from the From file
+// Created: 16/1/04
+//
+// --------------------------------------------------------------------------
+static void LoadFromIndex(IOStream &rFrom, FromIndexEntry *pIndex, int64_t NumEntries)
+{
+ ASSERT(pIndex != 0);
+ ASSERT(NumEntries >= 0);
+
+ // Get the starting point in the file
+ int64_t filePos = rFrom.GetPosition();
+
+ // Jump to the end of the file to read the index
+ rFrom.Seek(0 - ((NumEntries * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader)), IOStream::SeekType_End);
+
+ // Read block index header
+ file_BlockIndexHeader blkhdr;
+ if(!rFrom.ReadFullBuffer(&blkhdr, sizeof(blkhdr), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+ if(ntohl(blkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1
+ || (int64_t)box_ntoh64(blkhdr.mNumBlocks) != NumEntries)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // And then the block entries
+ for(int64_t b = 0; b < NumEntries; ++b)
+ {
+ // Read
+ file_BlockIndexEntry en;
+ if(!rFrom.ReadFullBuffer(&en, sizeof(en), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+
+ // Add to list
+ pIndex[b].mFilePosition = filePos;
+
+ // Encoded size?
+ int64_t encodedSize = box_ntoh64(en.mEncodedSize);
+ // Check that the block is actually there
+ if(encodedSize <= 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, OnCombineFromFileIsIncomplete)
+ }
+
+ // Move file pointer on
+ filePos += encodedSize;
+ }
+
+ // Store the position in the very last entry, so the size of the last entry can be calculated
+ pIndex[NumEntries].mFilePosition = filePos;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: static CopyData(IOStream &, IOStream &, int64_t, IOStream &, FromIndexEntry *, int64_t, IOStream &)
+// Purpose: Static. Copy data from the Diff and From file to the out file.
+// rDiffData is at beginning of data.
+// rDiffIndex at any position.
+// rFrom is at any position.
+// rOut is after the header, ready for data
+// Created: 16/1/04
+//
+// --------------------------------------------------------------------------
+static void CopyData(IOStream &rDiffData, IOStream &rDiffIndex, int64_t DiffNumBlocks,
+ IOStream &rFrom, FromIndexEntry *pFromIndex, int64_t FromNumBlocks, IOStream &rOut)
+{
+ // Jump to the end of the diff file to read the index
+ rDiffIndex.Seek(0 - ((DiffNumBlocks * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader)), IOStream::SeekType_End);
+
+ // Read block index header
+ file_BlockIndexHeader diffBlkhdr;
+ if(!rDiffIndex.ReadFullBuffer(&diffBlkhdr, sizeof(diffBlkhdr), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+ if(ntohl(diffBlkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1
+ || (int64_t)box_ntoh64(diffBlkhdr.mNumBlocks) != DiffNumBlocks)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // Record where the From file is
+ int64_t fromPos = rFrom.GetPosition();
+
+ // Buffer data
+ void *buffer = 0;
+ int bufferSize = 0;
+
+ try
+ {
+ // Read the blocks in!
+ for(int64_t b = 0; b < DiffNumBlocks; ++b)
+ {
+ // Read
+ file_BlockIndexEntry en;
+ if(!rDiffIndex.ReadFullBuffer(&en, sizeof(en), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+
+ // What's the size value stored in the entry
+ int64_t encodedSize = box_ntoh64(en.mEncodedSize);
+
+ // How much data will be read?
+ int32_t blockSize = 0;
+ if(encodedSize > 0)
+ {
+ // The block is actually in the diff file
+ blockSize = encodedSize;
+ }
+ else
+ {
+ // It's in the from file. First, check to see if it's valid
+ int64_t blockIdx = (0 - encodedSize);
+ if(blockIdx > FromNumBlocks)
+ {
+ // References a block which doesn't actually exist
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ // Calculate size. This operation is safe because of the extra entry at the end
+ blockSize = pFromIndex[blockIdx + 1].mFilePosition - pFromIndex[blockIdx].mFilePosition;
+ }
+ ASSERT(blockSize > 0);
+
+ // Make sure there's memory available to copy this
+ if(bufferSize < blockSize || buffer == 0)
+ {
+ // Free old block
+ if(buffer != 0)
+ {
+ ::free(buffer);
+ buffer = 0;
+ bufferSize = 0;
+ }
+ // Allocate new block
+ buffer = ::malloc(blockSize);
+ if(buffer == 0)
+ {
+ throw std::bad_alloc();
+ }
+ bufferSize = blockSize;
+ }
+ ASSERT(bufferSize >= blockSize);
+
+ // Load in data from one of the files
+ if(encodedSize > 0)
+ {
+ // Load from diff file
+ if(!rDiffData.ReadFullBuffer(buffer, blockSize, 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+ }
+ else
+ {
+ // Locate and read the data from the from file
+ int64_t blockIdx = (0 - encodedSize);
+ // Seek if necessary
+ if(fromPos != pFromIndex[blockIdx].mFilePosition)
+ {
+ rFrom.Seek(pFromIndex[blockIdx].mFilePosition, IOStream::SeekType_Absolute);
+ fromPos = pFromIndex[blockIdx].mFilePosition;
+ }
+ // Read
+ if(!rFrom.ReadFullBuffer(buffer, blockSize, 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+
+ // Update fromPos to current position
+ fromPos += blockSize;
+ }
+
+ // Write data to out file
+ rOut.Write(buffer, blockSize);
+ }
+
+ // Free buffer, if allocated
+ if(buffer != 0)
+ {
+ ::free(buffer);
+ buffer = 0;
+ }
+ }
+ catch(...)
+ {
+ if(buffer != 0)
+ {
+ ::free(buffer);
+ buffer = 0;
+ }
+ throw;
+ }
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: static WriteNewIndex(IOStream &, int64_t, FromIndexEntry *, int64_t, IOStream &)
+// Purpose: Write the index to the out file, just copying from the diff file and
+// adjusting the entries.
+// Created: 16/1/04
+//
+// --------------------------------------------------------------------------
+static void WriteNewIndex(IOStream &rDiff, int64_t DiffNumBlocks, FromIndexEntry *pFromIndex, int64_t FromNumBlocks, IOStream &rOut)
+{
+ // Jump to the end of the diff file to read the index
+ rDiff.Seek(0 - ((DiffNumBlocks * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader)), IOStream::SeekType_End);
+
+ // Read block index header
+ file_BlockIndexHeader diffBlkhdr;
+ if(!rDiff.ReadFullBuffer(&diffBlkhdr, sizeof(diffBlkhdr), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+ if(ntohl(diffBlkhdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1
+ || (int64_t)box_ntoh64(diffBlkhdr.mNumBlocks) != DiffNumBlocks)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // Write it out with a blanked out other file ID
+ diffBlkhdr.mOtherFileID = box_hton64(0);
+ rOut.Write(&diffBlkhdr, sizeof(diffBlkhdr));
+
+ // Rewrite the index
+ for(int64_t b = 0; b < DiffNumBlocks; ++b)
+ {
+ file_BlockIndexEntry en;
+ if(!rDiff.ReadFullBuffer(&en, sizeof(en), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+
+ // What's the size value stored in the entry
+ int64_t encodedSize = box_ntoh64(en.mEncodedSize);
+
+ // Need to adjust it?
+ if(encodedSize <= 0)
+ {
+ // This actually refers to a block in the from file. So rewrite this.
+ int64_t blockIdx = (0 - encodedSize);
+ if(blockIdx > FromNumBlocks)
+ {
+ // References a block which doesn't actually exist
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ // Calculate size. This operation is safe because of the extra entry at the end
+ int32_t blockSize = pFromIndex[blockIdx + 1].mFilePosition - pFromIndex[blockIdx].mFilePosition;
+ // Then replace entry
+ en.mEncodedSize = box_hton64(((uint64_t)blockSize));
+ }
+
+ // Write entry
+ rOut.Write(&en, sizeof(en));
+ }
+}
+
+
+
+
+
diff --git a/lib/backupstore/BackupStoreFileCryptVar.cpp b/lib/backupstore/BackupStoreFileCryptVar.cpp
new file mode 100644
index 00000000..e826de4e
--- /dev/null
+++ b/lib/backupstore/BackupStoreFileCryptVar.cpp
@@ -0,0 +1,31 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFileCryptVar.cpp
+// Purpose: Cryptographic keys for backup store files
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include "BackupStoreFileCryptVar.h"
+#include "BackupStoreFileWire.h"
+
+#include "MemLeakFindOn.h"
+
+CipherContext BackupStoreFileCryptVar::sBlowfishEncrypt;
+CipherContext BackupStoreFileCryptVar::sBlowfishDecrypt;
+
+#ifndef HAVE_OLD_SSL
+ CipherContext BackupStoreFileCryptVar::sAESEncrypt;
+ CipherContext BackupStoreFileCryptVar::sAESDecrypt;
+#endif
+
+// Default to blowfish
+CipherContext *BackupStoreFileCryptVar::spEncrypt = &BackupStoreFileCryptVar::sBlowfishEncrypt;
+uint8_t BackupStoreFileCryptVar::sEncryptCipherType = HEADER_BLOWFISH_ENCODING;
+
+CipherContext BackupStoreFileCryptVar::sBlowfishEncryptBlockEntry;
+CipherContext BackupStoreFileCryptVar::sBlowfishDecryptBlockEntry;
+
diff --git a/lib/backupstore/BackupStoreFileCryptVar.h b/lib/backupstore/BackupStoreFileCryptVar.h
new file mode 100644
index 00000000..566813c8
--- /dev/null
+++ b/lib/backupstore/BackupStoreFileCryptVar.h
@@ -0,0 +1,39 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFileCryptVar.h
+// Purpose: Cryptographic keys for backup store files
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPSTOREFILECRYPTVAR__H
+#define BACKUPSTOREFILECRYPTVAR__H
+
+#include "CipherContext.h"
+
+// Hide private static variables from the rest of the world by putting them
+// as static variables in a namespace.
+// -- don't put them as static class variables to avoid openssl/evp.h being
+// included all over the project.
+namespace BackupStoreFileCryptVar
+{
+ // Keys for the main file data
+ extern CipherContext sBlowfishEncrypt;
+ extern CipherContext sBlowfishDecrypt;
+ // Use AES when available
+#ifndef HAVE_OLD_SSL
+ extern CipherContext sAESEncrypt;
+ extern CipherContext sAESDecrypt;
+#endif
+ // How encoding will be done
+ extern CipherContext *spEncrypt;
+ extern uint8_t sEncryptCipherType;
+
+ // Keys for the block indicies
+ extern CipherContext sBlowfishEncryptBlockEntry;
+ extern CipherContext sBlowfishDecryptBlockEntry;
+}
+
+#endif // BACKUPSTOREFILECRYPTVAR__H
+
diff --git a/lib/backupstore/BackupStoreFileDiff.cpp b/lib/backupstore/BackupStoreFileDiff.cpp
new file mode 100644
index 00000000..fa8cb892
--- /dev/null
+++ b/lib/backupstore/BackupStoreFileDiff.cpp
@@ -0,0 +1,1047 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFileDiff.cpp
+// Purpose: Functions relating to diffing BackupStoreFiles
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <string.h>
+
+#include <new>
+#include <map>
+
+#ifdef HAVE_TIME_H
+ #include <time.h>
+#elif HAVE_SYS_TIME_H
+ #include <sys/time.h>
+#endif
+
+#include "BackupStoreConstants.h"
+#include "BackupStoreException.h"
+#include "BackupStoreFile.h"
+#include "BackupStoreFileCryptVar.h"
+#include "BackupStoreFileEncodeStream.h"
+#include "BackupStoreFileWire.h"
+#include "BackupStoreObjectMagic.h"
+#include "CommonException.h"
+#include "FileStream.h"
+#include "MD5Digest.h"
+#include "RollingChecksum.h"
+#include "Timer.h"
+
+#include "MemLeakFindOn.h"
+
+#include <cstring>
+
+using namespace BackupStoreFileCryptVar;
+using namespace BackupStoreFileCreation;
+
+// By default, don't trace out details of the diff as we go along -- would fill up logs significantly.
+// But it's useful for the test.
+#ifndef BOX_RELEASE_BUILD
+ bool BackupStoreFile::TraceDetailsOfDiffProcess = false;
+#endif
+
+static void LoadIndex(IOStream &rBlockIndex, int64_t ThisID, BlocksAvailableEntry **ppIndex, int64_t &rNumBlocksOut, int Timeout, bool &rCanDiffFromThis);
+static void FindMostUsedSizes(BlocksAvailableEntry *pIndex, int64_t NumBlocks, int32_t Sizes[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES]);
+static void SearchForMatchingBlocks(IOStream &rFile,
+ std::map<int64_t, int64_t> &rFoundBlocks, BlocksAvailableEntry *pIndex,
+ int64_t NumBlocks, int32_t Sizes[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES],
+ DiffTimer *pDiffTimer);
+static void SetupHashTable(BlocksAvailableEntry *pIndex, int64_t NumBlocks, int32_t BlockSize, BlocksAvailableEntry **pHashTable);
+static bool SecondStageMatch(BlocksAvailableEntry *pFirstInHashList, RollingChecksum &fastSum, uint8_t *pBeginnings, uint8_t *pEndings, int Offset, int32_t BlockSize, int64_t FileBlockNumber,
+BlocksAvailableEntry *pIndex, std::map<int64_t, int64_t> &rFoundBlocks);
+static void GenerateRecipe(BackupStoreFileEncodeStream::Recipe &rRecipe, BlocksAvailableEntry *pIndex, int64_t NumBlocks, std::map<int64_t, int64_t> &rFoundBlocks, int64_t SizeOfInputFile);
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::MoveStreamPositionToBlockIndex(IOStream &)
+// Purpose: Move the file pointer in this stream to just before the block index.
+// Assumes that the stream is at the beginning, seekable, and
+// reading from the stream is OK.
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreFile::MoveStreamPositionToBlockIndex(IOStream &rStream)
+{
+ // Size of file
+ int64_t fileSize = rStream.BytesLeftToRead();
+
+ // Get header
+ file_StreamFormat hdr;
+
+ // Read the header
+ if(!rStream.ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */, IOStream::TimeOutInfinite))
+ {
+ // Couldn't read header
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Check magic number
+ if(ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ && ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V0
+#endif
+ )
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // Work out where the index is
+ int64_t numBlocks = box_ntoh64(hdr.mNumBlocks);
+ int64_t blockHeaderPosFromEnd = ((numBlocks * sizeof(file_BlockIndexEntry)) + sizeof(file_BlockIndexHeader));
+
+ // Sanity check
+ if(blockHeaderPosFromEnd > static_cast<int64_t>(fileSize - sizeof(file_StreamFormat)))
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // Seek to that position
+ rStream.Seek(0 - blockHeaderPosFromEnd, IOStream::SeekType_End);
+
+ // Done. Stream now in right position (as long as the file is formatted correctly)
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::EncodeFileDiff(const char *, int64_t, const BackupStoreFilename &, int64_t, IOStream &, int64_t *)
+// Purpose: Similar to EncodeFile, but takes the object ID of the file it's
+// diffing from, and the index of the blocks in a stream. It'll then
+// calculate which blocks can be reused from that old file.
+// The timeout is the timeout value for reading the diff block index.
+// If pIsCompletelyDifferent != 0, it will be set to true if the
+// the two files are completely different (do not share any block), false otherwise.
+//
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupStoreFileEncodeStream> BackupStoreFile::EncodeFileDiff
+(
+ const std::string& Filename, int64_t ContainerID,
+ const BackupStoreFilename &rStoreFilename, int64_t DiffFromObjectID,
+ IOStream &rDiffFromBlockIndex, int Timeout, DiffTimer *pDiffTimer,
+ int64_t *pModificationTime, bool *pIsCompletelyDifferent)
+{
+ // Is it a symlink?
+ {
+ EMU_STRUCT_STAT st;
+ if(EMU_LSTAT(Filename.c_str(), &st) != 0)
+ {
+ THROW_EXCEPTION(CommonException, OSFileError)
+ }
+ if((st.st_mode & S_IFLNK) == S_IFLNK)
+ {
+ // Don't do diffs for symlinks
+ if(pIsCompletelyDifferent != 0)
+ {
+ *pIsCompletelyDifferent = true;
+ }
+ return EncodeFile(Filename, ContainerID, rStoreFilename, pModificationTime);
+ }
+ }
+
+ // Load in the blocks
+ BlocksAvailableEntry *pindex = 0;
+ int64_t blocksInIndex = 0;
+ bool canDiffFromThis = false;
+ LoadIndex(rDiffFromBlockIndex, DiffFromObjectID, &pindex, blocksInIndex, Timeout, canDiffFromThis);
+ // BOX_TRACE("Diff: Blocks in index: " << blocksInIndex);
+
+ if(!canDiffFromThis)
+ {
+ // Don't do diffing...
+ if(pIsCompletelyDifferent != 0)
+ {
+ *pIsCompletelyDifferent = true;
+ }
+ return EncodeFile(Filename, ContainerID, rStoreFilename, pModificationTime);
+ }
+
+ // Pointer to recipe we're going to create
+ BackupStoreFileEncodeStream::Recipe *precipe = 0;
+
+ try
+ {
+ // Find which sizes should be scanned
+ int32_t sizesToScan[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES];
+ FindMostUsedSizes(pindex, blocksInIndex, sizesToScan);
+
+ // Flag for reporting to the user
+ bool completelyDifferent;
+
+ // BLOCK
+ {
+ // Search the file to find matching blocks
+ std::map<int64_t, int64_t> foundBlocks; // map of offset in file to index in block index
+ int64_t sizeOfInputFile = 0;
+ // BLOCK
+ {
+ FileStream file(Filename);
+ // Get size of file
+ sizeOfInputFile = file.BytesLeftToRead();
+ // Find all those lovely matching blocks
+ SearchForMatchingBlocks(file, foundBlocks, pindex,
+ blocksInIndex, sizesToScan, pDiffTimer);
+
+ // Is it completely different?
+ completelyDifferent = (foundBlocks.size() == 0);
+ }
+
+ // Create a recipe -- if the two files are completely different, don't put the from file ID in the recipe.
+ precipe = new BackupStoreFileEncodeStream::Recipe(pindex, blocksInIndex, completelyDifferent?(0):(DiffFromObjectID));
+ BlocksAvailableEntry *pindexKeptRef = pindex; // we need this later, but must set pindex == 0 now, because of exceptions
+ pindex = 0; // Recipe now has ownership
+
+ // Fill it in
+ GenerateRecipe(*precipe, pindexKeptRef, blocksInIndex, foundBlocks, sizeOfInputFile);
+ }
+ // foundBlocks no longer required
+
+ // Create the stream
+ std::auto_ptr<BackupStoreFileEncodeStream> stream(
+ new BackupStoreFileEncodeStream);
+
+ // Do the initial setup
+ stream->Setup(Filename, precipe, ContainerID, rStoreFilename, pModificationTime);
+ precipe = 0; // Stream has taken ownership of this
+
+ // Tell user about completely different status?
+ if(pIsCompletelyDifferent != 0)
+ {
+ *pIsCompletelyDifferent = completelyDifferent;
+ }
+
+ // Return the stream for the caller
+ return stream;
+ }
+ catch(...)
+ {
+ // cleanup
+ if(pindex != 0)
+ {
+ ::free(pindex);
+ pindex = 0;
+ }
+ if(precipe != 0)
+ {
+ delete precipe;
+ precipe = 0;
+ }
+ throw;
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: static LoadIndex(IOStream &, int64_t, BlocksAvailableEntry **, int64_t, bool &)
+// Purpose: Read in an index, and decrypt, and store in the in memory block format.
+// rCanDiffFromThis is set to false if the version of the from file is too old.
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+static void LoadIndex(IOStream &rBlockIndex, int64_t ThisID, BlocksAvailableEntry **ppIndex, int64_t &rNumBlocksOut, int Timeout, bool &rCanDiffFromThis)
+{
+ // Reset
+ rNumBlocksOut = 0;
+ rCanDiffFromThis = false;
+
+ // Read header
+ file_BlockIndexHeader hdr;
+ if(!rBlockIndex.ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */, Timeout))
+ {
+ // Couldn't read header
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE
+ // Check against backwards comptaibility stuff
+ if(hdr.mMagicValue == (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0))
+ {
+ // Won't diff against old version
+
+ // Absorb rest of stream
+ char buffer[2048];
+ while(rBlockIndex.StreamDataLeft())
+ {
+ rBlockIndex.Read(buffer, sizeof(buffer), 1000 /* 1 sec timeout */);
+ }
+
+ // Tell caller
+ rCanDiffFromThis = false;
+ return;
+ }
+#endif
+
+ // Check magic
+ if(hdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1))
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // Check that we're not trying to diff against a file which references blocks from another file
+ if(((int64_t)box_ntoh64(hdr.mOtherFileID)) != 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, CannotDiffAnIncompleteStoreFile)
+ }
+
+ // Mark as an acceptable diff.
+ rCanDiffFromThis = true;
+
+ // Get basic information
+ int64_t numBlocks = box_ntoh64(hdr.mNumBlocks);
+ uint64_t entryIVBase = box_ntoh64(hdr.mEntryIVBase);
+
+ //TODO: Verify that these sizes look reasonable
+
+ // Allocate space for the index
+ BlocksAvailableEntry *pindex = (BlocksAvailableEntry*)::malloc(sizeof(BlocksAvailableEntry) * numBlocks);
+ if(pindex == 0)
+ {
+ throw std::bad_alloc();
+ }
+
+ try
+ {
+ for(int64_t b = 0; b < numBlocks; ++b)
+ {
+ // Read an entry from the stream
+ file_BlockIndexEntry entry;
+ if(!rBlockIndex.ReadFullBuffer(&entry, sizeof(entry), 0 /* not interested in bytes read if this fails */, Timeout))
+ {
+ // Couldn't read entry
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Calculate IV for this entry
+ uint64_t iv = entryIVBase;
+ iv += b;
+ // Network byte order
+ iv = box_hton64(iv);
+ sBlowfishDecryptBlockEntry.SetIV(&iv);
+
+ // Decrypt the encrypted section
+ file_BlockIndexEntryEnc entryEnc;
+ int sectionSize = sBlowfishDecryptBlockEntry.TransformBlock(&entryEnc, sizeof(entryEnc),
+ entry.mEnEnc, sizeof(entry.mEnEnc));
+ if(sectionSize != sizeof(entryEnc))
+ {
+ THROW_EXCEPTION(BackupStoreException, BlockEntryEncodingDidntGiveExpectedLength)
+ }
+
+ // Check that we're not trying to diff against a file which references blocks from another file
+ if(((int64_t)box_ntoh64(entry.mEncodedSize)) <= 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, CannotDiffAnIncompleteStoreFile)
+ }
+
+ // Store all the required information
+ pindex[b].mpNextInHashList = 0; // hash list not set up yet
+ pindex[b].mSize = ntohl(entryEnc.mSize);
+ pindex[b].mWeakChecksum = ntohl(entryEnc.mWeakChecksum);
+ ::memcpy(pindex[b].mStrongChecksum, entryEnc.mStrongChecksum, sizeof(pindex[b].mStrongChecksum));
+ }
+
+ // Store index pointer for called
+ ASSERT(ppIndex != 0);
+ *ppIndex = pindex;
+
+ // Store number of blocks for caller
+ rNumBlocksOut = numBlocks;
+
+ }
+ catch(...)
+ {
+ // clean up and send the exception along its way
+ ::free(pindex);
+ throw;
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: static FindMostUsedSizes(BlocksAvailableEntry *, int64_t, int32_t[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES])
+// Purpose: Finds the most commonly used block sizes in the index
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+static void FindMostUsedSizes(BlocksAvailableEntry *pIndex, int64_t NumBlocks, int32_t Sizes[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES])
+{
+ // Array for lengths
+ int64_t sizeCounts[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES];
+
+ // Set arrays to lots of zeros (= unused entries)
+ for(int l = 0; l < BACKUP_FILE_DIFF_MAX_BLOCK_SIZES; ++l)
+ {
+ Sizes[l] = 0;
+ sizeCounts[l] = 0;
+ }
+
+ // Array for collecting sizes
+ std::map<int32_t, int64_t> foundSizes;
+
+ // Run through blocks and make a count of the entries
+ for(int64_t b = 0; b < NumBlocks; ++b)
+ {
+ // Only if the block size is bigger than the minimum size we'll scan for
+ if(pIndex[b].mSize > BACKUP_FILE_DIFF_MIN_BLOCK_SIZE)
+ {
+ // Find entry?
+ std::map<int32_t, int64_t>::const_iterator f(foundSizes.find(pIndex[b].mSize));
+ if(f != foundSizes.end())
+ {
+ // Increment existing entry
+ foundSizes[pIndex[b].mSize] = foundSizes[pIndex[b].mSize] + 1;
+ }
+ else
+ {
+ // New entry
+ foundSizes[pIndex[b].mSize] = 1;
+ }
+ }
+ }
+
+ // Make the block sizes
+ for(std::map<int32_t, int64_t>::const_iterator i(foundSizes.begin()); i != foundSizes.end(); ++i)
+ {
+ // Find the position of the size in the array
+ for(int t = 0; t < BACKUP_FILE_DIFF_MAX_BLOCK_SIZES; ++t)
+ {
+ // Instead of sorting on the raw count of blocks,
+ // take the file area covered by this block size.
+ if(i->second * i->first > sizeCounts[t] * Sizes[t])
+ {
+ // Then this size belong before this entry -- shuffle them up
+ for(int s = (BACKUP_FILE_DIFF_MAX_BLOCK_SIZES - 1); s >= t; --s)
+ {
+ Sizes[s] = Sizes[s-1];
+ sizeCounts[s] = sizeCounts[s-1];
+ }
+
+ // Insert this size
+ Sizes[t] = i->first;
+ sizeCounts[t] = i->second;
+
+ // Shouldn't do any more searching
+ break;
+ }
+ }
+ }
+
+ // trace the size table in debug builds
+#ifndef BOX_RELEASE_BUILD
+ if(BackupStoreFile::TraceDetailsOfDiffProcess)
+ {
+ for(int t = 0; t < BACKUP_FILE_DIFF_MAX_BLOCK_SIZES; ++t)
+ {
+ BOX_TRACE("Diff block size " << t << ": " <<
+ Sizes[t] << " (count = " <<
+ sizeCounts[t] << ")");
+ }
+ }
+#endif
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: static SearchForMatchingBlocks(IOStream &, std::map<int64_t, int64_t> &, BlocksAvailableEntry *, int64_t, int32_t[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES])
+// Purpose: Find the matching blocks within the file.
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+static void SearchForMatchingBlocks(IOStream &rFile, std::map<int64_t, int64_t> &rFoundBlocks,
+ BlocksAvailableEntry *pIndex, int64_t NumBlocks,
+ int32_t Sizes[BACKUP_FILE_DIFF_MAX_BLOCK_SIZES], DiffTimer *pDiffTimer)
+{
+ Timer maximumDiffingTime(0, "MaximumDiffingTime");
+
+ if(pDiffTimer && pDiffTimer->IsManaged())
+ {
+ maximumDiffingTime = Timer(pDiffTimer->GetMaximumDiffingTime() *
+ MILLI_SEC_IN_SEC, "MaximumDiffingTime");
+ }
+
+ std::map<int64_t, int32_t> goodnessOfFit;
+
+ // Allocate the hash lookup table
+ BlocksAvailableEntry **phashTable = (BlocksAvailableEntry **)::malloc(sizeof(BlocksAvailableEntry *) * (64*1024));
+
+ // Choose a size for the buffer, just a little bit more than the maximum block size
+ int32_t bufSize = Sizes[0];
+ for(int z = 1; z < BACKUP_FILE_DIFF_MAX_BLOCK_SIZES; ++z)
+ {
+ if(Sizes[z] > bufSize) bufSize = Sizes[z];
+ }
+ bufSize += 4;
+ ASSERT(bufSize > Sizes[0]);
+ ASSERT(bufSize > 0);
+ if(bufSize > (BACKUP_FILE_MAX_BLOCK_SIZE + 1024))
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // TODO: Because we read in the file a scanned block size at a time,
+ // it is likely to be inefficient. Probably will be much better to
+ // calculate checksums for all block sizes in a single pass.
+
+ // Allocate the buffers.
+ uint8_t *pbuffer0 = (uint8_t *)::malloc(bufSize);
+ uint8_t *pbuffer1 = (uint8_t *)::malloc(bufSize);
+ try
+ {
+ // Check buffer allocation
+ if(pbuffer0 == 0 || pbuffer1 == 0 || phashTable == 0)
+ {
+ // If a buffer got allocated, it will be cleaned up in the catch block
+ throw std::bad_alloc();
+ }
+
+ // Flag to abort the run, if too many blocks are found -- avoid using
+ // huge amounts of processor time when files contain many similar blocks.
+ bool abortSearch = false;
+
+ // Search for each block size in turn
+ // NOTE: Do the smallest size first, so that the scheme for adding
+ // entries in the found list works as expected and replaces smallers block
+ // with larger blocks when it finds matches at the same offset in the file.
+ for(int s = BACKUP_FILE_DIFF_MAX_BLOCK_SIZES - 1; s >= 0; --s)
+ {
+ ASSERT(Sizes[s] <= bufSize);
+ BOX_TRACE("Diff pass " << s << ", for block size " <<
+ Sizes[s]);
+
+ // Check we haven't finished
+ if(Sizes[s] == 0)
+ {
+ // empty entry, try next size
+ continue;
+ }
+
+ // Set up the hash table entries
+ SetupHashTable(pIndex, NumBlocks, Sizes[s], phashTable);
+
+ // Shift file position to beginning
+ rFile.Seek(0, IOStream::SeekType_Absolute);
+
+ // Read first block
+ if(rFile.Read(pbuffer0, Sizes[s]) != Sizes[s])
+ {
+ // Size of file too short to match -- do next size
+ continue;
+ }
+
+ // Setup block pointers
+ uint8_t *beginnings = pbuffer0;
+ uint8_t *endings = pbuffer1;
+ int offset = 0;
+
+ // Calculate the first checksum, ready for rolling
+ RollingChecksum rolling(beginnings, Sizes[s]);
+
+ // Then roll, until the file is exhausted
+ int64_t fileBlockNumber = 0;
+ int64_t fileOffset = 0;
+ int rollOverInitialBytes = 0;
+ while(true)
+ {
+ if(maximumDiffingTime.HasExpired())
+ {
+ ASSERT(pDiffTimer != NULL);
+ BOX_INFO("MaximumDiffingTime reached - "
+ "suspending file diff");
+ abortSearch = true;
+ break;
+ }
+
+ if(pDiffTimer)
+ {
+ pDiffTimer->DoKeepAlive();
+ }
+
+ // Load in another block of data, and record how big it is
+ int bytesInEndings = rFile.Read(endings, Sizes[s]);
+ int tmp;
+
+ // Skip any bytes from a previous matched block
+ if(rollOverInitialBytes > 0 && offset < bytesInEndings)
+ {
+ int spaceLeft = bytesInEndings - offset;
+ int thisRoll = (rollOverInitialBytes > spaceLeft) ? spaceLeft : rollOverInitialBytes;
+
+ rolling.RollForwardSeveral(beginnings+offset, endings+offset, Sizes[s], thisRoll);
+
+ offset += thisRoll;
+ fileOffset += thisRoll;
+ rollOverInitialBytes -= thisRoll;
+
+ if(rollOverInitialBytes)
+ {
+ goto refresh;
+ }
+ }
+
+ if(goodnessOfFit.count(fileOffset))
+ {
+ tmp = goodnessOfFit[fileOffset];
+ }
+ else
+ {
+ tmp = 0;
+ }
+
+ if(tmp >= Sizes[s])
+ {
+ // Skip over bigger ready-matched blocks completely
+ rollOverInitialBytes = tmp;
+ int spaceLeft = bytesInEndings - offset;
+ int thisRoll = (rollOverInitialBytes > spaceLeft) ? spaceLeft : rollOverInitialBytes;
+
+ rolling.RollForwardSeveral(beginnings+offset, endings+offset, Sizes[s], thisRoll);
+
+ offset += thisRoll;
+ fileOffset += thisRoll;
+ rollOverInitialBytes -= thisRoll;
+
+ if(rollOverInitialBytes)
+ {
+ goto refresh;
+ }
+ }
+
+ while(offset < bytesInEndings)
+ {
+ // Is current checksum in hash list?
+ uint16_t hash = rolling.GetComponentForHashing();
+ if(phashTable[hash] != 0 && (goodnessOfFit.count(fileOffset) == 0 || goodnessOfFit[fileOffset] < Sizes[s]))
+ {
+ if(SecondStageMatch(phashTable[hash], rolling, beginnings, endings, offset, Sizes[s], fileBlockNumber, pIndex, rFoundBlocks))
+ {
+ BOX_TRACE("Found block match for " << hash << " of " << Sizes[s] << " bytes at offset " << fileOffset);
+ goodnessOfFit[fileOffset] = Sizes[s];
+
+ // Block matched, roll the checksum forward to the next block without doing
+ // any more comparisons, because these are pointless (as any more matches will be ignored when
+ // the recipe is generated) and just take up valuable processor time. Edge cases are
+ // especially nasty, using huge amounts of time and memory.
+ int skip = Sizes[s];
+ if(offset < bytesInEndings && skip > 0)
+ {
+ int spaceLeft = bytesInEndings - offset;
+ int thisRoll = (skip > spaceLeft) ? spaceLeft : skip;
+
+ rolling.RollForwardSeveral(beginnings+offset, endings+offset, Sizes[s], thisRoll);
+
+ offset += thisRoll;
+ fileOffset += thisRoll;
+ skip -= thisRoll;
+ }
+ // Not all the bytes necessary will have been skipped, so get them
+ // skipped after the next block is loaded.
+ rollOverInitialBytes = skip;
+
+ // End this loop, so the final byte isn't used again
+ break;
+ }
+ else
+ {
+ BOX_TRACE("False alarm match for " << hash << " of " << Sizes[s] << " bytes at offset " << fileOffset);
+ }
+
+ int64_t NumBlocksFound = static_cast<int64_t>(
+ rFoundBlocks.size());
+ int64_t MaxBlocksFound = NumBlocks *
+ BACKUP_FILE_DIFF_MAX_BLOCK_FIND_MULTIPLE;
+
+ if(NumBlocksFound > MaxBlocksFound)
+ {
+ abortSearch = true;
+ break;
+ }
+ }
+
+ // Roll checksum forward
+ rolling.RollForward(beginnings[offset], endings[offset], Sizes[s]);
+
+ // Increment offsets
+ ++offset;
+ ++fileOffset;
+ }
+
+ if(abortSearch) break;
+
+ refresh:
+ // Finished?
+ if(bytesInEndings != Sizes[s])
+ {
+ // No more data in file -- check the final block
+ // (Do a copy and paste of 5 lines of code instead of introducing a comparison for
+ // each byte of the file)
+ uint16_t hash = rolling.GetComponentForHashing();
+ if(phashTable[hash] != 0 && (goodnessOfFit.count(fileOffset) == 0 || goodnessOfFit[fileOffset] < Sizes[s]))
+ {
+ if(SecondStageMatch(phashTable[hash], rolling, beginnings, endings, offset, Sizes[s], fileBlockNumber, pIndex, rFoundBlocks))
+ {
+ goodnessOfFit[fileOffset] = Sizes[s];
+ }
+ }
+
+ // finish
+ break;
+ }
+
+ // Switch buffers, reset offset
+ beginnings = endings;
+ endings = (beginnings == pbuffer0)?(pbuffer1):(pbuffer0); // ie the other buffer
+ offset = 0;
+
+ // And count the blocks which have been done
+ ++fileBlockNumber;
+ }
+
+ if(abortSearch) break;
+ }
+
+ // Free buffers and hash table
+ ::free(pbuffer1);
+ pbuffer1 = 0;
+ ::free(pbuffer0);
+ pbuffer0 = 0;
+ ::free(phashTable);
+ phashTable = 0;
+ }
+ catch(...)
+ {
+ // Cleanup and throw
+ if(pbuffer1 != 0) ::free(pbuffer1);
+ if(pbuffer0 != 0) ::free(pbuffer0);
+ if(phashTable != 0) ::free(phashTable);
+ throw;
+ }
+
+#ifndef BOX_RELEASE_BUILD
+ if(BackupStoreFile::TraceDetailsOfDiffProcess)
+ {
+ // Trace out the found blocks in debug mode
+ BOX_TRACE("Diff: list of found blocks");
+ BOX_TRACE("======== ======== ======== ========");
+ BOX_TRACE(" Offset BlkIdx Size Movement");
+ for(std::map<int64_t, int64_t>::const_iterator i(rFoundBlocks.begin()); i != rFoundBlocks.end(); ++i)
+ {
+ int64_t orgLoc = 0;
+ for(int64_t b = 0; b < i->second; ++b)
+ {
+ orgLoc += pIndex[b].mSize;
+ }
+ BOX_TRACE(std::setw(8) << i->first << " " <<
+ std::setw(8) << i->second << " " <<
+ std::setw(8) << pIndex[i->second].mSize <<
+ " " <<
+ std::setw(8) << (i->first - orgLoc));
+ }
+ BOX_TRACE("======== ======== ======== ========");
+ }
+#endif
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: static SetupHashTable(BlocksAvailableEntry *, int64_t, in32_t, BlocksAvailableEntry **)
+// Purpose: Set up the hash table ready for a scan
+// Created: 14/1/04
+//
+// --------------------------------------------------------------------------
+static void SetupHashTable(BlocksAvailableEntry *pIndex, int64_t NumBlocks, int32_t BlockSize, BlocksAvailableEntry **pHashTable)
+{
+ // Set all entries in the hash table to zero
+ ::memset(pHashTable, 0, (sizeof(BlocksAvailableEntry *) * (64*1024)));
+
+ // Scan through the blocks, building the hash table
+ for(int64_t b = 0; b < NumBlocks; ++b)
+ {
+ // Only look at the required block size
+ if(pIndex[b].mSize == BlockSize)
+ {
+ // Get the value under which to hash this entry
+ uint16_t hash = RollingChecksum::ExtractHashingComponent(pIndex[b].mWeakChecksum);
+
+ // Already present in table?
+ if(pHashTable[hash] != 0)
+ {
+ //BOX_TRACE("Another hash entry for " << hash << " found");
+ // Yes -- need to set the pointer in this entry to the current entry to build the linked list
+ pIndex[b].mpNextInHashList = pHashTable[hash];
+ }
+
+ // Put a pointer to this entry in the hash table
+ pHashTable[hash] = pIndex + b;
+ }
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: static bool SecondStageMatch(xxx)
+// Purpose: When a match in the hash table is found, scan for second stage match using strong checksum.
+// Created: 14/1/04
+//
+// --------------------------------------------------------------------------
+static bool SecondStageMatch(BlocksAvailableEntry *pFirstInHashList, RollingChecksum &fastSum, uint8_t *pBeginnings, uint8_t *pEndings,
+ int Offset, int32_t BlockSize, int64_t FileBlockNumber, BlocksAvailableEntry *pIndex, std::map<int64_t, int64_t> &rFoundBlocks)
+{
+ // Check parameters
+ ASSERT(pBeginnings != 0);
+ ASSERT(pEndings != 0);
+ ASSERT(Offset >= 0);
+ ASSERT(BlockSize > 0);
+ ASSERT(pFirstInHashList != 0);
+ ASSERT(pIndex != 0);
+
+#ifndef BOX_RELEASE_BUILD
+ uint16_t DEBUG_Hash = fastSum.GetComponentForHashing();
+#endif
+ uint32_t Checksum = fastSum.GetChecksum();
+
+ // Before we go to the expense of the MD5, make sure it's a darn good match on the checksum we already know.
+ BlocksAvailableEntry *scan = pFirstInHashList;
+ bool found=false;
+ while(scan != 0)
+ {
+ if(scan->mWeakChecksum == Checksum)
+ {
+ found = true;
+ break;
+ }
+ scan = scan->mpNextInHashList;
+ }
+ if(!found)
+ {
+ return false;
+ }
+
+ // Calculate the strong MD5 digest for this block
+ MD5Digest strong;
+ // Add the data from the beginnings
+ strong.Add(pBeginnings + Offset, BlockSize - Offset);
+ // Add any data from the endings
+ if(Offset > 0)
+ {
+ strong.Add(pEndings, Offset);
+ }
+ strong.Finish();
+
+ // Then go through the entries in the hash list, comparing with the strong digest calculated
+ scan = pFirstInHashList;
+ //BOX_TRACE("second stage match");
+ while(scan != 0)
+ {
+ //BOX_TRACE("scan size " << scan->mSize <<
+ // ", block size " << BlockSize <<
+ // ", hash " << Hash);
+ ASSERT(scan->mSize == BlockSize);
+ ASSERT(RollingChecksum::ExtractHashingComponent(scan->mWeakChecksum) == DEBUG_Hash);
+
+ // Compare?
+ if(strong.DigestMatches(scan->mStrongChecksum))
+ {
+ //BOX_TRACE("Match!\n");
+ // Found! Add to list of found blocks...
+ int64_t fileOffset = (FileBlockNumber * BlockSize) + Offset;
+ int64_t blockIndex = (scan - pIndex); // pointer arthmitic is frowned upon. But most efficient way of doing it here -- alternative is to use more memory
+
+ // We do NOT search for smallest blocks first, as this code originally assumed.
+ // To prevent this from potentially overwriting a better match, the caller must determine
+ // the relative "goodness" of any existing match and this one, and avoid the call if it
+ // could be detrimental.
+ rFoundBlocks[fileOffset] = blockIndex;
+
+ // No point in searching further, report success
+ return true;
+ }
+
+ // Next
+ scan = scan->mpNextInHashList;
+ }
+
+ // Not matched
+ return false;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: static GenerateRecipe(BackupStoreFileEncodeStream::Recipe &, BlocksAvailableEntry *, int64_t, std::map<int64_t, int64_t> &)
+// Purpose: Fills in the recipe from the found block list
+// Created: 15/1/04
+//
+// --------------------------------------------------------------------------
+static void GenerateRecipe(BackupStoreFileEncodeStream::Recipe &rRecipe, BlocksAvailableEntry *pIndex,
+ int64_t NumBlocks, std::map<int64_t, int64_t> &rFoundBlocks, int64_t SizeOfInputFile)
+{
+ // NOTE: This function could be a lot more sophisiticated. For example, if
+ // a small block overlaps a big block like this
+ // ****
+ // *******************************
+ // then the small block will be used, not the big one. But it'd be better to
+ // just ignore the small block and keep the big one. However, some stats should
+ // be gathered about real world files before writing complex code which might
+ // go wrong.
+
+ // Initialise a blank instruction
+ BackupStoreFileEncodeStream::RecipeInstruction instruction;
+ #define RESET_INSTRUCTION \
+ instruction.mSpaceBefore = 0; \
+ instruction.mBlocks = 0; \
+ instruction.mpStartBlock = 0;
+ RESET_INSTRUCTION
+
+ // First, a special case for when there are no found blocks
+ if(rFoundBlocks.size() == 0)
+ {
+ // No blocks, just a load of space
+ instruction.mSpaceBefore = SizeOfInputFile;
+ rRecipe.push_back(instruction);
+
+ #ifndef BOX_RELEASE_BUILD
+ if(BackupStoreFile::TraceDetailsOfDiffProcess)
+ {
+ BOX_TRACE("Diff: Default recipe generated, " <<
+ SizeOfInputFile << " bytes of file");
+ }
+ #endif
+
+ // Don't do anything
+ return;
+ }
+
+ // Current location
+ int64_t loc = 0;
+
+ // Then iterate through the list, generating the recipe
+ std::map<int64_t, int64_t>::const_iterator i(rFoundBlocks.begin());
+ ASSERT(i != rFoundBlocks.end()); // check logic
+
+ // Counting for debug tracing
+#ifndef BOX_RELEASE_BUILD
+ int64_t debug_NewBytesFound = 0;
+ int64_t debug_OldBlocksUsed = 0;
+#endif
+
+ for(; i != rFoundBlocks.end(); ++i)
+ {
+ // Remember... map is (position in file) -> (index of block in pIndex)
+
+ if(i->first < loc)
+ {
+ // This block overlaps the last one
+ continue;
+ }
+ else if(i->first > loc)
+ {
+ // There's a gap between the end of the last thing and this block.
+ // If there's an instruction waiting, push it onto the list
+ if(instruction.mSpaceBefore != 0 || instruction.mpStartBlock != 0)
+ {
+ rRecipe.push_back(instruction);
+ }
+ // Start a new instruction, with the gap ready
+ RESET_INSTRUCTION
+ instruction.mSpaceBefore = i->first - loc;
+ // Move location forward to match
+ loc += instruction.mSpaceBefore;
+#ifndef BOX_RELEASE_BUILD
+ debug_NewBytesFound += instruction.mSpaceBefore;
+#endif
+ }
+
+ // First, does the current instruction need pushing back, because this block is not
+ // sequential to the last one?
+ if(instruction.mpStartBlock != 0 && (pIndex + i->second) != (instruction.mpStartBlock + instruction.mBlocks))
+ {
+ rRecipe.push_back(instruction);
+ RESET_INSTRUCTION
+ }
+
+ // Add in this block
+ if(instruction.mpStartBlock == 0)
+ {
+ // This block starts a new instruction
+ instruction.mpStartBlock = pIndex + i->second;
+ instruction.mBlocks = 1;
+ }
+ else
+ {
+ // It continues the previous section of blocks
+ instruction.mBlocks += 1;
+ }
+
+#ifndef BOX_RELEASE_BUILD
+ debug_OldBlocksUsed++;
+#endif
+
+ // Move location forward
+ loc += pIndex[i->second].mSize;
+ }
+
+ // Push the last instruction generated
+ rRecipe.push_back(instruction);
+
+ // Is there any space left at the end which needs sending?
+ if(loc != SizeOfInputFile)
+ {
+ RESET_INSTRUCTION
+ instruction.mSpaceBefore = SizeOfInputFile - loc;
+#ifndef BOX_RELEASE_BUILD
+ debug_NewBytesFound += instruction.mSpaceBefore;
+#endif
+ rRecipe.push_back(instruction);
+ }
+
+ // dump out the recipe
+#ifndef BOX_RELEASE_BUILD
+ BOX_TRACE("Diff: " <<
+ debug_NewBytesFound << " new bytes found, " <<
+ debug_OldBlocksUsed << " old blocks used");
+ if(BackupStoreFile::TraceDetailsOfDiffProcess)
+ {
+ BOX_TRACE("Diff: Recipe generated (size " << rRecipe.size());
+ BOX_TRACE("======== ========= ========");
+ BOX_TRACE("Space b4 FirstBlk NumBlks");
+ {
+ for(unsigned int e = 0; e < rRecipe.size(); ++e)
+ {
+ char b[64];
+#ifdef WIN32
+ sprintf(b, "%8I64d", (int64_t)(rRecipe[e].mpStartBlock - pIndex));
+#else
+ sprintf(b, "%8lld", (int64_t)(rRecipe[e].mpStartBlock - pIndex));
+#endif
+ BOX_TRACE(std::setw(8) <<
+ rRecipe[e].mSpaceBefore <<
+ " " <<
+ ((rRecipe[e].mpStartBlock == 0)?" -":b) <<
+ " " << std::setw(8) <<
+ rRecipe[e].mBlocks);
+ }
+ }
+ BOX_TRACE("======== ========= ========");
+ }
+#endif
+}
diff --git a/lib/backupstore/BackupStoreFileEncodeStream.cpp b/lib/backupstore/BackupStoreFileEncodeStream.cpp
new file mode 100644
index 00000000..b53c4c26
--- /dev/null
+++ b/lib/backupstore/BackupStoreFileEncodeStream.cpp
@@ -0,0 +1,717 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFileEncodeStream.cpp
+// Purpose: Implement stream-based file encoding for the backup store
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <string.h>
+
+#include "BackupClientFileAttributes.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreException.h"
+#include "BackupStoreFile.h"
+#include "BackupStoreFileCryptVar.h"
+#include "BackupStoreFileEncodeStream.h"
+#include "BackupStoreFileWire.h"
+#include "BackupStoreObjectMagic.h"
+#include "BoxTime.h"
+#include "FileStream.h"
+#include "Random.h"
+#include "RollingChecksum.h"
+
+#include "MemLeakFindOn.h"
+
+#include <cstring>
+
+using namespace BackupStoreFileCryptVar;
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::BackupStoreFileEncodeStream
+// Purpose: Constructor (opens file)
+// Created: 8/12/03
+//
+// --------------------------------------------------------------------------
+BackupStoreFileEncodeStream::BackupStoreFileEncodeStream()
+ : mpRecipe(0),
+ mpFile(0),
+ mpLogging(0),
+ mpRunStatusProvider(NULL),
+ mStatus(Status_Header),
+ mSendData(true),
+ mTotalBlocks(0),
+ mAbsoluteBlockNumber(-1),
+ mInstructionNumber(-1),
+ mNumBlocks(0),
+ mCurrentBlock(-1),
+ mCurrentBlockEncodedSize(0),
+ mPositionInCurrentBlock(0),
+ mBlockSize(BACKUP_FILE_MIN_BLOCK_SIZE),
+ mLastBlockSize(0),
+ mTotalBytesSent(0),
+ mpRawBuffer(0),
+ mAllocatedBufferSize(0),
+ mEntryIVBase(0)
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::~BackupStoreFileEncodeStream()
+// Purpose: Destructor
+// Created: 8/12/03
+//
+// --------------------------------------------------------------------------
+BackupStoreFileEncodeStream::~BackupStoreFileEncodeStream()
+{
+ // Free buffers
+ if(mpRawBuffer)
+ {
+ ::free(mpRawBuffer);
+ mpRawBuffer = 0;
+ }
+
+ // Close the file, which we might have open
+ if(mpFile)
+ {
+ delete mpFile;
+ mpFile = 0;
+ }
+
+ // Clear up logging stream
+ if(mpLogging)
+ {
+ delete mpLogging;
+ mpLogging = 0;
+ }
+
+ // Free the recipe
+ if(mpRecipe != 0)
+ {
+ delete mpRecipe;
+ mpRecipe = 0;
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::Setup(const char *, Recipe *, int64_t, const BackupStoreFilename &, int64_t *)
+// Purpose: Reads file information, and builds file header reading for sending.
+// Takes ownership of the Recipe.
+// Created: 8/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreFileEncodeStream::Setup(const std::string& Filename,
+ BackupStoreFileEncodeStream::Recipe *pRecipe,
+ int64_t ContainerID, const BackupStoreFilename &rStoreFilename,
+ int64_t *pModificationTime, ReadLoggingStream::Logger* pLogger,
+ RunStatusProvider* pRunStatusProvider)
+{
+ // Pointer to a blank recipe which we might create
+ BackupStoreFileEncodeStream::Recipe *pblankRecipe = 0;
+
+ try
+ {
+ // Get file attributes
+ box_time_t modTime = 0;
+ int64_t fileSize = 0;
+ BackupClientFileAttributes attr;
+ attr.ReadAttributes(Filename, false /* no zeroing of modification times */, &modTime,
+ 0 /* not interested in attr mod time */, &fileSize);
+
+ // Might need to create a blank recipe...
+ if(pRecipe == 0)
+ {
+ pblankRecipe = new BackupStoreFileEncodeStream::Recipe(0, 0);
+
+ BackupStoreFileEncodeStream::RecipeInstruction instruction;
+ instruction.mSpaceBefore = fileSize; // whole file
+ instruction.mBlocks = 0; // no blocks
+ instruction.mpStartBlock = 0; // no block
+ pblankRecipe->push_back(instruction);
+
+ pRecipe = pblankRecipe;
+ }
+
+ // Tell caller?
+ if(pModificationTime != 0)
+ {
+ *pModificationTime = modTime;
+ }
+
+ // Go through each instruction in the recipe and work out how many blocks
+ // it will add, and the max clear size of these blocks
+ int maxBlockClearSize = 0;
+ for(uint64_t inst = 0; inst < pRecipe->size(); ++inst)
+ {
+ if((*pRecipe)[inst].mSpaceBefore > 0)
+ {
+ // Calculate the number of blocks the space before requires
+ int64_t numBlocks;
+ int32_t blockSize, lastBlockSize;
+ CalculateBlockSizes((*pRecipe)[inst].mSpaceBefore, numBlocks, blockSize, lastBlockSize);
+ // Add to accumlated total
+ mTotalBlocks += numBlocks;
+ // Update maximum clear size
+ if(blockSize > maxBlockClearSize) maxBlockClearSize = blockSize;
+ if(lastBlockSize > maxBlockClearSize) maxBlockClearSize = lastBlockSize;
+ }
+
+ // Add number of blocks copied from the previous file
+ mTotalBlocks += (*pRecipe)[inst].mBlocks;
+
+ // Check for bad things
+ if((*pRecipe)[inst].mBlocks < 0 || ((*pRecipe)[inst].mBlocks != 0 && (*pRecipe)[inst].mpStartBlock == 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, Internal)
+ }
+
+ // Run through blocks to get the max clear size
+ for(int32_t b = 0; b < (*pRecipe)[inst].mBlocks; ++b)
+ {
+ if((*pRecipe)[inst].mpStartBlock[b].mSize > maxBlockClearSize) maxBlockClearSize = (*pRecipe)[inst].mpStartBlock[b].mSize;
+ }
+ }
+
+ // Send data? (symlinks don't have any data in them)
+ mSendData = !attr.IsSymLink();
+
+ // If not data is being sent, then the max clear block size is zero
+ if(!mSendData)
+ {
+ maxBlockClearSize = 0;
+ }
+
+ // Header
+ file_StreamFormat hdr;
+ hdr.mMagicValue = htonl(OBJECTMAGIC_FILE_MAGIC_VALUE_V1);
+ hdr.mNumBlocks = (mSendData)?(box_hton64(mTotalBlocks)):(0);
+ hdr.mContainerID = box_hton64(ContainerID);
+ hdr.mModificationTime = box_hton64(modTime);
+ // add a bit to make it harder to tell what's going on -- try not to give away too much info about file size
+ hdr.mMaxBlockClearSize = htonl(maxBlockClearSize + 128);
+ hdr.mOptions = 0; // no options defined yet
+
+ // Write header to stream
+ mData.Write(&hdr, sizeof(hdr));
+
+ // Write filename to stream
+ rStoreFilename.WriteToStream(mData);
+
+ // Write attributes to stream
+ attr.WriteToStream(mData);
+
+ // Allocate some buffers for writing data
+ if(mSendData)
+ {
+ // Open the file
+ mpFile = new FileStream(Filename);
+
+ if (pLogger)
+ {
+ // Create logging stream
+ mpLogging = new ReadLoggingStream(*mpFile,
+ *pLogger);
+ }
+ else
+ {
+ // re-use FileStream instead
+ mpLogging = mpFile;
+ mpFile = NULL;
+ }
+
+ // Work out the largest possible block required for the encoded data
+ mAllocatedBufferSize = BackupStoreFile::MaxBlockSizeForChunkSize(maxBlockClearSize);
+
+ // Then allocate two blocks of this size
+ mpRawBuffer = (uint8_t*)::malloc(mAllocatedBufferSize);
+ if(mpRawBuffer == 0)
+ {
+ throw std::bad_alloc();
+ }
+#ifndef BOX_RELEASE_BUILD
+ // In debug builds, make sure that the reallocation code is exercised.
+ mEncodedBuffer.Allocate(mAllocatedBufferSize / 4);
+#else
+ mEncodedBuffer.Allocate(mAllocatedBufferSize);
+#endif
+ }
+ else
+ {
+ // Write an empty block index for the symlink
+ file_BlockIndexHeader blkhdr;
+ blkhdr.mMagicValue = htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1);
+ blkhdr.mOtherFileID = box_hton64(0); // not other file ID
+ blkhdr.mEntryIVBase = box_hton64(0);
+ blkhdr.mNumBlocks = box_hton64(0);
+ mData.Write(&blkhdr, sizeof(blkhdr));
+ }
+
+ // Ready for reading
+ mData.SetForReading();
+
+ // Update stats
+ BackupStoreFile::msStats.mBytesInEncodedFiles += fileSize;
+
+ // Finally, store the pointer to the recipe, when we know exceptions won't occur
+ mpRecipe = pRecipe;
+ }
+ catch(...)
+ {
+ // Clean up any blank recipe
+ if(pblankRecipe != 0)
+ {
+ delete pblankRecipe;
+ pblankRecipe = 0;
+ }
+ throw;
+ }
+
+ mpRunStatusProvider = pRunStatusProvider;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::CalculateBlockSizes(int64_t &, int32_t &, int32_t &)
+// Purpose: Calculates the sizes of blocks in a section of the file
+// Created: 16/1/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreFileEncodeStream::CalculateBlockSizes(int64_t DataSize, int64_t &rNumBlocksOut, int32_t &rBlockSizeOut, int32_t &rLastBlockSizeOut)
+{
+ // How many blocks, and how big?
+ rBlockSizeOut = BACKUP_FILE_MIN_BLOCK_SIZE / 2;
+ do
+ {
+ rBlockSizeOut *= 2;
+
+ rNumBlocksOut = (DataSize + rBlockSizeOut - 1) / rBlockSizeOut;
+
+ } while(rBlockSizeOut < BACKUP_FILE_MAX_BLOCK_SIZE && rNumBlocksOut > BACKUP_FILE_INCREASE_BLOCK_SIZE_AFTER);
+
+ // Last block size
+ rLastBlockSizeOut = DataSize - ((rNumBlocksOut - 1) * rBlockSizeOut);
+
+ // Avoid small blocks?
+ if(rLastBlockSizeOut < BACKUP_FILE_AVOID_BLOCKS_LESS_THAN
+ && rNumBlocksOut > 1)
+ {
+ // Add the small bit of data to the last block
+ --rNumBlocksOut;
+ rLastBlockSizeOut += rBlockSizeOut;
+ }
+
+ // checks!
+ ASSERT((((rNumBlocksOut-1) * rBlockSizeOut) + rLastBlockSizeOut) == DataSize);
+ //TRACE4("CalcBlockSize, sz %lld, num %lld, blocksize %d, last %d\n", DataSize, rNumBlocksOut, (int32_t)rBlockSizeOut, (int32_t)rLastBlockSizeOut);
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::Read(void *, int, int)
+// Purpose: As interface -- generates encoded file data on the fly from the raw file
+// Created: 8/12/03
+//
+// --------------------------------------------------------------------------
+int BackupStoreFileEncodeStream::Read(void *pBuffer, int NBytes, int Timeout)
+{
+ // Check there's something to do.
+ if(mStatus == Status_Finished)
+ {
+ return 0;
+ }
+
+ if(mpRunStatusProvider && mpRunStatusProvider->StopRun())
+ {
+ THROW_EXCEPTION(BackupStoreException, SignalReceived);
+ }
+
+ int bytesToRead = NBytes;
+ uint8_t *buffer = (uint8_t*)pBuffer;
+
+ while(bytesToRead > 0 && mStatus != Status_Finished)
+ {
+ if(mStatus == Status_Header || mStatus == Status_BlockListing)
+ {
+ // Header or block listing phase -- send from the buffered stream
+
+ // Send bytes from the data buffer
+ int b = mData.Read(buffer, bytesToRead, Timeout);
+ bytesToRead -= b;
+ buffer += b;
+
+ // Check to see if all the data has been used from this stream
+ if(!mData.StreamDataLeft())
+ {
+ // Yes, move on to next phase (or finish, if there's no file data)
+ if(!mSendData)
+ {
+ mStatus = Status_Finished;
+ }
+ else
+ {
+ // Reset the buffer so it can be used for the next phase
+ mData.Reset();
+
+ // Get buffer ready for index?
+ if(mStatus == Status_Header)
+ {
+ // Just finished doing the stream header, create the block index header
+ file_BlockIndexHeader blkhdr;
+ blkhdr.mMagicValue = htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1);
+ ASSERT(mpRecipe != 0);
+ blkhdr.mOtherFileID = box_hton64(mpRecipe->GetOtherFileID());
+ blkhdr.mNumBlocks = box_hton64(mTotalBlocks);
+
+ // Generate the IV base
+ Random::Generate(&mEntryIVBase, sizeof(mEntryIVBase));
+ blkhdr.mEntryIVBase = box_hton64(mEntryIVBase);
+
+ mData.Write(&blkhdr, sizeof(blkhdr));
+ }
+
+ ++mStatus;
+ }
+ }
+ }
+ else if(mStatus == Status_Blocks)
+ {
+ // Block sending phase
+
+ if(mPositionInCurrentBlock >= mCurrentBlockEncodedSize)
+ {
+ // Next block!
+ ++mCurrentBlock;
+ ++mAbsoluteBlockNumber;
+ if(mCurrentBlock >= mNumBlocks)
+ {
+ // Output extra blocks for this instruction and move forward in file
+ if(mInstructionNumber >= 0)
+ {
+ SkipPreviousBlocksInInstruction();
+ }
+
+ // Is there another instruction to go?
+ ++mInstructionNumber;
+
+ // Skip instructions which don't contain any data
+ while(mInstructionNumber < static_cast<int64_t>(mpRecipe->size())
+ && (*mpRecipe)[mInstructionNumber].mSpaceBefore == 0)
+ {
+ SkipPreviousBlocksInInstruction();
+ ++mInstructionNumber;
+ }
+
+ if(mInstructionNumber >= static_cast<int64_t>(mpRecipe->size()))
+ {
+ // End of blocks, go to next phase
+ ++mStatus;
+
+ // Set the data to reading so the index can be written
+ mData.SetForReading();
+ }
+ else
+ {
+ // Get ready for this instruction
+ SetForInstruction();
+ }
+ }
+
+ // Can't use 'else' here as SetForInstruction() will change this
+ if(mCurrentBlock < mNumBlocks)
+ {
+ EncodeCurrentBlock();
+ }
+ }
+
+ // Send data from the current block (if there's data to send)
+ if(mPositionInCurrentBlock < mCurrentBlockEncodedSize)
+ {
+ // How much data to put in the buffer?
+ int s = mCurrentBlockEncodedSize - mPositionInCurrentBlock;
+ if(s > bytesToRead) s = bytesToRead;
+
+ // Copy it in
+ ::memcpy(buffer, mEncodedBuffer.mpBuffer + mPositionInCurrentBlock, s);
+
+ // Update variables
+ bytesToRead -= s;
+ buffer += s;
+ mPositionInCurrentBlock += s;
+ }
+ }
+ else
+ {
+ // Should never get here, as it'd be an invalid status
+ ASSERT(false);
+ }
+ }
+
+ // Add encoded size to stats
+ BackupStoreFile::msStats.mTotalFileStreamSize += (NBytes - bytesToRead);
+ mTotalBytesSent += (NBytes - bytesToRead);
+
+ // Return size of data to caller
+ return NBytes - bytesToRead;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::StorePreviousBlocksInInstruction()
+// Purpose: Private. Stores the blocks of the old file referenced in the current
+// instruction into the index and skips over the data in the file
+// Created: 16/1/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreFileEncodeStream::SkipPreviousBlocksInInstruction()
+{
+ // Check something is necessary
+ if((*mpRecipe)[mInstructionNumber].mpStartBlock == 0 || (*mpRecipe)[mInstructionNumber].mBlocks == 0)
+ {
+ return;
+ }
+
+ // Index of the first block in old file (being diffed from)
+ int firstIndex = mpRecipe->BlockPtrToIndex((*mpRecipe)[mInstructionNumber].mpStartBlock);
+
+ int64_t sizeToSkip = 0;
+
+ for(int32_t b = 0; b < (*mpRecipe)[mInstructionNumber].mBlocks; ++b)
+ {
+ // Update stats
+ BackupStoreFile::msStats.mBytesAlreadyOnServer += (*mpRecipe)[mInstructionNumber].mpStartBlock[b].mSize;
+
+ // Store the entry
+ StoreBlockIndexEntry(0 - (firstIndex + b),
+ (*mpRecipe)[mInstructionNumber].mpStartBlock[b].mSize,
+ (*mpRecipe)[mInstructionNumber].mpStartBlock[b].mWeakChecksum,
+ (*mpRecipe)[mInstructionNumber].mpStartBlock[b].mStrongChecksum);
+
+ // Increment the absolute block number -- kept encryption IV in sync
+ ++mAbsoluteBlockNumber;
+
+ // Add the size of this block to the size to skip
+ sizeToSkip += (*mpRecipe)[mInstructionNumber].mpStartBlock[b].mSize;
+ }
+
+ // Move forward in the stream
+ mpLogging->Seek(sizeToSkip, IOStream::SeekType_Relative);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::SetForInstruction()
+// Purpose: Private. Sets the state of the internal variables for the current instruction in the recipe
+// Created: 16/1/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreFileEncodeStream::SetForInstruction()
+{
+ // Calculate block sizes
+ CalculateBlockSizes((*mpRecipe)[mInstructionNumber].mSpaceBefore, mNumBlocks, mBlockSize, mLastBlockSize);
+
+ // Set variables
+ mCurrentBlock = 0;
+ mCurrentBlockEncodedSize = 0;
+ mPositionInCurrentBlock = 0;
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::EncodeCurrentBlock()
+// Purpose: Private. Encodes the current block, and writes the block data to the index
+// Created: 8/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreFileEncodeStream::EncodeCurrentBlock()
+{
+ // How big is the block, raw?
+ int blockRawSize = mBlockSize;
+ if(mCurrentBlock == (mNumBlocks - 1))
+ {
+ blockRawSize = mLastBlockSize;
+ }
+ ASSERT(blockRawSize < mAllocatedBufferSize);
+
+ // Check file open
+ if(mpLogging == 0)
+ {
+ // File should be open, but isn't. So logical error.
+ THROW_EXCEPTION(BackupStoreException, Internal)
+ }
+
+ // Read the data in
+ if(!mpLogging->ReadFullBuffer(mpRawBuffer, blockRawSize,
+ 0 /* not interested in size if failure */))
+ {
+ // TODO: Do something more intelligent, and abort
+ // this upload because the file has changed.
+ THROW_EXCEPTION(BackupStoreException,
+ Temp_FileEncodeStreamDidntReadBuffer)
+ }
+
+ // Encode it
+ mCurrentBlockEncodedSize = BackupStoreFile::EncodeChunk(mpRawBuffer,
+ blockRawSize, mEncodedBuffer);
+
+ //TRACE2("Encode: Encoded size of block %d is %d\n", (int32_t)mCurrentBlock, (int32_t)mCurrentBlockEncodedSize);
+
+ // Create block listing data -- generate checksums
+ RollingChecksum weakChecksum(mpRawBuffer, blockRawSize);
+ MD5Digest strongChecksum;
+ strongChecksum.Add(mpRawBuffer, blockRawSize);
+ strongChecksum.Finish();
+
+ // Add entry to the index
+ StoreBlockIndexEntry(mCurrentBlockEncodedSize, blockRawSize,
+ weakChecksum.GetChecksum(), strongChecksum.DigestAsData());
+
+ // Set vars to reading this block
+ mPositionInCurrentBlock = 0;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::StoreBlockIndexEntry(int64_t, int32_t, uint32_t, uint8_t *)
+// Purpose: Private. Adds an entry to the index currently being stored for sending at end of the stream.
+// Created: 16/1/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreFileEncodeStream::StoreBlockIndexEntry(int64_t EncSizeOrBlkIndex, int32_t ClearSize, uint32_t WeakChecksum, uint8_t *pStrongChecksum)
+{
+ // First, the encrypted section
+ file_BlockIndexEntryEnc entryEnc;
+ entryEnc.mSize = htonl(ClearSize);
+ entryEnc.mWeakChecksum = htonl(WeakChecksum);
+ ::memcpy(entryEnc.mStrongChecksum, pStrongChecksum, sizeof(entryEnc.mStrongChecksum));
+
+ // Then the clear section
+ file_BlockIndexEntry entry;
+ entry.mEncodedSize = box_hton64(((uint64_t)EncSizeOrBlkIndex));
+
+ // Then encrypt the encryted section
+ // Generate the IV from the block number
+ if(sBlowfishEncryptBlockEntry.GetIVLength() != sizeof(mEntryIVBase))
+ {
+ THROW_EXCEPTION(BackupStoreException, IVLengthForEncodedBlockSizeDoesntMeetLengthRequirements)
+ }
+ uint64_t iv = mEntryIVBase;
+ iv += mAbsoluteBlockNumber;
+ // Convert to network byte order before encrypting with it, so that restores work on
+ // platforms with different endiannesses.
+ iv = box_hton64(iv);
+ sBlowfishEncryptBlockEntry.SetIV(&iv);
+
+ // Encode the data
+ int encodedSize = sBlowfishEncryptBlockEntry.TransformBlock(entry.mEnEnc, sizeof(entry.mEnEnc), &entryEnc, sizeof(entryEnc));
+ if(encodedSize != sizeof(entry.mEnEnc))
+ {
+ THROW_EXCEPTION(BackupStoreException, BlockEntryEncodingDidntGiveExpectedLength)
+ }
+
+ // Save to data block for sending at the end of the stream
+ mData.Write(&entry, sizeof(entry));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::Write(const void *, int)
+// Purpose: As interface. Exceptions.
+// Created: 8/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreFileEncodeStream::Write(const void *pBuffer, int NBytes)
+{
+ THROW_EXCEPTION(BackupStoreException, CantWriteToEncodedFileStream)
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::StreamDataLeft()
+// Purpose: As interface -- end of stream reached?
+// Created: 8/12/03
+//
+// --------------------------------------------------------------------------
+bool BackupStoreFileEncodeStream::StreamDataLeft()
+{
+ return (mStatus != Status_Finished);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::StreamClosed()
+// Purpose: As interface
+// Created: 8/12/03
+//
+// --------------------------------------------------------------------------
+bool BackupStoreFileEncodeStream::StreamClosed()
+{
+ return true;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::Recipe::Recipe(BackupStoreFileCreation::BlocksAvailableEntry *, int64_t)
+// Purpose: Constructor. Takes ownership of the block index, and will delete it when it's deleted
+// Created: 15/1/04
+//
+// --------------------------------------------------------------------------
+BackupStoreFileEncodeStream::Recipe::Recipe(BackupStoreFileCreation::BlocksAvailableEntry *pBlockIndex,
+ int64_t NumBlocksInIndex, int64_t OtherFileID)
+ : mpBlockIndex(pBlockIndex),
+ mNumBlocksInIndex(NumBlocksInIndex),
+ mOtherFileID(OtherFileID)
+{
+ ASSERT((mpBlockIndex == 0) || (NumBlocksInIndex != 0))
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFileEncodeStream::Recipe::~Recipe()
+// Purpose: Destructor
+// Created: 15/1/04
+//
+// --------------------------------------------------------------------------
+BackupStoreFileEncodeStream::Recipe::~Recipe()
+{
+ // Free the block index, if there is one
+ if(mpBlockIndex != 0)
+ {
+ ::free(mpBlockIndex);
+ }
+}
+
+
+
+
diff --git a/lib/backupstore/BackupStoreFileEncodeStream.h b/lib/backupstore/BackupStoreFileEncodeStream.h
new file mode 100644
index 00000000..a169e036
--- /dev/null
+++ b/lib/backupstore/BackupStoreFileEncodeStream.h
@@ -0,0 +1,137 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFileEncodeStream.h
+// Purpose: Implement stream-based file encoding for the backup store
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPSTOREFILEENCODESTREAM__H
+#define BACKUPSTOREFILEENCODESTREAM__H
+
+#include <vector>
+
+#include "IOStream.h"
+#include "BackupStoreFilename.h"
+#include "CollectInBufferStream.h"
+#include "MD5Digest.h"
+#include "BackupStoreFile.h"
+#include "ReadLoggingStream.h"
+#include "RunStatusProvider.h"
+
+namespace BackupStoreFileCreation
+{
+ // Diffing and creation of files share some implementation details.
+ typedef struct _BlocksAvailableEntry
+ {
+ struct _BlocksAvailableEntry *mpNextInHashList;
+ int32_t mSize; // size in clear
+ uint32_t mWeakChecksum; // weak, rolling checksum
+ uint8_t mStrongChecksum[MD5Digest::DigestLength]; // strong digest based checksum
+ } BlocksAvailableEntry;
+
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: BackupStoreFileEncodeStream
+// Purpose: Encode a file into a stream
+// Created: 8/12/03
+//
+// --------------------------------------------------------------------------
+class BackupStoreFileEncodeStream : public IOStream
+{
+public:
+ BackupStoreFileEncodeStream();
+ ~BackupStoreFileEncodeStream();
+
+ typedef struct
+ {
+ int64_t mSpaceBefore; // amount of bytes which aren't taken out of blocks which go
+ int32_t mBlocks; // number of block to reuse, starting at this one
+ BackupStoreFileCreation::BlocksAvailableEntry *mpStartBlock; // may be null
+ } RecipeInstruction;
+
+ class Recipe : public std::vector<RecipeInstruction>
+ {
+ // NOTE: This class is rather tied in with the implementation of diffing.
+ public:
+ Recipe(BackupStoreFileCreation::BlocksAvailableEntry *pBlockIndex, int64_t NumBlocksInIndex,
+ int64_t OtherFileID = 0);
+ ~Recipe();
+
+ int64_t GetOtherFileID() {return mOtherFileID;}
+ int64_t BlockPtrToIndex(BackupStoreFileCreation::BlocksAvailableEntry *pBlock)
+ {
+ return pBlock - mpBlockIndex;
+ }
+
+ private:
+ BackupStoreFileCreation::BlocksAvailableEntry *mpBlockIndex;
+ int64_t mNumBlocksInIndex;
+ int64_t mOtherFileID;
+ };
+
+ void Setup(const std::string& Filename, Recipe *pRecipe, int64_t ContainerID,
+ const BackupStoreFilename &rStoreFilename,
+ int64_t *pModificationTime,
+ ReadLoggingStream::Logger* pLogger = NULL,
+ RunStatusProvider* pRunStatusProvider = NULL);
+
+ virtual int Read(void *pBuffer, int NBytes, int Timeout);
+ virtual void Write(const void *pBuffer, int NBytes);
+ virtual bool StreamDataLeft();
+ virtual bool StreamClosed();
+ int64_t GetTotalBytesSent() { return mTotalBytesSent; }
+
+private:
+ enum
+ {
+ Status_Header = 0,
+ Status_Blocks = 1,
+ Status_BlockListing = 2,
+ Status_Finished = 3
+ };
+
+private:
+ void EncodeCurrentBlock();
+ void CalculateBlockSizes(int64_t DataSize, int64_t &rNumBlocksOut, int32_t &rBlockSizeOut, int32_t &rLastBlockSizeOut);
+ void SkipPreviousBlocksInInstruction();
+ void SetForInstruction();
+ void StoreBlockIndexEntry(int64_t WncSizeOrBlkIndex, int32_t ClearSize, uint32_t WeakChecksum, uint8_t *pStrongChecksum);
+
+private:
+ Recipe *mpRecipe;
+ IOStream *mpFile; // source file
+ CollectInBufferStream mData; // buffer for header and index entries
+ IOStream *mpLogging;
+ RunStatusProvider* mpRunStatusProvider;
+ int mStatus;
+ bool mSendData; // true if there's file data to send (ie not a symlink)
+ int64_t mTotalBlocks; // Total number of blocks in the file
+ int64_t mAbsoluteBlockNumber; // The absolute block number currently being output
+ // Instruction number
+ int64_t mInstructionNumber;
+ // All the below are within the current instruction
+ int64_t mNumBlocks; // number of blocks. Last one will be a different size to the rest in most cases
+ int64_t mCurrentBlock;
+ int32_t mCurrentBlockEncodedSize;
+ int32_t mPositionInCurrentBlock; // for reading out
+ int32_t mBlockSize; // Basic block size of most of the blocks in the file
+ int32_t mLastBlockSize; // the size (unencoded) of the last block in the file
+ int64_t mTotalBytesSent;
+ // Buffers
+ uint8_t *mpRawBuffer; // buffer for raw data
+ BackupStoreFile::EncodingBuffer mEncodedBuffer;
+ // buffer for encoded data
+ int32_t mAllocatedBufferSize; // size of above two allocated blocks
+ uint64_t mEntryIVBase; // base for block entry IV
+};
+
+
+
+#endif // BACKUPSTOREFILEENCODESTREAM__H
+
diff --git a/lib/backupstore/BackupStoreFileRevDiff.cpp b/lib/backupstore/BackupStoreFileRevDiff.cpp
new file mode 100644
index 00000000..509eef61
--- /dev/null
+++ b/lib/backupstore/BackupStoreFileRevDiff.cpp
@@ -0,0 +1,258 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFileRevDiff.cpp
+// Purpose: Reverse a patch, to build a new patch from new to old files
+// Created: 12/7/04
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <new>
+#include <stdlib.h>
+
+#include "BackupStoreFile.h"
+#include "BackupStoreFileWire.h"
+#include "BackupStoreObjectMagic.h"
+#include "BackupStoreException.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreFilename.h"
+
+#include "MemLeakFindOn.h"
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFile::ReverseDiffFile(IOStream &, IOStream &, IOStream &, IOStream &, int64_t)
+// Purpose: Reverse a patch, to build a new patch from new to old files. Takes
+// two independent copies to the From file, for efficiency.
+// Created: 12/7/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreFile::ReverseDiffFile(IOStream &rDiff, IOStream &rFrom, IOStream &rFrom2, IOStream &rOut, int64_t ObjectIDOfFrom, bool *pIsCompletelyDifferent)
+{
+ // Read and copy the header from the from file to the out file -- beginnings of the patch
+ file_StreamFormat hdr;
+ if(!rFrom.ReadFullBuffer(&hdr, sizeof(hdr), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+ if(ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+ // Copy
+ rOut.Write(&hdr, sizeof(hdr));
+ // Copy over filename and attributes
+ // BLOCK
+ {
+ BackupStoreFilename filename;
+ filename.ReadFromStream(rFrom, IOStream::TimeOutInfinite);
+ filename.WriteToStream(rOut);
+ StreamableMemBlock attr;
+ attr.ReadFromStream(rFrom, IOStream::TimeOutInfinite);
+ attr.WriteToStream(rOut);
+ }
+
+ // Build an index of common blocks.
+ // For each block in the from file, we want to know it's index in the
+ // diff file. Allocate memory for this information.
+ int64_t fromNumBlocks = box_ntoh64(hdr.mNumBlocks);
+ int64_t *pfromIndexInfo = (int64_t*)::malloc(fromNumBlocks * sizeof(int64_t));
+ if(pfromIndexInfo == 0)
+ {
+ throw std::bad_alloc();
+ }
+
+ // Buffer data
+ void *buffer = 0;
+ int bufferSize = 0;
+
+ // flag
+ bool isCompletelyDifferent = true;
+
+ try
+ {
+ // Initialise the index to be all 0, ie not filled in yet
+ for(int64_t i = 0; i < fromNumBlocks; ++i)
+ {
+ pfromIndexInfo[i] = 0;
+ }
+
+ // Within the from file, skip to the index
+ MoveStreamPositionToBlockIndex(rDiff);
+
+ // Read in header of index
+ file_BlockIndexHeader diffIdxHdr;
+ if(!rDiff.ReadFullBuffer(&diffIdxHdr, sizeof(diffIdxHdr), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+ if(ntohl(diffIdxHdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // And then read in each entry
+ int64_t diffNumBlocks = box_ntoh64(diffIdxHdr.mNumBlocks);
+ for(int64_t b = 0; b < diffNumBlocks; ++b)
+ {
+ file_BlockIndexEntry e;
+ if(!rDiff.ReadFullBuffer(&e, sizeof(e), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Where's the block?
+ int64_t blockEn = box_ntoh64(e.mEncodedSize);
+ if(blockEn > 0)
+ {
+ // Block is in the delta file, is ignored for now -- not relevant to rebuilding the from file
+ }
+ else
+ {
+ // Block is in the original file, store which block it is in this file
+ int64_t fromIndex = 0 - blockEn;
+ if(fromIndex < 0 || fromIndex >= fromNumBlocks)
+ {
+ THROW_EXCEPTION(BackupStoreException, IncompatibleFromAndDiffFiles)
+ }
+
+ // Store information about where it is in the new file
+ // NOTE: This is slight different to how it'll be stored in the final index.
+ pfromIndexInfo[fromIndex] = -1 - b;
+ }
+ }
+
+ // Open the index for the second copy of the from file
+ MoveStreamPositionToBlockIndex(rFrom2);
+
+ // Read in header of index
+ file_BlockIndexHeader fromIdxHdr;
+ if(!rFrom2.ReadFullBuffer(&fromIdxHdr, sizeof(fromIdxHdr), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+ if(ntohl(fromIdxHdr.mMagicValue) != OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1
+ || box_ntoh64(fromIdxHdr.mOtherFileID) != 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // So, we can now start building the data in the file
+ int64_t filePosition = rFrom.GetPosition();
+ for(int64_t b = 0; b < fromNumBlocks; ++b)
+ {
+ // Read entry from from index
+ file_BlockIndexEntry e;
+ if(!rFrom2.ReadFullBuffer(&e, sizeof(e), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Get size
+ int64_t blockSize = box_hton64(e.mEncodedSize);
+ if(blockSize < 0)
+ {
+ THROW_EXCEPTION(BackupStoreException, BadBackupStoreFile)
+ }
+
+ // Copy this block?
+ if(pfromIndexInfo[b] == 0)
+ {
+ // Copy it, first move to file location
+ rFrom.Seek(filePosition, IOStream::SeekType_Absolute);
+
+ // Make sure there's memory available to copy this
+ if(bufferSize < blockSize || buffer == 0)
+ {
+ // Free old block
+ if(buffer != 0)
+ {
+ ::free(buffer);
+ buffer = 0;
+ bufferSize = 0;
+ }
+ // Allocate new block
+ buffer = ::malloc(blockSize);
+ if(buffer == 0)
+ {
+ throw std::bad_alloc();
+ }
+ bufferSize = blockSize;
+ }
+ ASSERT(bufferSize >= blockSize);
+
+ // Copy the block
+ if(!rFrom.ReadFullBuffer(buffer, blockSize, 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, FailedToReadBlockOnCombine)
+ }
+ rOut.Write(buffer, blockSize);
+
+ // Store the size
+ pfromIndexInfo[b] = blockSize;
+ }
+ else
+ {
+ // Block isn't needed, so it's not completely different
+ isCompletelyDifferent = false;
+ }
+ filePosition += blockSize;
+ }
+
+ // Then write the index, modified header first
+ fromIdxHdr.mOtherFileID = isCompletelyDifferent?0:(box_hton64(ObjectIDOfFrom));
+ rOut.Write(&fromIdxHdr, sizeof(fromIdxHdr));
+
+ // Move to start of index entries
+ rFrom.Seek(filePosition + sizeof(file_BlockIndexHeader), IOStream::SeekType_Absolute);
+
+ // Then copy modified entries
+ for(int64_t b = 0; b < fromNumBlocks; ++b)
+ {
+ // Read entry from from index
+ file_BlockIndexEntry e;
+ if(!rFrom.ReadFullBuffer(&e, sizeof(e), 0))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // Modify...
+ int64_t s = pfromIndexInfo[b];
+ // Adjust to reflect real block index (remember 0 has a different meaning here)
+ if(s < 0) ++s;
+ // Insert
+ e.mEncodedSize = box_hton64(s);
+ // Write
+ rOut.Write(&e, sizeof(e));
+ }
+ }
+ catch(...)
+ {
+ ::free(pfromIndexInfo);
+ if(buffer != 0)
+ {
+ ::free(buffer);
+ }
+ throw;
+ }
+
+ // Free memory used (oh for finally {} blocks)
+ ::free(pfromIndexInfo);
+ if(buffer != 0)
+ {
+ ::free(buffer);
+ }
+
+ // return completely different flag
+ if(pIsCompletelyDifferent != 0)
+ {
+ *pIsCompletelyDifferent = isCompletelyDifferent;
+ }
+}
+
+
+
diff --git a/lib/backupstore/BackupStoreFileWire.h b/lib/backupstore/BackupStoreFileWire.h
new file mode 100644
index 00000000..49e94aa5
--- /dev/null
+++ b/lib/backupstore/BackupStoreFileWire.h
@@ -0,0 +1,74 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFileWire.h
+// Purpose: On the wire / disc formats for backup store files
+// Created: 12/1/04
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPSTOREFILEWIRE__H
+#define BACKUPSTOREFILEWIRE__H
+
+#include "MD5Digest.h"
+
+// set packing to one byte
+#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS
+#include "BeginStructPackForWire.h"
+#else
+BEGIN_STRUCTURE_PACKING_FOR_WIRE
+#endif
+
+typedef struct
+{
+ int32_t mMagicValue; // also the version number
+ int64_t mNumBlocks; // number of blocks contained in the file
+ int64_t mContainerID;
+ int64_t mModificationTime;
+ int32_t mMaxBlockClearSize; // Maximum clear size that can be expected for a block
+ int32_t mOptions; // bitmask of options used
+ // Then a BackupStoreFilename
+ // Then a BackupClientFileAttributes
+} file_StreamFormat;
+
+typedef struct
+{
+ int32_t mMagicValue; // different magic value
+ int64_t mOtherFileID; // the file ID of the 'other' file which may be referenced by the index
+ uint64_t mEntryIVBase; // base value for block IV
+ int64_t mNumBlocks; // repeat of value in file header
+} file_BlockIndexHeader;
+
+typedef struct
+{
+ int32_t mSize; // size in clear
+ uint32_t mWeakChecksum; // weak, rolling checksum
+ uint8_t mStrongChecksum[MD5Digest::DigestLength]; // strong digest based checksum
+} file_BlockIndexEntryEnc;
+
+typedef struct
+{
+ union
+ {
+ int64_t mEncodedSize; // size encoded, if > 0
+ int64_t mOtherBlockIndex; // 0 - block number in other file, if <= 0
+ };
+ uint8_t mEnEnc[sizeof(file_BlockIndexEntryEnc)]; // Encoded section
+} file_BlockIndexEntry;
+
+// Use default packing
+#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS
+#include "EndStructPackForWire.h"
+#else
+END_STRUCTURE_PACKING_FOR_WIRE
+#endif
+
+// header for blocks of compressed data in files
+#define HEADER_CHUNK_IS_COMPRESSED 1 // bit
+#define HEADER_ENCODING_SHIFT 1 // shift value
+#define HEADER_BLOWFISH_ENCODING 1 // value stored in bits 1 -- 7
+#define HEADER_AES_ENCODING 2 // value stored in bits 1 -- 7
+
+
+#endif // BACKUPSTOREFILEWIRE__H
+
diff --git a/lib/backupstore/BackupStoreFilename.cpp b/lib/backupstore/BackupStoreFilename.cpp
new file mode 100644
index 00000000..72cd1acd
--- /dev/null
+++ b/lib/backupstore/BackupStoreFilename.cpp
@@ -0,0 +1,281 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFilename.cpp
+// Purpose: Filename for the backup store
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+#include "BackupStoreFilename.h"
+#include "Protocol.h"
+#include "BackupStoreException.h"
+#include "IOStream.h"
+#include "Guards.h"
+
+#include "MemLeakFindOn.h"
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilename::BackupStoreFilename()
+// Purpose: Default constructor -- creates an invalid filename
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+BackupStoreFilename::BackupStoreFilename()
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilename::BackupStoreFilename(const BackupStoreFilename &)
+// Purpose: Copy constructor
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+BackupStoreFilename::BackupStoreFilename(const BackupStoreFilename &rToCopy)
+ : mEncryptedName(rToCopy.mEncryptedName)
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilename::~BackupStoreFilename()
+// Purpose: Destructor
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+BackupStoreFilename::~BackupStoreFilename()
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilename::CheckValid(bool)
+// Purpose: Checks the encoded filename for validity
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+bool BackupStoreFilename::CheckValid(bool ExceptionIfInvalid) const
+{
+ bool ok = true;
+
+ if(mEncryptedName.size() < 2)
+ {
+ // Isn't long enough to have a header
+ ok = false;
+ }
+ else
+ {
+ // Check size is consistent
+ unsigned int dsize = BACKUPSTOREFILENAME_GET_SIZE(this->mEncryptedName);
+ if(dsize != mEncryptedName.size())
+ {
+ ok = false;
+ }
+
+ // And encoding is an accepted value
+ unsigned int encoding = BACKUPSTOREFILENAME_GET_ENCODING(this->mEncryptedName);
+ if(encoding < Encoding_Min || encoding > Encoding_Max)
+ {
+ ok = false;
+ }
+ }
+
+ // Exception?
+ if(!ok && ExceptionIfInvalid)
+ {
+ THROW_EXCEPTION(BackupStoreException, InvalidBackupStoreFilename)
+ }
+
+ return ok;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilename::ReadFromProtocol(Protocol &)
+// Purpose: Reads the filename from the protocol object
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+void BackupStoreFilename::ReadFromProtocol(Protocol &rProtocol)
+{
+ // Read the header
+ char hdr[2];
+ rProtocol.Read(hdr, 2);
+
+ // How big is it?
+ int dsize = BACKUPSTOREFILENAME_GET_SIZE(hdr);
+
+ // Fetch rest of data, relying on the Protocol to error on stupidly large sizes for us
+ std::string data;
+ rProtocol.Read(data, dsize - 2);
+
+ // assign to this string, storing the header and the extra data
+ mEncryptedName.assign(hdr, 2);
+ mEncryptedName.append(data.c_str(), data.size());
+
+ // Check it
+ CheckValid();
+
+ // Alert derived classes
+ EncodedFilenameChanged();
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilename::WriteToProtocol(Protocol &)
+// Purpose: Writes the filename to the protocol object
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+void BackupStoreFilename::WriteToProtocol(Protocol &rProtocol) const
+{
+ CheckValid();
+
+ rProtocol.Write(mEncryptedName.c_str(), mEncryptedName.size());
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilename::ReadFromStream(IOStream &)
+// Purpose: Reads the filename from a stream
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+void BackupStoreFilename::ReadFromStream(IOStream &rStream, int Timeout)
+{
+ // Read the header
+ char hdr[2];
+ if(!rStream.ReadFullBuffer(hdr, 2, 0 /* not interested in bytes read if this fails */, Timeout))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+
+ // How big is it?
+ unsigned int dsize = BACKUPSTOREFILENAME_GET_SIZE(hdr);
+
+ // Assume most filenames are small
+ char buf[256];
+ if(dsize < sizeof(buf))
+ {
+ // Fetch rest of data, relying on the Protocol to error on stupidly large sizes for us
+ if(!rStream.ReadFullBuffer(buf + 2, dsize - 2, 0 /* not interested in bytes read if this fails */, Timeout))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+ // Copy in header
+ buf[0] = hdr[0]; buf[1] = hdr[1];
+
+ // assign to this string, storing the header and the extra data
+ mEncryptedName.assign(buf, dsize);
+ }
+ else
+ {
+ // Block of memory to hold it
+ MemoryBlockGuard<char*> dataB(dsize+2);
+ char *data = dataB;
+
+ // Fetch rest of data, relying on the Protocol to error on stupidly large sizes for us
+ if(!rStream.ReadFullBuffer(data + 2, dsize - 2, 0 /* not interested in bytes read if this fails */, Timeout))
+ {
+ THROW_EXCEPTION(BackupStoreException, CouldntReadEntireStructureFromStream)
+ }
+ // Copy in header
+ data[0] = hdr[0]; data[1] = hdr[1];
+
+ // assign to this string, storing the header and the extra data
+ mEncryptedName.assign(data, dsize);
+ }
+
+ // Check it
+ CheckValid();
+
+ // Alert derived classes
+ EncodedFilenameChanged();
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilename::WriteToStream(IOStream &)
+// Purpose: Writes the filename to a stream
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+void BackupStoreFilename::WriteToStream(IOStream &rStream) const
+{
+ CheckValid();
+
+ rStream.Write(mEncryptedName.c_str(), mEncryptedName.size());
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilename::EncodedFilenameChanged()
+// Purpose: The encoded filename stored has changed
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+void BackupStoreFilename::EncodedFilenameChanged()
+{
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilename::IsEncrypted()
+// Purpose: Returns true if the filename is stored using an encrypting encoding
+// Created: 1/12/03
+//
+// --------------------------------------------------------------------------
+bool BackupStoreFilename::IsEncrypted() const
+{
+ return BACKUPSTOREFILENAME_GET_ENCODING(this->mEncryptedName) !=
+ Encoding_Clear;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilename::SetAsClearFilename(const char *)
+// Purpose: Sets this object to be a valid filename, but with a
+// filename in the clear. Used on the server to create
+// filenames when there's no way of encrypting it.
+// Created: 22/4/04
+//
+// --------------------------------------------------------------------------
+void BackupStoreFilename::SetAsClearFilename(const char *Clear)
+{
+ // Make std::string from the clear name
+ std::string toEncode(Clear);
+
+ // Make an encoded string
+ char hdr[2];
+ BACKUPSTOREFILENAME_MAKE_HDR(hdr, toEncode.size()+2, Encoding_Clear);
+ std::string encoded(hdr, 2);
+ encoded += toEncode;
+ ASSERT(encoded.size() == toEncode.size() + 2);
+
+ // Store the encoded string
+ mEncryptedName.assign(encoded);
+
+ // Stuff which must be done
+ EncodedFilenameChanged();
+ CheckValid(false);
+}
+
+
+
diff --git a/lib/backupstore/BackupStoreFilename.h b/lib/backupstore/BackupStoreFilename.h
new file mode 100644
index 00000000..80db9516
--- /dev/null
+++ b/lib/backupstore/BackupStoreFilename.h
@@ -0,0 +1,107 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFilename.h
+// Purpose: Filename for the backup store
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPSTOREFILENAME__H
+#define BACKUPSTOREFILENAME__H
+
+#include <string>
+
+class Protocol;
+class IOStream;
+
+// #define BACKUPSTOREFILEAME_MALLOC_ALLOC_BASE_TYPE
+// don't define this -- the problem of memory usage still appears without this.
+// It's just that this class really showed up the problem. Instead, malloc allocation
+// is globally defined in BoxPlatform.h, for troublesome libraries.
+
+#ifdef BACKUPSTOREFILEAME_MALLOC_ALLOC_BASE_TYPE
+ // Use a malloc_allocated string, because the STL default allocators really screw up with
+ // memory allocation, particularly with this class.
+ // Makes a few things a bit messy and inefficient with conversions.
+ // Given up using this, and use global malloc allocation instead, but thought it
+ // worth leaving this code in just in case it's useful for the future.
+ typedef std::basic_string<char, std::string_char_traits<char>, std::malloc_alloc> BackupStoreFilename_base;
+ // If this is changed, change GetClearFilename() back to returning a reference.
+#else
+ typedef std::string BackupStoreFilename_base;
+#endif
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: BackupStoreFilename
+// Purpose: Filename for the backup store
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+class BackupStoreFilename /* : public BackupStoreFilename_base */
+{
+private:
+ std::string mEncryptedName;
+
+public:
+ BackupStoreFilename();
+ BackupStoreFilename(const BackupStoreFilename &rToCopy);
+ virtual ~BackupStoreFilename();
+
+ bool CheckValid(bool ExceptionIfInvalid = true) const;
+
+ void ReadFromProtocol(Protocol &rProtocol);
+ void WriteToProtocol(Protocol &rProtocol) const;
+
+ void ReadFromStream(IOStream &rStream, int Timeout);
+ void WriteToStream(IOStream &rStream) const;
+
+ void SetAsClearFilename(const char *Clear);
+
+ // Check that it's encrypted
+ bool IsEncrypted() const;
+
+ // These enumerated types belong in the base class so
+ // the CheckValid() function can make sure that the encoding
+ // is a valid encoding
+ enum
+ {
+ Encoding_Min = 1,
+ Encoding_Clear = 1,
+ Encoding_Blowfish = 2,
+ Encoding_Max = 2
+ };
+
+ const std::string& GetEncodedFilename() const
+ {
+ return mEncryptedName;
+ }
+
+ bool operator==(const BackupStoreFilename& rOther) const
+ {
+ return mEncryptedName == rOther.mEncryptedName;
+ }
+
+ bool operator!=(const BackupStoreFilename& rOther) const
+ {
+ return mEncryptedName != rOther.mEncryptedName;
+ }
+
+protected:
+ virtual void EncodedFilenameChanged();
+ void SetEncodedFilename(const std::string &rEncoded)
+ {
+ mEncryptedName = rEncoded;
+ }
+};
+
+// On the wire utilities for class and derived class
+#define BACKUPSTOREFILENAME_GET_SIZE(hdr) (( ((uint8_t)((hdr)[0])) | ( ((uint8_t)((hdr)[1])) << 8)) >> 2)
+#define BACKUPSTOREFILENAME_GET_ENCODING(hdr) (((hdr)[0]) & 0x3)
+
+#define BACKUPSTOREFILENAME_MAKE_HDR(hdr, size, encoding) {uint16_t h = (((uint16_t)size) << 2) | (encoding); ((hdr)[0]) = h & 0xff; ((hdr)[1]) = h >> 8;}
+
+#endif // BACKUPSTOREFILENAME__H
+
diff --git a/lib/backupstore/BackupStoreFilenameClear.cpp b/lib/backupstore/BackupStoreFilenameClear.cpp
new file mode 100644
index 00000000..ad5666cf
--- /dev/null
+++ b/lib/backupstore/BackupStoreFilenameClear.cpp
@@ -0,0 +1,347 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFilenameClear.cpp
+// Purpose: BackupStoreFilenames in the clear
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+#include "BackupStoreFilenameClear.h"
+#include "BackupStoreException.h"
+#include "CipherContext.h"
+#include "CipherBlowfish.h"
+#include "Guards.h"
+#include "Logging.h"
+
+#include "MemLeakFindOn.h"
+
+// Hide private variables from the rest of the world
+namespace
+{
+ int sEncodeMethod = BackupStoreFilename::Encoding_Clear;
+ CipherContext sBlowfishEncrypt;
+ CipherContext sBlowfishDecrypt;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilenameClear::BackupStoreFilenameClear()
+// Purpose: Default constructor, creates an invalid filename
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+BackupStoreFilenameClear::BackupStoreFilenameClear()
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilenameClear::BackupStoreFilenameClear(const std::string &)
+// Purpose: Creates a filename, encoding from the given string
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+BackupStoreFilenameClear::BackupStoreFilenameClear(const std::string &rToEncode)
+{
+ SetClearFilename(rToEncode);
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilenameClear::BackupStoreFilenameClear(const BackupStoreFilenameClear &)
+// Purpose: Copy constructor
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+BackupStoreFilenameClear::BackupStoreFilenameClear(const BackupStoreFilenameClear &rToCopy)
+: BackupStoreFilename(rToCopy),
+ mClearFilename(rToCopy.mClearFilename)
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilenameClear::BackupStoreFilenameClear(const BackupStoreFilename &rToCopy)
+// Purpose: Copy from base class
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+BackupStoreFilenameClear::BackupStoreFilenameClear(const BackupStoreFilename &rToCopy)
+: BackupStoreFilename(rToCopy)
+{
+ // Will get a clear filename when it's required
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilenameClear::~BackupStoreFilenameClear()
+// Purpose: Destructor
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+BackupStoreFilenameClear::~BackupStoreFilenameClear()
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilenameClear::GetClearFilename()
+// Purpose: Get the unencoded filename
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+#ifdef BACKUPSTOREFILEAME_MALLOC_ALLOC_BASE_TYPE
+const std::string BackupStoreFilenameClear::GetClearFilename() const
+{
+ MakeClearAvailable();
+ // When modifying, remember to change back to reference return if at all possible
+ // -- returns an object rather than a reference to allow easy use with other code.
+ return std::string(mClearFilename.c_str(), mClearFilename.size());
+}
+#else
+const std::string &BackupStoreFilenameClear::GetClearFilename() const
+{
+ MakeClearAvailable();
+ return mClearFilename;
+}
+const std::string &BackupStoreFilenameClear::GetClearFilenameIfPossible(const std::string& alternative) const
+{
+ if(mClearFilename.empty() && !(sBlowfishDecrypt.IsInitialised()))
+ {
+ // encrypted and cannot decrypt
+ return alternative;
+ }
+ else
+ {
+ return GetClearFilename();
+ }
+}
+#endif
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilenameClear::SetClearFilename(const std::string &)
+// Purpose: Encode and make available the clear filename
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+void BackupStoreFilenameClear::SetClearFilename(const std::string &rToEncode)
+{
+ // Only allow Blowfish encodings
+ if(sEncodeMethod != Encoding_Blowfish)
+ {
+ THROW_EXCEPTION(BackupStoreException, FilenameEncryptionNotSetup)
+ }
+
+ // Make an encoded string with blowfish encryption
+ EncryptClear(rToEncode, sBlowfishEncrypt, Encoding_Blowfish);
+
+ // Store the clear filename
+ mClearFilename.assign(rToEncode.c_str(), rToEncode.size());
+
+ // Make sure we did the right thing
+ if(!CheckValid(false))
+ {
+ THROW_EXCEPTION(BackupStoreException, Internal)
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilenameClear::MakeClearAvailable()
+// Purpose: Private. Make sure the clear filename is available
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+void BackupStoreFilenameClear::MakeClearAvailable() const
+{
+ if(!mClearFilename.empty())
+ return; // nothing to do
+
+ // Check valid
+ CheckValid();
+
+ // Decode the header
+ int size = BACKUPSTOREFILENAME_GET_SIZE(GetEncodedFilename());
+ int encoding = BACKUPSTOREFILENAME_GET_ENCODING(GetEncodedFilename());
+
+ // Decode based on encoding given in the header
+ switch(encoding)
+ {
+ case Encoding_Clear:
+ BOX_WARNING("**** BackupStoreFilename encoded with "
+ "Clear encoding ****");
+ mClearFilename.assign(GetEncodedFilename().c_str() + 2,
+ size - 2);
+ break;
+
+ case Encoding_Blowfish:
+ DecryptEncoded(sBlowfishDecrypt);
+ break;
+
+ default:
+ THROW_EXCEPTION(BackupStoreException, UnknownFilenameEncoding)
+ break;
+ }
+}
+
+
+// Buffer for encoding and decoding -- do this all in one single buffer to
+// avoid lots of string allocation, which stuffs up memory usage.
+// These static memory vars are, of course, not thread safe, but we don't use threads.
+static int sEncDecBufferSize = 0;
+static MemoryBlockGuard<uint8_t *> *spEncDecBuffer = 0;
+
+static void EnsureEncDecBufferSize(int BufSize)
+{
+ if(spEncDecBuffer == 0)
+ {
+#ifndef WIN32
+ BOX_TRACE("Allocating filename encoding/decoding buffer "
+ "with size " << BufSize);
+#endif
+ spEncDecBuffer = new MemoryBlockGuard<uint8_t *>(BufSize);
+ MEMLEAKFINDER_NOT_A_LEAK(spEncDecBuffer);
+ MEMLEAKFINDER_NOT_A_LEAK(*spEncDecBuffer);
+ sEncDecBufferSize = BufSize;
+ }
+ else
+ {
+ if(sEncDecBufferSize < BufSize)
+ {
+ BOX_TRACE("Reallocating filename encoding/decoding "
+ "buffer from " << sEncDecBufferSize <<
+ " to " << BufSize);
+ spEncDecBuffer->Resize(BufSize);
+ sEncDecBufferSize = BufSize;
+ MEMLEAKFINDER_NOT_A_LEAK(*spEncDecBuffer);
+ }
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilenameClear::EncryptClear(const std::string &, CipherContext &, int)
+// Purpose: Private. Assigns the encoded filename string, encrypting.
+// Created: 1/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreFilenameClear::EncryptClear(const std::string &rToEncode, CipherContext &rCipherContext, int StoreAsEncoding)
+{
+ // Work out max size
+ int maxOutSize = rCipherContext.MaxOutSizeForInBufferSize(rToEncode.size()) + 4;
+
+ // Make sure encode/decode buffer has enough space
+ EnsureEncDecBufferSize(maxOutSize);
+
+ // Pointer to buffer
+ uint8_t *buffer = *spEncDecBuffer;
+
+ // Encode -- do entire block in one go
+ int encSize = rCipherContext.TransformBlock(buffer + 2, sEncDecBufferSize - 2, rToEncode.c_str(), rToEncode.size());
+ // and add in header size
+ encSize += 2;
+
+ // Adjust header
+ BACKUPSTOREFILENAME_MAKE_HDR(buffer, encSize, StoreAsEncoding);
+
+ // Store the encoded string
+ SetEncodedFilename(std::string((char*)buffer, encSize));
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilenameClear::DecryptEncoded(CipherContext &)
+// Purpose: Decrypt the encoded filename using the cipher context
+// Created: 1/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreFilenameClear::DecryptEncoded(CipherContext &rCipherContext) const
+{
+ const std::string& rEncoded = GetEncodedFilename();
+
+ // Work out max size
+ int maxOutSize = rCipherContext.MaxOutSizeForInBufferSize(rEncoded.size()) + 4;
+
+ // Make sure encode/decode buffer has enough space
+ EnsureEncDecBufferSize(maxOutSize);
+
+ // Pointer to buffer
+ uint8_t *buffer = *spEncDecBuffer;
+
+ // Decrypt
+ const char *str = rEncoded.c_str() + 2;
+ int sizeOut = rCipherContext.TransformBlock(buffer, sEncDecBufferSize, str, rEncoded.size() - 2);
+
+ // Assign to this
+ mClearFilename.assign((char*)buffer, sizeOut);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilenameClear::EncodedFilenameChanged()
+// Purpose: The encoded filename stored has changed
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+void BackupStoreFilenameClear::EncodedFilenameChanged()
+{
+ BackupStoreFilename::EncodedFilenameChanged();
+
+ // Delete stored filename in clear
+ mClearFilename.erase();
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilenameClear::SetBlowfishKey(const void *, int)
+// Purpose: Set the key used for Blowfish encryption of filenames
+// Created: 1/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreFilenameClear::SetBlowfishKey(const void *pKey, int KeyLength, const void *pIV, int IVLength)
+{
+ // Initialisation vector not used. Can't use a different vector for each filename as
+ // that would stop comparisions on the server working.
+ sBlowfishEncrypt.Reset();
+ sBlowfishEncrypt.Init(CipherContext::Encrypt, CipherBlowfish(CipherDescription::Mode_CBC, pKey, KeyLength));
+ ASSERT(sBlowfishEncrypt.GetIVLength() == IVLength);
+ sBlowfishEncrypt.SetIV(pIV);
+ sBlowfishDecrypt.Reset();
+ sBlowfishDecrypt.Init(CipherContext::Decrypt, CipherBlowfish(CipherDescription::Mode_CBC, pKey, KeyLength));
+ ASSERT(sBlowfishDecrypt.GetIVLength() == IVLength);
+ sBlowfishDecrypt.SetIV(pIV);
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreFilenameClear::SetEncodingMethod(int)
+// Purpose: Set the encoding method used for filenames
+// Created: 1/12/03
+//
+// --------------------------------------------------------------------------
+void BackupStoreFilenameClear::SetEncodingMethod(int Method)
+{
+ sEncodeMethod = Method;
+}
+
+
+
diff --git a/lib/backupstore/BackupStoreFilenameClear.h b/lib/backupstore/BackupStoreFilenameClear.h
new file mode 100644
index 00000000..595d1158
--- /dev/null
+++ b/lib/backupstore/BackupStoreFilenameClear.h
@@ -0,0 +1,61 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreFilenameClear.h
+// Purpose: BackupStoreFilenames in the clear
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPSTOREFILENAMECLEAR__H
+#define BACKUPSTOREFILENAMECLEAR__H
+
+#include "BackupStoreFilename.h"
+
+class CipherContext;
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: BackupStoreFilenameClear
+// Purpose: BackupStoreFilenames, handling conversion from and to the in the clear version
+// Created: 2003/08/26
+//
+// --------------------------------------------------------------------------
+class BackupStoreFilenameClear : public BackupStoreFilename
+{
+public:
+ BackupStoreFilenameClear();
+ BackupStoreFilenameClear(const std::string &rToEncode);
+ BackupStoreFilenameClear(const BackupStoreFilenameClear &rToCopy);
+ BackupStoreFilenameClear(const BackupStoreFilename &rToCopy);
+ virtual ~BackupStoreFilenameClear();
+
+ // Because we need to use a different allocator for this class to avoid
+ // nasty things happening, can't return this as a reference. Which is a
+ // pity. But probably not too bad.
+#ifdef BACKUPSTOREFILEAME_MALLOC_ALLOC_BASE_TYPE
+ const std::string GetClearFilename() const;
+#else
+ const std::string &GetClearFilename() const;
+ const std::string &GetClearFilenameIfPossible(const std::string& alternative) const;
+#endif
+ void SetClearFilename(const std::string &rToEncode);
+
+ // Setup for encryption of filenames
+ static void SetBlowfishKey(const void *pKey, int KeyLength, const void *pIV, int IVLength);
+ static void SetEncodingMethod(int Method);
+
+protected:
+ void MakeClearAvailable() const;
+ virtual void EncodedFilenameChanged();
+ void EncryptClear(const std::string &rToEncode, CipherContext &rCipherContext, int StoreAsEncoding);
+ void DecryptEncoded(CipherContext &rCipherContext) const;
+
+private:
+ mutable BackupStoreFilename_base mClearFilename;
+};
+
+#endif // BACKUPSTOREFILENAMECLEAR__H
+
+
diff --git a/lib/backupstore/BackupStoreInfo.cpp b/lib/backupstore/BackupStoreInfo.cpp
index 1d55fdf0..c58ae99a 100644
--- a/lib/backupstore/BackupStoreInfo.cpp
+++ b/lib/backupstore/BackupStoreInfo.cpp
@@ -11,6 +11,10 @@
#include <algorithm>
+<<<<<<< HEAD
+=======
+#include "Archive.h"
+>>>>>>> 0.12
#include "BackupStoreInfo.h"
#include "BackupStoreException.h"
#include "RaidFileWrite.h"
@@ -18,6 +22,7 @@
#include "MemLeakFindOn.h"
+<<<<<<< HEAD
// set packing to one byte
#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS
#include "BeginStructPackForWire.h"
@@ -55,14 +60,19 @@ typedef struct
END_STRUCTURE_PACKING_FOR_WIRE
#endif
+=======
+>>>>>>> 0.12
#ifdef BOX_RELEASE_BUILD
#define NUM_DELETED_DIRS_BLOCK 256
#else
#define NUM_DELETED_DIRS_BLOCK 2
#endif
+<<<<<<< HEAD
#define INFO_FILENAME "info"
+=======
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
// Function
@@ -79,8 +89,20 @@ BackupStoreInfo::BackupStoreInfo()
mClientStoreMarker(0),
mLastObjectIDUsed(-1),
mBlocksUsed(0),
+<<<<<<< HEAD
mBlocksInOldFiles(0),
mBlocksInDeletedFiles(0)
+=======
+ mBlocksInCurrentFiles(0),
+ mBlocksInOldFiles(0),
+ mBlocksInDeletedFiles(0),
+ mBlocksInDirectories(0),
+ mNumFiles(0),
+ mNumOldFiles(0),
+ mNumDeletedFiles(0),
+ mNumDirectories(0),
+ mAccountEnabled(true)
+>>>>>>> 0.12
{
}
@@ -106,6 +128,7 @@ BackupStoreInfo::~BackupStoreInfo()
// --------------------------------------------------------------------------
void BackupStoreInfo::CreateNew(int32_t AccountID, const std::string &rRootDir, int DiscSet, int64_t BlockSoftLimit, int64_t BlockHardLimit)
{
+<<<<<<< HEAD
// Initial header (is entire file)
info_StreamFormat hdr = {
htonl(INFO_MAGIC_VALUE), // mMagicValue
@@ -139,11 +162,29 @@ void BackupStoreInfo::CreateNew(int32_t AccountID, const std::string &rRootDir,
rf.Commit(true);
// Done.
+=======
+ BackupStoreInfo info;
+ info.mAccountID = AccountID;
+ info.mDiscSet = DiscSet;
+ info.mReadOnly = false;
+ info.mLastObjectIDUsed = 1;
+ info.mBlocksSoftLimit = BlockSoftLimit;
+ info.mBlocksHardLimit = BlockHardLimit;
+
+ // Generate the filename
+ ASSERT(rRootDir[rRootDir.size() - 1] == '/' ||
+ rRootDir[rRootDir.size() - 1] == DIRECTORY_SEPARATOR_ASCHAR);
+ info.mFilename = rRootDir + INFO_FILENAME;
+ info.mExtraData.SetForReading(); // extra data is empty in this case
+
+ info.Save(false);
+>>>>>>> 0.12
}
// --------------------------------------------------------------------------
//
// Function
+<<<<<<< HEAD
// Name: BackupStoreInfo::Load(int32_t, const std::string &, int, bool)
// Purpose: Loads the info from disc, given the root information. Can be marked as read only.
// Created: 2003/08/28
@@ -170,6 +211,51 @@ std::auto_ptr<BackupStoreInfo> BackupStoreInfo::Load(int32_t AccountID, const st
THROW_EXCEPTION(BackupStoreException, BadStoreInfoOnLoad)
}
+=======
+// Name: BackupStoreInfo::Load(int32_t, const std::string &,
+// int, bool)
+// Purpose: Loads the info from disc, given the root
+// information. Can be marked as read only.
+// Created: 2003/08/28
+//
+// --------------------------------------------------------------------------
+std::auto_ptr<BackupStoreInfo> BackupStoreInfo::Load(int32_t AccountID,
+ const std::string &rRootDir, int DiscSet, bool ReadOnly,
+ int64_t *pRevisionID)
+{
+ // Generate the filename
+ std::string fn(rRootDir + INFO_FILENAME);
+
+ // Open the file for reading (passing on optional request for revision ID)
+ std::auto_ptr<RaidFileRead> rf(RaidFileRead::Open(DiscSet, fn, pRevisionID));
+
+ // Read in format and version
+ int32_t magic;
+ if(!rf->ReadFullBuffer(&magic, sizeof(magic), 0))
+ {
+ THROW_FILE_ERROR("Failed to read store info file: "
+ "short read of magic number", fn,
+ BackupStoreException, CouldNotLoadStoreInfo);
+ }
+
+ bool v1 = false, v2 = false;
+
+ if(ntohl(magic) == INFO_MAGIC_VALUE_1)
+ {
+ v1 = true;
+ }
+ else if(ntohl(magic) == INFO_MAGIC_VALUE_2)
+ {
+ v2 = true;
+ }
+ else
+ {
+ THROW_FILE_ERROR("Failed to read store info file: "
+ "unknown magic " << BOX_FORMAT_HEX32(ntohl(magic)),
+ fn, BackupStoreException, BadStoreInfoOnLoad);
+ }
+
+>>>>>>> 0.12
// Make new object
std::auto_ptr<BackupStoreInfo> info(new BackupStoreInfo);
@@ -178,6 +264,7 @@ std::auto_ptr<BackupStoreInfo> BackupStoreInfo::Load(int32_t AccountID, const st
info->mDiscSet = DiscSet;
info->mFilename = fn;
info->mReadOnly = ReadOnly;
+<<<<<<< HEAD
// Insert info from file
info->mClientStoreMarker = box_ntoh64(hdr.mClientStoreMarker);
@@ -193,6 +280,75 @@ std::auto_ptr<BackupStoreInfo> BackupStoreInfo::Load(int32_t AccountID, const st
int64_t numDelObj = box_ntoh64(hdr.mNumberDeletedDirectories);
// Then load them in
+=======
+ int64_t numDelObj = 0;
+
+ if (v1)
+ {
+ // Read in a header
+ info_StreamFormat_1 hdr;
+ rf->Seek(0, IOStream::SeekType_Absolute);
+
+ if(!rf->ReadFullBuffer(&hdr, sizeof(hdr),
+ 0 /* not interested in bytes read if this fails */))
+ {
+ THROW_FILE_ERROR("Failed to read store info header",
+ fn, BackupStoreException, CouldNotLoadStoreInfo);
+ }
+
+ // Check it
+ if((int32_t)ntohl(hdr.mAccountID) != AccountID)
+ {
+ THROW_FILE_ERROR("Found wrong account ID in store info",
+ fn, BackupStoreException, BadStoreInfoOnLoad);
+ }
+
+ // Insert info from file
+ info->mClientStoreMarker = box_ntoh64(hdr.mClientStoreMarker);
+ info->mLastObjectIDUsed = box_ntoh64(hdr.mLastObjectIDUsed);
+ info->mBlocksUsed = box_ntoh64(hdr.mBlocksUsed);
+ info->mBlocksInOldFiles = box_ntoh64(hdr.mBlocksInOldFiles);
+ info->mBlocksInDeletedFiles = box_ntoh64(hdr.mBlocksInDeletedFiles);
+ info->mBlocksInDirectories = box_ntoh64(hdr.mBlocksInDirectories);
+ info->mBlocksSoftLimit = box_ntoh64(hdr.mBlocksSoftLimit);
+ info->mBlocksHardLimit = box_ntoh64(hdr.mBlocksHardLimit);
+
+ // Load up array of deleted objects
+ numDelObj = box_ntoh64(hdr.mNumberDeletedDirectories);
+ }
+ else if(v2)
+ {
+ Archive archive(*rf, IOStream::TimeOutInfinite);
+
+ // Check it
+ int32_t FileAccountID;
+ archive.Read(FileAccountID);
+ if (FileAccountID != AccountID)
+ {
+ THROW_FILE_ERROR("Found wrong account ID in store "
+ "info: " << BOX_FORMAT_HEX32(FileAccountID),
+ fn, BackupStoreException, BadStoreInfoOnLoad);
+ }
+
+ archive.Read(info->mAccountName);
+ archive.Read(info->mClientStoreMarker);
+ archive.Read(info->mLastObjectIDUsed);
+ archive.Read(info->mBlocksUsed);
+ archive.Read(info->mBlocksInCurrentFiles);
+ archive.Read(info->mBlocksInOldFiles);
+ archive.Read(info->mBlocksInDeletedFiles);
+ archive.Read(info->mBlocksInDirectories);
+ archive.Read(info->mBlocksSoftLimit);
+ archive.Read(info->mBlocksHardLimit);
+ archive.Read(info->mNumFiles);
+ archive.Read(info->mNumOldFiles);
+ archive.Read(info->mNumDeletedFiles);
+ archive.Read(info->mNumDirectories);
+ archive.Read(numDelObj);
+ }
+
+ // Then load the list of deleted directories
+>>>>>>> 0.12
if(numDelObj > 0)
{
int64_t objs[NUM_DELETED_DIRS_BLOCK];
@@ -224,6 +380,29 @@ std::auto_ptr<BackupStoreInfo> BackupStoreInfo::Load(int32_t AccountID, const st
{
THROW_EXCEPTION(BackupStoreException, BadStoreInfoOnLoad)
}
+<<<<<<< HEAD
+=======
+
+ if(v2)
+ {
+ Archive archive(*rf, IOStream::TimeOutInfinite);
+ archive.ReadIfPresent(info->mAccountEnabled, true);
+ }
+ else
+ {
+ info->mAccountEnabled = true;
+ }
+
+ // If there's any data left in the info file, from future additions to
+ // the file format, then we need to load it so that it won't be lost when
+ // we resave the file.
+ IOStream::pos_type bytesLeft = rf->BytesLeftToRead();
+ if (bytesLeft > 0)
+ {
+ rf->CopyStreamTo(info->mExtraData);
+ }
+ info->mExtraData.SetForReading();
+>>>>>>> 0.12
// return it to caller
return info;
@@ -238,18 +417,33 @@ std::auto_ptr<BackupStoreInfo> BackupStoreInfo::Load(int32_t AccountID, const st
// Created: 23/4/04
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
std::auto_ptr<BackupStoreInfo> BackupStoreInfo::CreateForRegeneration(int32_t AccountID, const std::string &rRootDir,
int DiscSet, int64_t LastObjectID, int64_t BlocksUsed, int64_t BlocksInOldFiles,
int64_t BlocksInDeletedFiles, int64_t BlocksInDirectories, int64_t BlockSoftLimit, int64_t BlockHardLimit)
{
// Generate the filename
std::string fn(rRootDir + DIRECTORY_SEPARATOR INFO_FILENAME);
+=======
+std::auto_ptr<BackupStoreInfo> BackupStoreInfo::CreateForRegeneration(
+ int32_t AccountID, const std::string& rAccountName,
+ const std::string &rRootDir, int DiscSet,
+ int64_t LastObjectID, int64_t BlocksUsed,
+ int64_t BlocksInCurrentFiles, int64_t BlocksInOldFiles,
+ int64_t BlocksInDeletedFiles, int64_t BlocksInDirectories,
+ int64_t BlockSoftLimit, int64_t BlockHardLimit,
+ bool AccountEnabled, IOStream& ExtraData)
+{
+ // Generate the filename
+ std::string fn(rRootDir + INFO_FILENAME);
+>>>>>>> 0.12
// Make new object
std::auto_ptr<BackupStoreInfo> info(new BackupStoreInfo);
// Put in basic info
info->mAccountID = AccountID;
+<<<<<<< HEAD
info->mDiscSet = DiscSet;
info->mFilename = fn;
info->mReadOnly = false;
@@ -258,12 +452,32 @@ std::auto_ptr<BackupStoreInfo> BackupStoreInfo::CreateForRegeneration(int32_t Ac
info->mClientStoreMarker = 0;
info->mLastObjectIDUsed = LastObjectID;
info->mBlocksUsed = BlocksUsed;
+=======
+ info->mAccountName = rAccountName;
+ info->mDiscSet = DiscSet;
+ info->mFilename = fn;
+ info->mReadOnly = false;
+
+ // Insert info starting info
+ info->mClientStoreMarker = 0;
+ info->mLastObjectIDUsed = LastObjectID;
+ info->mBlocksUsed = BlocksUsed;
+ info->mBlocksInCurrentFiles = BlocksInCurrentFiles;
+>>>>>>> 0.12
info->mBlocksInOldFiles = BlocksInOldFiles;
info->mBlocksInDeletedFiles = BlocksInDeletedFiles;
info->mBlocksInDirectories = BlocksInDirectories;
info->mBlocksSoftLimit = BlockSoftLimit;
info->mBlocksHardLimit = BlockHardLimit;
+<<<<<<< HEAD
+
+=======
+ info->mAccountEnabled = AccountEnabled;
+ ExtraData.CopyStreamTo(info->mExtraData);
+ info->mExtraData.SetForReading();
+
+>>>>>>> 0.12
// return it to caller
return info;
}
@@ -272,12 +486,20 @@ std::auto_ptr<BackupStoreInfo> BackupStoreInfo::CreateForRegeneration(int32_t Ac
// --------------------------------------------------------------------------
//
// Function
+<<<<<<< HEAD
// Name: BackupStoreInfo::Save()
+=======
+// Name: BackupStoreInfo::Save(bool allowOverwrite)
+>>>>>>> 0.12
// Purpose: Save modified info back to disc
// Created: 2003/08/28
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
void BackupStoreInfo::Save()
+=======
+void BackupStoreInfo::Save(bool allowOverwrite)
+>>>>>>> 0.12
{
// Make sure we're initialised (although should never come to this)
if(mFilename.empty() || mAccountID == -1 || mDiscSet == -1)
@@ -293,6 +515,7 @@ void BackupStoreInfo::Save()
// Then... open a write file
RaidFileWrite rf(mDiscSet, mFilename);
+<<<<<<< HEAD
rf.Open(true); // allow overwriting
// Make header
@@ -314,6 +537,34 @@ void BackupStoreInfo::Save()
// Write header
rf.Write(&hdr, sizeof(hdr));
+=======
+ rf.Open(allowOverwrite);
+
+ // Make header
+ int32_t magic = htonl(INFO_MAGIC_VALUE_2);
+ rf.Write(&magic, sizeof(magic));
+ Archive archive(rf, IOStream::TimeOutInfinite);
+
+ archive.Write(mAccountID);
+ archive.Write(mAccountName);
+ archive.Write(mClientStoreMarker);
+ archive.Write(mLastObjectIDUsed);
+ archive.Write(mBlocksUsed);
+ archive.Write(mBlocksInCurrentFiles);
+ archive.Write(mBlocksInOldFiles);
+ archive.Write(mBlocksInDeletedFiles);
+ archive.Write(mBlocksInDirectories);
+ archive.Write(mBlocksSoftLimit);
+ archive.Write(mBlocksHardLimit);
+ archive.Write(mNumFiles);
+ archive.Write(mNumOldFiles);
+ archive.Write(mNumDeletedFiles);
+ archive.Write(mNumDirectories);
+
+ int64_t numDelObj = mDeletedDirectories.size();
+ archive.Write(numDelObj);
+
+>>>>>>> 0.12
// Write the deleted object list
if(mDeletedDirectories.size() > 0)
{
@@ -341,6 +592,15 @@ void BackupStoreInfo::Save()
tosave -= b;
}
}
+<<<<<<< HEAD
+=======
+
+ archive.Write(mAccountEnabled);
+
+ mExtraData.Seek(0, IOStream::SeekType_Absolute);
+ mExtraData.CopyStreamTo(rf);
+ mExtraData.Seek(0, IOStream::SeekType_Absolute);
+>>>>>>> 0.12
// Commit it to disc, converting it to RAID now
rf.Commit(true);
@@ -349,7 +609,59 @@ void BackupStoreInfo::Save()
mIsModified = false;
}
+<<<<<<< HEAD
+=======
+int BackupStoreInfo::ReportChangesTo(BackupStoreInfo& rOldInfo)
+{
+ int numChanges = 0;
+
+ #define COMPARE(attribute) \
+ if (rOldInfo.Get ## attribute () != Get ## attribute ()) \
+ { \
+ BOX_ERROR(#attribute " changed from " << \
+ rOldInfo.Get ## attribute () << " to " << \
+ Get ## attribute ()); \
+ numChanges++; \
+ }
+
+ COMPARE(AccountID);
+ COMPARE(AccountName);
+ COMPARE(LastObjectIDUsed);
+ COMPARE(BlocksUsed);
+ COMPARE(BlocksInCurrentFiles);
+ COMPARE(BlocksInOldFiles);
+ COMPARE(BlocksInDeletedFiles);
+ COMPARE(BlocksInDirectories);
+ COMPARE(BlocksSoftLimit);
+ COMPARE(BlocksHardLimit);
+ COMPARE(NumFiles);
+ COMPARE(NumOldFiles);
+ COMPARE(NumDeletedFiles);
+ COMPARE(NumDirectories);
+
+ #undef COMPARE
+
+ return numChanges;
+}
+
+#define APPLY_DELTA(field, delta) \
+ if(mReadOnly) \
+ { \
+ THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) \
+ } \
+ \
+ if((field + delta) < 0) \
+ { \
+ THROW_EXCEPTION_MESSAGE(BackupStoreException, \
+ StoreInfoBlockDeltaMakesValueNegative, \
+ "Failed to reduce " << #field << " from " << \
+ field << " by " << delta); \
+ } \
+ \
+ field += delta; \
+ mIsModified = true;
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
@@ -361,6 +673,7 @@ void BackupStoreInfo::Save()
// --------------------------------------------------------------------------
void BackupStoreInfo::ChangeBlocksUsed(int64_t Delta)
{
+<<<<<<< HEAD
if(mReadOnly)
{
THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
@@ -373,6 +686,23 @@ void BackupStoreInfo::ChangeBlocksUsed(int64_t Delta)
mBlocksUsed += Delta;
mIsModified = true;
+=======
+ APPLY_DELTA(mBlocksUsed, Delta);
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreInfo::ChangeBlocksInCurrentFiles(int32_t)
+// Purpose: Change number of blocks in current files, by a delta
+// amount
+// Created: 2010/08/26
+//
+// --------------------------------------------------------------------------
+void BackupStoreInfo::ChangeBlocksInCurrentFiles(int64_t Delta)
+{
+ APPLY_DELTA(mBlocksInCurrentFiles, Delta);
+>>>>>>> 0.12
}
// --------------------------------------------------------------------------
@@ -385,6 +715,7 @@ void BackupStoreInfo::ChangeBlocksUsed(int64_t Delta)
// --------------------------------------------------------------------------
void BackupStoreInfo::ChangeBlocksInOldFiles(int64_t Delta)
{
+<<<<<<< HEAD
if(mReadOnly)
{
THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
@@ -397,6 +728,9 @@ void BackupStoreInfo::ChangeBlocksInOldFiles(int64_t Delta)
mBlocksInOldFiles += Delta;
mIsModified = true;
+=======
+ APPLY_DELTA(mBlocksInOldFiles, Delta);
+>>>>>>> 0.12
}
// --------------------------------------------------------------------------
@@ -409,6 +743,7 @@ void BackupStoreInfo::ChangeBlocksInOldFiles(int64_t Delta)
// --------------------------------------------------------------------------
void BackupStoreInfo::ChangeBlocksInDeletedFiles(int64_t Delta)
{
+<<<<<<< HEAD
if(mReadOnly)
{
THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
@@ -421,6 +756,9 @@ void BackupStoreInfo::ChangeBlocksInDeletedFiles(int64_t Delta)
mBlocksInDeletedFiles += Delta;
mIsModified = true;
+=======
+ APPLY_DELTA(mBlocksInDeletedFiles, Delta);
+>>>>>>> 0.12
}
// --------------------------------------------------------------------------
@@ -433,6 +771,7 @@ void BackupStoreInfo::ChangeBlocksInDeletedFiles(int64_t Delta)
// --------------------------------------------------------------------------
void BackupStoreInfo::ChangeBlocksInDirectories(int64_t Delta)
{
+<<<<<<< HEAD
if(mReadOnly)
{
THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
@@ -447,6 +786,30 @@ void BackupStoreInfo::ChangeBlocksInDirectories(int64_t Delta)
mIsModified = true;
}
+=======
+ APPLY_DELTA(mBlocksInDirectories, Delta);
+}
+
+void BackupStoreInfo::AdjustNumFiles(int64_t increase)
+{
+ APPLY_DELTA(mNumFiles, increase);
+}
+
+void BackupStoreInfo::AdjustNumOldFiles(int64_t increase)
+{
+ APPLY_DELTA(mNumOldFiles, increase);
+}
+
+void BackupStoreInfo::AdjustNumDeletedFiles(int64_t increase)
+{
+ APPLY_DELTA(mNumDeletedFiles, increase);
+}
+
+void BackupStoreInfo::AdjustNumDirectories(int64_t increase)
+{
+ APPLY_DELTA(mNumDirectories, increase);
+}
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
@@ -590,4 +953,26 @@ void BackupStoreInfo::SetClientStoreMarker(int64_t ClientStoreMarker)
}
+<<<<<<< HEAD
+=======
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: BackupStoreInfo::SetAccountName(const std::string&)
+// Purpose: Sets the account name
+// Created: 2008/08/22
+//
+// --------------------------------------------------------------------------
+void BackupStoreInfo::SetAccountName(const std::string& rName)
+{
+ if(mReadOnly)
+ {
+ THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly)
+ }
+
+ mAccountName = rName;
+
+ mIsModified = true;
+}
+>>>>>>> 0.12
diff --git a/lib/backupstore/BackupStoreInfo.h b/lib/backupstore/BackupStoreInfo.h
index a94ca9d6..db2a62ef 100644
--- a/lib/backupstore/BackupStoreInfo.h
+++ b/lib/backupstore/BackupStoreInfo.h
@@ -14,8 +14,56 @@
#include <string>
#include <vector>
+<<<<<<< HEAD
class BackupStoreCheck;
+=======
+#include "CollectInBufferStream.h"
+
+class BackupStoreCheck;
+
+// set packing to one byte
+#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS
+#include "BeginStructPackForWire.h"
+#else
+BEGIN_STRUCTURE_PACKING_FOR_WIRE
+#endif
+
+// ******************
+// make sure the defaults in CreateNew are modified!
+// ******************
+// Old version, grandfathered, do not change!
+typedef struct
+{
+ int32_t mMagicValue; // also the version number
+ int32_t mAccountID;
+ int64_t mClientStoreMarker;
+ int64_t mLastObjectIDUsed;
+ int64_t mBlocksUsed;
+ int64_t mBlocksInOldFiles;
+ int64_t mBlocksInDeletedFiles;
+ int64_t mBlocksInDirectories;
+ int64_t mBlocksSoftLimit;
+ int64_t mBlocksHardLimit;
+ uint32_t mCurrentMarkNumber;
+ uint32_t mOptionsPresent; // bit mask of optional elements present
+ int64_t mNumberDeletedDirectories;
+ // Then loads of int64_t IDs for the deleted directories
+} info_StreamFormat_1;
+
+#define INFO_MAGIC_VALUE_1 0x34832476
+#define INFO_MAGIC_VALUE_2 0x494e4632 /* INF2 */
+
+// Use default packing
+#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS
+#include "EndStructPackForWire.h"
+#else
+END_STRUCTURE_PACKING_FOR_WIRE
+#endif
+
+#define INFO_FILENAME "info"
+
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
// Class
@@ -46,23 +94,47 @@ public:
bool IsModified() const {return mIsModified;}
// Save modified infomation back to store
+<<<<<<< HEAD
void Save();
+=======
+ void Save(bool allowOverwrite = true);
+>>>>>>> 0.12
// Data access functions
int32_t GetAccountID() const {return mAccountID;}
int64_t GetLastObjectIDUsed() const {return mLastObjectIDUsed;}
int64_t GetBlocksUsed() const {return mBlocksUsed;}
+<<<<<<< HEAD
+=======
+ int64_t GetBlocksInCurrentFiles() const {return mBlocksInCurrentFiles;}
+>>>>>>> 0.12
int64_t GetBlocksInOldFiles() const {return mBlocksInOldFiles;}
int64_t GetBlocksInDeletedFiles() const {return mBlocksInDeletedFiles;}
int64_t GetBlocksInDirectories() const {return mBlocksInDirectories;}
const std::vector<int64_t> &GetDeletedDirectories() const {return mDeletedDirectories;}
int64_t GetBlocksSoftLimit() const {return mBlocksSoftLimit;}
int64_t GetBlocksHardLimit() const {return mBlocksHardLimit;}
+<<<<<<< HEAD
bool IsReadOnly() const {return mReadOnly;}
int GetDiscSetNumber() const {return mDiscSet;}
// Data modification functions
void ChangeBlocksUsed(int64_t Delta);
+=======
+ int64_t GetNumFiles() const {return mNumFiles;}
+ int64_t GetNumOldFiles() const {return mNumOldFiles;}
+ int64_t GetNumDeletedFiles() const {return mNumDeletedFiles;}
+ int64_t GetNumDirectories() const {return mNumDirectories;}
+ bool IsAccountEnabled() const {return mAccountEnabled;}
+ bool IsReadOnly() const {return mReadOnly;}
+ int GetDiscSetNumber() const {return mDiscSet;}
+
+ int ReportChangesTo(BackupStoreInfo& rOldInfo);
+
+ // Data modification functions
+ void ChangeBlocksUsed(int64_t Delta);
+ void ChangeBlocksInCurrentFiles(int64_t Delta);
+>>>>>>> 0.12
void ChangeBlocksInOldFiles(int64_t Delta);
void ChangeBlocksInDeletedFiles(int64_t Delta);
void ChangeBlocksInDirectories(int64_t Delta);
@@ -70,11 +142,19 @@ public:
void AddDeletedDirectory(int64_t DirID);
void RemovedDeletedDirectory(int64_t DirID);
void ChangeLimits(int64_t BlockSoftLimit, int64_t BlockHardLimit);
+<<<<<<< HEAD
+=======
+ void AdjustNumFiles(int64_t increase);
+ void AdjustNumOldFiles(int64_t increase);
+ void AdjustNumDeletedFiles(int64_t increase);
+ void AdjustNumDirectories(int64_t increase);
+>>>>>>> 0.12
// Object IDs
int64_t AllocateObjectID();
// Client marker set and get
+<<<<<<< HEAD
int64_t GetClientStoreMarker() {return mClientStoreMarker;}
void SetClientStoreMarker(int64_t ClientStoreMarker);
@@ -86,6 +166,35 @@ private:
private:
// Location information
int32_t mAccountID;
+=======
+ int64_t GetClientStoreMarker() const {return mClientStoreMarker;}
+ void SetClientStoreMarker(int64_t ClientStoreMarker);
+
+ const std::string& GetAccountName() const { return mAccountName; }
+ void SetAccountName(const std::string& rName);
+ const CollectInBufferStream& GetExtraData() const { return mExtraData; }
+ void SetAccountEnabled(bool IsEnabled) {mAccountEnabled = IsEnabled; }
+
+ /**
+ * @return a new BackupStoreInfo with the requested properties.
+ * This is exposed to allow testing, do not use otherwise!
+ */
+ static std::auto_ptr<BackupStoreInfo> CreateForRegeneration(
+ int32_t AccountID, const std::string &rAccountName,
+ const std::string &rRootDir, int DiscSet,
+ int64_t LastObjectID, int64_t BlocksUsed,
+ int64_t BlocksInCurrentFiles, int64_t BlocksInOldFiles,
+ int64_t BlocksInDeletedFiles, int64_t BlocksInDirectories,
+ int64_t BlockSoftLimit, int64_t BlockHardLimit,
+ bool AccountEnabled, IOStream& ExtraData);
+
+private:
+ // Location information
+ // Be VERY careful about changing types of these values, as
+ // they now define the sizes of fields on disk (via Archive).
+ int32_t mAccountID;
+ std::string mAccountName;
+>>>>>>> 0.12
int mDiscSet;
std::string mFilename;
bool mReadOnly;
@@ -97,15 +206,31 @@ private:
// Account information
int64_t mLastObjectIDUsed;
int64_t mBlocksUsed;
+<<<<<<< HEAD
+=======
+ int64_t mBlocksInCurrentFiles;
+>>>>>>> 0.12
int64_t mBlocksInOldFiles;
int64_t mBlocksInDeletedFiles;
int64_t mBlocksInDirectories;
int64_t mBlocksSoftLimit;
int64_t mBlocksHardLimit;
+<<<<<<< HEAD
std::vector<int64_t> mDeletedDirectories;
};
+=======
+ int64_t mNumFiles;
+ int64_t mNumOldFiles;
+ int64_t mNumDeletedFiles;
+ int64_t mNumDirectories;
+ std::vector<int64_t> mDeletedDirectories;
+ bool mAccountEnabled;
+ CollectInBufferStream mExtraData;
+};
+
+>>>>>>> 0.12
#endif // BACKUPSTOREINFO__H
diff --git a/lib/backupstore/BackupStoreObjectMagic.h b/lib/backupstore/BackupStoreObjectMagic.h
new file mode 100644
index 00000000..7ee600a2
--- /dev/null
+++ b/lib/backupstore/BackupStoreObjectMagic.h
@@ -0,0 +1,31 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: BackupStoreObjectMagic.h
+// Purpose: Magic values for the start of objects in the backup store
+// Created: 19/11/03
+//
+// --------------------------------------------------------------------------
+
+#ifndef BACKUPSTOREOBJECTMAGIC__H
+#define BACKUPSTOREOBJECTMAGIC__H
+
+// Each of these values is the first 4 bytes of the object file.
+// Remember to swap from network to host byte order.
+
+// Magic value for file streams
+#define OBJECTMAGIC_FILE_MAGIC_VALUE_V1 0x66696C65
+// Do not use v0 in any new code!
+#define OBJECTMAGIC_FILE_MAGIC_VALUE_V0 0x46494C45
+
+// Magic for the block index at the file stream -- used to
+// ensure streams are reordered as expected
+#define OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1 0x62696478
+// Do not use v0 in any new code!
+#define OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0 0x46426C6B
+
+// Magic value for directory streams
+#define OBJECTMAGIC_DIR_MAGIC_VALUE 0x4449525F
+
+#endif // BACKUPSTOREOBJECTMAGIC__H
+
diff --git a/lib/backupstore/BackupStoreRefCountDatabase.cpp b/lib/backupstore/BackupStoreRefCountDatabase.cpp
index f6db2ca4..642e260c 100644
--- a/lib/backupstore/BackupStoreRefCountDatabase.cpp
+++ b/lib/backupstore/BackupStoreRefCountDatabase.cpp
@@ -61,7 +61,11 @@ std::string BackupStoreRefCountDatabase::GetFilename(const
ASSERT(RootDir[RootDir.size() - 1] == '/' ||
RootDir[RootDir.size() - 1] == DIRECTORY_SEPARATOR_ASCHAR);
+<<<<<<< HEAD
std::string fn(RootDir + "refcount.db");
+=======
+ std::string fn(RootDir + REFCOUNT_FILENAME ".db");
+>>>>>>> 0.12
RaidFileController &rcontroller(RaidFileController::GetController());
RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(rAccount.GetDiscSet()));
return RaidFileUtil::MakeWriteFileName(rdiscSet, fn);
@@ -91,9 +95,15 @@ void BackupStoreRefCountDatabase::Create(const
// Open the file for writing
if (FileExists(Filename) && !AllowOverwrite)
{
+<<<<<<< HEAD
BOX_ERROR("Attempted to overwrite refcount database file: " <<
Filename);
THROW_EXCEPTION(RaidFileException, CannotOverwriteExistingFile);
+=======
+ THROW_FILE_ERROR("Failed to overwrite refcount database: "
+ "not allowed here", Filename, RaidFileException,
+ CannotOverwriteExistingFile);
+>>>>>>> 0.12
}
int flags = O_CREAT | O_BINARY | O_RDWR;
@@ -134,14 +144,26 @@ std::auto_ptr<BackupStoreRefCountDatabase> BackupStoreRefCountDatabase::Load(
refcount_StreamFormat hdr;
if(!dbfile->ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */))
{
+<<<<<<< HEAD
THROW_EXCEPTION(BackupStoreException, CouldNotLoadStoreInfo)
+=======
+ THROW_FILE_ERROR("Failed to read refcount database: "
+ "short read", filename, BackupStoreException,
+ CouldNotLoadStoreInfo);
+>>>>>>> 0.12
}
// Check it
if(ntohl(hdr.mMagicValue) != REFCOUNT_MAGIC_VALUE ||
(int32_t)ntohl(hdr.mAccountID) != rAccount.GetID())
{
+<<<<<<< HEAD
THROW_EXCEPTION(BackupStoreException, BadStoreInfoOnLoad)
+=======
+ THROW_FILE_ERROR("Failed to read refcount database: "
+ "bad magic number", filename, BackupStoreException,
+ BadStoreInfoOnLoad);
+>>>>>>> 0.12
}
// Make new object
@@ -158,6 +180,7 @@ std::auto_ptr<BackupStoreRefCountDatabase> BackupStoreRefCountDatabase::Load(
// --------------------------------------------------------------------------
//
// Function
+<<<<<<< HEAD
// Name: BackupStoreRefCountDatabase::Save()
// Purpose: Save modified info back to disc
// Created: 2003/08/28
@@ -241,6 +264,8 @@ void BackupStoreRefCountDatabase::Save()
// --------------------------------------------------------------------------
//
// Function
+=======
+>>>>>>> 0.12
// Name: BackupStoreRefCountDatabase::GetRefCount(int64_t
// ObjectID)
// Purpose: Get the number of references to the specified object
@@ -255,10 +280,17 @@ BackupStoreRefCountDatabase::GetRefCount(int64_t ObjectID) const
if (GetSize() < offset + GetEntrySize())
{
+<<<<<<< HEAD
BOX_ERROR("attempted read of unknown refcount for object " <<
BOX_FORMAT_OBJECTID(ObjectID));
THROW_EXCEPTION(BackupStoreException,
UnknownObjectRefCountRequested);
+=======
+ THROW_FILE_ERROR("Failed to read refcount database: "
+ "attempted read of unknown refcount for object " <<
+ BOX_FORMAT_OBJECTID(ObjectID), mFilename,
+ BackupStoreException, UnknownObjectRefCountRequested);
+>>>>>>> 0.12
}
mapDatabaseFile->Seek(offset, SEEK_SET);
@@ -267,9 +299,15 @@ BackupStoreRefCountDatabase::GetRefCount(int64_t ObjectID) const
if (mapDatabaseFile->Read(&refcount, sizeof(refcount)) !=
sizeof(refcount))
{
+<<<<<<< HEAD
BOX_LOG_SYS_ERROR("short read on refcount database: " <<
mFilename);
THROW_EXCEPTION(BackupStoreException, CouldNotLoadStoreInfo);
+=======
+ THROW_FILE_ERROR("Failed to read refcount database: "
+ "short read at offset " << offset, mFilename,
+ BackupStoreException, CouldNotLoadStoreInfo);
+>>>>>>> 0.12
}
return ntohl(refcount);
diff --git a/lib/backupstore/HousekeepStoreAccount.cpp b/lib/backupstore/HousekeepStoreAccount.cpp
new file mode 100644
index 00000000..75feda7f
--- /dev/null
+++ b/lib/backupstore/HousekeepStoreAccount.cpp
@@ -0,0 +1,1110 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: HousekeepStoreAccount.cpp
+// Purpose:
+// Created: 11/12/03
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdio.h>
+
+#include <map>
+
+#include "autogen_BackupStoreException.h"
+#include "BackupConstants.h"
+#include "BackupStoreAccountDatabase.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreDirectory.h"
+#include "BackupStoreFile.h"
+#include "BackupStoreInfo.h"
+#include "BackupStoreRefCountDatabase.h"
+#include "BufferedStream.h"
+#include "HousekeepStoreAccount.h"
+#include "NamedLock.h"
+#include "RaidFileRead.h"
+#include "RaidFileWrite.h"
+#include "StoreStructure.h"
+
+#include "MemLeakFindOn.h"
+
+// check every 32 directories scanned/files deleted
+#define POLL_INTERPROCESS_MSG_CHECK_FREQUENCY 32
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HousekeepStoreAccount::HousekeepStoreAccount(int, const std::string &, int, BackupStoreDaemon &)
+// Purpose: Constructor
+// Created: 11/12/03
+//
+// --------------------------------------------------------------------------
+HousekeepStoreAccount::HousekeepStoreAccount(int AccountID,
+ const std::string &rStoreRoot, int StoreDiscSet,
+ HousekeepingCallback* pHousekeepingCallback)
+ : mAccountID(AccountID),
+ mStoreRoot(rStoreRoot),
+ mStoreDiscSet(StoreDiscSet),
+ mpHousekeepingCallback(pHousekeepingCallback),
+ mDeletionSizeTarget(0),
+ mPotentialDeletionsTotalSize(0),
+ mMaxSizeInPotentialDeletions(0),
+ mBlocksUsed(0),
+ mBlocksInOldFiles(0),
+ mBlocksInDeletedFiles(0),
+ mBlocksInDirectories(0),
+ mBlocksUsedDelta(0),
+ mBlocksInOldFilesDelta(0),
+ mBlocksInDeletedFilesDelta(0),
+ mBlocksInDirectoriesDelta(0),
+ mFilesDeleted(0),
+ mEmptyDirectoriesDeleted(0),
+ mSuppressRefCountChangeWarnings(false),
+ mRefCountsAdjusted(0),
+ mCountUntilNextInterprocessMsgCheck(POLL_INTERPROCESS_MSG_CHECK_FREQUENCY)
+{
+ std::ostringstream tag;
+ tag << "hk=" << BOX_FORMAT_ACCOUNT(mAccountID);
+ mTagWithClientID.Change(tag.str());
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HousekeepStoreAccount::~HousekeepStoreAccount()
+// Purpose: Destructor
+// Created: 11/12/03
+//
+// --------------------------------------------------------------------------
+HousekeepStoreAccount::~HousekeepStoreAccount()
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HousekeepStoreAccount::DoHousekeeping()
+// Purpose: Perform the housekeeping
+// Created: 11/12/03
+//
+// --------------------------------------------------------------------------
+bool HousekeepStoreAccount::DoHousekeeping(bool KeepTryingForever)
+{
+ BOX_TRACE("Starting housekeeping on account " <<
+ BOX_FORMAT_ACCOUNT(mAccountID));
+
+ // Attempt to lock the account
+ std::string writeLockFilename;
+ StoreStructure::MakeWriteLockFilename(mStoreRoot, mStoreDiscSet,
+ writeLockFilename);
+ NamedLock writeLock;
+ if(!writeLock.TryAndGetLock(writeLockFilename.c_str(),
+ 0600 /* restrictive file permissions */))
+ {
+ if(KeepTryingForever)
+ {
+ BOX_WARNING("Failed to lock account for housekeeping, "
+ "still trying...");
+ while(!writeLock.TryAndGetLock(writeLockFilename,
+ 0600 /* restrictive file permissions */))
+ {
+ sleep(1);
+ }
+ }
+ else
+ {
+ // Couldn't lock the account -- just stop now
+ return false;
+ }
+ }
+
+ // Load the store info to find necessary info for the housekeeping
+ std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(mAccountID,
+ mStoreRoot, mStoreDiscSet, false /* Read/Write */));
+ std::auto_ptr<BackupStoreInfo> pOldInfo(
+ BackupStoreInfo::Load(mAccountID, mStoreRoot, mStoreDiscSet,
+ true /* Read Only */));
+
+ // If the account has a name, change the logging tag to include it
+ if(!(info->GetAccountName().empty()))
+ {
+ std::ostringstream tag;
+ tag << "hk=" << BOX_FORMAT_ACCOUNT(mAccountID) << "/" <<
+ info->GetAccountName();
+ mTagWithClientID.Change(tag.str());
+ }
+
+ // Calculate how much should be deleted
+ mDeletionSizeTarget = info->GetBlocksUsed() - info->GetBlocksSoftLimit();
+ if(mDeletionSizeTarget < 0)
+ {
+ mDeletionSizeTarget = 0;
+ }
+
+ // initialise the refcount database
+ mNewRefCounts.clear();
+ // try to pre-allocate as much memory as we need
+ mNewRefCounts.reserve(info->GetLastObjectIDUsed());
+ // initialise the refcount of the root entry
+ mNewRefCounts.resize(BACKUPSTORE_ROOT_DIRECTORY_ID + 1, 0);
+ mNewRefCounts[BACKUPSTORE_ROOT_DIRECTORY_ID] = 1;
+
+ // Scan the directory for potential things to delete
+ // This will also remove eligible items marked with RemoveASAP
+ bool continueHousekeeping = ScanDirectory(BACKUPSTORE_ROOT_DIRECTORY_ID);
+
+ // If scan directory stopped for some reason, probably parent
+ // instructed to terminate, stop now.
+ if(!continueHousekeeping)
+ {
+ // If any files were marked "delete now", then update
+ // the size of the store.
+ if(mBlocksUsedDelta != 0 ||
+ mBlocksInOldFilesDelta != 0 ||
+ mBlocksInDeletedFilesDelta != 0)
+ {
+ info->ChangeBlocksUsed(mBlocksUsedDelta);
+ info->ChangeBlocksInOldFiles(mBlocksInOldFilesDelta);
+ info->ChangeBlocksInDeletedFiles(mBlocksInDeletedFilesDelta);
+
+ // Save the store info back
+ info->ReportChangesTo(*pOldInfo);
+ info->Save();
+ }
+
+ return false;
+ }
+
+ // Log any difference in opinion between the values recorded in
+ // the store info, and the values just calculated for space usage.
+ // BLOCK
+ {
+ int64_t used = info->GetBlocksUsed();
+ int64_t usedOld = info->GetBlocksInOldFiles();
+ int64_t usedDeleted = info->GetBlocksInDeletedFiles();
+ int64_t usedDirectories = info->GetBlocksInDirectories();
+
+ // If the counts were wrong, taking into account RemoveASAP
+ // items deleted, log a message
+ if((used + mBlocksUsedDelta) != mBlocksUsed
+ || (usedOld + mBlocksInOldFilesDelta) != mBlocksInOldFiles
+ || (usedDeleted + mBlocksInDeletedFilesDelta) != mBlocksInDeletedFiles
+ || usedDirectories != mBlocksInDirectories)
+ {
+ // Log this
+ BOX_ERROR("Housekeeping on account " <<
+ BOX_FORMAT_ACCOUNT(mAccountID) << " found "
+ "and fixed wrong block counts: "
+ "used (" <<
+ (used + mBlocksUsedDelta) << "," <<
+ mBlocksUsed << "), old (" <<
+ (usedOld + mBlocksInOldFilesDelta) << "," <<
+ mBlocksInOldFiles << "), deleted (" <<
+ (usedDeleted + mBlocksInDeletedFilesDelta) <<
+ "," << mBlocksInDeletedFiles << "), dirs (" <<
+ usedDirectories << "," << mBlocksInDirectories
+ << ")");
+ }
+
+ // If the current values don't match, store them
+ if(used != mBlocksUsed
+ || usedOld != mBlocksInOldFiles
+ || usedDeleted != mBlocksInDeletedFiles
+ || usedDirectories != (mBlocksInDirectories + mBlocksInDirectoriesDelta))
+ {
+ // Set corrected values in store info
+ info->CorrectAllUsedValues(mBlocksUsed,
+ mBlocksInOldFiles, mBlocksInDeletedFiles,
+ mBlocksInDirectories + mBlocksInDirectoriesDelta);
+
+ info->ReportChangesTo(*pOldInfo);
+ info->Save();
+ }
+ }
+
+ // Reset the delta counts for files, as they will include
+ // RemoveASAP flagged files deleted during the initial scan.
+
+ // keep for reporting
+ int64_t removeASAPBlocksUsedDelta = mBlocksUsedDelta;
+
+ mBlocksUsedDelta = 0;
+ mBlocksInOldFilesDelta = 0;
+ mBlocksInDeletedFilesDelta = 0;
+
+ // Go and delete items from the accounts
+ bool deleteInterrupted = DeleteFiles();
+
+ // If that wasn't interrupted, remove any empty directories which
+ // are also marked as deleted in their containing directory
+ if(!deleteInterrupted)
+ {
+ deleteInterrupted = DeleteEmptyDirectories();
+ }
+
+ // Log deletion if anything was deleted
+ if(mFilesDeleted > 0 || mEmptyDirectoriesDeleted > 0)
+ {
+ BOX_INFO("Housekeeping on account " <<
+ BOX_FORMAT_ACCOUNT(mAccountID) << " "
+ "removed " <<
+ (0 - (mBlocksUsedDelta + removeASAPBlocksUsedDelta)) <<
+ " blocks (" << mFilesDeleted << " files, " <<
+ mEmptyDirectoriesDeleted << " dirs)" <<
+ (deleteInterrupted?" and was interrupted":""));
+ }
+
+ // We can only update the refcount database if we successfully
+ // finished our scan of all directories, otherwise we don't actually
+ // know which of the new counts are valid and which aren't
+ // (we might not have seen second references to some objects, etc.)
+
+ BackupStoreAccountDatabase::Entry account(mAccountID, mStoreDiscSet);
+ std::auto_ptr<BackupStoreRefCountDatabase> apReferences;
+
+ // try to load the reference count database
+ try
+ {
+ apReferences = BackupStoreRefCountDatabase::Load(account,
+ false);
+ }
+ catch(BoxException &e)
+ {
+ BOX_WARNING("Reference count database is missing or corrupted "
+ "during housekeeping, creating a new one.");
+ mSuppressRefCountChangeWarnings = true;
+ BackupStoreRefCountDatabase::CreateForRegeneration(account);
+ apReferences = BackupStoreRefCountDatabase::Load(account,
+ false);
+ }
+
+ int64_t LastUsedObjectIdOnDisk = apReferences->GetLastObjectIDUsed();
+
+ for (int64_t ObjectID = BACKUPSTORE_ROOT_DIRECTORY_ID;
+ ObjectID < mNewRefCounts.size(); ObjectID++)
+ {
+ if (ObjectID > LastUsedObjectIdOnDisk)
+ {
+ if (!mSuppressRefCountChangeWarnings)
+ {
+ BOX_WARNING("Reference count of object " <<
+ BOX_FORMAT_OBJECTID(ObjectID) <<
+ " not found in database, added"
+ " with " << mNewRefCounts[ObjectID] <<
+ " references");
+ }
+ apReferences->SetRefCount(ObjectID,
+ mNewRefCounts[ObjectID]);
+ mRefCountsAdjusted++;
+ LastUsedObjectIdOnDisk = ObjectID;
+ continue;
+ }
+
+ BackupStoreRefCountDatabase::refcount_t OldRefCount =
+ apReferences->GetRefCount(ObjectID);
+
+ if (OldRefCount != mNewRefCounts[ObjectID])
+ {
+ BOX_WARNING("Reference count of object " <<
+ BOX_FORMAT_OBJECTID(ObjectID) <<
+ " changed from " << OldRefCount <<
+ " to " << mNewRefCounts[ObjectID]);
+ apReferences->SetRefCount(ObjectID,
+ mNewRefCounts[ObjectID]);
+ mRefCountsAdjusted++;
+ }
+ }
+
+ // zero excess references in the database
+ for (int64_t ObjectID = mNewRefCounts.size();
+ ObjectID <= LastUsedObjectIdOnDisk; ObjectID++)
+ {
+ BackupStoreRefCountDatabase::refcount_t OldRefCount =
+ apReferences->GetRefCount(ObjectID);
+ BackupStoreRefCountDatabase::refcount_t NewRefCount = 0;
+
+ if (OldRefCount != NewRefCount)
+ {
+ BOX_WARNING("Reference count of object " <<
+ BOX_FORMAT_OBJECTID(ObjectID) <<
+ " changed from " << OldRefCount <<
+ " to " << NewRefCount << " (not found)");
+ apReferences->SetRefCount(ObjectID, NewRefCount);
+ mRefCountsAdjusted++;
+ }
+ }
+
+ // force file to be saved and closed before releasing the lock below
+ apReferences.reset();
+
+ // Make sure the delta's won't cause problems if the counts are
+ // really wrong, and it wasn't fixed because the store was
+ // updated during the scan.
+ if(mBlocksUsedDelta < (0 - info->GetBlocksUsed()))
+ {
+ mBlocksUsedDelta = (0 - info->GetBlocksUsed());
+ }
+ if(mBlocksInOldFilesDelta < (0 - info->GetBlocksInOldFiles()))
+ {
+ mBlocksInOldFilesDelta = (0 - info->GetBlocksInOldFiles());
+ }
+ if(mBlocksInDeletedFilesDelta < (0 - info->GetBlocksInDeletedFiles()))
+ {
+ mBlocksInDeletedFilesDelta = (0 - info->GetBlocksInDeletedFiles());
+ }
+ if(mBlocksInDirectoriesDelta < (0 - info->GetBlocksInDirectories()))
+ {
+ mBlocksInDirectoriesDelta = (0 - info->GetBlocksInDirectories());
+ }
+
+ // Update the usage counts in the store
+ info->ChangeBlocksUsed(mBlocksUsedDelta);
+ info->ChangeBlocksInOldFiles(mBlocksInOldFilesDelta);
+ info->ChangeBlocksInDeletedFiles(mBlocksInDeletedFilesDelta);
+ info->ChangeBlocksInDirectories(mBlocksInDirectoriesDelta);
+
+ // Save the store info back
+ info->ReportChangesTo(*pOldInfo);
+ info->Save();
+
+ // Explicity release the lock (would happen automatically on
+ // going out of scope, included for code clarity)
+ writeLock.ReleaseLock();
+
+ BOX_TRACE("Finished housekeeping on account " <<
+ BOX_FORMAT_ACCOUNT(mAccountID));
+ return true;
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HousekeepStoreAccount::MakeObjectFilename(int64_t, std::string &)
+// Purpose: Generate and place the filename for a given object ID
+// Created: 11/12/03
+//
+// --------------------------------------------------------------------------
+void HousekeepStoreAccount::MakeObjectFilename(int64_t ObjectID, std::string &rFilenameOut)
+{
+ // Delegate to utility function
+ StoreStructure::MakeObjectFilename(ObjectID, mStoreRoot, mStoreDiscSet, rFilenameOut, false /* don't bother ensuring the directory exists */);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HousekeepStoreAccount::ScanDirectory(int64_t)
+// Purpose: Private. Scan a directory for potentially deleteable
+// items, and add them to the list. Returns true if the
+// scan should continue.
+// Created: 11/12/03
+//
+// --------------------------------------------------------------------------
+bool HousekeepStoreAccount::ScanDirectory(int64_t ObjectID)
+{
+#ifndef WIN32
+ if((--mCountUntilNextInterprocessMsgCheck) <= 0)
+ {
+ mCountUntilNextInterprocessMsgCheck =
+ POLL_INTERPROCESS_MSG_CHECK_FREQUENCY;
+
+ // Check for having to stop
+ // Include account ID here as the specified account is locked
+ if(mpHousekeepingCallback && mpHousekeepingCallback->CheckForInterProcessMsg(mAccountID))
+ {
+ // Need to abort now
+ return false;
+ }
+ }
+#endif
+
+ // Get the filename
+ std::string objectFilename;
+ MakeObjectFilename(ObjectID, objectFilename);
+
+ // Open it.
+ std::auto_ptr<RaidFileRead> dirStream(RaidFileRead::Open(mStoreDiscSet,
+ objectFilename));
+
+ // Add the size of the directory on disc to the size being calculated
+ int64_t originalDirSizeInBlocks = dirStream->GetDiscUsageInBlocks();
+ mBlocksInDirectories += originalDirSizeInBlocks;
+ mBlocksUsed += originalDirSizeInBlocks;
+
+ // Read the directory in
+ BackupStoreDirectory dir;
+ BufferedStream buf(*dirStream);
+ dir.ReadFromStream(buf, IOStream::TimeOutInfinite);
+ dirStream->Close();
+
+ // Is it empty?
+ if(dir.GetNumberOfEntries() == 0)
+ {
+ // Add it to the list of directories to potentially delete
+ mEmptyDirectories.push_back(dir.GetObjectID());
+ }
+
+ // Calculate reference counts first, before we start requesting
+ // files to be deleted.
+ // BLOCK
+ {
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+
+ while((en = i.Next()) != 0)
+ {
+ // This directory references this object
+ if (mNewRefCounts.size() <= en->GetObjectID())
+ {
+ mNewRefCounts.resize(en->GetObjectID() + 1, 0);
+ }
+ mNewRefCounts[en->GetObjectID()]++;
+ }
+ }
+
+ // BLOCK
+ {
+ // Remove any files which are marked for removal as soon
+ // as they become old or deleted.
+ bool deletedSomething = false;
+ do
+ {
+ // Iterate through the directory
+ deletedSomething = false;
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next(BackupStoreDirectory::Entry::Flags_File)) != 0)
+ {
+ int16_t enFlags = en->GetFlags();
+ if((enFlags & BackupStoreDirectory::Entry::Flags_RemoveASAP) != 0
+ && (enFlags & (BackupStoreDirectory::Entry::Flags_Deleted | BackupStoreDirectory::Entry::Flags_OldVersion)) != 0)
+ {
+ // Delete this immediately.
+ DeleteFile(ObjectID, en->GetObjectID(), dir, objectFilename, originalDirSizeInBlocks);
+
+ // flag as having done something
+ deletedSomething = true;
+
+ // Must start the loop from the beginning again, as iterator is now
+ // probably invalid.
+ break;
+ }
+ }
+ } while(deletedSomething);
+ }
+
+ // BLOCK
+ {
+ // Add files to the list of potential deletions
+
+ // map to count the distance from the mark
+ typedef std::pair<std::string, int32_t> version_t;
+ std::map<version_t, int32_t> markVersionAges;
+ // map of pair (filename, mark number) -> version age
+
+ // NOTE: use a reverse iterator to allow the distance from mark stuff to work
+ BackupStoreDirectory::ReverseIterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+
+ while((en = i.Next(BackupStoreDirectory::Entry::Flags_File)) != 0)
+ {
+ // Update recalculated usage sizes
+ int16_t enFlags = en->GetFlags();
+ int64_t enSizeInBlocks = en->GetSizeInBlocks();
+ mBlocksUsed += enSizeInBlocks;
+ if(enFlags & BackupStoreDirectory::Entry::Flags_OldVersion) mBlocksInOldFiles += enSizeInBlocks;
+ if(enFlags & BackupStoreDirectory::Entry::Flags_Deleted) mBlocksInDeletedFiles += enSizeInBlocks;
+
+ // Work out ages of this version from the last mark
+ int32_t enVersionAge = 0;
+ std::map<version_t, int32_t>::iterator enVersionAgeI(
+ markVersionAges.find(
+ version_t(en->GetName().GetEncodedFilename(),
+ en->GetMarkNumber())));
+ if(enVersionAgeI != markVersionAges.end())
+ {
+ enVersionAge = enVersionAgeI->second + 1;
+ enVersionAgeI->second = enVersionAge;
+ }
+ else
+ {
+ markVersionAges[version_t(en->GetName().GetEncodedFilename(), en->GetMarkNumber())] = enVersionAge;
+ }
+ // enVersionAge is now the age of this version.
+
+ // Potentially add it to the list if it's deleted, if it's an old version or deleted
+ if((enFlags & (BackupStoreDirectory::Entry::Flags_Deleted | BackupStoreDirectory::Entry::Flags_OldVersion)) != 0)
+ {
+ // Is deleted / old version.
+ DelEn d;
+ d.mObjectID = en->GetObjectID();
+ d.mInDirectory = ObjectID;
+ d.mSizeInBlocks = en->GetSizeInBlocks();
+ d.mMarkNumber = en->GetMarkNumber();
+ d.mVersionAgeWithinMark = enVersionAge;
+ d.mIsFlagDeleted = (enFlags &
+ BackupStoreDirectory::Entry::Flags_Deleted)
+ ? true : false;
+
+ // Add it to the list
+ mPotentialDeletions.insert(d);
+
+ // Update various counts
+ mPotentialDeletionsTotalSize += d.mSizeInBlocks;
+ if(d.mSizeInBlocks > mMaxSizeInPotentialDeletions) mMaxSizeInPotentialDeletions = d.mSizeInBlocks;
+
+ // Too much in the list of potential deletions?
+ // (check against the deletion target + the max size in deletions, so that we never delete things
+ // and take the total size below the deletion size target)
+ if(mPotentialDeletionsTotalSize > (mDeletionSizeTarget + mMaxSizeInPotentialDeletions))
+ {
+ int64_t sizeToRemove = mPotentialDeletionsTotalSize - (mDeletionSizeTarget + mMaxSizeInPotentialDeletions);
+ bool recalcMaxSize = false;
+
+ while(sizeToRemove > 0)
+ {
+ // Make iterator for the last element, while checking that there's something there in the first place.
+ std::set<DelEn, DelEnCompare>::iterator i(mPotentialDeletions.end());
+ if(i != mPotentialDeletions.begin())
+ {
+ // Nothing left in set
+ break;
+ }
+ // Make this into an iterator pointing to the last element in the set
+ --i;
+
+ // Delete this one?
+ if(sizeToRemove > i->mSizeInBlocks)
+ {
+ sizeToRemove -= i->mSizeInBlocks;
+ if(i->mSizeInBlocks >= mMaxSizeInPotentialDeletions)
+ {
+ // Will need to recalculate the maximum size now, because we've just deleted that element
+ recalcMaxSize = true;
+ }
+ mPotentialDeletions.erase(i);
+ }
+ else
+ {
+ // Over the size to remove, so stop now
+ break;
+ }
+ }
+
+ if(recalcMaxSize)
+ {
+ // Because an object which was the maximum size recorded was deleted from the set
+ // it's necessary to recalculate this maximum.
+ mMaxSizeInPotentialDeletions = 0;
+ std::set<DelEn, DelEnCompare>::const_iterator i(mPotentialDeletions.begin());
+ for(; i != mPotentialDeletions.end(); ++i)
+ {
+ if(i->mSizeInBlocks > mMaxSizeInPotentialDeletions)
+ {
+ mMaxSizeInPotentialDeletions = i->mSizeInBlocks;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ {
+ // Recurse into subdirectories
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next(BackupStoreDirectory::Entry::Flags_Dir)) != 0)
+ {
+ // Next level
+ ASSERT((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) == BackupStoreDirectory::Entry::Flags_Dir);
+
+ if(!ScanDirectory(en->GetObjectID()))
+ {
+ // Halting operation
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HousekeepStoreAccount::DelEnCompare::operator()(const HousekeepStoreAccount::DelEn &, const HousekeepStoreAccount::DelEnd &)
+// Purpose: Comparison function for set
+// Created: 11/12/03
+//
+// --------------------------------------------------------------------------
+bool HousekeepStoreAccount::DelEnCompare::operator()(const HousekeepStoreAccount::DelEn &x, const HousekeepStoreAccount::DelEn &y)
+{
+ // STL spec says this:
+ // A Strict Weak Ordering is a Binary Predicate that compares two objects, returning true if the first precedes the second.
+
+ // The sort order here is intended to preserve the entries of most value, that is, the newest objects
+ // which are on a mark boundary.
+
+ // Reverse order age, so oldest goes first
+ if(x.mVersionAgeWithinMark > y.mVersionAgeWithinMark)
+ {
+ return true;
+ }
+ else if(x.mVersionAgeWithinMark < y.mVersionAgeWithinMark)
+ {
+ return false;
+ }
+
+ // but mark number in ascending order, so that the oldest marks are deleted first
+ if(x.mMarkNumber < y.mMarkNumber)
+ {
+ return true;
+ }
+ else if(x.mMarkNumber > y.mMarkNumber)
+ {
+ return false;
+ }
+
+ // Just compare object ID now to put the oldest objects first
+ return x.mObjectID < y.mObjectID;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HousekeepStoreAccount::DeleteFiles()
+// Purpose: Delete the files targeted for deletion, returning
+// true if the operation was interrupted
+// Created: 15/12/03
+//
+// --------------------------------------------------------------------------
+bool HousekeepStoreAccount::DeleteFiles()
+{
+ // Only delete files if the deletion target is greater than zero
+ // (otherwise we delete one file each time round, which gradually deletes the old versions)
+ if(mDeletionSizeTarget <= 0)
+ {
+ // Not interrupted
+ return false;
+ }
+
+ // Iterate through the set of potential deletions, until enough has been deleted.
+ // (there is likely to be more in the set than should be actually deleted).
+ for(std::set<DelEn, DelEnCompare>::iterator i(mPotentialDeletions.begin()); i != mPotentialDeletions.end(); ++i)
+ {
+#ifndef WIN32
+ if((--mCountUntilNextInterprocessMsgCheck) <= 0)
+ {
+ mCountUntilNextInterprocessMsgCheck = POLL_INTERPROCESS_MSG_CHECK_FREQUENCY;
+ // Check for having to stop
+ if(mpHousekeepingCallback && mpHousekeepingCallback->CheckForInterProcessMsg(mAccountID)) // include account ID here as the specified account is now locked
+ {
+ // Need to abort now
+ return true;
+ }
+ }
+#endif
+
+ // Load up the directory it's in
+ // Get the filename
+ std::string dirFilename;
+ BackupStoreDirectory dir;
+ int64_t dirSizeInBlocksOrig = 0;
+ {
+ MakeObjectFilename(i->mInDirectory, dirFilename);
+ std::auto_ptr<RaidFileRead> dirStream(RaidFileRead::Open(mStoreDiscSet, dirFilename));
+ dirSizeInBlocksOrig = dirStream->GetDiscUsageInBlocks();
+ dir.ReadFromStream(*dirStream, IOStream::TimeOutInfinite);
+ }
+
+ // Delete the file
+ DeleteFile(i->mInDirectory, i->mObjectID, dir, dirFilename, dirSizeInBlocksOrig);
+ BOX_INFO("Housekeeping removed " <<
+ (i->mIsFlagDeleted ? "deleted" : "old") <<
+ " file " << BOX_FORMAT_OBJECTID(i->mObjectID) <<
+ " from dir " << BOX_FORMAT_OBJECTID(i->mInDirectory));
+
+ // Stop if the deletion target has been matched or exceeded
+ // (checking here rather than at the beginning will tend to reduce the
+ // space to slightly less than the soft limit, which will allow the backup
+ // client to start uploading files again)
+ if((0 - mBlocksUsedDelta) >= mDeletionSizeTarget)
+ {
+ break;
+ }
+ }
+
+ return false;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HousekeepStoreAccount::DeleteFile(int64_t, int64_t,
+// BackupStoreDirectory &, const std::string &, int64_t)
+// Purpose: Delete a file. Takes the directory already loaded
+// in and the filename, for efficiency in both the
+// usage scenarios.
+// Created: 15/7/04
+//
+// --------------------------------------------------------------------------
+void HousekeepStoreAccount::DeleteFile(int64_t InDirectory, int64_t ObjectID, BackupStoreDirectory &rDirectory, const std::string &rDirectoryFilename, int64_t OriginalDirSizeInBlocks)
+{
+ // Find the entry inside the directory
+ bool wasDeleted = false;
+ bool wasOldVersion = false;
+ int64_t deletedFileSizeInBlocks = 0;
+ // A pointer to an object which requires committing if the directory save goes OK
+ std::auto_ptr<RaidFileWrite> padjustedEntry;
+ // BLOCK
+ {
+ BackupStoreDirectory::Entry *pentry = rDirectory.FindEntryByID(ObjectID);
+ if(pentry == 0)
+ {
+ BOX_ERROR("Housekeeping on account " <<
+ BOX_FORMAT_ACCOUNT(mAccountID) << " "
+ "found error: object " <<
+ BOX_FORMAT_OBJECTID(ObjectID) << " "
+ "not found in dir " <<
+ BOX_FORMAT_OBJECTID(InDirectory) << ", "
+ "indicates logic error/corruption? Run "
+ "bbstoreaccounts check <accid> fix");
+ return;
+ }
+
+ // Record the flags it's got set
+ wasDeleted = ((pentry->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) != 0);
+ wasOldVersion = ((pentry->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) != 0);
+ // Check this should be deleted
+ if(!wasDeleted && !wasOldVersion)
+ {
+ // Things changed size we were last around
+ return;
+ }
+
+ // Record size
+ deletedFileSizeInBlocks = pentry->GetSizeInBlocks();
+
+ // If the entry is involved in a chain of patches, it needs to be handled
+ // a bit more carefully.
+ if(pentry->GetDependsNewer() != 0 && pentry->GetDependsOlder() == 0)
+ {
+ // This entry is a patch from a newer entry. Just need to update the info on that entry.
+ BackupStoreDirectory::Entry *pnewer = rDirectory.FindEntryByID(pentry->GetDependsNewer());
+ if(pnewer == 0 || pnewer->GetDependsOlder() != ObjectID)
+ {
+ THROW_EXCEPTION(BackupStoreException, PatchChainInfoBadInDirectory);
+ }
+ // Change the info in the newer entry so that this no longer points to this entry
+ pnewer->SetDependsOlder(0);
+ }
+ else if(pentry->GetDependsOlder() != 0)
+ {
+ BackupStoreDirectory::Entry *polder = rDirectory.FindEntryByID(pentry->GetDependsOlder());
+ if(pentry->GetDependsNewer() == 0)
+ {
+ // There exists an older version which depends on this one. Need to combine the two over that one.
+
+ // Adjust the other entry in the directory
+ if(polder == 0 || polder->GetDependsNewer() != ObjectID)
+ {
+ THROW_EXCEPTION(BackupStoreException, PatchChainInfoBadInDirectory);
+ }
+ // Change the info in the older entry so that this no longer points to this entry
+ polder->SetDependsNewer(0);
+ }
+ else
+ {
+ // This entry is in the middle of a chain, and two patches need combining.
+
+ // First, adjust the directory entries
+ BackupStoreDirectory::Entry *pnewer = rDirectory.FindEntryByID(pentry->GetDependsNewer());
+ if(pnewer == 0 || pnewer->GetDependsOlder() != ObjectID
+ || polder == 0 || polder->GetDependsNewer() != ObjectID)
+ {
+ THROW_EXCEPTION(BackupStoreException, PatchChainInfoBadInDirectory);
+ }
+ // Remove the middle entry from the linked list by simply using the values from this entry
+ pnewer->SetDependsOlder(pentry->GetDependsOlder());
+ polder->SetDependsNewer(pentry->GetDependsNewer());
+ }
+
+ // COMMON CODE to both cases
+
+ // Generate the filename of the older version
+ std::string objFilenameOlder;
+ MakeObjectFilename(pentry->GetDependsOlder(), objFilenameOlder);
+ // Open it twice (it's the diff)
+ std::auto_ptr<RaidFileRead> pdiff(RaidFileRead::Open(mStoreDiscSet, objFilenameOlder));
+ std::auto_ptr<RaidFileRead> pdiff2(RaidFileRead::Open(mStoreDiscSet, objFilenameOlder));
+ // Open this file
+ std::string objFilename;
+ MakeObjectFilename(ObjectID, objFilename);
+ std::auto_ptr<RaidFileRead> pobjectBeingDeleted(RaidFileRead::Open(mStoreDiscSet, objFilename));
+ // And open a write file to overwrite the other directory entry
+ padjustedEntry.reset(new RaidFileWrite(mStoreDiscSet,
+ objFilenameOlder, mNewRefCounts[ObjectID]));
+ padjustedEntry->Open(true /* allow overwriting */);
+
+ if(pentry->GetDependsNewer() == 0)
+ {
+ // There exists an older version which depends on this one. Need to combine the two over that one.
+ BackupStoreFile::CombineFile(*pdiff, *pdiff2, *pobjectBeingDeleted, *padjustedEntry);
+ }
+ else
+ {
+ // This entry is in the middle of a chain, and two patches need combining.
+ BackupStoreFile::CombineDiffs(*pobjectBeingDeleted, *pdiff, *pdiff2, *padjustedEntry);
+ }
+ // The file will be committed later when the directory is safely commited.
+
+ // Work out the adjusted size
+ int64_t newSize = padjustedEntry->GetDiscUsageInBlocks();
+ int64_t sizeDelta = newSize - polder->GetSizeInBlocks();
+ mBlocksUsedDelta += sizeDelta;
+ if((polder->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) != 0)
+ {
+ mBlocksInDeletedFilesDelta += sizeDelta;
+ }
+ if((polder->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) != 0)
+ {
+ mBlocksInOldFilesDelta += sizeDelta;
+ }
+ polder->SetSizeInBlocks(newSize);
+ }
+
+ // pentry no longer valid
+ }
+
+ // Delete it from the directory
+ rDirectory.DeleteEntry(ObjectID);
+
+ // Save directory back to disc
+ // BLOCK
+ int64_t dirRevisedSize = 0;
+ {
+ RaidFileWrite writeDir(mStoreDiscSet, rDirectoryFilename,
+ mNewRefCounts[InDirectory]);
+ writeDir.Open(true /* allow overwriting */);
+ rDirectory.WriteToStream(writeDir);
+
+ // get the disc usage (must do this before commiting it)
+ dirRevisedSize = writeDir.GetDiscUsageInBlocks();
+
+ // Commit directory
+ writeDir.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY);
+
+ // adjust usage counts for this directory
+ if(dirRevisedSize > 0)
+ {
+ int64_t adjust = dirRevisedSize - OriginalDirSizeInBlocks;
+ mBlocksUsedDelta += adjust;
+ mBlocksInDirectoriesDelta += adjust;
+ }
+ }
+
+ // Commit any new adjusted entry
+ if(padjustedEntry.get() != 0)
+ {
+ padjustedEntry->Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY);
+ padjustedEntry.reset(); // delete it now
+ }
+
+ // Drop reference count by one. If it reaches zero, delete the file.
+ if(--mNewRefCounts[ObjectID] == 0)
+ {
+ // Delete from disc
+ BOX_TRACE("Removing unreferenced object " <<
+ BOX_FORMAT_OBJECTID(ObjectID));
+ std::string objFilename;
+ MakeObjectFilename(ObjectID, objFilename);
+ RaidFileWrite del(mStoreDiscSet, objFilename,
+ mNewRefCounts[ObjectID]);
+ del.Delete();
+ }
+ else
+ {
+ BOX_TRACE("Preserving object " <<
+ BOX_FORMAT_OBJECTID(ObjectID) << " with " <<
+ mNewRefCounts[ObjectID] << " references");
+ }
+
+ // Adjust counts for the file
+ ++mFilesDeleted;
+ mBlocksUsedDelta -= deletedFileSizeInBlocks;
+ if(wasDeleted) mBlocksInDeletedFilesDelta -= deletedFileSizeInBlocks;
+ if(wasOldVersion) mBlocksInOldFilesDelta -= deletedFileSizeInBlocks;
+
+ // Delete the directory?
+ // Do this if... dir has zero entries, and is marked as deleted in it's containing directory
+ if(rDirectory.GetNumberOfEntries() == 0)
+ {
+ // Candidate for deletion
+ mEmptyDirectories.push_back(InDirectory);
+ }
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: HousekeepStoreAccount::DeleteEmptyDirectories()
+// Purpose: Remove any empty directories which are also marked as deleted in their containing directory,
+// returning true if the opertaion was interrupted
+// Created: 15/12/03
+//
+// --------------------------------------------------------------------------
+bool HousekeepStoreAccount::DeleteEmptyDirectories()
+{
+ while(mEmptyDirectories.size() > 0)
+ {
+ std::vector<int64_t> toExamine;
+
+ // Go through list
+ for(std::vector<int64_t>::const_iterator i(mEmptyDirectories.begin()); i != mEmptyDirectories.end(); ++i)
+ {
+#ifndef WIN32
+ if((--mCountUntilNextInterprocessMsgCheck) <= 0)
+ {
+ mCountUntilNextInterprocessMsgCheck = POLL_INTERPROCESS_MSG_CHECK_FREQUENCY;
+ // Check for having to stop
+ if(mpHousekeepingCallback && mpHousekeepingCallback->CheckForInterProcessMsg(mAccountID)) // include account ID here as the specified account is now locked
+ {
+ // Need to abort now
+ return true;
+ }
+ }
+#endif
+
+ // Do not delete the root directory
+ if(*i == BACKUPSTORE_ROOT_DIRECTORY_ID)
+ {
+ continue;
+ }
+
+ DeleteEmptyDirectory(*i, toExamine);
+ }
+
+ // Remove contents of empty directories
+ mEmptyDirectories.clear();
+ // Swap in new, so it's examined next time round
+ mEmptyDirectories.swap(toExamine);
+ }
+
+ // Not interrupted
+ return false;
+}
+
+void HousekeepStoreAccount::DeleteEmptyDirectory(int64_t dirId,
+ std::vector<int64_t>& rToExamine)
+{
+ // Load up the directory to potentially delete
+ std::string dirFilename;
+ BackupStoreDirectory dir;
+ int64_t dirSizeInBlocks = 0;
+
+ // BLOCK
+ {
+ MakeObjectFilename(dirId, dirFilename);
+ // Check it actually exists (just in case it gets
+ // added twice to the list)
+ if(!RaidFileRead::FileExists(mStoreDiscSet, dirFilename))
+ {
+ // doesn't exist, next!
+ return;
+ }
+ // load
+ std::auto_ptr<RaidFileRead> dirStream(
+ RaidFileRead::Open(mStoreDiscSet, dirFilename));
+ dirSizeInBlocks = dirStream->GetDiscUsageInBlocks();
+ dir.ReadFromStream(*dirStream, IOStream::TimeOutInfinite);
+ }
+
+ // Make sure this directory is actually empty
+ if(dir.GetNumberOfEntries() != 0)
+ {
+ // Not actually empty, try next one
+ return;
+ }
+
+ // Candidate for deletion... open containing directory
+ std::string containingDirFilename;
+ BackupStoreDirectory containingDir;
+ int64_t containingDirSizeInBlocksOrig = 0;
+ {
+ MakeObjectFilename(dir.GetContainerID(), containingDirFilename);
+ std::auto_ptr<RaidFileRead> containingDirStream(
+ RaidFileRead::Open(mStoreDiscSet,
+ containingDirFilename));
+ containingDirSizeInBlocksOrig =
+ containingDirStream->GetDiscUsageInBlocks();
+ containingDir.ReadFromStream(*containingDirStream,
+ IOStream::TimeOutInfinite);
+ }
+
+ // Find the entry
+ BackupStoreDirectory::Entry *pdirentry =
+ containingDir.FindEntryByID(dir.GetObjectID());
+ if((pdirentry != 0) && ((pdirentry->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) != 0))
+ {
+ // Should be deleted
+ containingDir.DeleteEntry(dir.GetObjectID());
+
+ // Is the containing dir now a candidate for deletion?
+ if(containingDir.GetNumberOfEntries() == 0)
+ {
+ rToExamine.push_back(containingDir.GetObjectID());
+ }
+
+ // Write revised parent directory
+ RaidFileWrite writeDir(mStoreDiscSet, containingDirFilename,
+ mNewRefCounts[containingDir.GetObjectID()]);
+ writeDir.Open(true /* allow overwriting */);
+ containingDir.WriteToStream(writeDir);
+
+ // get the disc usage (must do this before commiting it)
+ int64_t dirSize = writeDir.GetDiscUsageInBlocks();
+
+ // Commit directory
+ writeDir.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY);
+
+ // adjust usage counts for this directory
+ if(dirSize > 0)
+ {
+ int64_t adjust = dirSize - containingDirSizeInBlocksOrig;
+ mBlocksUsedDelta += adjust;
+ mBlocksInDirectoriesDelta += adjust;
+ }
+
+
+ if (--mNewRefCounts[dir.GetObjectID()] == 0)
+ {
+ // Delete the directory itself
+ RaidFileWrite del(mStoreDiscSet, dirFilename,
+ mNewRefCounts[dir.GetObjectID()]);
+ del.Delete();
+ }
+
+ BOX_INFO("Housekeeping removed empty deleted dir " <<
+ BOX_FORMAT_OBJECTID(dirId));
+
+ // And adjust usage counts for the directory that's
+ // just been deleted
+ mBlocksUsedDelta -= dirSizeInBlocks;
+ mBlocksInDirectoriesDelta -= dirSizeInBlocks;
+
+ // Update count
+ ++mEmptyDirectoriesDeleted;
+ }
+}
+
diff --git a/lib/backupstore/HousekeepStoreAccount.h b/lib/backupstore/HousekeepStoreAccount.h
new file mode 100644
index 00000000..cfa05a2e
--- /dev/null
+++ b/lib/backupstore/HousekeepStoreAccount.h
@@ -0,0 +1,113 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: HousekeepStoreAccount.h
+// Purpose: Action class to perform housekeeping on a store account
+// Created: 11/12/03
+//
+// --------------------------------------------------------------------------
+
+#ifndef HOUSEKEEPSTOREACCOUNT__H
+#define HOUSEKEEPSTOREACCOUNT__H
+
+#include <string>
+#include <set>
+#include <vector>
+
+class BackupStoreDirectory;
+
+class HousekeepingCallback
+{
+ public:
+ virtual ~HousekeepingCallback() {}
+ virtual bool CheckForInterProcessMsg(int AccountNum = 0, int MaximumWaitTime = 0) = 0;
+};
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: HousekeepStoreAccount
+// Purpose: Action class to perform housekeeping on a store account
+// Created: 11/12/03
+//
+// --------------------------------------------------------------------------
+class HousekeepStoreAccount
+{
+public:
+ HousekeepStoreAccount(int AccountID, const std::string &rStoreRoot,
+ int StoreDiscSet, HousekeepingCallback* pHousekeepingCallback);
+ ~HousekeepStoreAccount();
+
+ bool DoHousekeeping(bool KeepTryingForever = false);
+ int GetRefCountsAdjusted() { return mRefCountsAdjusted; }
+
+private:
+ // utility functions
+ void MakeObjectFilename(int64_t ObjectID, std::string &rFilenameOut);
+
+ bool ScanDirectory(int64_t ObjectID);
+ bool DeleteFiles();
+ bool DeleteEmptyDirectories();
+ void DeleteEmptyDirectory(int64_t dirId,
+ std::vector<int64_t>& rToExamine);
+ void DeleteFile(int64_t InDirectory, int64_t ObjectID, BackupStoreDirectory &rDirectory, const std::string &rDirectoryFilename, int64_t OriginalDirSizeInBlocks);
+
+private:
+ typedef struct
+ {
+ int64_t mObjectID;
+ int64_t mInDirectory;
+ int64_t mSizeInBlocks;
+ int32_t mMarkNumber;
+ int32_t mVersionAgeWithinMark; // 0 == current, 1 latest old version, etc
+ bool mIsFlagDeleted; // false for files flagged "Old"
+ } DelEn;
+
+ struct DelEnCompare
+ {
+ bool operator()(const DelEn &x, const DelEn &y);
+ };
+
+ int mAccountID;
+ std::string mStoreRoot;
+ int mStoreDiscSet;
+ HousekeepingCallback* mpHousekeepingCallback;
+
+ int64_t mDeletionSizeTarget;
+
+ std::set<DelEn, DelEnCompare> mPotentialDeletions;
+ int64_t mPotentialDeletionsTotalSize;
+ int64_t mMaxSizeInPotentialDeletions;
+
+ // List of directories which are empty, and might be good for deleting
+ std::vector<int64_t> mEmptyDirectories;
+
+ // The re-calculated blocks used stats
+ int64_t mBlocksUsed;
+ int64_t mBlocksInOldFiles;
+ int64_t mBlocksInDeletedFiles;
+ int64_t mBlocksInDirectories;
+
+ // Deltas from deletion
+ int64_t mBlocksUsedDelta;
+ int64_t mBlocksInOldFilesDelta;
+ int64_t mBlocksInDeletedFilesDelta;
+ int64_t mBlocksInDirectoriesDelta;
+
+ // Deletion count
+ int64_t mFilesDeleted;
+ int64_t mEmptyDirectoriesDeleted;
+
+ // New reference count list
+ std::vector<uint32_t> mNewRefCounts;
+ bool mSuppressRefCountChangeWarnings;
+ int mRefCountsAdjusted;
+
+ // Poll frequency
+ int mCountUntilNextInterprocessMsgCheck;
+
+ Logging::Tagger mTagWithClientID;
+};
+
+#endif // HOUSEKEEPSTOREACCOUNT__H
+
diff --git a/lib/backupstore/Makefile.extra b/lib/backupstore/Makefile.extra
new file mode 100644
index 00000000..c55fd549
--- /dev/null
+++ b/lib/backupstore/Makefile.extra
@@ -0,0 +1,15 @@
+MAKEPROTOCOL = ../../lib/server/makeprotocol.pl
+
+GEN_CMD = $(MAKEPROTOCOL) backupprotocol.txt
+
+# AUTOGEN SEEDING
+autogen_BackupProtocol.cpp autogen_BackupProtocol.h: $(MAKEPROTOCOL) backupprotocol.txt
+ $(_PERL) $(GEN_CMD)
+
+
+MAKEEXCEPTION = ../../lib/common/makeexception.pl
+
+# AUTOGEN SEEDING
+autogen_BackupStoreException.h autogen_BackupStoreException.cpp: $(MAKEEXCEPTION) BackupStoreException.txt
+ $(_PERL) $(MAKEEXCEPTION) BackupStoreException.txt
+
diff --git a/lib/backupstore/RunStatusProvider.h b/lib/backupstore/RunStatusProvider.h
new file mode 100644
index 00000000..89f361ca
--- /dev/null
+++ b/lib/backupstore/RunStatusProvider.h
@@ -0,0 +1,29 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: RunStatusProvider.h
+// Purpose: Declares the RunStatusProvider interface.
+// Created: 2008/08/14
+//
+// --------------------------------------------------------------------------
+
+#ifndef RUNSTATUSPROVIDER__H
+#define RUNSTATUSPROVIDER__H
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: RunStatusProvider
+// Purpose: Provides a StopRun() method which returns true if
+// the current backup should be halted.
+// Created: 2005/11/15
+//
+// --------------------------------------------------------------------------
+class RunStatusProvider
+{
+ public:
+ virtual ~RunStatusProvider() { }
+ virtual bool StopRun() = 0;
+};
+
+#endif // RUNSTATUSPROVIDER__H
diff --git a/lib/backupstore/backupprotocol.txt b/lib/backupstore/backupprotocol.txt
new file mode 100644
index 00000000..aa987e70
--- /dev/null
+++ b/lib/backupstore/backupprotocol.txt
@@ -0,0 +1,235 @@
+#
+# backup protocol definition
+#
+
+Name Backup
+IdentString Box-Backup:v=C
+ServerContextClass BackupStoreContext BackupStoreContext.h
+
+AddType Filename BackupStoreFilenameClear BackupStoreFilenameClear.h
+
+ImplementLog Server syslog
+ImplementLog Client syslog
+ImplementLog Client file
+
+LogTypeToText Filename "%s" VAR.GetClearFilenameIfPossible("OPAQUE").c_str()
+
+BEGIN_OBJECTS
+
+# -------------------------------------------------------------------------------------
+# Session commands
+# -------------------------------------------------------------------------------------
+
+Error 0 IsError(Type,SubType) Reply
+ int32 Type
+ int32 SubType
+ CONSTANT ErrorType 1000
+ CONSTANT Err_WrongVersion 1
+ CONSTANT Err_NotInRightProtocolPhase 2
+ CONSTANT Err_BadLogin 3
+ CONSTANT Err_CannotLockStoreForWriting 4
+ CONSTANT Err_SessionReadOnly 5
+ CONSTANT Err_FileDoesNotVerify 6
+ CONSTANT Err_DoesNotExist 7
+ CONSTANT Err_DirectoryAlreadyExists 8
+ CONSTANT Err_CannotDeleteRoot 9
+ CONSTANT Err_TargetNameExists 10
+ CONSTANT Err_StorageLimitExceeded 11
+ CONSTANT Err_DiffFromFileDoesNotExist 12
+ CONSTANT Err_DoesNotExistInDirectory 13
+ CONSTANT Err_PatchConsistencyError 14
+ CONSTANT Err_MultiplyReferencedObject 15
+ CONSTANT Err_DisabledAccount 16
+
+Version 1 Command(Version) Reply
+ int32 Version
+
+
+Login 2 Command(LoginConfirmed)
+ int32 ClientID
+ int32 Flags
+ CONSTANT Flags_ReadOnly 1
+
+
+LoginConfirmed 3 Reply
+ int64 ClientStoreMarker
+ int64 BlocksUsed
+ int64 BlocksSoftLimit
+ int64 BlocksHardLimit
+
+
+Finished 4 Command(Finished) Reply EndsConversation
+
+
+# generic success object
+Success 5 Reply
+ int64 ObjectID
+
+
+SetClientStoreMarker 6 Command(Success)
+ int64 ClientStoreMarker
+
+
+# -------------------------------------------------------------------------------------
+# Generic object commands
+# -------------------------------------------------------------------------------------
+
+GetObject 10 Command(Success)
+ int64 ObjectID
+ CONSTANT NoObject 0
+ # reply has stream following, if ObjectID != NoObject
+
+
+MoveObject 11 Command(Success)
+ int64 ObjectID
+ int64 MoveFromDirectory
+ int64 MoveToDirectory
+ int32 Flags
+ Filename NewFilename
+
+ CONSTANT Flags_MoveAllWithSameName 1
+ CONSTANT Flags_AllowMoveOverDeletedObject 2
+
+# consider this an object command as, although it deals with directory entries,
+# it's not specific to either a file or a directory
+
+
+GetObjectName 12 Command(ObjectName)
+ int64 ObjectID
+ int64 ContainingDirectoryID
+ CONSTANT ObjectID_DirectoryOnly 0
+
+ # set ObjectID to ObjectID_DirectoryOnly to only get info on the directory
+
+
+ObjectName 13 Reply
+ int32 NumNameElements
+ int64 ModificationTime
+ int64 AttributesHash
+ int16 Flags
+ # NumNameElements is zero if the object doesn't exist
+ CONSTANT NumNameElements_ObjectDoesntExist 0
+ # a stream of Filename objects follows, if and only if NumNameElements > 0
+
+
+# -------------------------------------------------------------------------------------
+# Directory commands
+# -------------------------------------------------------------------------------------
+
+CreateDirectory 20 Command(Success) StreamWithCommand
+ int64 ContainingDirectoryID
+ int64 AttributesModTime
+ Filename DirectoryName
+ # stream following containing attributes
+
+
+ListDirectory 21 Command(Success)
+ int64 ObjectID
+ int16 FlagsMustBeSet
+ int16 FlagsNotToBeSet
+ bool SendAttributes
+ # make sure these flags are synced with those in BackupStoreDirectory
+ CONSTANT Flags_INCLUDE_EVERYTHING -1
+ CONSTANT Flags_EXCLUDE_NOTHING 0
+ CONSTANT Flags_EXCLUDE_EVERYTHING 15
+ CONSTANT Flags_File 1
+ CONSTANT Flags_Dir 2
+ CONSTANT Flags_Deleted 4
+ CONSTANT Flags_OldVersion 8
+ # make sure this is the same as in BackupStoreConstants.h
+ CONSTANT RootDirectory 1
+
+ # reply has stream following Success object, containing a stored BackupStoreDirectory
+
+
+ChangeDirAttributes 22 Command(Success) StreamWithCommand
+ int64 ObjectID
+ int64 AttributesModTime
+ # stream following containing attributes
+
+
+DeleteDirectory 23 Command(Success)
+ int64 ObjectID
+
+UndeleteDirectory 24 Command(Success)
+ int64 ObjectID
+ # may not have exactly the desired effect if files within in have been deleted before the directory was deleted.
+
+
+# -------------------------------------------------------------------------------------
+# File commands
+# -------------------------------------------------------------------------------------
+
+StoreFile 30 Command(Success) StreamWithCommand
+ int64 DirectoryObjectID
+ int64 ModificationTime
+ int64 AttributesHash
+ int64 DiffFromFileID # 0 if the file is not a diff
+ Filename Filename
+ # then send a stream containing the encoded file
+
+
+GetFile 31 Command(Success)
+ int64 InDirectory
+ int64 ObjectID
+ # error returned if not a file, or does not exist
+ # reply has stream following, containing an encoded file IN STREAM ORDER
+ # (use GetObject to get it in file order)
+
+
+SetReplacementFileAttributes 32 Command(Success) StreamWithCommand
+ int64 InDirectory
+ int64 AttributesHash
+ Filename Filename
+ # stream follows containing attributes
+
+
+DeleteFile 33 Command(Success)
+ int64 InDirectory
+ Filename Filename
+ # will return 0 if the object couldn't be found in the specified directory
+
+
+GetBlockIndexByID 34 Command(Success)
+ int64 ObjectID
+
+ # stream of the block index follows the reply
+ # returns an error if the object didn't exist
+
+
+GetBlockIndexByName 35 Command(Success)
+ int64 InDirectory
+ Filename Filename
+
+ # Success object contains the found ID -- or 0 if the entry wasn't found in the directory
+ # stream of the block index follows the reply if found ID != 0
+
+
+UndeleteFile 36 Command(Success)
+ int64 InDirectory
+ int64 ObjectID
+ # will return 0 if the object couldn't be found in the specified directory
+
+
+# -------------------------------------------------------------------------------------
+# Information commands
+# -------------------------------------------------------------------------------------
+
+GetAccountUsage 40 Command(AccountUsage)
+ # no data members
+
+AccountUsage 41 Reply
+ int64 BlocksUsed
+ int64 BlocksInOldFiles
+ int64 BlocksInDeletedFiles
+ int64 BlocksInDirectories
+ int64 BlocksSoftLimit
+ int64 BlocksHardLimit
+ int32 BlockSize
+
+GetIsAlive 42 Command(IsAlive)
+ # no data members
+
+IsAlive 43 Reply
+ # no data members
+
diff --git a/lib/common/Archive.h b/lib/common/Archive.h
index b70f12c4..139cc5fd 100644
--- a/lib/common/Archive.h
+++ b/lib/common/Archive.h
@@ -45,6 +45,10 @@ public:
{
Write((int) Item);
}
+<<<<<<< HEAD
+=======
+ void WriteExact(uint32_t Item) { Write((int)Item); }
+>>>>>>> 0.12
void Write(int Item)
{
int32_t privItem = htonl(Item);
@@ -55,6 +59,10 @@ public:
int64_t privItem = box_hton64(Item);
mrStream.Write(&privItem, sizeof(privItem));
}
+<<<<<<< HEAD
+=======
+ void WriteExact(uint64_t Item) { Write(Item); }
+>>>>>>> 0.12
void Write(uint64_t Item)
{
uint64_t privItem = box_hton64(Item);
@@ -79,7 +87,11 @@ public:
int privItem;
Read(privItem);
+<<<<<<< HEAD
if (privItem)
+=======
+ if(privItem)
+>>>>>>> 0.12
{
rItemOut = true;
}
@@ -88,6 +100,16 @@ public:
rItemOut = false;
}
}
+<<<<<<< HEAD
+=======
+ void ReadIfPresent(bool &rItemOut, bool ValueIfNotPresent)
+ {
+ int privItem;
+ ReadIfPresent(privItem, ValueIfNotPresent ? 1 : 0);
+ rItemOut = privItem ? true : false;
+ }
+ void ReadExact(uint32_t &rItemOut) { Read((int&)rItemOut); }
+>>>>>>> 0.12
void Read(int &rItemOut)
{
int32_t privItem;
@@ -97,6 +119,28 @@ public:
}
rItemOut = ntohl(privItem);
}
+<<<<<<< HEAD
+=======
+ void ReadIfPresent(int &rItemOut, int ValueIfNotPresent)
+ {
+ int32_t privItem;
+ int bytesRead;
+ if(mrStream.ReadFullBuffer(&privItem, sizeof(privItem), &bytesRead))
+ {
+ rItemOut = ntohl(privItem);
+ }
+ else if(bytesRead == 0)
+ {
+ // item is simply not present
+ rItemOut = ValueIfNotPresent;
+ }
+ else
+ {
+ // bad number of remaining bytes
+ THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead)
+ }
+ }
+>>>>>>> 0.12
void Read(int64_t &rItemOut)
{
int64_t privItem;
@@ -106,6 +150,10 @@ public:
}
rItemOut = box_ntoh64(privItem);
}
+<<<<<<< HEAD
+=======
+ void ReadExact(uint64_t &rItemOut) { Read(rItemOut); }
+>>>>>>> 0.12
void Read(uint64_t &rItemOut)
{
uint64_t privItem;
diff --git a/lib/common/BannerText.h b/lib/common/BannerText.h
index e40224da..ae3ff841 100644
--- a/lib/common/BannerText.h
+++ b/lib/common/BannerText.h
@@ -10,9 +10,19 @@
#ifndef BANNERTEXT__H
#define BANNERTEXT__H
+<<<<<<< HEAD
#define BANNER_TEXT(UtilityName) \
"Box " UtilityName " v" BOX_VERSION ", (c) Ben Summers and " \
"contributors 2003-2010"
+=======
+#ifdef NEED_BOX_VERSION_H
+# include "BoxVersion.h"
+#endif
+
+#define BANNER_TEXT(UtilityName) \
+ "Box " UtilityName " v" BOX_VERSION ", (c) Ben Summers and " \
+ "contributors 2003-2011"
+>>>>>>> 0.12
#endif // BANNERTEXT__H
diff --git a/lib/common/Box.h b/lib/common/Box.h
index 158fab7b..91b4967b 100644
--- a/lib/common/Box.h
+++ b/lib/common/Box.h
@@ -17,6 +17,11 @@
#include "BoxPlatform.h"
+<<<<<<< HEAD
+=======
+#include <memory>
+
+>>>>>>> 0.12
// uncomment this line to enable full memory leak finding on all
// malloc-ed blocks (at least, ones used by the STL)
//#define MEMLEAKFINDER_FULL_MALLOC_MONITORING
@@ -38,7 +43,10 @@
#include "Logging.h"
#ifndef BOX_RELEASE_BUILD
+<<<<<<< HEAD
+=======
+>>>>>>> 0.12
extern bool AssertFailuresToSyslog;
#define ASSERT_FAILS_TO_SYSLOG_ON {AssertFailuresToSyslog = true;}
void BoxDebugAssertFailed(const char *cond, const char *file, int line);
@@ -69,7 +77,10 @@
// Exception names
#define EXCEPTION_CODENAMES_EXTENDED
+<<<<<<< HEAD
+=======
+>>>>>>> 0.12
#else
#define ASSERT_FAILS_TO_SYSLOG_ON
#define ASSERT(cond)
@@ -80,9 +91,26 @@
// Box Backup builds release get extra information for exception logging
#define EXCEPTION_CODENAMES_EXTENDED
#define EXCEPTION_CODENAMES_EXTENDED_WITH_DESCRIPTION
+<<<<<<< HEAD
#endif
+=======
+#endif
+
+#if defined DEBUG_LEAKS
+ #ifdef PLATFORM_DISABLE_MEM_LEAK_TESTING
+ #error Compiling with DEBUG_LEAKS enabled, but not supported on this platform
+ #else
+ #define BOX_MEMORY_LEAK_TESTING
+ #endif
+#elif defined BOX_RELEASE_BUILD
+ #ifndef PLATFORM_DISABLE_MEM_LEAK_TESTING
+ #define BOX_MEMORY_LEAK_TESTING
+ #endif
+#endif // DEBUG_LEAKS || BOX_RELEASE_BUILD
+
+>>>>>>> 0.12
#ifdef BOX_MEMORY_LEAK_TESTING
// Memory leak testing
#include "MemLeakFinder.h"
@@ -103,8 +131,23 @@
#define THROW_EXCEPTION(type, subtype) \
{ \
+<<<<<<< HEAD
if(!HideExceptionMessageGuard::ExceptionsHidden()) \
{ \
+=======
+ if((!HideExceptionMessageGuard::ExceptionsHidden() \
+ && !HideSpecificExceptionGuard::IsHidden( \
+ type::ExceptionType, type::subtype)) \
+ || Logging::Guard::IsGuardingFrom(Log::EVERYTHING)) \
+ { \
+ std::auto_ptr<Logging::Guard> guard; \
+ \
+ if(Logging::Guard::IsGuardingFrom(Log::EVERYTHING)) \
+ { \
+ guard.reset(new Logging::Guard(Log::EVERYTHING)); \
+ } \
+ \
+>>>>>>> 0.12
OPTIONAL_DO_BACKTRACE \
BOX_WARNING("Exception thrown: " \
#type "(" #subtype ") " \
@@ -117,12 +160,32 @@
{ \
std::ostringstream _box_throw_line; \
_box_throw_line << message; \
+<<<<<<< HEAD
if(!HideExceptionMessageGuard::ExceptionsHidden()) \
{ \
OPTIONAL_DO_BACKTRACE \
BOX_WARNING("Exception thrown: " \
#type "(" #subtype ") (" << message << \
") at " __FILE__ "(" << __LINE__ << ")") \
+=======
+ if((!HideExceptionMessageGuard::ExceptionsHidden() \
+ && !HideSpecificExceptionGuard::IsHidden( \
+ type::ExceptionType, type::subtype)) \
+ || Logging::Guard::IsGuardingFrom(Log::EVERYTHING)) \
+ { \
+ std::auto_ptr<Logging::Guard> guard; \
+ \
+ if(Logging::Guard::IsGuardingFrom(Log::EVERYTHING)) \
+ { \
+ guard.reset(new Logging::Guard(Log::EVERYTHING)); \
+ } \
+ \
+ OPTIONAL_DO_BACKTRACE \
+ BOX_WARNING("Exception thrown: " \
+ #type "(" #subtype ") (" << \
+ _box_throw_line.str() << \
+ ") at " __FILE__ ":" << __LINE__) \
+>>>>>>> 0.12
} \
throw type(type::subtype, _box_throw_line.str()); \
}
diff --git a/lib/common/BoxConfig-MSVC.h b/lib/common/BoxConfig-MSVC.h
index bb3ffb30..bfa0dcaf 100644
--- a/lib/common/BoxConfig-MSVC.h
+++ b/lib/common/BoxConfig-MSVC.h
@@ -76,6 +76,12 @@
/* Define to 1 if you have the <execinfo.h> header file. */
/* #undef HAVE_EXECINFO_H */
+<<<<<<< HEAD
+=======
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+>>>>>>> 0.12
/* Define to 1 if you have the `flock' function. */
/* #undef HAVE_FLOCK */
@@ -182,7 +188,11 @@
/* Define to 1 if you have the `setproctitle' function. */
/* #undef HAVE_SETPROCTITLE */
+<<<<<<< HEAD
+=======
+#define HAVE_SETPROCTITLE 1
+>>>>>>> 0.12
/* Define to 1 if you have the `setxattr' function. */
/* #undef HAVE_SETXATTR */
diff --git a/lib/common/BoxException.h b/lib/common/BoxException.h
index a8f5d7a6..ad5aba4f 100644
--- a/lib/common/BoxException.h
+++ b/lib/common/BoxException.h
@@ -29,6 +29,10 @@ public:
virtual unsigned int GetType() const throw() = 0;
virtual unsigned int GetSubType() const throw() = 0;
+<<<<<<< HEAD
+=======
+ virtual const std::string& GetMessage() const = 0;
+>>>>>>> 0.12
private:
};
diff --git a/lib/common/BoxPlatform.h b/lib/common/BoxPlatform.h
index 617aa031..2c7ffcf6 100644
--- a/lib/common/BoxPlatform.h
+++ b/lib/common/BoxPlatform.h
@@ -23,7 +23,11 @@
#ifdef _MSC_VER
#include "BoxConfig-MSVC.h"
+<<<<<<< HEAD
#include "BoxVersion.h"
+=======
+#define NEED_BOX_VERSION_H
+>>>>>>> 0.12
#else
#include "BoxConfig.h"
#endif
@@ -159,7 +163,19 @@
#define INFTIM -1
#endif
+<<<<<<< HEAD
// for Unix compatibility with Windows :-)
+=======
+// Define O_BINARY for Unix compatibility with Windows :-)
+// MSVC 2010 and newer MinGW define this in fcntl.h, which is probably
+// not included by this point, so include it now so that we can detect
+// if we need O_BINARY
+
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif
+
+>>>>>>> 0.12
#ifndef O_BINARY
#define O_BINARY 0
#endif
diff --git a/lib/common/BoxPortsAndFiles.h.in b/lib/common/BoxPortsAndFiles.h.in
index 41bad0ba..55616da7 100644
--- a/lib/common/BoxPortsAndFiles.h.in
+++ b/lib/common/BoxPortsAndFiles.h.in
@@ -32,12 +32,24 @@
#define BOX_GET_DEFAULT_BBSTORED_CONFIG_FILE \
GetDefaultConfigFilePath("bbstored.conf").c_str()
#else
+<<<<<<< HEAD
#define BOX_FILE_BBACKUPD_DEFAULT_CONFIG "@sysconfdir_expanded@/boxbackup/bbackupd.conf"
#define BOX_FILE_RAIDFILE_DEFAULT_CONFIG "@sysconfdir_expanded@/boxbackup/raidfile.conf"
#define BOX_FILE_BBSTORED_DEFAULT_CONFIG "@sysconfdir_expanded@/boxbackup/bbstored.conf"
#define BOX_FILE_BBACKUPD_OLD_CONFIG "@sysconfdir_expanded@/box/bbackupd.conf"
#define BOX_FILE_RAIDFILE_OLD_CONFIG "@sysconfdir_expanded@/box/raidfile.conf"
#define BOX_FILE_BBSTORED_OLD_CONFIG "@sysconfdir_expanded@/box/bbstored.conf"
+=======
+ #define BOX_FILE_BBACKUPD_OLD_CONFIG "@sysconfdir_expanded@/box/bbackupd.conf"
+ #define BOX_FILE_RAIDFILE_OLD_CONFIG "@sysconfdir_expanded@/box/raidfile.conf"
+ #define BOX_FILE_BBSTORED_OLD_CONFIG "@sysconfdir_expanded@/box/bbstored.conf"
+ #define BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE \
+ std::string("@sysconfdir_expanded@/boxbackup/bbackupd.conf")
+ #define BOX_GET_DEFAULT_RAIDFILE_CONFIG_FILE \
+ std::string("@sysconfdir_expanded@/boxbackup/raidfile.conf")
+ #define BOX_GET_DEFAULT_BBSTORED_CONFIG_FILE \
+ std::string("@sysconfdir_expanded@/boxbackup/bbstored.conf")
+>>>>>>> 0.12
#endif
#endif // BOXPORTSANDFILES__H
diff --git a/lib/common/BoxTime.cpp b/lib/common/BoxTime.cpp
index d05c0a6c..ead3410b 100644
--- a/lib/common/BoxTime.cpp
+++ b/lib/common/BoxTime.cpp
@@ -94,3 +94,55 @@ std::string FormatTime(box_time_t time, bool includeDate, bool showMicros)
return buf.str();
}
+<<<<<<< HEAD
+=======
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: ShortSleep(box_time_t duration)
+// Purpose: Sleeps for the specified duration as accurately
+// and efficiently as possible.
+// Created: 2011/01/11
+//
+// --------------------------------------------------------------------------
+
+void ShortSleep(box_time_t duration, bool logDuration)
+{
+ if(logDuration)
+ {
+ BOX_TRACE("Sleeping for " << BoxTimeToMicroSeconds(duration) <<
+ " microseconds");
+ }
+
+#ifdef WIN32
+ Sleep(BoxTimeToMilliSeconds(duration));
+#else
+ struct timespec ts;
+ memset(&ts, 0, sizeof(ts));
+ ts.tv_sec = duration / MICRO_SEC_IN_SEC;
+ ts.tv_nsec = duration % MICRO_SEC_IN_SEC;
+
+ while (nanosleep(&ts, &ts) == -1 && errno == EINTR)
+ {
+ // FIXME evil hack for OSX, where ts.tv_sec contains
+ // a negative number interpreted as unsigned 32-bit
+ // when nanosleep() returns later than expected.
+
+ int32_t secs = (int32_t) ts.tv_sec;
+ int64_t remain_ns = ((int64_t)secs * 1000000000) + ts.tv_nsec;
+
+ if (remain_ns < 0)
+ {
+ BOX_WARNING("nanosleep interrupted " <<
+ ((float)(0 - remain_ns) / 1000000000) <<
+ " secs late");
+ return;
+ }
+
+ BOX_TRACE("nanosleep interrupted with " << remain_ns <<
+ " nanosecs remaining, sleeping again");
+ }
+#endif
+}
+
+>>>>>>> 0.12
diff --git a/lib/common/BoxTime.h b/lib/common/BoxTime.h
index 6681bbbd..d688ff10 100644
--- a/lib/common/BoxTime.h
+++ b/lib/common/BoxTime.h
@@ -11,15 +11,25 @@
#define BOXTIME__H
// Time is presented as an unsigned 64 bit integer, in microseconds
+<<<<<<< HEAD
typedef uint64_t box_time_t;
+=======
+typedef int64_t box_time_t;
+>>>>>>> 0.12
#define NANO_SEC_IN_SEC (1000000000LL)
#define NANO_SEC_IN_USEC (1000)
#define NANO_SEC_IN_USEC_LL (1000LL)
#define MICRO_SEC_IN_SEC (1000000)
#define MICRO_SEC_IN_SEC_LL (1000000LL)
+<<<<<<< HEAD
#define MILLI_SEC_IN_NANO_SEC (1000)
#define MILLI_SEC_IN_NANO_SEC_LL (1000LL)
+=======
+#define MICRO_SEC_IN_MILLI_SEC (1000)
+#define MILLI_SEC_IN_SEC (1000)
+#define MILLI_SEC_IN_SEC_LL (1000LL)
+>>>>>>> 0.12
box_time_t GetCurrentBoxTime();
@@ -27,13 +37,24 @@ inline box_time_t SecondsToBoxTime(time_t Seconds)
{
return ((box_time_t)Seconds * MICRO_SEC_IN_SEC_LL);
}
+<<<<<<< HEAD
+=======
+inline uint64_t MilliSecondsToBoxTime(int64_t milliseconds)
+{
+ return ((box_time_t)milliseconds * 1000);
+}
+>>>>>>> 0.12
inline time_t BoxTimeToSeconds(box_time_t Time)
{
return Time / MICRO_SEC_IN_SEC_LL;
}
inline uint64_t BoxTimeToMilliSeconds(box_time_t Time)
{
+<<<<<<< HEAD
return Time / MILLI_SEC_IN_NANO_SEC_LL;
+=======
+ return Time / MILLI_SEC_IN_SEC_LL;
+>>>>>>> 0.12
}
inline uint64_t BoxTimeToMicroSeconds(box_time_t Time)
{
@@ -43,4 +64,9 @@ inline uint64_t BoxTimeToMicroSeconds(box_time_t Time)
std::string FormatTime(box_time_t time, bool includeDate,
bool showMicros = false);
+<<<<<<< HEAD
+=======
+void ShortSleep(box_time_t duration, bool logDuration);
+
+>>>>>>> 0.12
#endif // BOXTIME__H
diff --git a/lib/common/CommonException.txt b/lib/common/CommonException.txt
index b2819886..885a9197 100644
--- a/lib/common/CommonException.txt
+++ b/lib/common/CommonException.txt
@@ -45,3 +45,16 @@ IOStreamGetLineNotEnoughDataToIgnore 37 Bad value passed to IOStreamGetLine::Ign
TempDirPathTooLong 38 Your temporary directory path is too long. Check the TMP and TEMP environment variables.
ArchiveBlockIncompleteRead 39 The Store Object Info File is too short or corrupted, and will be rewritten automatically when the next backup completes.
AccessDenied 40 Access to the file or directory was denied. Please check the permissions.
+<<<<<<< HEAD
+=======
+DatabaseOpenFailed 41 Failed to open the database file
+DatabaseReadFailed 42 Failed to read a record from the database file
+DatabaseWriteFailed 43 Failed to write a record from the database file
+DatabaseDeleteFailed 44 Failed to delete a record from the database file
+DatabaseCloseFailed 45 Failed to close the database file
+DatabaseRecordNotFound 46 The database does not contain the expected record
+DatabaseRecordAlreadyExists 47 The database already contains a record with this key, which was not expected
+DatabaseRecordBadSize 48 The database contains a record with an invalid size
+DatabaseIterateFailed 49 Failed to iterate over the database keys
+ReferenceNotFound 50 The database does not contain an expected reference
+>>>>>>> 0.12
diff --git a/lib/common/Database.h b/lib/common/Database.h
new file mode 100644
index 00000000..94239ab8
--- /dev/null
+++ b/lib/common/Database.h
@@ -0,0 +1,31 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: Database.h
+// Purpose: Database (QDBM) utility macros
+// Created: 2010/03/10
+//
+// --------------------------------------------------------------------------
+
+#ifndef DATABASE__H
+#define DATABASE__H
+
+#include "Logging.h"
+
+#define BOX_DBM_MESSAGE(stuff) stuff << " (qdbm): " << dperrmsg(dpecode)
+
+#define BOX_LOG_DBM_ERROR(stuff) \
+ BOX_ERROR(BOX_DBM_MESSAGE(stuff))
+
+#define THROW_DBM_ERROR(message, filename, exception, subtype) \
+ BOX_LOG_DBM_ERROR(message << ": " << filename); \
+ THROW_EXCEPTION_MESSAGE(exception, subtype, \
+ BOX_DBM_MESSAGE(message << ": " << filename));
+
+#define ASSERT_DBM_OK(operation, message, filename, exception, subtype) \
+ if(!(operation)) \
+ { \
+ THROW_DBM_ERROR(message, filename, exception, subtype); \
+ }
+
+#endif // DATABASE__H
diff --git a/lib/common/DebugMemLeakFinder.cpp b/lib/common/DebugMemLeakFinder.cpp
index 72891cd1..ecc4eb12 100644
--- a/lib/common/DebugMemLeakFinder.cpp
+++ b/lib/common/DebugMemLeakFinder.cpp
@@ -7,11 +7,18 @@
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
#ifndef BOX_RELEASE_BUILD
#include "Box.h"
+=======
+#include "Box.h"
+
+#ifdef BOX_MEMORY_LEAK_TESTING
+
+>>>>>>> 0.12
#undef malloc
#undef realloc
#undef free
@@ -20,11 +27,21 @@
#include <unistd.h>
#endif
+<<<<<<< HEAD
#include <map>
#include <stdio.h>
#include <string.h>
#include <set>
#include <cstdlib> // for std::atexit
+=======
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <cstdlib> // for std::atexit
+#include <map>
+#include <set>
+>>>>>>> 0.12
#include "MemLeakFinder.h"
@@ -73,6 +90,16 @@ namespace
size_t sNotLeaksPreNum = 0;
}
+<<<<<<< HEAD
+=======
+void memleakfinder_report_on_signal(int unused)
+{
+ // this is not safe! do not send SIGUSR1 to a process
+ // in a production environment!
+ memleakfinder_report_usage_summary();
+}
+
+>>>>>>> 0.12
void memleakfinder_init()
{
ASSERT(!memleakfinder_initialised);
@@ -84,6 +111,24 @@ void memleakfinder_init()
}
memleakfinder_initialised = true;
+<<<<<<< HEAD
+=======
+
+ #if defined WIN32
+ // no signals, no way to trigger event yet
+ #else
+ struct sigaction newact, oldact;
+ newact.sa_handler = memleakfinder_report_on_signal;
+ newact.sa_flags = SA_RESTART;
+ sigemptyset(&newact.sa_mask);
+ if (::sigaction(SIGUSR1, &newact, &oldact) != 0)
+ {
+ BOX_ERROR("Failed to install USR1 signal handler");
+ THROW_EXCEPTION(CommonException, Internal);
+ }
+ ASSERT(oldact.sa_handler == 0);
+ #endif // WIN32
+>>>>>>> 0.12
}
MemLeakSuppressionGuard::MemLeakSuppressionGuard()
@@ -141,6 +186,19 @@ void *memleakfinder_malloc(size_t size, const char *file, int line)
return b;
}
+<<<<<<< HEAD
+=======
+void *memleakfinder_calloc(size_t blocks, size_t size, const char *file, int line)
+{
+ void *block = memleakfinder_malloc(blocks * size, file, line);
+ if (block != 0)
+ {
+ memset(block, 0, blocks * size);
+ }
+ return block;
+}
+
+>>>>>>> 0.12
void *memleakfinder_realloc(void *ptr, size_t size)
{
InternalAllocGuard guard;
@@ -346,6 +404,88 @@ int memleakfinder_numleaks()
return n;
}
+<<<<<<< HEAD
+=======
+// Summarise all blocks allocated and still allocated, for memory usage
+// diagnostics.
+void memleakfinder_report_usage_summary()
+{
+ InternalAllocGuard guard;
+
+ ASSERT(!sTrackingDataDestroyed);
+
+ typedef std::map<std::string, std::pair<uint64_t, uint64_t> > usage_map_t;
+ usage_map_t usage;
+
+ for(std::map<void *, MallocBlockInfo>::const_iterator
+ i(sMallocBlocks.begin()); i != sMallocBlocks.end(); ++i)
+ {
+ std::ostringstream buf;
+ buf << i->second.file << ":" << i->second.line;
+ std::string key = buf.str();
+
+ usage_map_t::iterator ui = usage.find(key);
+ if(ui == usage.end())
+ {
+ usage[key] = std::pair<uint64_t, uint64_t>(1,
+ i->second.size);
+ }
+ else
+ {
+ ui->second.first++;
+ ui->second.second += i->second.size;
+ }
+ }
+
+ for(std::map<void *, ObjectInfo>::const_iterator
+ i(sObjectBlocks.begin()); i != sObjectBlocks.end(); ++i)
+ {
+ std::ostringstream buf;
+ buf << i->second.file << ":" << i->second.line;
+ std::string key = buf.str();
+
+ usage_map_t::iterator ui = usage.find(key);
+ if(ui == usage.end())
+ {
+ usage[key] = std::pair<uint64_t, uint64_t>(1,
+ i->second.size);
+ }
+ else
+ {
+ ui->second.first++;
+ ui->second.second += i->second.size;
+ }
+ }
+
+ #ifndef DEBUG_LEAKS
+ BOX_WARNING("Memory use: support not compiled in :(");
+ #else
+ if(usage.empty())
+ {
+ BOX_WARNING("Memory use: none detected?!");
+ }
+ else
+ {
+ uint64_t blocks = 0, bytes = 0;
+ BOX_WARNING("Memory use: report follows");
+
+ for(usage_map_t::iterator i = usage.begin(); i != usage.end();
+ i++)
+ {
+ BOX_WARNING("Memory use: " << i->first << ": " <<
+ i->second.first << " blocks, " <<
+ i->second.second << " bytes");
+ blocks += i->second.first;
+ bytes += i->second.second;
+ }
+
+ BOX_WARNING("Memory use: report ends, total: " << blocks <<
+ " blocks, " << bytes << " bytes");
+ }
+ #endif // DEBUG_LEAKS
+}
+
+>>>>>>> 0.12
void memleakfinder_reportleaks_file(FILE *file)
{
InternalAllocGuard guard;
@@ -549,4 +689,8 @@ void operator delete(void *ptr) throw ()
internal_delete(ptr);
}
+<<<<<<< HEAD
#endif // BOX_RELEASE_BUILD
+=======
+#endif // BOX_MEMORY_LEAK_TESTING
+>>>>>>> 0.12
diff --git a/lib/common/ExcludeList.cpp b/lib/common/ExcludeList.cpp
index edbf1a6a..c7c80ed2 100644
--- a/lib/common/ExcludeList.cpp
+++ b/lib/common/ExcludeList.cpp
@@ -101,11 +101,14 @@ std::string ExcludeList::ReplaceSlashesRegex(const std::string& input) const
output.replace(pos, 1, "\\" DIRECTORY_SEPARATOR);
}
+<<<<<<< HEAD
for (std::string::iterator i = output.begin(); i != output.end(); i++)
{
*i = tolower(*i);
}
+=======
+>>>>>>> 0.12
return output;
}
#endif
@@ -185,17 +188,30 @@ void ExcludeList::AddRegexEntries(const std::string &rEntries)
try
{
std::string entry = *i;
+<<<<<<< HEAD
+=======
+ int flags = REG_EXTENDED | REG_NOSUB;
+>>>>>>> 0.12
// Convert any forward slashes in the string
// to appropriately escaped backslashes
#ifdef WIN32
entry = ReplaceSlashesRegex(entry);
+<<<<<<< HEAD
#endif
// Compile
int errcode = ::regcomp(pregex, entry.c_str(),
REG_EXTENDED | REG_NOSUB);
+=======
+ flags |= REG_ICASE; // Windows convention
+ #endif
+
+ // Compile
+ int errcode = ::regcomp(pregex, entry.c_str(),
+ flags);
+>>>>>>> 0.12
if (errcode != 0)
{
@@ -238,6 +254,10 @@ bool ExcludeList::IsExcluded(const std::string &rTest) const
std::string test = rTest;
#ifdef WIN32
+<<<<<<< HEAD
+=======
+ // converts to lower case as well
+>>>>>>> 0.12
test = ReplaceSlashesDefinite(test);
#endif
diff --git a/lib/common/FdGetLine.cpp b/lib/common/FdGetLine.cpp
index 9b53288b..2d2f7c6a 100644
--- a/lib/common/FdGetLine.cpp
+++ b/lib/common/FdGetLine.cpp
@@ -20,6 +20,7 @@
#include "MemLeakFindOn.h"
+<<<<<<< HEAD
// utility whitespace function
inline bool iw(int c)
{
@@ -27,6 +28,8 @@ inline bool iw(int c)
}
+=======
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
// Function
@@ -36,12 +39,16 @@ inline bool iw(int c)
//
// --------------------------------------------------------------------------
FdGetLine::FdGetLine(int fd)
+<<<<<<< HEAD
: mFileHandle(fd),
mLineNumber(0),
mBufferBegin(0),
mBytesInBuffer(0),
mPendingEOF(false),
mEOF(false)
+=======
+: mFileHandle(fd)
+>>>>>>> 0.12
{
if(mFileHandle < 0) {THROW_EXCEPTION(CommonException, BadArguments)}
//printf("FdGetLine buffer size = %d\n", sizeof(mBuffer));
@@ -74,6 +81,7 @@ FdGetLine::~FdGetLine()
std::string FdGetLine::GetLine(bool Preprocess)
{
if(mFileHandle == -1) {THROW_EXCEPTION(CommonException, GetLineNoHandle)}
+<<<<<<< HEAD
// EOF?
if(mEOF) {THROW_EXCEPTION(CommonException, GetLineEOF)}
@@ -194,6 +202,55 @@ std::string FdGetLine::GetLine(bool Preprocess)
// Return a sub string
return r.substr(begin, end - begin + 1);
}
+=======
+
+ std::string r;
+ bool result = GetLineInternal(r, Preprocess);
+
+ if(!result)
+ {
+ // should never fail for FdGetLine
+ THROW_EXCEPTION(CommonException, Internal);
+ }
+
+ return r;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: FdGetLine::ReadMore()
+// Purpose: Read more bytes from the handle, possible the
+// console, into mBuffer and return the number of
+// bytes read, 0 on EOF or -1 on error.
+// Created: 2011/04/22
+//
+// --------------------------------------------------------------------------
+int FdGetLine::ReadMore(int Timeout)
+{
+ int bytes;
+
+#ifdef WIN32
+ if (mFileHandle == _fileno(stdin))
+ {
+ bytes = console_read(mBuffer, sizeof(mBuffer));
+ }
+ else
+ {
+ bytes = ::read(mFileHandle, mBuffer, sizeof(mBuffer));
+ }
+#else // !WIN32
+ bytes = ::read(mFileHandle, mBuffer, sizeof(mBuffer));
+#endif // WIN32
+
+ if(bytes == 0)
+ {
+ mPendingEOF = true;
+ }
+
+ return bytes;
+>>>>>>> 0.12
}
@@ -202,7 +259,11 @@ std::string FdGetLine::GetLine(bool Preprocess)
// Function
// Name: FdGetLine::DetachFile()
// Purpose: Detaches the file handle, setting the file pointer correctly.
+<<<<<<< HEAD
// Probably not good for sockets...
+=======
+// Probably not good for sockets...
+>>>>>>> 0.12
// Created: 2003/07/24
//
// --------------------------------------------------------------------------
@@ -225,4 +286,7 @@ void FdGetLine::DetachFile()
mFileHandle = -1;
}
+<<<<<<< HEAD
+=======
+>>>>>>> 0.12
diff --git a/lib/common/FdGetLine.h b/lib/common/FdGetLine.h
index df43c3c9..6b609fe8 100644
--- a/lib/common/FdGetLine.h
+++ b/lib/common/FdGetLine.h
@@ -12,6 +12,7 @@
#include <string>
+<<<<<<< HEAD
#ifdef BOX_RELEASE_BUILD
#define FDGETLINE_BUFFER_SIZE 1024
#elif defined WIN32
@@ -25,6 +26,9 @@
// Just a very large upper bound for line size to avoid
// people sending lots of data over sockets and causing memory problems.
#define FDGETLINE_MAX_LINE_SIZE (1024*256)
+=======
+#include "GetLine.h"
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
@@ -34,15 +38,24 @@
// Created: 2003/07/24
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
class FdGetLine
{
public:
FdGetLine(int fd);
~FdGetLine();
+=======
+class FdGetLine : public GetLine
+{
+public:
+ FdGetLine(int fd);
+ virtual ~FdGetLine();
+>>>>>>> 0.12
private:
FdGetLine(const FdGetLine &rToCopy);
public:
+<<<<<<< HEAD
std::string GetLine(bool Preprocess = false);
bool IsEOF() {return mEOF;}
int GetLineNumber() {return mLineNumber;}
@@ -59,6 +72,21 @@ private:
int mBytesInBuffer;
bool mPendingEOF;
bool mEOF;
+=======
+ virtual std::string GetLine(bool Preprocess = false);
+ // Call to detach, setting file pointer correctly to last bit read.
+ // Only works for lseek-able file descriptors.
+ void DetachFile();
+ // if we read 0 bytes from an fd, it must be end of stream,
+ // because we don't support timeouts
+ virtual bool IsStreamDataLeft() { return false; }
+
+protected:
+ int ReadMore(int Timeout = IOStream::TimeOutInfinite);
+
+private:
+ int mFileHandle;
+>>>>>>> 0.12
};
#endif // FDGETLINE__H
diff --git a/lib/common/FileModificationTime.cpp b/lib/common/FileModificationTime.cpp
index 1109b15f..bc35b7e6 100644
--- a/lib/common/FileModificationTime.cpp
+++ b/lib/common/FileModificationTime.cpp
@@ -16,7 +16,11 @@
#include "MemLeakFindOn.h"
+<<<<<<< HEAD
box_time_t FileModificationTime(EMU_STRUCT_STAT &st)
+=======
+box_time_t FileModificationTime(const EMU_STRUCT_STAT &st)
+>>>>>>> 0.12
{
#ifndef HAVE_STRUCT_STAT_ST_MTIMESPEC
box_time_t datamodified = ((int64_t)st.st_mtime) * (MICRO_SEC_IN_SEC_LL);
@@ -28,7 +32,11 @@ box_time_t FileModificationTime(EMU_STRUCT_STAT &st)
return datamodified;
}
+<<<<<<< HEAD
box_time_t FileAttrModificationTime(EMU_STRUCT_STAT &st)
+=======
+box_time_t FileAttrModificationTime(const EMU_STRUCT_STAT &st)
+>>>>>>> 0.12
{
box_time_t statusmodified =
#ifdef HAVE_STRUCT_STAT_ST_MTIMESPEC
@@ -47,7 +55,11 @@ box_time_t FileAttrModificationTime(EMU_STRUCT_STAT &st)
return statusmodified;
}
+<<<<<<< HEAD
box_time_t FileModificationTimeMaxModAndAttr(EMU_STRUCT_STAT &st)
+=======
+box_time_t FileModificationTimeMaxModAndAttr(const EMU_STRUCT_STAT &st)
+>>>>>>> 0.12
{
#ifndef HAVE_STRUCT_STAT_ST_MTIMESPEC
box_time_t datamodified = ((int64_t)st.st_mtime) * (MICRO_SEC_IN_SEC_LL);
diff --git a/lib/common/FileModificationTime.h b/lib/common/FileModificationTime.h
index e6e6c172..ffff29c7 100644
--- a/lib/common/FileModificationTime.h
+++ b/lib/common/FileModificationTime.h
@@ -14,9 +14,15 @@
#include "BoxTime.h"
+<<<<<<< HEAD
box_time_t FileModificationTime(EMU_STRUCT_STAT &st);
box_time_t FileAttrModificationTime(EMU_STRUCT_STAT &st);
box_time_t FileModificationTimeMaxModAndAttr(EMU_STRUCT_STAT &st);
+=======
+box_time_t FileModificationTime(const EMU_STRUCT_STAT &st);
+box_time_t FileAttrModificationTime(const EMU_STRUCT_STAT &st);
+box_time_t FileModificationTimeMaxModAndAttr(const EMU_STRUCT_STAT &st);
+>>>>>>> 0.12
#endif // FILEMODIFICATIONTIME__H
diff --git a/lib/common/FileStream.cpp b/lib/common/FileStream.cpp
index 5be8237c..68209b15 100644
--- a/lib/common/FileStream.cpp
+++ b/lib/common/FileStream.cpp
@@ -190,6 +190,7 @@ int FileStream::Read(void *pBuffer, int NBytes, int Timeout)
}
else
{
+<<<<<<< HEAD
BOX_LOG_WIN_ERROR("Failed to read from file: " << mFileName);
r = -1;
}
@@ -205,6 +206,24 @@ int FileStream::Read(void *pBuffer, int NBytes, int Timeout)
{
THROW_EXCEPTION(CommonException, OSFileReadError)
}
+=======
+ THROW_WIN_FILE_ERROR("Failed to read from file", mFileName,
+ CommonException, OSFileReadError);
+ }
+
+ if(r == -1)
+ {
+ THROW_EXCEPTION(CommonException, OSFileReadError)
+ }
+#else
+ int r = ::read(mOSFileHandle, pBuffer, NBytes);
+ if(r == -1)
+ {
+ THROW_SYS_FILE_ERROR("Failed to read from file", mFileName,
+ CommonException, OSFileReadError);
+ }
+#endif
+>>>>>>> 0.12
if(r == 0)
{
@@ -228,7 +247,11 @@ IOStream::pos_type FileStream::BytesLeftToRead()
EMU_STRUCT_STAT st;
if(EMU_FSTAT(mOSFileHandle, &st) != 0)
{
+<<<<<<< HEAD
THROW_EXCEPTION(CommonException, OSFileError)
+=======
+ BOX_LOG_SYS_ERROR(BOX_FILE_MESSAGE("Failed to stat file", mFileName));
+>>>>>>> 0.12
}
return st.st_size - GetPosition();
@@ -262,14 +285,24 @@ void FileStream::Write(const void *pBuffer, int NBytes)
if ((res == 0) || (numBytesWritten != (DWORD)NBytes))
{
+<<<<<<< HEAD
// DWORD err = GetLastError();
THROW_EXCEPTION(CommonException, OSFileWriteError)
+=======
+ THROW_WIN_FILE_ERROR("Failed to write to file", mFileName,
+ CommonException, OSFileWriteError);
+>>>>>>> 0.12
}
#else
if(::write(mOSFileHandle, pBuffer, NBytes) != NBytes)
{
+<<<<<<< HEAD
BOX_LOG_SYS_ERROR("Failed to write to file: " << mFileName);
THROW_EXCEPTION(CommonException, OSFileWriteError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to write to file", mFileName,
+ CommonException, OSFileWriteError);
+>>>>>>> 0.12
}
#endif
}
@@ -292,18 +325,35 @@ IOStream::pos_type FileStream::GetPosition() const
#ifdef WIN32
LARGE_INTEGER conv;
+<<<<<<< HEAD
conv.HighPart = 0;
conv.LowPart = 0;
conv.LowPart = SetFilePointer(this->mOSFileHandle, 0, &conv.HighPart, FILE_CURRENT);
+=======
+ conv.HighPart = 0;
+ conv.LowPart = SetFilePointer(this->mOSFileHandle, 0, &conv.HighPart, FILE_CURRENT);
+
+ if(conv.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
+ {
+ THROW_WIN_FILE_ERROR("Failed to seek in file", mFileName,
+ CommonException, OSFileError);
+ }
+
+>>>>>>> 0.12
return (IOStream::pos_type)conv.QuadPart;
#else // ! WIN32
off_t p = ::lseek(mOSFileHandle, 0, SEEK_CUR);
if(p == -1)
{
+<<<<<<< HEAD
THROW_EXCEPTION(CommonException, OSFileError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to seek in file", mFileName,
+ CommonException, OSFileError);
+>>>>>>> 0.12
}
return (IOStream::pos_type)p;
@@ -328,18 +378,31 @@ void FileStream::Seek(IOStream::pos_type Offset, int SeekType)
#ifdef WIN32
LARGE_INTEGER conv;
+<<<<<<< HEAD
+=======
+>>>>>>> 0.12
conv.QuadPart = Offset;
DWORD retVal = SetFilePointer(this->mOSFileHandle, conv.LowPart, &conv.HighPart, ConvertSeekTypeToOSWhence(SeekType));
if(retVal == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
{
+<<<<<<< HEAD
THROW_EXCEPTION(CommonException, OSFileError)
+=======
+ THROW_WIN_FILE_ERROR("Failed to seek in file", mFileName,
+ CommonException, OSFileError);
+>>>>>>> 0.12
}
#else // ! WIN32
if(::lseek(mOSFileHandle, Offset, ConvertSeekTypeToOSWhence(SeekType)) == -1)
{
+<<<<<<< HEAD
THROW_EXCEPTION(CommonException, OSFileError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to seek in file", mFileName,
+ CommonException, OSFileError);
+>>>>>>> 0.12
}
#endif // WIN32
@@ -365,12 +428,26 @@ void FileStream::Close()
#ifdef WIN32
if(::CloseHandle(mOSFileHandle) == 0)
+<<<<<<< HEAD
#else
if(::close(mOSFileHandle) != 0)
#endif
{
THROW_EXCEPTION(CommonException, OSFileCloseError)
}
+=======
+ {
+ THROW_WIN_FILE_ERROR("Failed to close file", mFileName,
+ CommonException, OSFileCloseError);
+ }
+#else // ! WIN32
+ if(::close(mOSFileHandle) != 0)
+ {
+ THROW_SYS_FILE_ERROR("Failed to close file", mFileName,
+ CommonException, OSFileCloseError);
+ }
+#endif // WIN32
+>>>>>>> 0.12
mOSFileHandle = INVALID_FILE;
mIsEOF = true;
@@ -401,7 +478,11 @@ bool FileStream::StreamDataLeft()
// --------------------------------------------------------------------------
bool FileStream::StreamClosed()
{
+<<<<<<< HEAD
return mIsEOF;
+=======
+ return (mOSFileHandle == INVALID_FILE);
+>>>>>>> 0.12
}
// --------------------------------------------------------------------------
diff --git a/lib/common/GetLine.cpp b/lib/common/GetLine.cpp
new file mode 100644
index 00000000..e6b26c8a
--- /dev/null
+++ b/lib/common/GetLine.cpp
@@ -0,0 +1,176 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: GetLine.cpp
+// Purpose: Common base class for line based file descriptor reading
+// Created: 2011/04/22
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+ #include <unistd.h>
+#endif
+
+#include "GetLine.h"
+#include "CommonException.h"
+
+#include "MemLeakFindOn.h"
+
+// utility whitespace function
+inline bool iw(int c)
+{
+ return (c == ' ' || c == '\t' || c == '\v' || c == '\f'); // \r, \n are already excluded
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: GetLine::GetLine(int)
+// Purpose: Constructor, taking file descriptor
+// Created: 2011/04/22
+//
+// --------------------------------------------------------------------------
+GetLine::GetLine()
+: mLineNumber(0),
+ mBufferBegin(0),
+ mBytesInBuffer(0),
+ mPendingEOF(false),
+ mEOF(false)
+{ }
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: GetLine::GetLineInternal(std::string &, bool, int)
+// Purpose: Gets a line from the file, returning it in rOutput.
+// If Preprocess is true, leading and trailing
+// whitespace is removed, and comments (after #) are
+// deleted. Returns true if a line is available now,
+// false if retrying may get a line (eg timeout,
+// signal), and exceptions if it's EOF.
+// Created: 2011/04/22
+//
+// --------------------------------------------------------------------------
+bool GetLine::GetLineInternal(std::string &rOutput, bool Preprocess,
+ int Timeout)
+{
+ // EOF?
+ if(mEOF) {THROW_EXCEPTION(CommonException, GetLineEOF)}
+
+ // Initialise string to stored into
+ rOutput = mPendingString;
+ mPendingString.erase();
+
+ bool foundLineEnd = false;
+
+ while(!foundLineEnd && !mEOF)
+ {
+ // Use any bytes left in the buffer
+ while(mBufferBegin < mBytesInBuffer)
+ {
+ int c = mBuffer[mBufferBegin++];
+ if(c == '\r')
+ {
+ // Ignore nasty Windows line ending extra chars
+ }
+ else if(c == '\n')
+ {
+ // Line end!
+ foundLineEnd = true;
+ break;
+ }
+ else
+ {
+ // Add to string
+ rOutput += c;
+ }
+
+ // Implicit line ending at EOF
+ if(mBufferBegin >= mBytesInBuffer && mPendingEOF)
+ {
+ foundLineEnd = true;
+ }
+ }
+
+ // Check size
+ if(rOutput.size() > GETLINE_MAX_LINE_SIZE)
+ {
+ THROW_EXCEPTION(CommonException, GetLineTooLarge)
+ }
+
+ // Read more in?
+ if(!foundLineEnd && mBufferBegin >= mBytesInBuffer && !mPendingEOF)
+ {
+ int bytes = ReadMore(Timeout);
+
+ // Error?
+ if(bytes == -1)
+ {
+ THROW_EXCEPTION(CommonException, OSFileError)
+ }
+
+ // Adjust buffer info
+ mBytesInBuffer = bytes;
+ mBufferBegin = 0;
+
+ // No data returned?
+ if(bytes == 0 && IsStreamDataLeft())
+ {
+ // store string away
+ mPendingString = rOutput;
+ // Return false;
+ return false;
+ }
+ }
+
+ // EOF?
+ if(mPendingEOF && mBufferBegin >= mBytesInBuffer)
+ {
+ // File is EOF, and now we've depleted the buffer completely, so tell caller as well.
+ mEOF = true;
+ }
+ }
+
+ if(Preprocess)
+ {
+ // Check for comment char, but char before must be whitespace
+ // end points to a gap between characters, may equal start if
+ // the string to be extracted has zero length, and indexes the
+ // first character not in the string (== length, or a # mark
+ // or whitespace)
+ int end = 0;
+ int size = rOutput.size();
+ while(end < size)
+ {
+ if(rOutput[end] == '#' && (end == 0 || (iw(rOutput[end-1]))))
+ {
+ break;
+ }
+ end++;
+ }
+
+ // Remove whitespace
+ int begin = 0;
+ while(begin < size && iw(rOutput[begin]))
+ {
+ begin++;
+ }
+
+ while(end > begin && end <= size && iw(rOutput[end-1]))
+ {
+ end--;
+ }
+
+ // Return a sub string
+ rOutput = rOutput.substr(begin, end - begin);
+ }
+
+ return true;
+}
+
+
diff --git a/lib/common/GetLine.h b/lib/common/GetLine.h
new file mode 100644
index 00000000..0eeb3c71
--- /dev/null
+++ b/lib/common/GetLine.h
@@ -0,0 +1,67 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: GetLine.h
+// Purpose: Common base class for line based file descriptor reading
+// Created: 2011/04/22
+//
+// --------------------------------------------------------------------------
+
+#ifndef GETLINE__H
+#define GETLINE__H
+
+#include <string>
+
+#ifdef BOX_RELEASE_BUILD
+ #define GETLINE_BUFFER_SIZE 1024
+#elif defined WIN32
+ // need enough space for at least one unicode character
+ // in UTF-8 when calling console_read() from bbackupquery
+ #define GETLINE_BUFFER_SIZE 5
+#else
+ #define GETLINE_BUFFER_SIZE 4
+#endif
+
+// Just a very large upper bound for line size to avoid
+// people sending lots of data over sockets and causing memory problems.
+#define GETLINE_MAX_LINE_SIZE (1024*256)
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: GetLine
+// Purpose: Common base class for line based file descriptor reading
+// Created: 2011/04/22
+//
+// --------------------------------------------------------------------------
+class GetLine
+{
+protected:
+ GetLine();
+
+private:
+ GetLine(const GetLine &rToCopy);
+
+public:
+ virtual bool IsEOF() {return mEOF;}
+ int GetLineNumber() {return mLineNumber;}
+ virtual ~GetLine() { }
+
+protected:
+ bool GetLineInternal(std::string &rOutput,
+ bool Preprocess = false,
+ int Timeout = IOStream::TimeOutInfinite);
+ virtual int ReadMore(int Timeout = IOStream::TimeOutInfinite) = 0;
+ virtual bool IsStreamDataLeft() = 0;
+
+ char mBuffer[GETLINE_BUFFER_SIZE];
+ int mLineNumber;
+ int mBufferBegin;
+ int mBytesInBuffer;
+ bool mPendingEOF;
+ std::string mPendingString;
+ bool mEOF;
+};
+
+#endif // GETLINE__H
+
diff --git a/lib/common/IOStreamGetLine.cpp b/lib/common/IOStreamGetLine.cpp
index 27a77c29..9a40f3eb 100644
--- a/lib/common/IOStreamGetLine.cpp
+++ b/lib/common/IOStreamGetLine.cpp
@@ -13,6 +13,7 @@
#include "MemLeakFindOn.h"
+<<<<<<< HEAD
// utility whitespace function
inline bool iw(int c)
{
@@ -20,6 +21,8 @@ inline bool iw(int c)
}
+=======
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
// Function
@@ -29,12 +32,16 @@ inline bool iw(int c)
//
// --------------------------------------------------------------------------
IOStreamGetLine::IOStreamGetLine(IOStream &Stream)
+<<<<<<< HEAD
: mrStream(Stream),
mLineNumber(0),
mBufferBegin(0),
mBytesInBuffer(0),
mPendingEOF(false),
mEOF(false)
+=======
+: mrStream(Stream)
+>>>>>>> 0.12
{
}
@@ -66,6 +73,7 @@ IOStreamGetLine::~IOStreamGetLine()
// --------------------------------------------------------------------------
bool IOStreamGetLine::GetLine(std::string &rOutput, bool Preprocess, int Timeout)
{
+<<<<<<< HEAD
// EOF?
if(mEOF) {THROW_EXCEPTION(CommonException, GetLineEOF)}
@@ -178,6 +186,32 @@ bool IOStreamGetLine::GetLine(std::string &rOutput, bool Preprocess, int Timeout
rOutput = r.substr(begin, end - begin + 1);
return true;
}
+=======
+ return GetLineInternal(rOutput, Preprocess, Timeout);
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: IOStreamGetLine::ReadMore()
+// Purpose: Read more bytes from the handle, possible the
+// console, into mBuffer and return the number of
+// bytes read, 0 on EOF or -1 on error.
+// Created: 2011/04/22
+//
+// --------------------------------------------------------------------------
+int IOStreamGetLine::ReadMore(int Timeout)
+{
+ int bytes = mrStream.Read(mBuffer, sizeof(mBuffer), Timeout);
+
+ if(!mrStream.StreamDataLeft())
+ {
+ mPendingEOF = true;
+ }
+
+ return bytes;
+>>>>>>> 0.12
}
diff --git a/lib/common/IOStreamGetLine.h b/lib/common/IOStreamGetLine.h
index 9a5d1818..1693e8ac 100644
--- a/lib/common/IOStreamGetLine.h
+++ b/lib/common/IOStreamGetLine.h
@@ -12,6 +12,7 @@
#include <string>
+<<<<<<< HEAD
#include "IOStream.h"
#ifdef BOX_RELEASE_BUILD
@@ -24,6 +25,11 @@
// people sending lots of data over sockets and causing memory problems.
#define IOSTREAMGETLINE_MAX_LINE_SIZE (1024*256)
+=======
+#include "GetLine.h"
+#include "IOStream.h"
+
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
// Class
@@ -32,29 +38,49 @@
// Created: 2003/07/24
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
class IOStreamGetLine
{
public:
IOStreamGetLine(IOStream &Stream);
~IOStreamGetLine();
+=======
+class IOStreamGetLine : public GetLine
+{
+public:
+ IOStreamGetLine(IOStream &Stream);
+ virtual ~IOStreamGetLine();
+>>>>>>> 0.12
private:
IOStreamGetLine(const IOStreamGetLine &rToCopy);
public:
bool GetLine(std::string &rOutput, bool Preprocess = false, int Timeout = IOStream::TimeOutInfinite);
+<<<<<<< HEAD
bool IsEOF() {return mEOF;}
int GetLineNumber() {return mLineNumber;}
+=======
+>>>>>>> 0.12
// Call to detach, setting file pointer correctly to last bit read.
// Only works for lseek-able file descriptors.
void DetachFile();
+<<<<<<< HEAD
+=======
+ virtual bool IsStreamDataLeft()
+ {
+ return mrStream.StreamDataLeft();
+ }
+
+>>>>>>> 0.12
// For doing interesting stuff with the remaining data...
// Be careful with this!
const void *GetBufferedData() const {return mBuffer + mBufferBegin;}
int GetSizeOfBufferedData() const {return mBytesInBuffer - mBufferBegin;}
void IgnoreBufferedData(int BytesToIgnore);
IOStream &GetUnderlyingStream() {return mrStream;}
+<<<<<<< HEAD
private:
char mBuffer[IOSTREAMGETLINE_BUFFER_SIZE];
@@ -65,6 +91,14 @@ private:
bool mPendingEOF;
bool mEOF;
std::string mPendingString;
+=======
+
+protected:
+ int ReadMore(int Timeout = IOStream::TimeOutInfinite);
+
+private:
+ IOStream &mrStream;
+>>>>>>> 0.12
};
#endif // IOSTREAMGETLINE__H
diff --git a/lib/common/Logging.cpp b/lib/common/Logging.cpp
index 296443ea..4a7e5e0a 100644
--- a/lib/common/Logging.cpp
+++ b/lib/common/Logging.cpp
@@ -22,6 +22,12 @@
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
+<<<<<<< HEAD
+=======
+#ifdef WIN32
+ #include <process.h>
+#endif
+>>>>>>> 0.12
#include <cstring>
#include <iomanip>
@@ -43,6 +49,15 @@ Log::Level Logging::sGlobalLevel = Log::EVERYTHING;
Logging Logging::sGlobalLogging; //automatic initialisation
std::string Logging::sProgramName;
+<<<<<<< HEAD
+=======
+HideSpecificExceptionGuard::SuppressedExceptions_t
+ HideSpecificExceptionGuard::sSuppressedExceptions;
+
+int Logging::Guard::sGuardCount = 0;
+Log::Level Logging::Guard::sOriginalLevel = Log::INVALID;
+
+>>>>>>> 0.12
Logging::Logging()
{
ASSERT(!spConsole);
@@ -244,6 +259,15 @@ Logger::~Logger()
Logging::Remove(this);
}
+<<<<<<< HEAD
+=======
+bool Logger::IsEnabled(Log::Level level)
+{
+ return Logging::IsEnabled(level) &&
+ (int)mCurrentLevel >= (int)level;
+}
+
+>>>>>>> 0.12
bool Console::sShowTime = false;
bool Console::sShowTimeMicros = false;
bool Console::sShowTag = false;
@@ -343,11 +367,28 @@ bool Console::Log(Log::Level level, const std::string& rFile,
#ifdef WIN32
std::string output = buf.str();
+<<<<<<< HEAD
ConvertUtf8ToConsole(output.c_str(), output);
fprintf(target, "%s\n", output.c_str());
#else
fprintf(target, "%s\n", buf.str().c_str());
#endif
+=======
+ if(ConvertUtf8ToConsole(output.c_str(), output) == false)
+ {
+ fprintf(target, "%s (and failed to convert to console encoding)\n",
+ output.c_str());
+ }
+ else
+ {
+ fprintf(target, "%s\n", output.c_str());
+ }
+ #else
+ fprintf(target, "%s\n", buf.str().c_str());
+ #endif
+
+ fflush(target);
+>>>>>>> 0.12
return true;
}
@@ -446,13 +487,28 @@ int Syslog::GetNamedFacility(const std::string& rFacility)
bool FileLogger::Log(Log::Level Level, const std::string& rFile,
int line, std::string& rMessage)
{
+<<<<<<< HEAD
+=======
+ if (mLogFile.StreamClosed())
+ {
+ /* skip this logger to allow logging failure to open
+ the log file, without causing an infinite loop */
+ return true;
+ }
+
+>>>>>>> 0.12
if (Level > GetLevel())
{
return true;
}
/* avoid infinite loop if this throws an exception */
+<<<<<<< HEAD
Logging::Remove(this);
+=======
+ Log::Level oldLevel = GetLevel();
+ Filter(Log::NOTHING);
+>>>>>>> 0.12
std::ostringstream buf;
buf << FormatTime(GetCurrentBoxTime(), true, false);
@@ -492,7 +548,12 @@ bool FileLogger::Log(Log::Level Level, const std::string& rFile,
mLogFile.Write(output.c_str(), output.length());
+<<<<<<< HEAD
Logging::Add(this);
+=======
+ // no infinite loop, reset to saved logging level
+ Filter(oldLevel);
+>>>>>>> 0.12
return true;
}
@@ -516,3 +577,21 @@ std::string PrintEscapedBinaryData(const std::string& rInput)
return output.str();
}
+<<<<<<< HEAD
+=======
+
+bool HideSpecificExceptionGuard::IsHidden(int type, int subtype)
+{
+ for (SuppressedExceptions_t::iterator
+ i = sSuppressedExceptions.begin();
+ i != sSuppressedExceptions.end(); i++)
+ {
+ if(i->first == type && i->second == subtype)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+>>>>>>> 0.12
diff --git a/lib/common/Logging.h b/lib/common/Logging.h
index 15400711..bc0203bb 100644
--- a/lib/common/Logging.h
+++ b/lib/common/Logging.h
@@ -10,6 +10,11 @@
#ifndef LOGGING__H
#define LOGGING__H
+<<<<<<< HEAD
+=======
+#include <assert.h>
+
+>>>>>>> 0.12
#include <cerrno>
#include <cstring>
#include <iomanip>
@@ -41,6 +46,7 @@
if (Logging::IsEnabled(Log::TRACE)) \
{ BOX_LOG(Log::TRACE, stuff) }
+<<<<<<< HEAD
#define BOX_SYS_ERROR(stuff) \
stuff << ": " << std::strerror(errno) << " (" << errno << ")"
@@ -66,6 +72,48 @@ inline std::string GetNativeErrorMessage()
return _box_log_line.str();
#endif
}
+=======
+#define BOX_SYS_ERRNO_MESSAGE(error_number, stuff) \
+ stuff << ": " << std::strerror(error_number) << \
+ " (" << error_number << ")"
+
+#define BOX_FILE_MESSAGE(filename, message) \
+ message << ": " << filename
+
+#define BOX_SYS_FILE_ERRNO_MESSAGE(filename, error_number, message) \
+ BOX_SYS_ERRNO_MESSAGE(error_number, BOX_FILE_MESSAGE(filename, message))
+
+#define BOX_SYS_ERROR_MESSAGE(stuff) \
+ BOX_SYS_ERRNO_MESSAGE(errno, stuff)
+
+#define BOX_LOG_SYS_WARNING(stuff) \
+ BOX_WARNING(BOX_SYS_ERROR_MESSAGE(stuff))
+#define BOX_LOG_SYS_ERROR(stuff) \
+ BOX_ERROR(BOX_SYS_ERROR_MESSAGE(stuff))
+#define BOX_LOG_SYS_ERRNO(error_number, stuff) \
+ BOX_ERROR(BOX_SYS_ERRNO_MESSAGE(error_number, stuff))
+#define BOX_LOG_SYS_FATAL(stuff) \
+ BOX_FATAL(BOX_SYS_ERROR_MESSAGE(stuff))
+
+#define THROW_SYS_ERROR_NUMBER(message, error_number, exception, subtype) \
+ THROW_EXCEPTION_MESSAGE(exception, subtype, \
+ BOX_SYS_ERRNO_MESSAGE(error_number, message))
+
+#define THROW_SYS_ERROR(message, exception, subtype) \
+ THROW_SYS_ERROR_NUMBER(message, errno, exception, subtype)
+
+#define THROW_SYS_FILE_ERROR(message, filename, exception, subtype) \
+ THROW_SYS_ERROR_NUMBER(BOX_FILE_MESSAGE(filename, message), \
+ errno, exception, subtype)
+
+#define THROW_SYS_FILE_ERRNO(message, filename, error_number, exception, subtype) \
+ THROW_SYS_ERROR_NUMBER(BOX_FILE_MESSAGE(filename, message), \
+ error_number, exception, subtype)
+
+#define THROW_FILE_ERROR(message, filename, exception, subtype) \
+ THROW_EXCEPTION_MESSAGE(exception, subtype, \
+ BOX_FILE_MESSAGE(filename, message))
+>>>>>>> 0.12
#ifdef WIN32
#define BOX_LOG_WIN_ERROR(stuff) \
@@ -78,14 +126,40 @@ inline std::string GetNativeErrorMessage()
BOX_WARNING(stuff << ": " << GetErrorMessage(number))
#define BOX_LOG_NATIVE_ERROR(stuff) BOX_LOG_WIN_ERROR(stuff)
#define BOX_LOG_NATIVE_WARNING(stuff) BOX_LOG_WIN_WARNING(stuff)
+<<<<<<< HEAD
+=======
+ #define BOX_WIN_ERRNO_MESSAGE(error_number, stuff) \
+ stuff << ": " << GetErrorMessage(error_number)
+ #define THROW_WIN_ERROR_NUMBER(message, error_number, exception, subtype) \
+ THROW_EXCEPTION_MESSAGE(exception, subtype, \
+ BOX_WIN_ERRNO_MESSAGE(error_number, message))
+ #define THROW_WIN_FILE_ERRNO(message, filename, error_number, exception, subtype) \
+ THROW_WIN_ERROR_NUMBER(BOX_FILE_MESSAGE(filename, message), \
+ error_number, exception, subtype)
+ #define THROW_WIN_FILE_ERROR(message, filename, exception, subtype) \
+ THROW_WIN_FILE_ERRNO(message, filename, GetLastError(), \
+ exception, subtype)
+>>>>>>> 0.12
#else
#define BOX_LOG_NATIVE_ERROR(stuff) BOX_LOG_SYS_ERROR(stuff)
#define BOX_LOG_NATIVE_WARNING(stuff) BOX_LOG_SYS_WARNING(stuff)
#endif
+<<<<<<< HEAD
#define BOX_LOG_SOCKET_ERROR(_type, _name, _port, stuff) \
BOX_LOG_NATIVE_ERROR(stuff << " (type " << _type << ", name " << \
_name << ", port " << _port << ")")
+=======
+#ifdef WIN32
+# define BOX_LOG_SOCKET_ERROR(_type, _name, _port, stuff) \
+ BOX_LOG_WIN_ERROR_NUMBER(stuff << " (type " << _type << ", name " << \
+ _name << ", port " << _port << ")", WSAGetLastError())
+#else
+# define BOX_LOG_SOCKET_ERROR(_type, _name, _port, stuff) \
+ BOX_LOG_NATIVE_ERROR(stuff << " (type " << _type << ", name " << \
+ _name << ", port " << _port << ")")
+#endif
+>>>>>>> 0.12
#define BOX_FORMAT_HEX32(number) \
std::hex << \
@@ -110,6 +184,15 @@ inline std::string GetNativeErrorMessage()
std::setw(6) << \
timespec.tv_usec
+<<<<<<< HEAD
+=======
+#define BOX_FORMAT_MICROSECONDS(t) \
+ (int)((t) / 1000000) << "." << \
+ std::setw(6) << \
+ std::setfill('0') << \
+ (int)((t) % 1000000) << " seconds"
+
+>>>>>>> 0.12
#undef ERROR
namespace Log
@@ -157,8 +240,32 @@ class Logger
virtual const char* GetType() = 0;
Log::Level GetLevel() { return mCurrentLevel; }
+<<<<<<< HEAD
virtual void SetProgramName(const std::string& rProgramName) = 0;
+=======
+ bool IsEnabled(Log::Level level);
+
+ virtual void SetProgramName(const std::string& rProgramName) = 0;
+
+ class Guard
+ {
+ private:
+ Logger& mLogger;
+ Log::Level mOldLevel;
+
+ public:
+ Guard(Logger& Logger)
+ : mLogger(Logger)
+ {
+ mOldLevel = Logger.GetLevel();
+ }
+ ~Guard()
+ {
+ mLogger.Filter(mOldLevel);
+ }
+ };
+>>>>>>> 0.12
};
// --------------------------------------------------------------------------
@@ -266,22 +373,54 @@ class Logging
static void SetProgramName(const std::string& rProgramName);
static std::string GetProgramName() { return sProgramName; }
static void SetFacility(int facility);
+<<<<<<< HEAD
+=======
+ static Console& GetConsole() { return *spConsole; }
+ static Syslog& GetSyslog() { return *spSyslog; }
+>>>>>>> 0.12
class Guard
{
private:
Log::Level mOldLevel;
+<<<<<<< HEAD
+=======
+ static int sGuardCount;
+ static Log::Level sOriginalLevel;
+>>>>>>> 0.12
public:
Guard(Log::Level newLevel)
{
mOldLevel = Logging::GetGlobalLevel();
+<<<<<<< HEAD
+=======
+ if(sGuardCount == 0)
+ {
+ sOriginalLevel = mOldLevel;
+ }
+ sGuardCount++;
+>>>>>>> 0.12
Logging::SetGlobalLevel(newLevel);
}
~Guard()
{
+<<<<<<< HEAD
Logging::SetGlobalLevel(mOldLevel);
}
+=======
+ sGuardCount--;
+ Logging::SetGlobalLevel(mOldLevel);
+ }
+
+ static bool IsActive() { return (sGuardCount > 0); }
+ static Log::Level GetOriginalLevel() { return sOriginalLevel; }
+ static bool IsGuardingFrom(Log::Level originalLevel)
+ {
+ return IsActive() &&
+ (int)sOriginalLevel >= (int)originalLevel;
+ }
+>>>>>>> 0.12
};
class Tagger
@@ -290,15 +429,33 @@ class Logging
std::string mOldTag;
public:
+<<<<<<< HEAD
Tagger(const std::string& rTempTag)
{
mOldTag = Logging::GetProgramName();
+=======
+ Tagger()
+ : mOldTag(Logging::GetProgramName())
+ {
+ }
+ Tagger(const std::string& rTempTag)
+ : mOldTag(Logging::GetProgramName())
+ {
+>>>>>>> 0.12
Logging::SetProgramName(mOldTag + " " + rTempTag);
}
~Tagger()
{
Logging::SetProgramName(mOldTag);
}
+<<<<<<< HEAD
+=======
+
+ void Change(const std::string& newTempTag)
+ {
+ Logging::SetProgramName(mOldTag + " " + newTempTag);
+ }
+>>>>>>> 0.12
};
};
@@ -341,6 +498,33 @@ class HideExceptionMessageGuard
bool mOldHiddenState;
};
+<<<<<<< HEAD
+=======
+class HideSpecificExceptionGuard
+{
+ private:
+ std::pair<int, int> mExceptionCode;
+
+ public:
+ typedef std::vector<std::pair<int, int> > SuppressedExceptions_t;
+ static SuppressedExceptions_t sSuppressedExceptions;
+
+ HideSpecificExceptionGuard(int type, int subtype)
+ : mExceptionCode(std::pair<int, int>(type, subtype))
+ {
+ sSuppressedExceptions.push_back(mExceptionCode);
+ }
+ ~HideSpecificExceptionGuard()
+ {
+ SuppressedExceptions_t::reverse_iterator i =
+ sSuppressedExceptions.rbegin();
+ assert(*i == mExceptionCode);
+ sSuppressedExceptions.pop_back();
+ }
+ static bool IsHidden(int type, int subtype);
+};
+
+>>>>>>> 0.12
std::string PrintEscapedBinaryData(const std::string& rInput);
#endif // LOGGING__H
diff --git a/lib/common/MainHelper.h b/lib/common/MainHelper.h
index d91bc2f9..453b5b5f 100644
--- a/lib/common/MainHelper.h
+++ b/lib/common/MainHelper.h
@@ -12,7 +12,16 @@
#include <stdio.h>
+<<<<<<< HEAD
#include "BoxException.h"
+=======
+#ifdef NEED_BOX_VERSION_H
+# include "BoxVersion.h"
+#endif
+
+#include "BoxException.h"
+#include "Logging.h"
+>>>>>>> 0.12
#define MAINHELPER_START \
if(argc == 2 && ::strcmp(argv[1], "--version") == 0) \
@@ -20,6 +29,7 @@
MEMLEAKFINDER_INIT \
MEMLEAKFINDER_START \
try {
+<<<<<<< HEAD
#define MAINHELPER_END \
} catch(BoxException &e) { \
printf("Exception: %s (%d/%d)\n", e.what(), e.GetType(), e.GetSubType()); \
@@ -30,6 +40,17 @@
} catch(...) { \
printf("Exception: <UNKNOWN>\n"); \
return 1; }
+=======
+
+#define MAINHELPER_END \
+ } catch(std::exception &e) { \
+ BOX_FATAL(e.what()); \
+ return 1; \
+ } catch(...) { \
+ BOX_FATAL("UNKNOWN"); \
+ return 1; \
+ }
+>>>>>>> 0.12
#ifdef BOX_MEMORY_LEAK_TESTING
#define MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT(file, marker) \
diff --git a/lib/common/MemBlockStream.cpp b/lib/common/MemBlockStream.cpp
index 538a7ef8..554fc6ae 100644
--- a/lib/common/MemBlockStream.cpp
+++ b/lib/common/MemBlockStream.cpp
@@ -22,6 +22,23 @@
//
// Function
// Name: MemBlockStream::MemBlockStream()
+<<<<<<< HEAD
+=======
+// Purpose: Constructor with no contents
+// Created: 2012/11/07
+//
+// --------------------------------------------------------------------------
+MemBlockStream::MemBlockStream()
+: mpBuffer(NULL),
+ mBytesInBuffer(0),
+ mReadPosition(0)
+{ }
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: MemBlockStream::MemBlockStream()
+>>>>>>> 0.12
// Purpose: Constructor (doesn't copy block, careful with lifetimes)
// Created: 2003/09/05
//
@@ -69,7 +86,10 @@ MemBlockStream::MemBlockStream(const CollectInBufferStream &rBuffer)
ASSERT(mBytesInBuffer >= 0);
}
+<<<<<<< HEAD
+=======
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
// Function
diff --git a/lib/common/MemBlockStream.h b/lib/common/MemBlockStream.h
index f78ff8e6..ed0bf4ff 100644
--- a/lib/common/MemBlockStream.h
+++ b/lib/common/MemBlockStream.h
@@ -27,6 +27,10 @@ class CollectInBufferStream;
class MemBlockStream : public IOStream
{
public:
+<<<<<<< HEAD
+=======
+ MemBlockStream();
+>>>>>>> 0.12
MemBlockStream(const void *pBuffer, int Size);
MemBlockStream(const StreamableMemBlock &rBlock);
MemBlockStream(const CollectInBufferStream &rBuffer);
@@ -41,6 +45,11 @@ public:
virtual void Seek(pos_type Offset, int SeekType);
virtual bool StreamDataLeft();
virtual bool StreamClosed();
+<<<<<<< HEAD
+=======
+ virtual const void* GetBuffer() const { return mpBuffer; }
+ virtual int GetSize() const { return mBytesInBuffer; }
+>>>>>>> 0.12
private:
const char *mpBuffer;
diff --git a/lib/common/MemLeakFinder.h b/lib/common/MemLeakFinder.h
index ca207bd5..e4e9a1ae 100644
--- a/lib/common/MemLeakFinder.h
+++ b/lib/common/MemLeakFinder.h
@@ -28,6 +28,10 @@ class MemLeakSuppressionGuard
extern "C"
{
void *memleakfinder_malloc(size_t size, const char *file, int line);
+<<<<<<< HEAD
+=======
+ void *memleakfinder_calloc(size_t blocks, size_t size, const char *file, int line);
+>>>>>>> 0.12
void *memleakfinder_realloc(void *ptr, size_t size);
void memleakfinder_free(void *ptr);
}
@@ -36,6 +40,11 @@ void memleakfinder_init();
int memleakfinder_numleaks();
+<<<<<<< HEAD
+=======
+void memleakfinder_report_usage_summary();
+
+>>>>>>> 0.12
void memleakfinder_reportleaks();
void memleakfinder_reportleaks_appendfile(const char *filename, const char *markertext);
@@ -54,6 +63,10 @@ void *operator new[](size_t size, const char *file, int line);
// define the malloc functions now, if required
#ifdef MEMLEAKFINDER_FULL_MALLOC_MONITORING
#define malloc(X) memleakfinder_malloc(X, __FILE__, __LINE__)
+<<<<<<< HEAD
+=======
+ #define calloc(X, Y) memleakfinder_calloc(X, Y, __FILE__, __LINE__)
+>>>>>>> 0.12
#define realloc memleakfinder_realloc
#define free memleakfinder_free
#define MEMLEAKFINDER_MALLOC_MONITORING_DEFINED
diff --git a/lib/common/RateLimitingStream.cpp b/lib/common/RateLimitingStream.cpp
new file mode 100644
index 00000000..8876f146
--- /dev/null
+++ b/lib/common/RateLimitingStream.cpp
@@ -0,0 +1,95 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: RateLimitingStream.cpp
+// Purpose: Rate-limiting write-only wrapper around IOStreams
+// Created: 2011/01/11
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+#include "RateLimitingStream.h"
+#include "CommonException.h"
+
+#include <string.h>
+
+#include "MemLeakFindOn.h"
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: RateLimitingStream::RateLimitingStream(const char *, int, int)
+// Purpose: Constructor, set up buffer
+// Created: 2011/01/11
+//
+// --------------------------------------------------------------------------
+RateLimitingStream::RateLimitingStream(IOStream& rSink, size_t targetBytesPerSecond)
+: mrSink(rSink), mStartTime(GetCurrentBoxTime()), mTotalBytesRead(0),
+ mTargetBytesPerSecond(targetBytesPerSecond)
+{ }
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: RateLimitingStream::Read(void *pBuffer, int NBytes,
+// int Timeout)
+// Purpose: Reads bytes to the underlying stream at no more than
+// a fixed rate
+// Created: 2011/01/11
+//
+// --------------------------------------------------------------------------
+int RateLimitingStream::Read(void *pBuffer, int NBytes, int Timeout)
+{
+ if(NBytes > 0 && (size_t)NBytes > mTargetBytesPerSecond)
+ {
+ // Limit to one second's worth of data for performance
+ BOX_TRACE("Reducing read size from " << NBytes << " to " <<
+ mTargetBytesPerSecond << " to smooth upload rate");
+ NBytes = mTargetBytesPerSecond;
+ }
+
+ int bytesReadThisTime = mrSink.Read(pBuffer, NBytes, Timeout);
+
+ // How many bytes we will have written after this write finishes?
+ mTotalBytesRead += bytesReadThisTime;
+
+ // When should it be completed by?
+ box_time_t desiredFinishTime = mStartTime +
+ SecondsToBoxTime(mTotalBytesRead / mTargetBytesPerSecond);
+
+ // How long do we have to wait?
+ box_time_t currentTime = GetCurrentBoxTime();
+ int64_t waitTime = desiredFinishTime - currentTime;
+
+ // How are we doing so far? (for logging only)
+ box_time_t currentDuration = currentTime - mStartTime;
+
+ // in case our timer is not very accurate, don't divide by zero on first pass
+ if(currentDuration == 0)
+ {
+ BOX_TRACE("Current rate not yet known, sending immediately");
+ return bytesReadThisTime;
+ }
+
+ uint64_t effectiveRateSoFar = (mTotalBytesRead * MICRO_SEC_IN_SEC_LL)
+ / currentDuration;
+
+ if(waitTime > 0)
+ {
+ BOX_TRACE("Current rate " << effectiveRateSoFar <<
+ " higher than desired rate " << mTargetBytesPerSecond <<
+ ", sleeping for " << BoxTimeToMilliSeconds(waitTime) <<
+ " ms");
+ ShortSleep(waitTime, false);
+ }
+ else
+ {
+ BOX_TRACE("Current rate " << effectiveRateSoFar <<
+ " lower than desired rate " << mTargetBytesPerSecond <<
+ ", sending immediately (would have sent " <<
+ (BoxTimeToMilliSeconds(-waitTime)) << " ms ago)");
+ }
+
+ return bytesReadThisTime;
+}
+
diff --git a/lib/common/RateLimitingStream.h b/lib/common/RateLimitingStream.h
new file mode 100644
index 00000000..a322b99b
--- /dev/null
+++ b/lib/common/RateLimitingStream.h
@@ -0,0 +1,71 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: RateLimitingStream.h
+// Purpose: Rate-limiting write-only wrapper around IOStreams
+// Created: 2011/01/11
+//
+// --------------------------------------------------------------------------
+
+#ifndef RATELIMITINGSTREAM__H
+#define RATELIMITINGSTREAM__H
+
+#include "BoxTime.h"
+#include "IOStream.h"
+
+class RateLimitingStream : public IOStream
+{
+private:
+ IOStream& mrSink;
+ box_time_t mStartTime;
+ uint64_t mTotalBytesRead;
+ size_t mTargetBytesPerSecond;
+
+public:
+ RateLimitingStream(IOStream& rSink, size_t targetBytesPerSecond);
+ virtual ~RateLimitingStream() { }
+
+ // This is the only magic
+ virtual int Read(void *pBuffer, int NBytes,
+ int Timeout = IOStream::TimeOutInfinite);
+
+ // Everything else is delegated to the sink
+ virtual void Write(const void *pBuffer, int NBytes)
+ {
+ Write(pBuffer, NBytes);
+ }
+ virtual pos_type BytesLeftToRead()
+ {
+ return mrSink.BytesLeftToRead();
+ }
+ virtual pos_type GetPosition() const
+ {
+ return mrSink.GetPosition();
+ }
+ virtual void Seek(IOStream::pos_type Offset, int SeekType)
+ {
+ mrSink.Seek(Offset, SeekType);
+ }
+ virtual void Flush(int Timeout = IOStream::TimeOutInfinite)
+ {
+ mrSink.Flush(Timeout);
+ }
+ virtual void Close()
+ {
+ mrSink.Close();
+ }
+ virtual bool StreamDataLeft()
+ {
+ return mrSink.StreamDataLeft();
+ }
+ virtual bool StreamClosed()
+ {
+ return mrSink.StreamClosed();
+ }
+
+private:
+ RateLimitingStream(const RateLimitingStream &rToCopy)
+ : mrSink(rToCopy.mrSink) { /* do not call */ }
+};
+
+#endif // RATELIMITINGSTREAM__H
diff --git a/lib/common/StreamableMemBlock.cpp b/lib/common/StreamableMemBlock.cpp
index cf431022..ce2e1f98 100644
--- a/lib/common/StreamableMemBlock.cpp
+++ b/lib/common/StreamableMemBlock.cpp
@@ -3,6 +3,10 @@
// File
// Name: StreamableMemBlock.cpp
// Purpose: Memory blocks which can be loaded and saved from streams
+<<<<<<< HEAD
+=======
+// with a header indicating the size of the block.
+>>>>>>> 0.12
// Created: 2003/09/05
//
// --------------------------------------------------------------------------
diff --git a/lib/common/StreamableMemBlock.h b/lib/common/StreamableMemBlock.h
index 250c0aea..3fcc62b3 100644
--- a/lib/common/StreamableMemBlock.h
+++ b/lib/common/StreamableMemBlock.h
@@ -2,7 +2,12 @@
//
// File
// Name: StreamableMemBlock.h
+<<<<<<< HEAD
// Purpose: Memory blocks which can be loaded and saved from streams
+=======
+// Purpose: Memory blocks which can be loaded and saved from streams,
+// with a header indicating the size of the block.
+>>>>>>> 0.12
// Created: 2003/09/05
//
// --------------------------------------------------------------------------
diff --git a/lib/common/Test.cpp b/lib/common/Test.cpp
index 56638058..b627ac18 100644
--- a/lib/common/Test.cpp
+++ b/lib/common/Test.cpp
@@ -21,6 +21,10 @@
#include <unistd.h>
#endif
+<<<<<<< HEAD
+=======
+#include "BoxTime.h"
+>>>>>>> 0.12
#include "Test.h"
bool TestFileExists(const char *Filename)
@@ -43,10 +47,17 @@ bool TestDirExists(const char *Filename)
}
// -1 if doesn't exist
+<<<<<<< HEAD
int TestGetFileSize(const char *Filename)
{
EMU_STRUCT_STAT st;
if(EMU_STAT(Filename, &st) == 0)
+=======
+int TestGetFileSize(const std::string& Filename)
+{
+ EMU_STRUCT_STAT st;
+ if(EMU_STAT(Filename.c_str(), &st) == 0)
+>>>>>>> 0.12
{
return st.st_size;
}
@@ -451,6 +462,7 @@ void wait_for_operation(int seconds, const char* message)
void safe_sleep(int seconds)
{
+<<<<<<< HEAD
BOX_TRACE("sleeping for " << seconds << " seconds");
#ifdef WIN32
@@ -482,5 +494,8 @@ void safe_sleep(int seconds)
"sleeping again");
}
#endif
+=======
+ ShortSleep(SecondsToBoxTime(seconds), true);
+>>>>>>> 0.12
}
diff --git a/lib/common/Test.h b/lib/common/Test.h
index 08ba4542..a09a64af 100644
--- a/lib/common/Test.h
+++ b/lib/common/Test.h
@@ -51,6 +51,7 @@ extern std::string bbackupd_args, bbstored_args, bbackupquery_args, test_args;
#define TEST_THAT_ABORTONFAIL(condition) {if(!(condition)) TEST_ABORT_WITH_MESSAGE("Condition [" #condition "] failed")}
// NOTE: The 0- bit is to allow this to work with stuff which has negative constants for flags (eg ConnectionException)
+<<<<<<< HEAD
#define TEST_CHECK_THROWS(statement, excepttype, subtype) \
{ \
bool didthrow = false; \
@@ -76,6 +77,35 @@ extern std::string bbackupd_args, bbstored_args, bbackupquery_args, test_args;
{ \
TEST_FAIL_WITH_MESSAGE("Didn't throw exception " #excepttype "(" #subtype ")") \
} \
+=======
+#define TEST_CHECK_THROWS(statement, excepttype, subtype) \
+ { \
+ bool didthrow = false; \
+ HideExceptionMessageGuard hide; \
+ BOX_TRACE("Exception logging disabled at " __FILE__ ":" \
+ << __LINE__); \
+ try \
+ { \
+ statement; \
+ } \
+ catch(excepttype &e) \
+ { \
+ if(e.GetSubType() != ((unsigned int)excepttype::subtype) \
+ && e.GetSubType() != (unsigned int)(0-excepttype::subtype)) \
+ { \
+ throw; \
+ } \
+ didthrow = true; \
+ } \
+ catch(...) \
+ { \
+ throw; \
+ } \
+ if(!didthrow) \
+ { \
+ TEST_FAIL_WITH_MESSAGE("Didn't throw exception " #excepttype "(" #subtype ")") \
+ } \
+>>>>>>> 0.12
}
// utility macro for comparing two strings in a line
@@ -91,7 +121,11 @@ extern std::string bbackupd_args, bbstored_args, bbackupquery_args, test_args;
\
if(_exp_str != _found_str) \
{ \
+<<<<<<< HEAD
BOX_WARNING("Expected <" << _exp_str << "> but found <" << \
+=======
+ BOX_ERROR("Expected <" << _exp_str << "> but found <" << \
+>>>>>>> 0.12
_found_str << ">"); \
\
std::ostringstream _oss3; \
@@ -127,20 +161,34 @@ extern std::string bbackupd_args, bbstored_args, bbackupquery_args, test_args;
} \
}
+<<<<<<< HEAD
+=======
+>>>>>>> 0.12
// utility macro for testing a line
#define TEST_LINE(_condition, _line) \
TEST_THAT(_condition); \
if (!(_condition)) \
{ \
+<<<<<<< HEAD
printf("Test failed on <%s>\n", _line.c_str()); \
+=======
+ std::ostringstream _ossl; \
+ _ossl << _line; \
+ std::string _line_str = _ossl.str(); \
+ printf("Test failed on <%s>\n", _line_str.c_str()); \
+>>>>>>> 0.12
}
bool TestFileExists(const char *Filename);
bool TestDirExists(const char *Filename);
// -1 if doesn't exist
+<<<<<<< HEAD
int TestGetFileSize(const char *Filename);
+=======
+int TestGetFileSize(const std::string& Filename);
+>>>>>>> 0.12
std::string ConvertPaths(const std::string& rOriginal);
int RunCommand(const std::string& rCommandLine);
bool ServerIsAlive(int pid);
diff --git a/lib/common/Timer.cpp b/lib/common/Timer.cpp
index 137ad45f..390ddf8e 100644
--- a/lib/common/Timer.cpp
+++ b/lib/common/Timer.cpp
@@ -8,12 +8,25 @@
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
#ifdef WIN32
#define _WIN32_WINNT 0x0500
#endif
#include "Box.h"
+=======
+#include "Box.h"
+
+#ifdef WIN32
+# ifndef _WIN32_WINNT
+# define _WIN32_WINNT 0x0500
+# elif _WIN32_WINNT < 0x0500
+# error Timers require at least Windows 2000 headers
+# endif
+#endif
+
+>>>>>>> 0.12
#include <signal.h>
#include <cstring>
@@ -26,7 +39,11 @@ std::vector<Timer*>* Timers::spTimers = NULL;
bool Timers::sRescheduleNeeded = false;
#define TIMER_ID "timer " << mName << " (" << this << ") "
+<<<<<<< HEAD
#define TIMER_ID_OF(t) "timer " << (t).GetName() << " (" << &(t) << ") "
+=======
+#define TIMER_ID_OF(t) "timer " << (t).GetName() << " (" << &(t) << ")"
+>>>>>>> 0.12
typedef void (*sighandler_t)(int);
@@ -115,6 +132,10 @@ void Timers::Add(Timer& rTimer)
{
ASSERT(spTimers);
ASSERT(&rTimer);
+<<<<<<< HEAD
+=======
+ BOX_TRACE(TIMER_ID_OF(rTimer) " added to global queue, rescheduling");
+>>>>>>> 0.12
spTimers->push_back(&rTimer);
Reschedule();
}
@@ -132,6 +153,10 @@ void Timers::Remove(Timer& rTimer)
{
ASSERT(spTimers);
ASSERT(&rTimer);
+<<<<<<< HEAD
+=======
+ BOX_TRACE(TIMER_ID_OF(rTimer) " removed from global queue, rescheduling");
+>>>>>>> 0.12
bool restart = true;
while (restart)
@@ -166,10 +191,13 @@ void Timers::RescheduleIfNeeded()
}
}
+<<<<<<< HEAD
#define FORMAT_MICROSECONDS(t) \
(int)(t / 1000000) << "." << \
(int)(t % 1000000) << " seconds"
+=======
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
// Function
@@ -232,6 +260,7 @@ void Timers::Reschedule()
if (timeToExpiry <= 0)
{
+<<<<<<< HEAD
/*
BOX_TRACE("timer " << *i << " has expired, "
"triggering it");
@@ -239,6 +268,11 @@ void Timers::Reschedule()
BOX_TRACE(TIMER_ID_OF(**i) "has expired, "
"triggering " <<
FORMAT_MICROSECONDS(-timeToExpiry) <<
+=======
+ BOX_TRACE(TIMER_ID_OF(**i) " has expired, "
+ "triggering " <<
+ BOX_FORMAT_MICROSECONDS(-timeToExpiry) <<
+>>>>>>> 0.12
" late");
rTimer.OnExpire();
spTimers->erase(i);
@@ -248,8 +282,13 @@ void Timers::Reschedule()
else
{
/*
+<<<<<<< HEAD
BOX_TRACE("timer " << *i << " has not "
"expired, triggering in " <<
+=======
+ BOX_TRACE(TIMER_ID_OF(**i) " has not expired, "
+ "triggering in " <<
+>>>>>>> 0.12
FORMAT_MICROSECONDS(timeToExpiry) <<
" seconds");
*/
@@ -290,8 +329,13 @@ void Timers::Reschedule()
}
else
{
+<<<<<<< HEAD
BOX_TRACE("timer: next event: " << nameOfNextEvent <<
" expires in " << FORMAT_MICROSECONDS(timeToNextEvent));
+=======
+ BOX_TRACE("timer: next event: " << nameOfNextEvent << " at " <<
+ FormatTime(timeNow + timeToNextEvent, false, true));
+>>>>>>> 0.12
}
struct itimerval timeout;
@@ -331,7 +375,11 @@ void Timers::SignalHandler(int unused)
// --------------------------------------------------------------------------
//
// Function
+<<<<<<< HEAD
// Name: Timer::Timer(size_t timeoutSecs,
+=======
+// Name: Timer::Timer(size_t timeoutMillis,
+>>>>>>> 0.12
// const std::string& rName)
// Purpose: Standard timer constructor, takes a timeout in
// seconds from now, and an optional name for
@@ -340,14 +388,20 @@ void Timers::SignalHandler(int unused)
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
Timer::Timer(size_t timeoutSecs, const std::string& rName)
: mExpires(GetCurrentBoxTime() + SecondsToBoxTime(timeoutSecs)),
+=======
+Timer::Timer(size_t timeoutMillis, const std::string& rName)
+: mExpires(0),
+>>>>>>> 0.12
mExpired(false),
mName(rName)
#ifdef WIN32
, mTimerHandle(INVALID_HANDLE_VALUE)
#endif
{
+<<<<<<< HEAD
#ifndef BOX_RELEASE_BUILD
if (timeoutSecs == 0)
{
@@ -370,72 +424,125 @@ Timer::Timer(size_t timeoutSecs, const std::string& rName)
Timers::Add(*this);
Start(timeoutSecs * MICRO_SEC_IN_SEC_LL);
}
+=======
+ Set(timeoutMillis, true /* isInit */);
+>>>>>>> 0.12
}
// --------------------------------------------------------------------------
//
// Function
// Name: Timer::Start()
+<<<<<<< HEAD
// Purpose: This internal function initialises an OS TimerQueue
// timer on Windows, while on Unixes there is only a
// single global timer, managed by the Timers class,
// so this method does nothing.
+=======
+// Purpose: This internal function recalculates the remaining
+// time (timeout) from the expiry time, and then calls
+// Start(timeoutMillis).
+>>>>>>> 0.12
// Created: 27/07/2008
//
// --------------------------------------------------------------------------
void Timer::Start()
{
+<<<<<<< HEAD
#ifdef WIN32
+=======
+>>>>>>> 0.12
box_time_t timeNow = GetCurrentBoxTime();
int64_t timeToExpiry = mExpires - timeNow;
if (timeToExpiry <= 0)
{
BOX_WARNING(TIMER_ID << "fudging expiry from -" <<
+<<<<<<< HEAD
FORMAT_MICROSECONDS(-timeToExpiry))
timeToExpiry = 1;
}
Start(timeToExpiry);
#endif
+=======
+ BOX_FORMAT_MICROSECONDS(-timeToExpiry))
+ timeToExpiry = 1;
+ }
+
+ Start(timeToExpiry / MICRO_SEC_IN_MILLI_SEC);
+>>>>>>> 0.12
}
// --------------------------------------------------------------------------
//
// Function
+<<<<<<< HEAD
// Name: Timer::Start(int64_t delayInMicros)
// Purpose: This internal function initialises an OS TimerQueue
// timer on Windows, with a specified delay already
// calculated to save us doing it again. Like
// Timer::Start(), on Unixes it does nothing.
+=======
+// Name: Timer::Start(int64_t timeoutMillis)
+// Purpose: This internal function adds this timer to the global
+// timer list, and on Windows it initialises an OS
+// TimerQueue timer for it.
+>>>>>>> 0.12
// Created: 27/07/2008
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
void Timer::Start(int64_t delayInMicros)
{
+=======
+void Timer::Start(int64_t timeoutMillis)
+{
+ ASSERT(mExpires != 0);
+ Timers::Add(*this);
+
+>>>>>>> 0.12
#ifdef WIN32
// only call me once!
ASSERT(mTimerHandle == INVALID_HANDLE_VALUE);
+<<<<<<< HEAD
int64_t delayInMillis = delayInMicros / 1000;
+=======
+>>>>>>> 0.12
// Windows XP always seems to fire timers up to 20 ms late,
// at least on my test laptop. Not critical in practice, but our
// tests are precise enough that they will fail if we don't
// correct for it.
+<<<<<<< HEAD
delayInMillis -= 20;
// Set a system timer to call our timer routine
if (CreateTimerQueueTimer(&mTimerHandle, NULL, TimerRoutine,
(PVOID)this, delayInMillis, 0, WT_EXECUTEINTIMERTHREAD)
+=======
+ timeoutMillis -= 20;
+
+ // Set a system timer to call our timer routine
+ if (CreateTimerQueueTimer(&mTimerHandle, NULL, TimerRoutine,
+ (PVOID)this, timeoutMillis, 0, WT_EXECUTEINTIMERTHREAD)
+>>>>>>> 0.12
== FALSE)
{
BOX_ERROR(TIMER_ID "failed to create timer: " <<
GetErrorMessage(GetLastError()));
mTimerHandle = INVALID_HANDLE_VALUE;
}
+<<<<<<< HEAD
+=======
+ else
+ {
+ BOX_INFO(TIMER_ID << "set for " << timeoutMillis << " ms");
+ }
+>>>>>>> 0.12
#endif
}
@@ -443,15 +550,29 @@ void Timer::Start(int64_t delayInMicros)
//
// Function
// Name: Timer::Stop()
+<<<<<<< HEAD
// Purpose: This internal function deletes the associated OS
// TimerQueue timer on Windows, and on Unixes does
// nothing.
+=======
+// Purpose: This internal function removes us from the global
+// list of timers, resets our expiry time, and on
+// Windows it deletes the associated OS TimerQueue timer.
+>>>>>>> 0.12
// Created: 27/07/2008
//
// --------------------------------------------------------------------------
void Timer::Stop()
{
+<<<<<<< HEAD
+=======
+ if (mExpires != 0)
+ {
+ Timers::Remove(*this);
+ }
+
+>>>>>>> 0.12
#ifdef WIN32
if (mTimerHandle != INVALID_HANDLE_VALUE)
{
@@ -481,10 +602,37 @@ Timer::~Timer()
BOX_TRACE(TIMER_ID "destroyed");
#endif
+<<<<<<< HEAD
Timers::Remove(*this);
Stop();
}
+=======
+ Stop();
+}
+
+void Timer::LogAssignment(const Timer &From)
+{
+ #ifndef BOX_RELEASE_BUILD
+ BOX_TRACE(TIMER_ID "initialised from " << TIMER_ID_OF(From));
+
+ if (From.mExpired)
+ {
+ BOX_TRACE(TIMER_ID "already expired, will not fire");
+ }
+ else if (From.mExpires == 0)
+ {
+ BOX_TRACE(TIMER_ID "has no expiry time, will not fire");
+ }
+ else
+ {
+ BOX_TRACE(TIMER_ID "will fire at " <<
+ FormatTime(From.mExpires, false, true));
+ }
+ #endif
+}
+
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
// Function
@@ -504,6 +652,7 @@ Timer::Timer(const Timer& rToCopy)
, mTimerHandle(INVALID_HANDLE_VALUE)
#endif
{
+<<<<<<< HEAD
#ifndef BOX_RELEASE_BUILD
if (mExpired)
{
@@ -527,6 +676,12 @@ Timer::Timer(const Timer& rToCopy)
if (!mExpired && mExpires != 0)
{
Timers::Add(*this);
+=======
+ LogAssignment(rToCopy);
+
+ if (!mExpired && mExpires != 0)
+ {
+>>>>>>> 0.12
Start();
}
}
@@ -545,6 +700,7 @@ Timer::Timer(const Timer& rToCopy)
Timer& Timer::operator=(const Timer& rToCopy)
{
+<<<<<<< HEAD
#ifndef BOX_RELEASE_BUILD
if (rToCopy.mExpired)
{
@@ -566,6 +722,10 @@ Timer& Timer::operator=(const Timer& rToCopy)
#endif
Timers::Remove(*this);
+=======
+ LogAssignment(rToCopy);
+
+>>>>>>> 0.12
Stop();
mExpires = rToCopy.mExpires;
@@ -574,7 +734,10 @@ Timer& Timer::operator=(const Timer& rToCopy)
if (!mExpired && mExpires != 0)
{
+<<<<<<< HEAD
Timers::Add(*this);
+=======
+>>>>>>> 0.12
Start();
}
@@ -584,6 +747,70 @@ Timer& Timer::operator=(const Timer& rToCopy)
// --------------------------------------------------------------------------
//
// Function
+<<<<<<< HEAD
+=======
+// Name: Timer::Reset(size_t timeoutMillis)
+// Purpose: Simple reset operation for an existing Timer. Avoids
+// the need to create a temporary timer just to modify
+// an existing one.
+// Created: 17/11/2012
+//
+// --------------------------------------------------------------------------
+
+void Timer::Reset(size_t timeoutMillis)
+{
+ Set(timeoutMillis, false /* isInit */);
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Timer::Reset(size_t timeoutMillis)
+// Purpose: Internal set/reset operation for an existing Timer.
+// Shared by constructor and Reset().
+// Created: 17/11/2012
+//
+// --------------------------------------------------------------------------
+
+void Timer::Set(size_t timeoutMillis, bool isInit)
+{
+ Stop();
+ mExpired = false;
+
+ if (timeoutMillis == 0)
+ {
+ mExpires = 0;
+ }
+ else
+ {
+ mExpires = GetCurrentBoxTime() +
+ MilliSecondsToBoxTime(timeoutMillis);
+ }
+
+ #ifndef BOX_RELEASE_BUILD
+ if (timeoutMillis == 0)
+ {
+ BOX_TRACE(TIMER_ID << (isInit ? "initialised" : "reset") <<
+ " for " << timeoutMillis << " ms, will not fire");
+ }
+ else
+ {
+ BOX_TRACE(TIMER_ID << (isInit ? "initialised" : "reset") <<
+ " for " << timeoutMillis << " ms, to fire at " <<
+ FormatTime(mExpires, false, true));
+ }
+ #endif
+
+ if (mExpires != 0)
+ {
+ Start(timeoutMillis);
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+>>>>>>> 0.12
// Name: Timer::OnExpire()
// Purpose: Method called by Timers::Reschedule (on Unixes)
// on next poll after timer expires, or from
diff --git a/lib/common/Timer.h b/lib/common/Timer.h
index 42b2e00f..d3e46b2a 100644
--- a/lib/common/Timer.h
+++ b/lib/common/Timer.h
@@ -53,7 +53,11 @@ class Timers
class Timer
{
public:
+<<<<<<< HEAD
Timer(size_t timeoutSecs, const std::string& rName = "");
+=======
+ Timer(size_t timeoutMillis, const std::string& rName = "");
+>>>>>>> 0.12
virtual ~Timer();
Timer(const Timer &);
Timer &operator=(const Timer &);
@@ -67,6 +71,10 @@ public:
}
const std::string& GetName() const { return mName; }
+<<<<<<< HEAD
+=======
+ virtual void Reset(size_t timeoutMillis);
+>>>>>>> 0.12
private:
box_time_t mExpires;
@@ -74,8 +82,15 @@ private:
std::string mName;
void Start();
+<<<<<<< HEAD
void Start(int64_t delayInMicros);
void Stop();
+=======
+ void Start(int64_t timeoutMillis);
+ void Stop();
+ void LogAssignment(const Timer &From);
+ virtual void Set(size_t timeoutMillis, bool isReset);
+>>>>>>> 0.12
#ifdef WIN32
HANDLE mTimerHandle;
diff --git a/lib/common/UnixUser.cpp b/lib/common/UnixUser.cpp
index f81b474c..7e69b10a 100644
--- a/lib/common/UnixUser.cpp
+++ b/lib/common/UnixUser.cpp
@@ -31,13 +31,21 @@
// Created: 21/1/04
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
UnixUser::UnixUser(const char *Username)
+=======
+UnixUser::UnixUser(const std::string& Username)
+>>>>>>> 0.12
: mUID(0),
mGID(0),
mRevertOnDestruction(false)
{
// Get password info
+<<<<<<< HEAD
struct passwd *pwd = ::getpwnam(Username);
+=======
+ struct passwd *pwd = ::getpwnam(Username.c_str());
+>>>>>>> 0.12
if(pwd == 0)
{
THROW_EXCEPTION(CommonException, CouldNotLookUpUsername)
diff --git a/lib/common/UnixUser.h b/lib/common/UnixUser.h
index c895eb2a..361971b8 100644
--- a/lib/common/UnixUser.h
+++ b/lib/common/UnixUser.h
@@ -13,7 +13,11 @@
class UnixUser
{
public:
+<<<<<<< HEAD
UnixUser(const char *Username);
+=======
+ UnixUser(const std::string& Username);
+>>>>>>> 0.12
UnixUser(uid_t UID, gid_t GID);
~UnixUser();
private:
diff --git a/lib/common/Utils.cpp b/lib/common/Utils.cpp
index 6f21330d..3137d980 100644
--- a/lib/common/Utils.cpp
+++ b/lib/common/Utils.cpp
@@ -24,9 +24,23 @@
#include <cxxabi.h>
#endif
+<<<<<<< HEAD
#include "Utils.h"
#include "CommonException.h"
#include "Logging.h"
+=======
+#ifdef HAVE_DLFCN_H
+ #include <dlfcn.h>
+#endif
+
+#ifdef NEED_BOX_VERSION_H
+# include "BoxVersion.h"
+#endif
+
+#include "CommonException.h"
+#include "Logging.h"
+#include "Utils.h"
+>>>>>>> 0.12
#include "MemLeakFindOn.h"
@@ -73,16 +87,84 @@ void SplitString(const std::string &String, char SplitOn, std::vector<std::strin
}
#ifdef SHOW_BACKTRACE_ON_EXCEPTION
+<<<<<<< HEAD
void DumpStackBacktrace()
{
void *array[10];
size_t size = backtrace (array, 10);
char **strings = backtrace_symbols (array, size);
+=======
+static std::string demangle(const std::string& mangled_name)
+{
+ #ifdef HAVE_CXXABI_H
+ int status;
+
+#include "MemLeakFindOff.h"
+ char* result = abi::__cxa_demangle(mangled_name.c_str(),
+ NULL, NULL, &status);
+#include "MemLeakFindOn.h"
+
+ if (result == NULL)
+ {
+ if (status == 0)
+ {
+ BOX_WARNING("Demangle failed but no error: " <<
+ mangled_name);
+ }
+ else if (status == -1)
+ {
+ BOX_WARNING("Demangle failed with "
+ "memory allocation error: " <<
+ mangled_name);
+ }
+ else if (status == -2)
+ {
+ // Probably non-C++ name, don't demangle
+ /*
+ BOX_WARNING("Demangle failed with "
+ "with invalid name: " <<
+ mangled_name);
+ */
+ }
+ else if (status == -3)
+ {
+ BOX_WARNING("Demangle failed with "
+ "with invalid argument: " <<
+ mangled_name);
+ }
+ else
+ {
+ BOX_WARNING("Demangle failed with "
+ "with unknown error " << status <<
+ ": " << mangled_name);
+ }
+
+ return std::string(mangled_name);
+ }
+ else
+ {
+ std::string output = result;
+#include "MemLeakFindOff.h"
+ free(result);
+#include "MemLeakFindOn.h"
+ return output;
+ }
+ #else // !HAVE_CXXABI_H
+ return mangled_name;
+ #endif // HAVE_CXXABI_H
+}
+
+void DumpStackBacktrace()
+{
+ void *array[10];
+ size_t size = backtrace(array, 10);
+>>>>>>> 0.12
BOX_TRACE("Obtained " << size << " stack frames.");
for(size_t i = 0; i < size; i++)
{
+<<<<<<< HEAD
// Demangling code copied from
// cctbx_sources/boost_adaptbx/meta_ext.cpp, BSD license
@@ -155,6 +237,40 @@ void DumpStackBacktrace()
#include "MemLeakFindOn.h"
}
#endif
+=======
+ std::ostringstream output;
+ output << "Stack frame " << i << ": ";
+
+ #ifdef HAVE_DLADDR
+ Dl_info info;
+ int result = dladdr(array[i], &info);
+
+ if(result == 0)
+ {
+ BOX_LOG_SYS_WARNING("Failed to resolve "
+ "backtrace address " << array[i]);
+ output << "unresolved address " << array[i];
+ }
+ else if(info.dli_sname == NULL)
+ {
+ output << "unknown address " << array[i];
+ }
+ else
+ {
+ uint64_t diff = (uint64_t) array[i];
+ diff -= (uint64_t) info.dli_saddr;
+ output << demangle(info.dli_sname) << "+" <<
+ (void *)diff;
+ }
+ #else
+ output << "address " << array[i];
+ #endif // HAVE_DLADDR
+
+ BOX_TRACE(output.str());
+ }
+}
+#endif // SHOW_BACKTRACE_ON_EXCEPTION
+>>>>>>> 0.12
@@ -313,3 +429,34 @@ std::string FormatUsageLineStart(const std::string& rName,
return result.str();
}
+<<<<<<< HEAD
+=======
+
+std::string BoxGetTemporaryDirectoryName()
+{
+#ifdef WIN32
+ // http://msdn.microsoft.com/library/default.asp?
+ // url=/library/en-us/fileio/fs/creating_and_using_a_temporary_file.asp
+
+ DWORD dwRetVal;
+ char lpPathBuffer[1024];
+ DWORD dwBufSize = sizeof(lpPathBuffer);
+
+ // Get the temp path.
+ dwRetVal = GetTempPath(dwBufSize, // length of the buffer
+ lpPathBuffer); // buffer for path
+ if (dwRetVal > dwBufSize)
+ {
+ THROW_EXCEPTION(CommonException, TempDirPathTooLong)
+ }
+
+ return std::string(lpPathBuffer);
+#elif defined TEMP_DIRECTORY_NAME
+ return std::string(TEMP_DIRECTORY_NAME);
+#else
+ #error non-static temporary directory names not supported yet
+#endif
+}
+
+
+>>>>>>> 0.12
diff --git a/lib/common/Utils.h b/lib/common/Utils.h
index 8d98a520..8a938981 100644
--- a/lib/common/Utils.h
+++ b/lib/common/Utils.h
@@ -39,6 +39,11 @@ std::string FormatUsageBar(int64_t Blocks, int64_t Bytes, int64_t Max,
std::string FormatUsageLineStart(const std::string& rName,
bool MachineReadable);
+<<<<<<< HEAD
+=======
+std::string BoxGetTemporaryDirectoryName();
+
+>>>>>>> 0.12
#include "MemLeakFindOff.h"
#endif // UTILS__H
diff --git a/lib/common/ZeroStream.cpp b/lib/common/ZeroStream.cpp
index 9d87d76a..26fd62cb 100644
--- a/lib/common/ZeroStream.cpp
+++ b/lib/common/ZeroStream.cpp
@@ -152,7 +152,11 @@ void ZeroStream::Close()
// --------------------------------------------------------------------------
bool ZeroStream::StreamDataLeft()
{
+<<<<<<< HEAD
return false;
+=======
+ return (BytesLeftToRead() > 0);
+>>>>>>> 0.12
}
// --------------------------------------------------------------------------
diff --git a/lib/common/makeexception.pl.in b/lib/common/makeexception.pl.in
index 76b9b02b..593740da 100755
--- a/lib/common/makeexception.pl.in
+++ b/lib/common/makeexception.pl.in
@@ -191,7 +191,11 @@ unsigned int ${class}Exception::GetSubType() const throw()
const char *${class}Exception::what() const throw()
{
#ifdef EXCEPTION_CODENAMES_EXTENDED
+<<<<<<< HEAD
if(mSubType < 0 || mSubType > (sizeof(whats) / sizeof(whats[0])))
+=======
+ if(mSubType > (sizeof(whats) / sizeof(whats[0])))
+>>>>>>> 0.12
{
return "${class}";
}
diff --git a/lib/crypto/CipherAES.h b/lib/crypto/CipherAES.h
index 50b96dc3..dbcce9e5 100644
--- a/lib/crypto/CipherAES.h
+++ b/lib/crypto/CipherAES.h
@@ -37,6 +37,18 @@ public:
// Setup any other parameters
virtual void SetupParameters(EVP_CIPHER_CTX *pCipherContext) const;
+<<<<<<< HEAD
+=======
+ virtual std::string GetCipherName() const
+ {
+ std::ostringstream out;
+ out << "AES";
+ out << mKeyLength;
+ return out.str();
+ }
+ virtual CipherMode GetCipherMode() const { return mMode; }
+
+>>>>>>> 0.12
private:
CipherDescription::CipherMode mMode;
const void *mpKey;
diff --git a/lib/crypto/CipherBlowfish.h b/lib/crypto/CipherBlowfish.h
index b3bcf028..27903a3e 100644
--- a/lib/crypto/CipherBlowfish.h
+++ b/lib/crypto/CipherBlowfish.h
@@ -38,6 +38,18 @@ public:
// Setup any other parameters
virtual void SetupParameters(EVP_CIPHER_CTX *pCipherContext) const;
+<<<<<<< HEAD
+=======
+ virtual std::string GetCipherName() const
+ {
+ std::ostringstream out;
+ out << "AES";
+ out << mKeyLength;
+ return out.str();
+ }
+ virtual CipherMode GetCipherMode() const { return mMode; }
+
+>>>>>>> 0.12
#ifdef HAVE_OLD_SSL
CipherDescription *Clone() const;
void SetIV(const void *pIV);
diff --git a/lib/crypto/CipherContext.cpp b/lib/crypto/CipherContext.cpp
index e5cd9b0e..6621f79a 100644
--- a/lib/crypto/CipherContext.cpp
+++ b/lib/crypto/CipherContext.cpp
@@ -13,6 +13,10 @@
#include "CipherContext.h"
#include "CipherDescription.h"
#include "CipherException.h"
+<<<<<<< HEAD
+=======
+#include "CryptoUtils.h"
+>>>>>>> 0.12
#include "Random.h"
#include "MemLeakFindOn.h"
@@ -26,12 +30,21 @@
//
// --------------------------------------------------------------------------
CipherContext::CipherContext()
+<<<<<<< HEAD
: mInitialised(false),
mWithinTransform(false),
mPaddingOn(true)
#ifdef HAVE_OLD_SSL
, mFunction(Decrypt),
mpDescription(0)
+=======
+: mInitialised(false),
+ mWithinTransform(false),
+ mPaddingOn(true),
+ mFunction(None)
+#ifdef HAVE_OLD_SSL
+, mpDescription(0)
+>>>>>>> 0.12
#endif
{
}
@@ -64,6 +77,31 @@ CipherContext::~CipherContext()
// --------------------------------------------------------------------------
//
// Function
+<<<<<<< HEAD
+=======
+// Name: CipherContext::LogError(const std::string& operation)
+// Purpose: Logs and clears any OpenSSL errors, returning the
+// most recent error message for use in exception
+// messages.
+//
+// It's essential to clear the OpenSSL error queue after
+// ANY failed OpenSSL operation, because OpenSSL may
+// decide that a later non-blocking read (returning -1
+// with errno == EAGAIN) is actually an error if there's
+// any errors left in the queue. See SSL_get_error
+// (called from SocketStreamTLS::Read) for the details.
+// Created: 26/04/12
+//
+// --------------------------------------------------------------------------
+std::string CipherContext::LogError(const std::string& operation)
+{
+ return CryptoUtils::LogError(operation);
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+>>>>>>> 0.12
// Name: CipherContext::Init(CipherContext::CipherFunction, const CipherDescription &)
// Purpose: Initialises the context, specifying the direction for the encryption, and a
// description of the cipher to use, it's keys, etc
@@ -82,10 +120,17 @@ void CipherContext::Init(CipherContext::CipherFunction Function, const CipherDes
THROW_EXCEPTION(CipherException, BadArguments)
}
+<<<<<<< HEAD
+=======
+ // Store function for later
+ mFunction = Function;
+
+>>>>>>> 0.12
// Initialise the cipher
#ifndef HAVE_OLD_SSL
EVP_CIPHER_CTX_init(&ctx); // no error return code, even though the docs says it does
+<<<<<<< HEAD
if(EVP_CipherInit_ex(&ctx, rDescription.GetCipher(), NULL, NULL, NULL, Function) != 1)
#else
// Store function for later
@@ -96,10 +141,27 @@ void CipherContext::Init(CipherContext::CipherFunction Function, const CipherDes
#endif
{
THROW_EXCEPTION(CipherException, EVPInitFailure)
+=======
+ if(EVP_CipherInit_ex(&ctx, rDescription.GetCipher(), NULL, NULL, NULL,
+ (mFunction == Encrypt) ? 1 : 0) != 1)
+#else
+ // Use old version of init call
+ if(EVP_CipherInit(&ctx, rDescription.GetCipher(), NULL, NULL,
+ (mFunction == Encrypt) ? 1 : 0) != 1)
+#endif
+ {
+ THROW_EXCEPTION_MESSAGE(CipherException, EVPInitFailure,
+ "Failed to initialise " << rDescription.GetFullName()
+ << "cipher: " << LogError("initialising cipher"));
+>>>>>>> 0.12
}
try
{
+<<<<<<< HEAD
+=======
+ mCipherName = rDescription.GetFullName();
+>>>>>>> 0.12
#ifndef HAVE_OLD_SSL
// Let the description set up everything else
rDescription.SetupParameters(&ctx);
@@ -114,6 +176,12 @@ void CipherContext::Init(CipherContext::CipherFunction Function, const CipherDes
}
catch(...)
{
+<<<<<<< HEAD
+=======
+ THROW_EXCEPTION_MESSAGE(CipherException, EVPInitFailure,
+ "Failed to configure " << mCipherName << " cipher: " <<
+ LogError("configuring cipher"));
+>>>>>>> 0.12
EVP_CIPHER_CTX_cleanup(&ctx);
throw;
}
@@ -174,7 +242,13 @@ void CipherContext::Begin()
// Initialise the cipher context again
if(EVP_CipherInit(&ctx, NULL, NULL, NULL, -1) != 1)
{
+<<<<<<< HEAD
THROW_EXCEPTION(CipherException, EVPInitFailure)
+=======
+ THROW_EXCEPTION_MESSAGE(CipherException, EVPInitFailure,
+ "Failed to reset " << mCipherName << " cipher: " <<
+ LogError("resetting cipher"));
+>>>>>>> 0.12
}
// Mark as being within a transform
@@ -227,7 +301,13 @@ int CipherContext::Transform(void *pOutBuffer, int OutLength, const void *pInBuf
int outLength = OutLength;
if(EVP_CipherUpdate(&ctx, (unsigned char*)pOutBuffer, &outLength, (unsigned char*)pInBuffer, InLength) != 1)
{
+<<<<<<< HEAD
THROW_EXCEPTION(CipherException, EVPUpdateFailure)
+=======
+ THROW_EXCEPTION_MESSAGE(CipherException, EVPUpdateFailure,
+ "Failed to " << GetFunction() << " (update) " <<
+ mCipherName << " cipher: " << LogError(GetFunction()));
+>>>>>>> 0.12
}
return outLength;
@@ -273,9 +353,18 @@ int CipherContext::Final(void *pOutBuffer, int OutLength)
// Do the transform
int outLength = OutLength;
#ifndef HAVE_OLD_SSL
+<<<<<<< HEAD
if(EVP_CipherFinal_ex(&ctx, (unsigned char*)pOutBuffer, &outLength) != 1)
{
THROW_EXCEPTION(CipherException, EVPFinalFailure)
+=======
+ if(EVP_CipherFinal(&ctx, (unsigned char*)pOutBuffer, &outLength) != 1)
+ {
+ mWithinTransform = false;
+ THROW_EXCEPTION_MESSAGE(CipherException, EVPFinalFailure,
+ "Failed to " << GetFunction() << " (final) " <<
+ mCipherName << " cipher: " << LogError(GetFunction()));
+>>>>>>> 0.12
}
#else
OldOpenSSLFinal((unsigned char*)pOutBuffer, outLength);
@@ -353,7 +442,12 @@ void CipherContext::OldOpenSSLFinal(unsigned char *Buffer, int &rOutLengthOut)
}
}
// Reinitialise the cipher for the next time around
+<<<<<<< HEAD
if(EVP_CipherInit(&ctx, mpDescription->GetCipher(), NULL, NULL, mFunction) != 1)
+=======
+ if(EVP_CipherInit(&ctx, mpDescription->GetCipher(), NULL, NULL,
+ (mFunction == Encrypt) ? 1 : 0) != 1)
+>>>>>>> 0.12
{
THROW_EXCEPTION(CipherException, EVPInitFailure)
}
@@ -451,6 +545,7 @@ int CipherContext::TransformBlock(void *pOutBuffer, int OutLength, const void *p
// Do the entire block
int outLength = 0;
+<<<<<<< HEAD
try
{
// Update
@@ -482,6 +577,31 @@ int CipherContext::TransformBlock(void *pOutBuffer, int OutLength, const void *p
#endif
throw;
}
+=======
+
+ // Update
+ outLength = OutLength;
+ if(EVP_CipherUpdate(&ctx, (unsigned char*)pOutBuffer, &outLength, (unsigned char*)pInBuffer, InLength) != 1)
+ {
+ THROW_EXCEPTION_MESSAGE(CipherException, EVPUpdateFailure,
+ "Failed to " << GetFunction() << " (update) " <<
+ mCipherName << " cipher: " << LogError(GetFunction()));
+ }
+
+ // Finalise
+ int outLength2 = OutLength - outLength;
+#ifndef HAVE_OLD_SSL
+ if(EVP_CipherFinal(&ctx, ((unsigned char*)pOutBuffer) + outLength, &outLength2) != 1)
+ {
+ THROW_EXCEPTION_MESSAGE(CipherException, EVPFinalFailure,
+ "Failed to " << GetFunction() << " (final) " <<
+ mCipherName << " cipher: " << LogError(GetFunction()));
+ }
+#else
+ OldOpenSSLFinal(((unsigned char*)pOutBuffer) + outLength, outLength2);
+#endif
+ outLength += outLength2;
+>>>>>>> 0.12
return outLength;
}
@@ -531,7 +651,13 @@ void CipherContext::SetIV(const void *pIV)
// Set IV
if(EVP_CipherInit(&ctx, NULL, NULL, (unsigned char *)pIV, -1) != 1)
{
+<<<<<<< HEAD
THROW_EXCEPTION(CipherException, EVPInitFailure)
+=======
+ THROW_EXCEPTION_MESSAGE(CipherException, EVPInitFailure,
+ "Failed to " << GetFunction() << " (set IV) " <<
+ mCipherName << " cipher: " << LogError(GetFunction()));
+>>>>>>> 0.12
}
#ifdef HAVE_OLD_SSL
@@ -576,6 +702,7 @@ const void *CipherContext::SetRandomIV(int &rLengthOut)
// Generate some random data
Random::Generate(mGeneratedIV, ivLen);
+<<<<<<< HEAD
// Set IV
if(EVP_CipherInit(&ctx, NULL, NULL, mGeneratedIV, -1) != 1)
@@ -590,6 +717,9 @@ const void *CipherContext::SetRandomIV(int &rLengthOut)
mpDescription->SetIV(mGeneratedIV);
}
#endif
+=======
+ SetIV(mGeneratedIV);
+>>>>>>> 0.12
// Return the IV and it's length
rLengthOut = ivLen;
diff --git a/lib/crypto/CipherContext.h b/lib/crypto/CipherContext.h
index 64ce52d8..092c10d5 100644
--- a/lib/crypto/CipherContext.h
+++ b/lib/crypto/CipherContext.h
@@ -35,12 +35,23 @@ public:
private:
CipherContext(const CipherContext &); // no copying
CipherContext &operator=(const CipherContext &); // no assignment
+<<<<<<< HEAD
+=======
+protected:
+ std::string LogError(const std::string& operation);
+>>>>>>> 0.12
public:
typedef enum
{
+<<<<<<< HEAD
Decrypt = 0,
Encrypt = 1
+=======
+ None = 0,
+ Decrypt,
+ Encrypt
+>>>>>>> 0.12
} CipherFunction;
void Init(CipherContext::CipherFunction Function, const CipherDescription &rDescription);
@@ -61,6 +72,13 @@ public:
const void *SetRandomIV(int &rLengthOut);
void UsePadding(bool Padding = true);
+<<<<<<< HEAD
+=======
+ const char* GetFunction() const
+ {
+ return (mFunction == Encrypt) ? "encrypt" : "decrypt";
+ }
+>>>>>>> 0.12
#ifdef HAVE_OLD_SSL
void OldOpenSSLFinal(unsigned char *Buffer, int &rOutLengthOut);
@@ -72,8 +90,14 @@ private:
bool mWithinTransform;
bool mPaddingOn;
uint8_t mGeneratedIV[CIPHERCONTEXT_MAX_GENERATED_IV_LENGTH];
+<<<<<<< HEAD
#ifdef HAVE_OLD_SSL
CipherFunction mFunction;
+=======
+ CipherFunction mFunction;
+ std::string mCipherName;
+#ifdef HAVE_OLD_SSL
+>>>>>>> 0.12
CipherDescription *mpDescription;
#endif
};
diff --git a/lib/crypto/CipherDescription.h b/lib/crypto/CipherDescription.h
index f825eefa..15438494 100644
--- a/lib/crypto/CipherDescription.h
+++ b/lib/crypto/CipherDescription.h
@@ -34,7 +34,11 @@ public:
// Return OpenSSL cipher object
virtual const EVP_CIPHER *GetCipher() const = 0;
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
// Setup any other parameters
virtual void SetupParameters(EVP_CIPHER_CTX *pCipherContext) const = 0;
@@ -47,6 +51,26 @@ public:
Mode_OFB = 3
} CipherMode;
+<<<<<<< HEAD
+=======
+ virtual std::string GetCipherName() const = 0;
+ virtual CipherMode GetCipherMode() const = 0;
+ virtual std::string GetFullName() const
+ {
+ std::ostringstream out;
+ out << GetCipherName() << "-";
+ switch (GetCipherMode())
+ {
+ case Mode_ECB: out << "ECB"; break;
+ case Mode_CBC: out << "CBC"; break;
+ case Mode_CFB: out << "CFB"; break;
+ case Mode_OFB: out << "OFB"; break;
+ default: out << "unknown";
+ }
+ return out.str();
+ }
+
+>>>>>>> 0.12
#ifdef HAVE_OLD_SSL
// For the old version of OpenSSL, we need to be able to store cipher descriptions.
virtual CipherDescription *Clone() const = 0;
diff --git a/lib/crypto/CryptoUtils.cpp b/lib/crypto/CryptoUtils.cpp
new file mode 100644
index 00000000..3e4aa15f
--- /dev/null
+++ b/lib/crypto/CryptoUtils.cpp
@@ -0,0 +1,46 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: CryptoUtils.cpp
+// Purpose: Utility functions for dealing with the OpenSSL library
+// Created: 2012/04/26
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#define TLS_CLASS_IMPLEMENTATION_CPP
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include "CryptoUtils.h"
+
+#include "MemLeakFindOn.h"
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: CryptoUtils::LogError(const char *)
+// Purpose: Logs an error from the OpenSSL library
+// Created: 2012/04/26
+//
+// --------------------------------------------------------------------------
+std::string CryptoUtils::LogError(const std::string& rErrorDuringAction)
+{
+ unsigned long errcode;
+ char errname[256]; // SSL docs say at least 120 bytes
+ std::string firstError;
+
+ while((errcode = ERR_get_error()) != 0)
+ {
+ ::ERR_error_string_n(errcode, errname, sizeof(errname));
+ if(firstError.empty())
+ {
+ firstError = errname;
+ }
+ BOX_ERROR("SSL or crypto error: " << rErrorDuringAction <<
+ ": " << errname);
+ }
+ return firstError;
+}
+
diff --git a/lib/crypto/CryptoUtils.h b/lib/crypto/CryptoUtils.h
new file mode 100644
index 00000000..fe0e51a3
--- /dev/null
+++ b/lib/crypto/CryptoUtils.h
@@ -0,0 +1,27 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: CryptoUtils.h
+// Purpose: Utility functions for dealing with the OpenSSL library
+// Created: 2012/04/26
+//
+// --------------------------------------------------------------------------
+
+#ifndef CRYPTOUTILS__H
+#define CRYPTOUTILS__H
+
+// --------------------------------------------------------------------------
+//
+// Namespace
+// Name: CryptoUtils
+// Purpose: Utility functions for dealing with the OpenSSL library
+// Created: 2003/08/06
+//
+// --------------------------------------------------------------------------
+namespace CryptoUtils
+{
+ std::string LogError(const std::string& rErrorDuringAction);
+};
+
+#endif // CRYPTOUTILS__H
+
diff --git a/lib/raidfile/RaidFileController.cpp b/lib/raidfile/RaidFileController.cpp
index 2cc6976b..84c2a520 100644
--- a/lib/raidfile/RaidFileController.cpp
+++ b/lib/raidfile/RaidFileController.cpp
@@ -164,13 +164,26 @@ RaidFileDiscSet &RaidFileController::GetDiscSet(unsigned int DiscSetNum)
{
if(DiscSetNum < 0 || DiscSetNum >= mSetList.size())
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, NoSuchDiscSet)
+=======
+ THROW_EXCEPTION_MESSAGE(RaidFileException, NoSuchDiscSet, DiscSetNum <<
+ " (" << mSetList.size() << " disc sets configured)")
+>>>>>>> 0.12
}
return mSetList[DiscSetNum];
}
+<<<<<<< HEAD
+=======
+// Overload to make usable in gdb debugger.
+int RaidFileDiscSet::GetSetNumForWriteFiles(const char* filename) const
+{
+ return GetSetNumForWriteFiles(std::string(filename));
+}
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
diff --git a/lib/raidfile/RaidFileController.h b/lib/raidfile/RaidFileController.h
index 216bdf3a..b2b8cb0d 100644
--- a/lib/raidfile/RaidFileController.h
+++ b/lib/raidfile/RaidFileController.h
@@ -49,7 +49,11 @@ public:
int GetSetID() const {return mSetID;}
int GetSetNumForWriteFiles(const std::string &rFilename) const;
+<<<<<<< HEAD
+=======
+ int GetSetNumForWriteFiles(const char* filename) const;
+>>>>>>> 0.12
unsigned int GetBlockSize() const {return mBlockSize;}
// Is this disc set a non-RAID disc set? (ie files never get transformed to raid storage)
diff --git a/lib/raidfile/RaidFileRead.cpp b/lib/raidfile/RaidFileRead.cpp
index 0a79be57..3a7a5893 100644
--- a/lib/raidfile/RaidFileRead.cpp
+++ b/lib/raidfile/RaidFileRead.cpp
@@ -12,7 +12,14 @@
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
+<<<<<<< HEAD
#include <unistd.h>
+=======
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+>>>>>>> 0.12
#include <sys/stat.h>
#include <sys/types.h>
@@ -1022,8 +1029,13 @@ std::auto_ptr<RaidFileRead> RaidFileRead::Open(int SetNumber, const std::string
RaidFileUtil::ExistType existance = RaidFileUtil::RaidFileExists(rdiscSet, Filename, &startDisc, &existingFiles, pRevisionID);
if(existance == RaidFileUtil::NoFile)
{
+<<<<<<< HEAD
BOX_ERROR("Expected raidfile " << Filename << " does not exist");
THROW_EXCEPTION(RaidFileException, RaidFileDoesntExist)
+=======
+ THROW_FILE_ERROR("Expected raidfile does not exist",
+ Filename, RaidFileException, RaidFileDoesntExist);
+>>>>>>> 0.12
}
else if(existance == RaidFileUtil::NonRaid)
{
@@ -1086,9 +1098,25 @@ std::auto_ptr<RaidFileRead> RaidFileRead::Open(int SetNumber, const std::string
{
stripe2errno = errno;
}
+<<<<<<< HEAD
if(stripe1errno != 0 || stripe2errno != 0)
{
THROW_EXCEPTION(RaidFileException, ErrorOpeningFileForRead)
+=======
+
+ if(stripe1errno != 0)
+ {
+ THROW_SYS_FILE_ERRNO("Failed to open RaidFile",
+ stripe1Filename, stripe1errno,
+ RaidFileException, ErrorOpeningFileForRead);
+ }
+
+ if(stripe2errno != 0)
+ {
+ THROW_SYS_FILE_ERRNO("Failed to open RaidFile",
+ stripe2Filename, stripe2errno,
+ RaidFileException, ErrorOpeningFileForRead);
+>>>>>>> 0.12
}
// stat stripe 1 to find ('half' of) length...
@@ -1107,11 +1135,28 @@ std::auto_ptr<RaidFileRead> RaidFileRead::Open(int SetNumber, const std::string
length += st.st_size;
// Handle errors
+<<<<<<< HEAD
if(stripe1errno != 0 || stripe2errno != 0)
{
THROW_EXCEPTION(RaidFileException, OSError)
}
+=======
+ if(stripe1errno != 0)
+ {
+ THROW_SYS_FILE_ERRNO("Failed to stat RaidFile",
+ stripe1Filename, stripe1errno,
+ RaidFileException, OSError);
+ }
+
+ if(stripe2errno != 0)
+ {
+ THROW_SYS_FILE_ERRNO("Failed to stat RaidFile",
+ stripe2Filename, stripe2errno,
+ RaidFileException, OSError);
+ }
+
+>>>>>>> 0.12
// Make a nice object to represent this file
return std::auto_ptr<RaidFileRead>(new RaidFileRead_Raid(SetNumber, Filename, stripe1, stripe2, -1, length, rdiscSet.GetBlockSize(), false /* actually we don't know */));
}
@@ -1238,17 +1283,40 @@ std::auto_ptr<RaidFileRead> RaidFileRead::Open(int SetNumber, const std::string
ASSERT(sizeof(FileSizeType) == 8); // compiler bug (I think) prevents from using 0 - sizeof(FileSizeType)...
if(::lseek(parity, -8 /*(0 - sizeof(FileSizeType))*/, SEEK_END) == -1)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to seek "
+ "in parity RaidFile",
+ parityFilename,
+ RaidFileException, OSError);
+>>>>>>> 0.12
}
// Read it in
if(::read(parity, &parityLastData, sizeof(parityLastData)) != sizeof(parityLastData))
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
}
// Set back to beginning of file
if(::lseek(parity, 0, SEEK_SET) == -1)
{
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to read "
+ "parity RaidFile",
+ parityFilename,
+ RaidFileException, OSError);
+ }
+
+ // Set back to beginning of file
+ if(::lseek(parity, 0, SEEK_SET) == -1)
+ {
+ THROW_SYS_FILE_ERROR("Failed to seek "
+ "in parity RaidFile",
+ parityFilename,
+ RaidFileException, OSError);
+>>>>>>> 0.12
}
}
@@ -1271,7 +1339,14 @@ std::auto_ptr<RaidFileRead> RaidFileRead::Open(int SetNumber, const std::string
// to get the size from the FileSizeType value at end of the file.
if(::fstat(stripe1, &st) != 0)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to "
+ "stat RaidFile stripe 1",
+ stripe1Filename,
+ RaidFileException, OSError);
+>>>>>>> 0.12
}
pos_type stripe1Size = st.st_size;
// Is size integral?
@@ -1305,17 +1380,38 @@ std::auto_ptr<RaidFileRead> RaidFileRead::Open(int SetNumber, const std::string
ASSERT(btr <= (int)sizeof(FileSizeType));
if(::lseek(stripe1, 0 - btr, SEEK_END) == -1)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to "
+ "seek in RaidFile stripe 1",
+ stripe1Filename,
+ RaidFileException, OSError);
+>>>>>>> 0.12
}
// Read it in
if(::read(stripe1, &stripe1LastData, btr) != btr)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to "
+ "read RaidFile stripe 1",
+ stripe1Filename,
+ RaidFileException, OSError);
+>>>>>>> 0.12
}
// Set back to beginning of file
if(::lseek(stripe1, 0, SEEK_SET) == -1)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to "
+ "seek in RaidFile stripe 1",
+ stripe1Filename,
+ RaidFileException, OSError);
+>>>>>>> 0.12
}
}
// Lovely!
@@ -1337,7 +1433,14 @@ std::auto_ptr<RaidFileRead> RaidFileRead::Open(int SetNumber, const std::string
// Get size of stripe2 file
if(::fstat(stripe2, &st) != 0)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to "
+ "stat RaidFile stripe 2",
+ stripe2Filename,
+ RaidFileException, OSError);
+>>>>>>> 0.12
}
pos_type stripe2Size = st.st_size;
@@ -1406,7 +1509,12 @@ std::auto_ptr<RaidFileRead> RaidFileRead::Open(int SetNumber, const std::string
}
}
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, FileIsDamagedNotRecoverable)
+=======
+ THROW_FILE_ERROR("Failed to recover RaidFile", Filename,
+ RaidFileException, FileIsDamagedNotRecoverable);
+>>>>>>> 0.12
// Avoid compiler warning -- it'll never get here...
return std::auto_ptr<RaidFileRead>();
@@ -1540,7 +1648,11 @@ bool RaidFileRead::ReadDirectoryContents(int SetNumber, const std::string &rDirN
{
// build name
std::string dn(rdiscSet[l] + DIRECTORY_SEPARATOR + rDirName);
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
// read the contents...
DIR *dirHandle = 0;
try
diff --git a/lib/raidfile/RaidFileWrite.cpp b/lib/raidfile/RaidFileWrite.cpp
index f24c2422..cb1f9699 100644
--- a/lib/raidfile/RaidFileWrite.cpp
+++ b/lib/raidfile/RaidFileWrite.cpp
@@ -11,10 +11,24 @@
#include <errno.h>
#include <fcntl.h>
+<<<<<<< HEAD
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/file.h>
+=======
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_SYS_FILE_H
+# include <sys/file.h>
+#endif
+>>>>>>> 0.12
#include <stdio.h>
#include <string.h>
@@ -95,7 +109,32 @@ RaidFileWrite::~RaidFileWrite()
{
if(mOSFileHandle != -1)
{
+<<<<<<< HEAD
Discard();
+=======
+ // We must not throw exceptions from the destructor
+ // http://stackoverflow.com/a/130123
+ try
+ {
+ Discard();
+ }
+ catch(BoxException &e)
+ {
+ BOX_ERROR("Failed to discard RaidFile update "
+ "in destructor: " << e.what() << " (" <<
+ e.GetType() << "/" << e.GetSubType() << ")");
+ }
+ catch(std::exception &e)
+ {
+ BOX_ERROR("Failed to discard RaidFile update "
+ "in destructor: " << e.what());
+ }
+ catch(...)
+ {
+ BOX_ERROR("Failed to discard RaidFile update "
+ "in destructor: unknown exception");
+ }
+>>>>>>> 0.12
}
}
@@ -126,25 +165,45 @@ void RaidFileWrite::Open(bool AllowOverwrite)
RaidFileUtil::ExistType existance = RaidFileUtil::RaidFileExists(rdiscSet, mFilename);
if(existance != RaidFileUtil::NoFile)
{
+<<<<<<< HEAD
BOX_ERROR("Attempted to overwrite raidfile " <<
mSetNumber << " " << mFilename);
THROW_EXCEPTION(RaidFileException, CannotOverwriteExistingFile)
+=======
+ THROW_FILE_ERROR("Attempted to overwrite raidfile " <<
+ mSetNumber, mFilename, RaidFileException,
+ CannotOverwriteExistingFile);
+>>>>>>> 0.12
}
}
// Get the filename for the write file
+<<<<<<< HEAD
std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename));
// Add on a temporary extension
writeFilename += 'X';
// Attempt to open
mOSFileHandle = ::open(writeFilename.c_str(),
+=======
+ mTempFilename = RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename);
+ // Add on a temporary extension
+ mTempFilename += 'X';
+
+ // Attempt to open
+ mOSFileHandle = ::open(mTempFilename.c_str(),
+>>>>>>> 0.12
O_WRONLY | O_CREAT | O_BINARY,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if(mOSFileHandle == -1)
{
+<<<<<<< HEAD
BOX_LOG_SYS_ERROR("Failed to open file: " << writeFilename);
THROW_EXCEPTION(RaidFileException, ErrorOpeningWriteFile)
+=======
+ THROW_SYS_FILE_ERROR("Failed to open RaidFile", mTempFilename,
+ RaidFileException, ErrorOpeningWriteFile);
+>>>>>>> 0.12
}
// Get a lock on the write file
@@ -164,6 +223,7 @@ void RaidFileWrite::Open(bool AllowOverwrite)
if (0)
#endif
{
+<<<<<<< HEAD
// Lock was not obtained.
bool wasLocked = (errno == errnoBlock);
// Close the file
@@ -173,18 +233,54 @@ void RaidFileWrite::Open(bool AllowOverwrite)
if(wasLocked)
{
THROW_EXCEPTION(RaidFileException, FileIsCurrentlyOpenForWriting)
+=======
+ int errnoSaved = errno;
+
+ // Lock was not obtained.
+ bool wasLocked = (errno == errnoBlock);
+
+ // Close the file
+ ::close(mOSFileHandle);
+ mOSFileHandle = -1;
+
+ // Report an exception?
+ if(wasLocked)
+ {
+ THROW_SYS_FILE_ERRNO("Failed to lock RaidFile, "
+ "already locked", mTempFilename, errnoSaved,
+ RaidFileException,
+ FileIsCurrentlyOpenForWriting);
+>>>>>>> 0.12
}
else
{
// Random error occured
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERRNO("Failed to lock RaidFile",
+ mTempFilename, errnoSaved, RaidFileException,
+ OSError);
+>>>>>>> 0.12
}
}
// Truncate it to size zero
if(::ftruncate(mOSFileHandle, 0) != 0)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, ErrorOpeningWriteFileOnTruncate)
+=======
+ int errnoSaved = errno;
+
+ // Close the file
+ ::close(mOSFileHandle);
+ mOSFileHandle = -1;
+
+ THROW_SYS_FILE_ERRNO("Failed to truncate RaidFile",
+ mTempFilename, errnoSaved, RaidFileException,
+ ErrorOpeningWriteFileOnTruncate);
+>>>>>>> 0.12
}
// Done!
@@ -210,9 +306,16 @@ void RaidFileWrite::Write(const void *pBuffer, int Length)
int written = ::write(mOSFileHandle, pBuffer, Length);
if(written != Length)
{
+<<<<<<< HEAD
BOX_LOG_SYS_ERROR("RaidFileWrite failed, Length = " <<
Length << ", written = " << written);
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to write to RaidFile (attempted "
+ "to write " << Length << " bytes but managed only " <<
+ written << ")", mTempFilename, RaidFileException,
+ OSError);
+>>>>>>> 0.12
}
}
@@ -236,7 +339,12 @@ IOStream::pos_type RaidFileWrite::GetPosition() const
off_t p = ::lseek(mOSFileHandle, 0, SEEK_CUR);
if(p == -1)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to get position in RaidFile",
+ mTempFilename, RaidFileException, OSError);
+>>>>>>> 0.12
}
return p;
@@ -261,7 +369,12 @@ void RaidFileWrite::Seek(IOStream::pos_type SeekTo, int SeekType)
// Seek...
if(::lseek(mOSFileHandle, SeekTo, ConvertSeekTypeToOSWhence(SeekType)) == -1)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to set position in RaidFile",
+ mTempFilename, RaidFileException, OSError);
+>>>>>>> 0.12
}
}
@@ -283,9 +396,14 @@ void RaidFileWrite::Commit(bool ConvertToRaidNow)
if (mRefCount == 0)
{
+<<<<<<< HEAD
BOX_ERROR("Attempted to modify object " << mFilename <<
", which has no references");
THROW_EXCEPTION(RaidFileException,
+=======
+ THROW_FILE_ERROR("Attempted to modify object file with "
+ "no references", mTempFilename, RaidFileException,
+>>>>>>> 0.12
RequestedModifyUnreferencedFile);
}
@@ -296,7 +414,12 @@ void RaidFileWrite::Commit(bool ConvertToRaidNow)
// Close file...
if(::close(mOSFileHandle) != 0)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_WIN_FILE_ERROR("Failed to close RaidFile for rename",
+ mTempFilename, RaidFileException, OSError);
+>>>>>>> 0.12
}
mOSFileHandle = -1;
#endif // WIN32
@@ -310,26 +433,47 @@ void RaidFileWrite::Commit(bool ConvertToRaidNow)
#ifdef WIN32
// need to delete the target first
+<<<<<<< HEAD
if(::unlink(renameTo.c_str()) != 0 &&
GetLastError() != ERROR_FILE_NOT_FOUND)
{
BOX_LOG_WIN_ERROR("Failed to delete file: " << renameTo);
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ if(::unlink(renameTo.c_str()) != 0)
+ {
+ DWORD errorNumber = GetLastError();
+ if (errorNumber != ERROR_FILE_NOT_FOUND)
+ {
+ THROW_WIN_FILE_ERRNO("Failed to delete file", renameTo,
+ errorNumber, RaidFileException, OSError);
+ }
+>>>>>>> 0.12
}
#endif
if(::rename(renameFrom.c_str(), renameTo.c_str()) != 0)
{
+<<<<<<< HEAD
BOX_LOG_SYS_ERROR("Failed to rename file: " << renameFrom <<
" to " << renameTo);
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_ERROR("Failed to rename file: " << renameFrom <<
+ " to " << renameTo, RaidFileException, OSError);
+>>>>>>> 0.12
}
#ifndef WIN32
// Close file...
if(::close(mOSFileHandle) != 0)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to close committed RaidFile",
+ mTempFilename, RaidFileException, OSError);
+>>>>>>> 0.12
}
mOSFileHandle = -1;
#endif // !WIN32
@@ -376,8 +520,13 @@ void RaidFileWrite::Discard()
::close(mOSFileHandle) != 0)
#endif // !WIN32
{
+<<<<<<< HEAD
BOX_LOG_SYS_ERROR("Failed to delete file: " << writeFilename);
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to delete file", writeFilename,
+ RaidFileException, OSError);
+>>>>>>> 0.12
}
// reset file handle
@@ -678,10 +827,16 @@ void RaidFileWrite::Delete()
{
if (mRefCount != 0 && mRefCount != -1)
{
+<<<<<<< HEAD
BOX_ERROR("Attempted to delete object " << mFilename <<
" which has " << mRefCount << " references");
THROW_EXCEPTION(RaidFileException,
RequestedDeleteReferencedFile);
+=======
+ THROW_FILE_ERROR("Attempted to delete object with " <<
+ mRefCount << " references", mFilename,
+ RaidFileException, RequestedDeleteReferencedFile);
+>>>>>>> 0.12
}
// Get disc set
@@ -692,7 +847,13 @@ void RaidFileWrite::Delete()
RaidFileUtil::ExistType existance = RaidFileUtil::RaidFileExists(rdiscSet, mFilename);
if(existance == RaidFileUtil::NoFile)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, RaidFileDoesntExist)
+=======
+ THROW_FILE_ERROR("Attempted to delete object which doesn't "
+ "exist", mFilename, RaidFileException,
+ RaidFileDoesntExist);
+>>>>>>> 0.12
}
// Get the filename for the write file
@@ -731,7 +892,12 @@ void RaidFileWrite::Delete()
// Check something happened
if(!deletedSomething)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_FILE_ERROR("Failed to delete a RaidFile stripe set",
+ mFilename, RaidFileException, OSError);
+>>>>>>> 0.12
}
}
@@ -802,11 +968,24 @@ void RaidFileWrite::CreateDirectory(const RaidFileDiscSet &rSet, const std::stri
if(errno == EEXIST)
{
// No. Bad things.
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, FileExistsInDirectoryCreation)
}
else
{
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to create "
+ "RaidFile directory", dn,
+ RaidFileException,
+ FileExistsInDirectoryCreation);
+ }
+ else
+ {
+ THROW_SYS_FILE_ERROR("Failed to create "
+ "RaidFile directory", dn,
+ RaidFileException, OSError);
+>>>>>>> 0.12
}
}
}
@@ -889,7 +1068,12 @@ IOStream::pos_type RaidFileWrite::GetFileSize()
struct stat st;
if(fstat(mOSFileHandle, &st) != 0)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to stat RaidFile", mTempFilename,
+ RaidFileException, OSError);
+>>>>>>> 0.12
}
return st.st_size;
@@ -918,7 +1102,12 @@ IOStream::pos_type RaidFileWrite::GetDiscUsageInBlocks()
struct stat st;
if(fstat(mOSFileHandle, &st) != 0)
{
+<<<<<<< HEAD
THROW_EXCEPTION(RaidFileException, OSError)
+=======
+ THROW_SYS_FILE_ERROR("Failed to stat RaidFile", mTempFilename,
+ RaidFileException, OSError);
+>>>>>>> 0.12
}
// Then return calculation
diff --git a/lib/raidfile/RaidFileWrite.h b/lib/raidfile/RaidFileWrite.h
index 418f90ee..52621676 100644
--- a/lib/raidfile/RaidFileWrite.h
+++ b/lib/raidfile/RaidFileWrite.h
@@ -27,7 +27,22 @@ class RaidFileDiscSet;
class RaidFileWrite : public IOStream
{
public:
+<<<<<<< HEAD
RaidFileWrite(int SetNumber, const std::string &Filename);
+=======
+ // TODO FIXME we should remove this constructor, and ensure that
+ // anyone who writes to a RaidFile knows what the reference count
+ // is before doing so. That requires supporting regenerating the
+ // reference count database in BackupStoreCheck, and using a real
+ // database instead of an in-memory array in HousekeepStoreAccount,
+ // and supporting multiple databases at a time (old and new) in
+ // BackupStoreRefCountDatabase, and I don't have time to make those
+ // changes right now. We may even absolutely need to have a full
+ // reference database, not just reference counts, to implement
+ // snapshots.
+ RaidFileWrite(int SetNumber, const std::string &Filename);
+
+>>>>>>> 0.12
RaidFileWrite(int SetNumber, const std::string &Filename, int refcount);
~RaidFileWrite();
private:
@@ -59,7 +74,11 @@ private:
private:
int mSetNumber;
+<<<<<<< HEAD
std::string mFilename;
+=======
+ std::string mFilename, mTempFilename;
+>>>>>>> 0.12
int mOSFileHandle;
int mRefCount;
};
diff --git a/lib/server/Daemon.cpp b/lib/server/Daemon.cpp
index 8b4f1d0c..9b96647b 100644
--- a/lib/server/Daemon.cpp
+++ b/lib/server/Daemon.cpp
@@ -25,6 +25,7 @@
#ifdef WIN32
#include <ws2tcpip.h>
+<<<<<<< HEAD
#endif
#include <iostream>
@@ -36,6 +37,26 @@
#include "UnixUser.h"
#include "FileModificationTime.h"
#include "Logging.h"
+=======
+ #include <process.h>
+#endif
+
+#include "depot.h"
+
+#include <iostream>
+
+#ifdef NEED_BOX_VERSION_H
+# include "BoxVersion.h"
+#endif
+
+#include "Configuration.h"
+#include "Daemon.h"
+#include "FileModificationTime.h"
+#include "Guards.h"
+#include "Logging.h"
+#include "ServerException.h"
+#include "UnixUser.h"
+>>>>>>> 0.12
#include "Utils.h"
#include "MemLeakFindOn.h"
@@ -64,6 +85,10 @@ Daemon::Daemon()
mKeepConsoleOpenAfterFork(false),
#endif
mHaveConfigFile(false),
+<<<<<<< HEAD
+=======
+ mLogFileLevel(Log::INVALID),
+>>>>>>> 0.12
mAppName(DaemonName())
{
// In debug builds, switch on assert failure logging to syslog
@@ -100,15 +125,25 @@ std::string Daemon::GetOptionString()
{
return "c:"
#ifndef WIN32
+<<<<<<< HEAD
"DFK"
#endif
"hkPqQt:TUvVW:";
+=======
+ "DF"
+ #endif
+ "hkKo:O:PqQt:TUvVW:";
+>>>>>>> 0.12
}
void Daemon::Usage()
{
std::cout <<
DaemonBanner() << "\n"
+<<<<<<< HEAD
+=======
+ "(built with QDBM " << dpversion << ")\n"
+>>>>>>> 0.12
"\n"
"Usage: " << mAppName << " [options] [config file]\n"
"\n"
@@ -121,6 +156,7 @@ void Daemon::Usage()
" -F Do not fork into background, but fork to serve multiple clients\n"
#endif
" -k Keep console open after fork, keep writing log messages to it\n"
+<<<<<<< HEAD
#ifndef WIN32
" -K Stop writing log messages to console while daemon is running\n"
" -P Show process ID (PID) in console output\n"
@@ -133,6 +169,21 @@ void Daemon::Usage()
" -t <tag> Tag console output with specified marker\n"
" -T Timestamp console output\n"
" -U Timestamp console output with microseconds\n";
+=======
+ " -K Stop writing log messages to console while daemon is running\n"
+ " -o <file> Log to a file, defaults to maximum verbosity\n"
+ " -O <level> Set file log verbosity to error/warning/notice/info/trace/everything\n"
+ " -P Show process ID (PID) in console output\n"
+ " -q Run more quietly, reduce verbosity level by one, can repeat\n"
+ " -Q Run at minimum verbosity, log nothing to console and system\n"
+ " -t <tag> Tag console output with specified marker\n"
+ " -T Timestamp console output\n"
+ " -U Timestamp console output with microseconds\n"
+ " -v Run more verbosely, increase verbosity level by one, can repeat\n"
+ " -V Run at maximum verbosity, log everything to console and system\n"
+ " -W <level> Set verbosity to error/warning/notice/info/trace/everything\n"
+ ;
+>>>>>>> 0.12
}
// --------------------------------------------------------------------------
@@ -171,6 +222,16 @@ int Daemon::ProcessOption(signed int option)
break;
#endif // !WIN32
+<<<<<<< HEAD
+=======
+ case 'h':
+ {
+ Usage();
+ return 2;
+ }
+ break;
+
+>>>>>>> 0.12
case 'k':
{
mKeepConsoleOpenAfterFork = true;
@@ -183,10 +244,28 @@ int Daemon::ProcessOption(signed int option)
}
break;
+<<<<<<< HEAD
case 'h':
{
Usage();
return 2;
+=======
+ case 'o':
+ {
+ mLogFile = optarg;
+ mLogFileLevel = Log::EVERYTHING;
+ }
+ break;
+
+ case 'O':
+ {
+ mLogFileLevel = Logging::GetNamedLevel(optarg);
+ if (mLogFileLevel == Log::INVALID)
+ {
+ BOX_FATAL("Invalid logging level: " << optarg);
+ return 2;
+ }
+>>>>>>> 0.12
}
break;
@@ -215,6 +294,28 @@ int Daemon::ProcessOption(signed int option)
}
break;
+<<<<<<< HEAD
+=======
+ case 't':
+ {
+ Logging::SetProgramName(optarg);
+ Console::SetShowTag(true);
+ }
+ break;
+
+ case 'T':
+ {
+ Console::SetShowTime(true);
+ }
+ break;
+
+ case 'U':
+ {
+ Console::SetShowTime(true);
+ Console::SetShowTimeMicros(true);
+ }
+ break;
+>>>>>>> 0.12
case 'v':
{
@@ -240,12 +341,17 @@ int Daemon::ProcessOption(signed int option)
mLogLevel = Logging::GetNamedLevel(optarg);
if (mLogLevel == Log::INVALID)
{
+<<<<<<< HEAD
BOX_FATAL("Invalid logging level");
+=======
+ BOX_FATAL("Invalid logging level: " << optarg);
+>>>>>>> 0.12
return 2;
}
}
break;
+<<<<<<< HEAD
case 't':
{
Logging::SetProgramName(optarg);
@@ -266,6 +372,8 @@ int Daemon::ProcessOption(signed int option)
}
break;
+=======
+>>>>>>> 0.12
case '?':
{
BOX_FATAL("Unknown option on command line: "
@@ -295,6 +403,7 @@ int Daemon::ProcessOption(signed int option)
// Created: 2003/07/29
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[])
{
// Find filename of config file
@@ -305,6 +414,41 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[])
mLogLevel = Log::NOTICE; // need an int to do math with
#else
mLogLevel = Log::INFO; // need an int to do math with
+=======
+int Daemon::Main(const std::string& rDefaultConfigFile, int argc,
+ const char *argv[])
+{
+ // Find filename of config file
+ mConfigFileName = rDefaultConfigFile;
+ mAppName = argv[0];
+
+ int ret = ProcessOptions(argc, argv);
+ if (ret != 0)
+ {
+ return ret;
+ }
+
+ return Main(mConfigFileName);
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Daemon::ProcessOptions(int argc, const char *argv[])
+// Purpose: Parses command-line options. Useful when you have
+// a local Daemon object and don't intend to fork()
+// or call Main().
+// Created: 2008/11/04
+//
+// --------------------------------------------------------------------------
+
+int Daemon::ProcessOptions(int argc, const char *argv[])
+{
+ #ifdef BOX_RELEASE_BUILD
+ mLogLevel = Log::NOTICE;
+ #else
+ mLogLevel = Log::INFO;
+>>>>>>> 0.12
#endif
if (argc == 2 && strcmp(argv[1], "/?") == 0)
@@ -359,7 +503,17 @@ int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[])
Logging::FilterConsole((Log::Level)mLogLevel);
Logging::FilterSyslog ((Log::Level)mLogLevel);
+<<<<<<< HEAD
return Main(mConfigFileName);
+=======
+ if (mLogFileLevel != Log::INVALID)
+ {
+ mapLogFileLogger.reset(
+ new FileLogger(mLogFile, mLogFileLevel));
+ }
+
+ return 0;
+>>>>>>> 0.12
}
// --------------------------------------------------------------------------
@@ -381,7 +535,11 @@ bool Daemon::Configure(const std::string& rConfigFileName)
try
{
+<<<<<<< HEAD
if (!FileExists(rConfigFileName.c_str()))
+=======
+ if (!FileExists(rConfigFileName))
+>>>>>>> 0.12
{
BOX_FATAL("The main configuration file for " <<
DaemonName() << " was not found: " <<
@@ -969,9 +1127,20 @@ void Daemon::SetProcessTitle(const char *format, ...)
char title[256];
::vsnprintf(title, sizeof(title), format, args);
+<<<<<<< HEAD
// Set process title
::setproctitle("%s", title);
+=======
+#ifdef WIN32
+ StringCchCatA(title, sizeof(title)," - " PACKAGE_NAME);
+ SetConsoleTitleA(title);
+#else // !WIN32
+ // Set process title
+ ::setproctitle("%s", title);
+#endif
+
+>>>>>>> 0.12
#endif // HAVE_SETPROCTITLE
}
diff --git a/lib/server/Daemon.h b/lib/server/Daemon.h
index a3212a00..f2fa98c3 100644
--- a/lib/server/Daemon.h
+++ b/lib/server/Daemon.h
@@ -40,8 +40,14 @@ private:
Daemon(const Daemon &rToCopy);
public:
+<<<<<<< HEAD
virtual int Main(const char *DefaultConfigFile, int argc,
const char *argv[]);
+=======
+ virtual int Main(const std::string& rDefaultConfigFile, int argc,
+ const char *argv[]);
+ virtual int ProcessOptions(int argc, const char *argv[]);
+>>>>>>> 0.12
/* override this Main() if you want custom option processing: */
virtual int Main(const std::string &rConfigFile);
@@ -99,6 +105,12 @@ private:
bool mKeepConsoleOpenAfterFork;
bool mHaveConfigFile;
int mLogLevel; // need an int to do math with
+<<<<<<< HEAD
+=======
+ std::string mLogFile;
+ Log::Level mLogFileLevel;
+ std::auto_ptr<FileLogger> mapLogFileLogger;
+>>>>>>> 0.12
static Daemon *spDaemon;
std::string mAppName;
};
diff --git a/lib/server/Message.cpp b/lib/server/Message.cpp
new file mode 100644
index 00000000..2ff9e6ae
--- /dev/null
+++ b/lib/server/Message.cpp
@@ -0,0 +1,125 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: Message.h
+// Purpose: Protocol object base class
+// Created: 2003/08/19
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+#include "Message.h"
+#include "CommonException.h"
+
+#include "MemLeakFindOn.h"
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Message::Message()
+// Purpose: Default constructor
+// Created: 2003/08/19
+//
+// --------------------------------------------------------------------------
+Message::Message()
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Message::Message()
+// Purpose: Destructor
+// Created: 2003/08/19
+//
+// --------------------------------------------------------------------------
+Message::~Message()
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Message::Message()
+// Purpose: Copy constructor
+// Created: 2003/08/19
+//
+// --------------------------------------------------------------------------
+Message::Message(const Message &rToCopy)
+{
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Message::IsError(int &, int &)
+// Purpose: Does this represent an error, and if so, what is the type and subtype?
+// Created: 2003/08/19
+//
+// --------------------------------------------------------------------------
+bool Message::IsError(int &rTypeOut, int &rSubTypeOut) const
+{
+ return false;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Message::IsConversationEnd()
+// Purpose: Does this command end the conversation?
+// Created: 2003/08/19
+//
+// --------------------------------------------------------------------------
+bool Message::IsConversationEnd() const
+{
+ return false;
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Message::GetType()
+// Purpose: Return type of the object
+// Created: 2003/08/19
+//
+// --------------------------------------------------------------------------
+int Message::GetType() const
+{
+ // This isn't implemented in the base class!
+ THROW_EXCEPTION(CommonException, Internal)
+}
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Message::SetPropertiesFromStreamData(Protocol &)
+// Purpose: Set the properties of the object from the stream data ready in the Protocol object
+// Created: 2003/08/19
+//
+// --------------------------------------------------------------------------
+void Message::SetPropertiesFromStreamData(Protocol &rProtocol)
+{
+ // This isn't implemented in the base class!
+ THROW_EXCEPTION(CommonException, Internal)
+}
+
+
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Message::WritePropertiesToStreamData(Protocol &)
+// Purpose: Write the properties of the object into the stream data in the Protocol object
+// Created: 2003/08/19
+//
+// --------------------------------------------------------------------------
+void Message::WritePropertiesToStreamData(Protocol &rProtocol) const
+{
+ // This isn't implemented in the base class!
+ THROW_EXCEPTION(CommonException, Internal)
+}
+
+
+
diff --git a/lib/server/Message.h b/lib/server/Message.h
new file mode 100644
index 00000000..0d073d49
--- /dev/null
+++ b/lib/server/Message.h
@@ -0,0 +1,69 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: Message.h
+// Purpose: Protocol object base class
+// Created: 2003/08/19
+//
+// --------------------------------------------------------------------------
+
+#ifndef PROTOCOLOBJECT__H
+#define PROTOCOLOBJECT__H
+
+#include <memory>
+
+class Protocol;
+class ProtocolContext;
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: Message
+// Purpose: Basic object representation of objects to pass through a Protocol session
+// Created: 2003/08/19
+//
+// --------------------------------------------------------------------------
+class Message
+{
+public:
+ Message();
+ virtual ~Message();
+ Message(const Message &rToCopy);
+
+ // Info about this object
+ virtual int GetType() const;
+ virtual bool IsError(int &rTypeOut, int &rSubTypeOut) const;
+ virtual bool IsConversationEnd() const;
+
+ // reading and writing with Protocol objects
+ virtual void SetPropertiesFromStreamData(Protocol &rProtocol);
+ virtual void WritePropertiesToStreamData(Protocol &rProtocol) const;
+
+ virtual void LogSysLog(const char *Action) const { }
+ virtual void LogFile(const char *Action, FILE *file) const { }
+};
+
+/*
+class Reply;
+
+class Request : public Message
+{
+public:
+ Request() { }
+ virtual ~Request() { }
+ Request(const Request &rToCopy) { }
+ virtual std::auto_ptr<Reply> DoCommand(Protocol &rProtocol,
+ ProtocolContext &rContext) = 0;
+};
+
+class Reply : public Message
+{
+public:
+ Reply() { }
+ virtual ~Reply() { }
+ Reply(const Reply &rToCopy) { }
+};
+*/
+
+#endif // PROTOCOLOBJECT__H
+
diff --git a/lib/server/Protocol.cpp b/lib/server/Protocol.cpp
index 5dc5d0b1..6333b1db 100644
--- a/lib/server/Protocol.cpp
+++ b/lib/server/Protocol.cpp
@@ -11,8 +11,14 @@
#include <sys/types.h>
+<<<<<<< HEAD
#include <stdlib.h>
#include <string.h>
+=======
+#include <cstdlib>
+#include <cstring>
+#include <cstdio>
+>>>>>>> 0.12
#include <new>
@@ -44,6 +50,7 @@
//
// --------------------------------------------------------------------------
Protocol::Protocol(IOStream &rStream)
+<<<<<<< HEAD
: mrStream(rStream),
mHandshakeDone(false),
mMaxObjectSize(PROTOCOL_DEFAULT_MAXOBJSIZE),
@@ -55,6 +62,19 @@ Protocol::Protocol(IOStream &rStream)
mValidDataSize(-1),
mLastErrorType(NoError),
mLastErrorSubType(NoError)
+=======
+: mrStream(rStream),
+ mHandshakeDone(false),
+ mMaxObjectSize(PROTOCOL_DEFAULT_MAXOBJSIZE),
+ mTimeout(PROTOCOL_DEFAULT_TIMEOUT),
+ mpBuffer(0),
+ mBufferSize(0),
+ mReadOffset(-1),
+ mWriteOffset(-1),
+ mValidDataSize(-1),
+ mLogToSysLog(false),
+ mLogToFile(NULL)
+>>>>>>> 0.12
{
BOX_TRACE("Send block allocation size is " <<
PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK);
@@ -82,6 +102,7 @@ Protocol::~Protocol()
// --------------------------------------------------------------------------
//
// Function
+<<<<<<< HEAD
// Name: Protocol::GetLastError(int &, int &)
// Purpose: Returns true if there was an error, and type and subtype if there was.
// Created: 2003/08/19
@@ -110,6 +131,8 @@ bool Protocol::GetLastError(int &rTypeOut, int &rSubTypeOut)
// --------------------------------------------------------------------------
//
// Function
+=======
+>>>>>>> 0.12
// Name: Protocol::Handshake()
// Purpose: Handshake with peer (exchange ident strings)
// Created: 2003/08/20
@@ -127,7 +150,11 @@ void Protocol::Handshake()
PW_Handshake hsSend;
::memset(&hsSend, 0, sizeof(hsSend));
// Copy in ident string
+<<<<<<< HEAD
::strncpy(hsSend.mIdent, GetIdentString(), sizeof(hsSend.mIdent));
+=======
+ ::strncpy(hsSend.mIdent, GetProtocolIdentString(), sizeof(hsSend.mIdent));
+>>>>>>> 0.12
// Send it
mrStream.Write(&hsSend, sizeof(hsSend));
@@ -200,7 +227,11 @@ void Protocol::CheckAndReadHdr(void *hdr)
// Created: 2003/08/19
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
std::auto_ptr<ProtocolObject> Protocol::Receive()
+=======
+std::auto_ptr<Message> Protocol::ReceiveInternal()
+>>>>>>> 0.12
{
// Get object header
PW_ObjectHeader objHeader;
@@ -220,7 +251,11 @@ std::auto_ptr<ProtocolObject> Protocol::Receive()
}
// Create a blank object
+<<<<<<< HEAD
std::auto_ptr<ProtocolObject> obj(MakeProtocolObject(ntohl(objHeader.mObjType)));
+=======
+ std::auto_ptr<Message> obj(MakeMessage(ntohl(objHeader.mObjType)));
+>>>>>>> 0.12
// Make sure memory is allocated to read it into
EnsureBufferAllocated(objSize);
@@ -272,7 +307,11 @@ std::auto_ptr<ProtocolObject> Protocol::Receive()
// Created: 2003/08/19
//
// --------------------------------------------------------------------------
+<<<<<<< HEAD
void Protocol::Send(const ProtocolObject &rObject)
+=======
+void Protocol::SendInternal(const Message &rObject)
+>>>>>>> 0.12
{
// Check usage
if(mValidDataSize != -1 || mWriteOffset != -1 || mReadOffset != -1)
@@ -715,6 +754,20 @@ void Protocol::SendStream(IOStream &rStream)
// Can't send this using the fixed size header
uncertainSize = true;
}
+<<<<<<< HEAD
+=======
+
+ if(streamSize == 0)
+ {
+ // Server protocol will throw an assertion failure if we
+ // try to send a stream whose size is definitely zero:
+ // ASSERT FAILED: [BytesToRead > 0] at PartialReadStream.cpp:31
+ // so catch this on the client side to help debugging
+ THROW_EXCEPTION_MESSAGE(ServerException, Protocol_BadUsage,
+ "Sending a stream with a definite size of zero "
+ "is not allowed in the protocol");
+ }
+>>>>>>> 0.12
// Inform sub class
InformStreamSending(streamSize);
@@ -854,7 +907,30 @@ int Protocol::SendStreamSendBlock(uint8_t *Block, int BytesInBlock)
// --------------------------------------------------------------------------
void Protocol::InformStreamReceiving(u_int32_t Size)
{
+<<<<<<< HEAD
// Do nothing
+=======
+ if(GetLogToSysLog())
+ {
+ if(Size == Protocol::ProtocolStream_SizeUncertain)
+ {
+ BOX_TRACE("Receiving stream, size uncertain");
+ }
+ else
+ {
+ BOX_TRACE("Receiving stream, size " << Size);
+ }
+ }
+
+ if(GetLogToFile())
+ {
+ ::fprintf(GetLogToFile(),
+ (Size == Protocol::ProtocolStream_SizeUncertain)
+ ? "Receiving stream, size uncertain\n"
+ : "Receiving stream, size %d\n", Size);
+ ::fflush(GetLogToFile());
+ }
+>>>>>>> 0.12
}
// --------------------------------------------------------------------------
@@ -867,7 +943,30 @@ void Protocol::InformStreamReceiving(u_int32_t Size)
// --------------------------------------------------------------------------
void Protocol::InformStreamSending(u_int32_t Size)
{
+<<<<<<< HEAD
// Do nothing
+=======
+ if(GetLogToSysLog())
+ {
+ if(Size == Protocol::ProtocolStream_SizeUncertain)
+ {
+ BOX_TRACE("Sending stream, size uncertain");
+ }
+ else
+ {
+ BOX_TRACE("Sending stream, size " << Size);
+ }
+ }
+
+ if(GetLogToFile())
+ {
+ ::fprintf(GetLogToFile(),
+ (Size == Protocol::ProtocolStream_SizeUncertain)
+ ? "Sending stream, size uncertain\n"
+ : "Sending stream, size %d\n", Size);
+ ::fflush(GetLogToFile());
+ }
+>>>>>>> 0.12
}
diff --git a/lib/server/Protocol.h b/lib/server/Protocol.h
index e037e33c..dc660ad5 100644
--- a/lib/server/Protocol.h
+++ b/lib/server/Protocol.h
@@ -12,12 +12,22 @@
#include <sys/types.h>
+<<<<<<< HEAD
class IOStream;
#include "ProtocolObject.h"
+=======
+>>>>>>> 0.12
#include <memory>
#include <vector>
#include <string>
+<<<<<<< HEAD
+=======
+#include "Message.h"
+
+class IOStream;
+
+>>>>>>> 0.12
// default timeout is 15 minutes
#define PROTOCOL_DEFAULT_TIMEOUT (15*60*1000)
// 16 default maximum object size -- should be enough
@@ -40,11 +50,22 @@ public:
private:
Protocol(const Protocol &rToCopy);
+<<<<<<< HEAD
public:
void Handshake();
std::auto_ptr<ProtocolObject> Receive();
void Send(const ProtocolObject &rObject);
+=======
+protected:
+ // Unsafe to make public, as they may allow sending objects
+ // from a different protocol. The derived class prevents this.
+ std::auto_ptr<Message> ReceiveInternal();
+ void SendInternal(const Message &rObject);
+
+public:
+ void Handshake();
+>>>>>>> 0.12
std::auto_ptr<IOStream> ReceiveStream();
void SendStream(IOStream &rStream);
@@ -54,8 +75,11 @@ public:
UnknownError = 0
};
+<<<<<<< HEAD
bool GetLastError(int &rTypeOut, int &rSubTypeOut);
+=======
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
// Function
@@ -87,7 +111,11 @@ public:
// --------------------------------------------------------------------------
void SetMaxObjectSize(unsigned int NewMaxObjSize) {mMaxObjectSize = NewMaxObjSize;}
+<<<<<<< HEAD
// For ProtocolObject derived classes
+=======
+ // For Message derived classes
+>>>>>>> 0.12
void Read(void *Buffer, int Size);
void Read(std::string &rOut, int Size);
void Read(int64_t &rOut);
@@ -168,11 +196,23 @@ public:
{
ProtocolStream_SizeUncertain = 0xffffffff
};
+<<<<<<< HEAD
protected:
virtual std::auto_ptr<ProtocolObject> MakeProtocolObject(int ObjType) = 0;
virtual const char *GetIdentString() = 0;
void SetError(int Type, int SubType) {mLastErrorType = Type; mLastErrorSubType = SubType;}
+=======
+ bool GetLogToSysLog() { return mLogToSysLog; }
+ FILE *GetLogToFile() { return mLogToFile; }
+ void SetLogToSysLog(bool Log = false) {mLogToSysLog = Log;}
+ void SetLogToFile(FILE *File = 0) {mLogToFile = File;}
+
+protected:
+ virtual std::auto_ptr<Message> MakeMessage(int ObjType) = 0;
+ virtual const char *GetProtocolIdentString() = 0;
+
+>>>>>>> 0.12
void CheckAndReadHdr(void *hdr); // don't use type here to avoid dependency
// Will be used for logging
@@ -183,7 +223,10 @@ private:
void EnsureBufferAllocated(int Size);
int SendStreamSendBlock(uint8_t *Block, int BytesInBlock);
+<<<<<<< HEAD
private:
+=======
+>>>>>>> 0.12
IOStream &mrStream;
bool mHandshakeDone;
unsigned int mMaxObjectSize;
@@ -193,8 +236,17 @@ private:
int mReadOffset;
int mWriteOffset;
int mValidDataSize;
+<<<<<<< HEAD
int mLastErrorType;
int mLastErrorSubType;
+=======
+ bool mLogToSysLog;
+ FILE *mLogToFile;
+};
+
+class ProtocolContext
+{
+>>>>>>> 0.12
};
#endif // PROTOCOL__H
diff --git a/lib/server/SSLLib.cpp b/lib/server/SSLLib.cpp
index de7a941b..6feaae4a 100644
--- a/lib/server/SSLLib.cpp
+++ b/lib/server/SSLLib.cpp
@@ -18,6 +18,10 @@
#include <wincrypt.h>
#endif
+<<<<<<< HEAD
+=======
+#include "CryptoUtils.h"
+>>>>>>> 0.12
#include "SSLLib.h"
#include "ServerException.h"
@@ -39,8 +43,14 @@ void SSLLib::Initialise()
{
if(!::SSL_library_init())
{
+<<<<<<< HEAD
LogError("initialising OpenSSL");
THROW_EXCEPTION(ServerException, SSLLibraryInitialisationError)
+=======
+ THROW_EXCEPTION_MESSAGE(ServerException,
+ SSLLibraryInitialisationError,
+ CryptoUtils::LogError("initialising OpenSSL"));
+>>>>>>> 0.12
}
// More helpful error messages
@@ -89,6 +99,7 @@ void SSLLib::Initialise()
}
+<<<<<<< HEAD
// --------------------------------------------------------------------------
//
// Function
@@ -109,3 +120,5 @@ void SSLLib::LogError(const std::string& rErrorDuringAction)
}
}
+=======
+>>>>>>> 0.12
diff --git a/lib/server/SSLLib.h b/lib/server/SSLLib.h
index ff4aab19..b679d623 100644
--- a/lib/server/SSLLib.h
+++ b/lib/server/SSLLib.h
@@ -29,7 +29,10 @@
namespace SSLLib
{
void Initialise();
+<<<<<<< HEAD
void LogError(const std::string& rErrorDuringAction);
+=======
+>>>>>>> 0.12
};
#endif // SSLLIB__H
diff --git a/lib/server/ServerException.txt b/lib/server/ServerException.txt
index ed591b73..f8c558c6 100644
--- a/lib/server/ServerException.txt
+++ b/lib/server/ServerException.txt
@@ -13,7 +13,11 @@ SocketOpenError 10
SocketPollError 11
SocketCloseError 13
SocketNameUNIXPathTooLong 14
+<<<<<<< HEAD
SocketBindError 16 Check the ListenAddresses directive in your config file -- must refer to local IP addresses only
+=======
+SocketBindError 16 Check the ListenAddresses directive (bbstored) or CommandSocket (bbackupd) in your config file -- must refer to local IP addresses (or existing writable path) only
+>>>>>>> 0.12
SocketAcceptError 17
ServerStreamBadListenAddrs 18
ServerForkError 19
diff --git a/lib/server/ServerStream.h b/lib/server/ServerStream.h
index e49dbcbe..8625832d 100644
--- a/lib/server/ServerStream.h
+++ b/lib/server/ServerStream.h
@@ -48,6 +48,18 @@ private:
ServerStream(const ServerStream &rToCopy)
{
}
+<<<<<<< HEAD
+=======
+
+ std::string mConnectionDetails;
+
+protected:
+ const std::string& GetConnectionDetails()
+ {
+ return mConnectionDetails;
+ }
+
+>>>>>>> 0.12
public:
virtual const char *DaemonName() const
@@ -122,6 +134,13 @@ public:
protected:
virtual void NotifyListenerIsReady() { }
+<<<<<<< HEAD
+=======
+ virtual void LogConnectionDetails(std::string details)
+ {
+ BOX_NOTICE("Handling incoming connection from " << details);
+ }
+>>>>>>> 0.12
public:
virtual void Run2(bool &rChildExit)
@@ -237,8 +256,14 @@ public:
{
// Get the incoming connection
// (with zero wait time)
+<<<<<<< HEAD
std::string logMessage;
std::auto_ptr<StreamType> connection(psocket->Accept(0, &logMessage));
+=======
+ std::auto_ptr<StreamType> connection(
+ psocket->Accept(0,
+ &mConnectionDetails));
+>>>>>>> 0.12
// Was there one (there should be...)
if(connection.get())
@@ -264,6 +289,10 @@ public:
// Set up daemon
EnterChild();
SetProcessTitle("transaction");
+<<<<<<< HEAD
+=======
+ LogConnectionDetails(mConnectionDetails);
+>>>>>>> 0.12
// Memory leak test the forked process
#ifdef BOX_MEMORY_LEAK_TESTING
@@ -281,7 +310,13 @@ public:
}
// Log it
+<<<<<<< HEAD
BOX_NOTICE("Message from child process " << pid << ": " << logMessage);
+=======
+ BOX_TRACE("Forked child process " << pid <<
+ " to handle connection from " <<
+ mConnectionDetails);
+>>>>>>> 0.12
}
else
{
diff --git a/lib/server/Socket.cpp b/lib/server/Socket.cpp
index 4a83bdb0..69a11330 100644
--- a/lib/server/Socket.cpp
+++ b/lib/server/Socket.cpp
@@ -123,6 +123,7 @@ void Socket::NameLookupToSockAddr(SocketAllAddr &addr, int &sockDomain,
// --------------------------------------------------------------------------
void Socket::LogIncomingConnection(const struct sockaddr *addr, socklen_t addrlen)
{
+<<<<<<< HEAD
if(addr == NULL) {THROW_EXCEPTION(CommonException, BadArguments)}
switch(addr->sa_family)
@@ -144,6 +145,10 @@ void Socket::LogIncomingConnection(const struct sockaddr *addr, socklen_t addrle
BOX_WARNING("Incoming connection of unknown type");
break;
}
+=======
+ BOX_INFO("Incoming connection from " <<
+ IncomingConnectionLogMessage(addr, addrlen));
+>>>>>>> 0.12
}
// --------------------------------------------------------------------------
@@ -161,20 +166,40 @@ std::string Socket::IncomingConnectionLogMessage(const struct sockaddr *addr, so
switch(addr->sa_family)
{
case AF_UNIX:
+<<<<<<< HEAD
return std::string("Incoming connection from local (UNIX socket)");
+=======
+ return std::string("local (UNIX socket)");
+>>>>>>> 0.12
break;
case AF_INET:
{
+<<<<<<< HEAD
char msg[256]; // more than enough
sockaddr_in *a = (sockaddr_in*)addr;
sprintf(msg, "Incoming connection from %s port %d", inet_ntoa(a->sin_addr), ntohs(a->sin_port));
return std::string(msg);
+=======
+ sockaddr_in *a = (sockaddr_in*)addr;
+ std::ostringstream oss;
+ oss << inet_ntoa(a->sin_addr) << " port " <<
+ ntohs(a->sin_port);
+ return oss.str();
+>>>>>>> 0.12
}
break;
default:
+<<<<<<< HEAD
return std::string("Incoming connection of unknown type");
+=======
+ {
+ std::ostringstream oss;
+ oss << "unknown socket type " << addr->sa_family;
+ return oss.str();
+ }
+>>>>>>> 0.12
break;
}
diff --git a/lib/server/SocketListen.h b/lib/server/SocketListen.h
index 586adf22..635b15e8 100644
--- a/lib/server/SocketListen.h
+++ b/lib/server/SocketListen.h
@@ -87,12 +87,24 @@ public:
{
Close();
}
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
private:
SocketListen(const SocketListen &rToCopy)
{
}
+<<<<<<< HEAD
public:
+=======
+
+ int mType, mPort;
+ std::string mName;
+
+public:
+>>>>>>> 0.12
enum
{
MaxMultipleListenSockets = MaxMultiListenSockets
@@ -108,8 +120,13 @@ public:
if(::close(mSocketHandle) == -1)
#endif
{
+<<<<<<< HEAD
BOX_LOG_SYS_ERROR("Failed to close network "
"socket");
+=======
+ BOX_LOG_SOCKET_ERROR(mType, mName, mPort,
+ "Failed to close network socket");
+>>>>>>> 0.12
THROW_EXCEPTION(ServerException,
SocketCloseError)
}
@@ -127,6 +144,13 @@ public:
// ------------------------------------------------------------------
void Listen(Socket::Type Type, const char *Name, int Port = 0)
{
+<<<<<<< HEAD
+=======
+ mType = Type;
+ mName = Name;
+ mPort = Port;
+
+>>>>>>> 0.12
if(mSocketHandle != -1)
{
THROW_EXCEPTION(ServerException, SocketAlreadyOpen);
@@ -144,7 +168,12 @@ public:
0 /* let OS choose protocol */);
if(mSocketHandle == -1)
{
+<<<<<<< HEAD
BOX_LOG_SYS_ERROR("Failed to create a network socket");
+=======
+ BOX_LOG_SOCKET_ERROR(Type, Name, Port,
+ "Failed to create a network socket");
+>>>>>>> 0.12
THROW_EXCEPTION(ServerException, SocketOpenError)
}
@@ -158,7 +187,12 @@ public:
&option, sizeof(option)) == -1)
#endif
{
+<<<<<<< HEAD
BOX_LOG_SYS_ERROR("Failed to set socket options");
+=======
+ BOX_LOG_SOCKET_ERROR(Type, Name, Port,
+ "Failed to set socket options");
+>>>>>>> 0.12
THROW_EXCEPTION(ServerException, SocketOpenError)
}
@@ -166,10 +200,25 @@ public:
if(::bind(mSocketHandle, &addr.sa_generic, addrLen) == -1
|| ::listen(mSocketHandle, ListenBacklog) == -1)
{
+<<<<<<< HEAD
// Dispose of the socket
::close(mSocketHandle);
mSocketHandle = -1;
THROW_EXCEPTION(ServerException, SocketBindError)
+=======
+ int err_number = errno;
+
+ BOX_LOG_SOCKET_ERROR(Type, Name, Port,
+ "Failed to bind socket");
+
+ // Dispose of the socket
+ ::close(mSocketHandle);
+ mSocketHandle = -1;
+
+ THROW_SYS_FILE_ERRNO("Failed to bind or listen "
+ "on socket", Name, err_number,
+ ServerException, SocketBindError);
+>>>>>>> 0.12
}
}
@@ -222,7 +271,11 @@ public:
// signal?
if(errno == EINTR)
{
+<<<<<<< HEAD
BOX_ERROR("Failed to accept "
+=======
+ BOX_INFO("Failed to accept "
+>>>>>>> 0.12
"connection: interrupted by "
"signal");
// return nothing
@@ -230,8 +283,13 @@ public:
}
else
{
+<<<<<<< HEAD
BOX_LOG_SYS_ERROR("Failed to poll "
"connection");
+=======
+ BOX_LOG_SOCKET_ERROR(mType, mName, mPort,
+ "Failed to poll connection");
+>>>>>>> 0.12
THROW_EXCEPTION(ServerException,
SocketPollError)
}
@@ -250,7 +308,12 @@ public:
// Got socket (or error), unlock (implicit in destruction)
if(sock == -1)
{
+<<<<<<< HEAD
BOX_LOG_SYS_ERROR("Failed to accept connection");
+=======
+ BOX_LOG_SOCKET_ERROR(mType, mName, mPort,
+ "Failed to accept connection");
+>>>>>>> 0.12
THROW_EXCEPTION(ServerException, SocketAcceptError)
}
diff --git a/lib/server/SocketStream.cpp b/lib/server/SocketStream.cpp
index 95b4b4f4..7c92ebba 100644
--- a/lib/server/SocketStream.cpp
+++ b/lib/server/SocketStream.cpp
@@ -154,14 +154,24 @@ void SocketStream::Open(Socket::Type Type, const std::string& rName, int Port)
int sockDomain = 0;
SocketAllAddr addr;
int addrLen = 0;
+<<<<<<< HEAD
Socket::NameLookupToSockAddr(addr, sockDomain, Type, rName, Port, addrLen);
+=======
+ Socket::NameLookupToSockAddr(addr, sockDomain, Type, rName, Port,
+ addrLen);
+>>>>>>> 0.12
// Create the socket
mSocketHandle = ::socket(sockDomain, SOCK_STREAM,
0 /* let OS choose protocol */);
if(mSocketHandle == INVALID_SOCKET_VALUE)
{
+<<<<<<< HEAD
BOX_LOG_SYS_ERROR("Failed to create a network socket");
+=======
+ BOX_LOG_SOCKET_ERROR(Type, rName, Port,
+ "Failed to create a network socket");
+>>>>>>> 0.12
THROW_EXCEPTION(ServerException, SocketOpenError)
}
@@ -169,6 +179,7 @@ void SocketStream::Open(Socket::Type Type, const std::string& rName, int Port)
if(::connect(mSocketHandle, &addr.sa_generic, addrLen) == -1)
{
// Dispose of the socket
+<<<<<<< HEAD
#ifdef WIN32
DWORD err = WSAGetLastError();
::closesocket(mSocketHandle);
@@ -179,6 +190,13 @@ void SocketStream::Open(Socket::Type Type, const std::string& rName, int Port)
BOX_LOG_SYS_ERROR("Failed to connect to socket (type " <<
Type << ", name " << rName << ", port " << Port <<
")");
+=======
+ BOX_LOG_SOCKET_ERROR(Type, rName, Port,
+ "Failed to connect to socket");
+#ifdef WIN32
+ ::closesocket(mSocketHandle);
+#else // !WIN32
+>>>>>>> 0.12
::close(mSocketHandle);
#endif // WIN32
diff --git a/lib/server/SocketStream.h b/lib/server/SocketStream.h
index 2b582f21..aa62e4e9 100644
--- a/lib/server/SocketStream.h
+++ b/lib/server/SocketStream.h
@@ -51,7 +51,10 @@ public:
virtual bool GetPeerCredentials(uid_t &rUidOut, gid_t &rGidOut);
protected:
+<<<<<<< HEAD
tOSSocketHandle GetSocketHandle();
+=======
+>>>>>>> 0.12
void MarkAsReadClosed() {mReadClosed = true;}
void MarkAsWriteClosed() {mWriteClosed = true;}
@@ -69,6 +72,14 @@ public:
off_t GetBytesWritten() const {return mBytesWritten;}
void ResetCounters() {mBytesRead = mBytesWritten = 0;}
bool IsOpened() { return mSocketHandle != INVALID_SOCKET_VALUE; }
+<<<<<<< HEAD
+=======
+
+ /**
+ * Only for use by NiceSocketStream!
+ */
+ tOSSocketHandle GetSocketHandle();
+>>>>>>> 0.12
};
#endif // SOCKETSTREAM__H
diff --git a/lib/server/SocketStreamTLS.cpp b/lib/server/SocketStreamTLS.cpp
index 19fdadd4..6f1cc46a 100644
--- a/lib/server/SocketStreamTLS.cpp
+++ b/lib/server/SocketStreamTLS.cpp
@@ -19,11 +19,20 @@
#include <poll.h>
#endif
+<<<<<<< HEAD
#include "SocketStreamTLS.h"
#include "SSLLib.h"
#include "ServerException.h"
#include "TLSContext.h"
#include "BoxTime.h"
+=======
+#include "BoxTime.h"
+#include "CryptoUtils.h"
+#include "ServerException.h"
+#include "SocketStreamTLS.h"
+#include "SSLLib.h"
+#include "TLSContext.h"
+>>>>>>> 0.12
#include "MemLeakFindOn.h"
@@ -124,7 +133,11 @@ void SocketStreamTLS::Handshake(const TLSContext &rContext, bool IsServer)
mpBIO = ::BIO_new(::BIO_s_socket());
if(mpBIO == 0)
{
+<<<<<<< HEAD
SSLLib::LogError("creating socket bio");
+=======
+ CryptoUtils::LogError("creating socket bio");
+>>>>>>> 0.12
THROW_EXCEPTION(ServerException, TLSAllocationFailed)
}
@@ -135,7 +148,11 @@ void SocketStreamTLS::Handshake(const TLSContext &rContext, bool IsServer)
mpSSL = ::SSL_new(rContext.GetRawContext());
if(mpSSL == 0)
{
+<<<<<<< HEAD
SSLLib::LogError("creating SSL object");
+=======
+ CryptoUtils::LogError("creating SSL object");
+>>>>>>> 0.12
THROW_EXCEPTION(ServerException, TLSAllocationFailed)
}
@@ -203,12 +220,20 @@ void SocketStreamTLS::Handshake(const TLSContext &rContext, bool IsServer)
// Error occured
if(IsServer)
{
+<<<<<<< HEAD
SSLLib::LogError("accepting connection");
+=======
+ CryptoUtils::LogError("accepting connection");
+>>>>>>> 0.12
THROW_EXCEPTION(ConnectionException, Conn_TLSHandshakeFailed)
}
else
{
+<<<<<<< HEAD
SSLLib::LogError("connecting");
+=======
+ CryptoUtils::LogError("connecting");
+>>>>>>> 0.12
THROW_EXCEPTION(ConnectionException, Conn_TLSHandshakeFailed)
}
}
@@ -335,7 +360,11 @@ int SocketStreamTLS::Read(void *pBuffer, int NBytes, int Timeout)
break;
default:
+<<<<<<< HEAD
SSLLib::LogError("reading");
+=======
+ CryptoUtils::LogError("reading");
+>>>>>>> 0.12
THROW_EXCEPTION(ConnectionException, Conn_TLSReadFailed)
break;
}
@@ -400,7 +429,11 @@ void SocketStreamTLS::Write(const void *pBuffer, int NBytes)
break;
default:
+<<<<<<< HEAD
SSLLib::LogError("writing");
+=======
+ CryptoUtils::LogError("writing");
+>>>>>>> 0.12
THROW_EXCEPTION(ConnectionException, Conn_TLSWriteFailed)
break;
}
@@ -442,7 +475,11 @@ void SocketStreamTLS::Shutdown(bool Read, bool Write)
if(::SSL_shutdown(mpSSL) < 0)
{
+<<<<<<< HEAD
SSLLib::LogError("shutting down");
+=======
+ CryptoUtils::LogError("shutting down");
+>>>>>>> 0.12
THROW_EXCEPTION(ConnectionException, Conn_TLSShutdownFailed)
}
diff --git a/lib/server/TLSContext.cpp b/lib/server/TLSContext.cpp
index ebc7384a..1f06d602 100644
--- a/lib/server/TLSContext.cpp
+++ b/lib/server/TLSContext.cpp
@@ -12,7 +12,11 @@
#define TLS_CLASS_IMPLEMENTATION_CPP
#include <openssl/ssl.h>
+<<<<<<< HEAD
#include "TLSContext.h"
+=======
+#include "CryptoUtils.h"
+>>>>>>> 0.12
#include "ServerException.h"
#include "SSLLib.h"
#include "TLSContext.h"
@@ -77,14 +81,22 @@ void TLSContext::Initialise(bool AsServer, const char *CertificatesFile, const c
{
std::string msg = "loading certificates from ";
msg += CertificatesFile;
+<<<<<<< HEAD
SSLLib::LogError(msg);
+=======
+ CryptoUtils::LogError(msg);
+>>>>>>> 0.12
THROW_EXCEPTION(ServerException, TLSLoadCertificatesFailed)
}
if(::SSL_CTX_use_PrivateKey_file(mpContext, PrivateKeyFile, SSL_FILETYPE_PEM) != 1)
{
std::string msg = "loading private key from ";
msg += PrivateKeyFile;
+<<<<<<< HEAD
SSLLib::LogError(msg);
+=======
+ CryptoUtils::LogError(msg);
+>>>>>>> 0.12
THROW_EXCEPTION(ServerException, TLSLoadPrivateKeyFailed)
}
@@ -93,7 +105,11 @@ void TLSContext::Initialise(bool AsServer, const char *CertificatesFile, const c
{
std::string msg = "loading CA cert from ";
msg += TrustedCAsFile;
+<<<<<<< HEAD
SSLLib::LogError(msg);
+=======
+ CryptoUtils::LogError(msg);
+>>>>>>> 0.12
THROW_EXCEPTION(ServerException, TLSLoadTrustedCAsFailed)
}
@@ -105,7 +121,11 @@ void TLSContext::Initialise(bool AsServer, const char *CertificatesFile, const c
// Setup allowed ciphers
if(::SSL_CTX_set_cipher_list(mpContext, CIPHER_LIST) != 1)
{
+<<<<<<< HEAD
SSLLib::LogError("setting cipher list to " CIPHER_LIST);
+=======
+ CryptoUtils::LogError("setting cipher list to " CIPHER_LIST);
+>>>>>>> 0.12
THROW_EXCEPTION(ServerException, TLSSetCiphersFailed)
}
}
diff --git a/lib/server/TcpNice.cpp b/lib/server/TcpNice.cpp
new file mode 100644
index 00000000..20619e49
--- /dev/null
+++ b/lib/server/TcpNice.cpp
@@ -0,0 +1,235 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: TcpNice.cpp
+// Purpose: Calculator for adaptive TCP window sizing to support
+// low-priority background flows using the stochastic
+// algorithm, as described in
+// http://www.thlab.net/~lmassoul/p18-key.pdf
+// Created: 11/02/2012
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include "TcpNice.h"
+#include "Logging.h"
+#include "BoxTime.h"
+
+#ifdef HAVE_NETINET_TCP_H
+# include <netinet/tcp.h>
+#endif
+
+#ifdef HAVE_WINSOCK2_H
+# include <winsock2.h>
+#endif
+
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+
+#include "MemLeakFindOn.h"
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: TcpNice::TcpNice()
+// Purpose: Initialise state of the calculator
+// Created: 11/02/2012
+//
+// --------------------------------------------------------------------------
+TcpNice::TcpNice()
+: mLastWindowSize(1),
+ mGammaPercent(100),
+ mAlphaStar(100),
+ mDeltaPercent(10)
+{
+ mRateEstimateMovingAverage[0] = 0;
+ mRateEstimateMovingAverage[1] = 0;
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: int GetNextWindowSize(int bytesChange,
+// box_time_t timeElapsed, int rttEstimateMillis)
+// Purpose: Calculate the next recommended window size, given the
+// number of bytes sent since the previous recommendation,
+// and the time elapsed.
+// Created: 11/02/2012
+//
+// --------------------------------------------------------------------------
+int TcpNice::GetNextWindowSize(int bytesChange, box_time_t timeElapsed,
+ int rttEstimateMicros)
+{
+ int epsilon = (mAlphaStar * 1000000) / rttEstimateMicros;
+
+ // timeElapsed is in microseconds, so this will fail for T > 2000 seconds
+ int rateLastPeriod = ((uint64_t)bytesChange * 1000000 / timeElapsed);
+
+ int rawAdjustment = epsilon + rateLastPeriod -
+ mRateEstimateMovingAverage[0];
+
+ int gammaAdjustment = (rawAdjustment * mGammaPercent) / 100;
+
+ int newWindowSize = mLastWindowSize + gammaAdjustment;
+
+ int newRateEstimateMovingAverage =
+ (((100 - mDeltaPercent) * mRateEstimateMovingAverage[1]) / 100) +
+ ((mDeltaPercent * rateLastPeriod) / 100);
+
+ /*
+ * b is the number of bytes sent during the previous control period
+ * T is the length (in us) of the previous control period
+ * rtt is the round trip time (in us) reported by the kernel on the socket
+ * e is epsilon, a parameter of the formula, calculated as alpha/rtt
+ * rb is the actual rate (goodput) over the previous period
+ * rbhat is the previous (last-but-one) EWMA rate estimate
+ * raw is the unscaled adjustment to the window size
+ * gamma is the scaled adjustment to the window size
+ * wb is the final window size
+ */
+
+ BOX_TRACE("TcpNice: "
+ "b=" << bytesChange << ", "
+ "T=" << timeElapsed << ", "
+ "rtt=" << rttEstimateMicros << ", "
+ "e=" << epsilon << ", "
+ "rb=" << rateLastPeriod << ", "
+ "rbhat=" << newRateEstimateMovingAverage << ", "
+ "raw=" << rawAdjustment << ", "
+ "gamma=" << gammaAdjustment << ", "
+ "wb=" << newWindowSize);
+
+ mRateEstimateMovingAverage[0] = mRateEstimateMovingAverage[1];
+ mRateEstimateMovingAverage[1] = newRateEstimateMovingAverage;
+ mLastWindowSize = newWindowSize;
+
+ return newWindowSize;
+}
+
+// --------------------------------------------------------------------------
+//
+// Constructor
+// Name: NiceSocketStream::NiceSocketStream(
+// std::auto_ptr<SocketStream> apSocket)
+// Purpose: Initialise state of the socket wrapper
+// Created: 11/02/2012
+//
+// --------------------------------------------------------------------------
+
+NiceSocketStream::NiceSocketStream(std::auto_ptr<SocketStream> apSocket)
+: mapSocket(apSocket),
+ mTcpNice(),
+ mBytesWrittenThisPeriod(0),
+ mPeriodStartTime(GetCurrentBoxTime()),
+ mTimeIntervalMillis(1000),
+ mEnabled(false)
+{ }
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: NiceSocketStream::Write(const void *pBuffer, int NBytes)
+// Purpose: Writes bytes to the underlying stream, adjusting window size
+// using a TcpNice calculator.
+// Created: 2012/02/11
+//
+// --------------------------------------------------------------------------
+void NiceSocketStream::Write(const void *pBuffer, int NBytes)
+{
+#if HAVE_DECL_SO_SNDBUF && HAVE_DECL_TCP_INFO
+ if(mEnabled && mapTimer.get() && mapTimer->HasExpired())
+ {
+ box_time_t newPeriodStart = GetCurrentBoxTime();
+ box_time_t elapsed = newPeriodStart - mPeriodStartTime;
+ int socket = mapSocket->GetSocketHandle();
+ int rtt = 50; // WAG
+
+# if HAVE_DECL_SOL_TCP && HAVE_DECL_TCP_INFO && HAVE_STRUCT_TCP_INFO_TCPI_RTT
+ struct tcp_info info;
+ socklen_t optlen = sizeof(info);
+ if(getsockopt(socket, SOL_TCP, TCP_INFO, &info, &optlen) == -1)
+ {
+ BOX_LOG_SYS_WARNING("getsockopt(" << socket << ", SOL_TCP, "
+ "TCP_INFO) failed");
+ }
+ else if(optlen != sizeof(info))
+ {
+ BOX_WARNING("getsockopt(" << socket << ", SOL_TCP, "
+ "TCP_INFO) return structure size " << optlen << ", "
+ "expected " << sizeof(info));
+ }
+ else
+ {
+ rtt = info.tcpi_rtt;
+ }
+# endif
+
+ int newWindow = mTcpNice.GetNextWindowSize(mBytesWrittenThisPeriod,
+ elapsed, rtt);
+
+ if(setsockopt(socket, SOL_SOCKET, SO_SNDBUF, &newWindow,
+ sizeof(newWindow)) == -1)
+ {
+ BOX_LOG_SYS_WARNING("getsockopt(" << socket << ", SOL_SOCKET, "
+ "SO_SNDBUF, " << newWindow << ") failed");
+ }
+
+ StopTimer();
+ }
+
+ if(mEnabled && !mapTimer.get())
+ {
+ // Don't start the timer until we receive the first data to write,
+ // as diffing might take a long time and we don't want to bias
+ // the TcpNice algorithm by running while we don't have bulk data
+ // to send.
+ StartTimer();
+ mPeriodStartTime = GetCurrentBoxTime();
+ mBytesWrittenThisPeriod = 0;
+ }
+
+ mBytesWrittenThisPeriod += NBytes;
+#endif // HAVE_DECL_SO_SNDBUF
+
+ mapSocket->Write(pBuffer, NBytes);
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: NiceSocketStream::SetEnabled(bool enabled)
+// Purpose: Update the enabled status, and if disabling, cancel the
+// timer and set a sensible window size.
+// Created: 2012/02/12
+//
+// --------------------------------------------------------------------------
+
+void NiceSocketStream::SetEnabled(bool enabled)
+{
+ mEnabled = enabled;
+
+ if(!enabled)
+ {
+ StopTimer();
+#if HAVE_DECL_SO_SNDBUF
+ int socket = mapSocket->GetSocketHandle();
+ int newWindow = 1<<17;
+ if(setsockopt(socket, SOL_SOCKET, SO_SNDBUF,
+# ifdef WIN32
+ // optval is a const char * on Windows, even
+ // though the argument is a boolean or integer,
+ // for reasons best known to Microsoft!
+ (const char *)&newWindow,
+# else
+ &newWindow,
+# endif
+ sizeof(newWindow)) == -1)
+ {
+ BOX_LOG_SYS_WARNING("getsockopt(" << socket << ", SOL_SOCKET, "
+ "SO_SNDBUF, " << newWindow << ") failed");
+ }
+#endif
+ }
+}
diff --git a/lib/server/TcpNice.h b/lib/server/TcpNice.h
new file mode 100644
index 00000000..e2027749
--- /dev/null
+++ b/lib/server/TcpNice.h
@@ -0,0 +1,174 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: TcpNice.h
+// Purpose: Calculator for adaptive TCP window sizing to support
+// low-priority background flows using the stochastic
+// algorithm, as described in
+// http://www.thlab.net/~lmassoul/p18-key.pdf
+// Created: 11/02/2012
+//
+// --------------------------------------------------------------------------
+
+#ifndef TCPNICE__H
+#define TCPNICE__H
+
+#include <memory>
+
+#include "SocketStream.h"
+#include "Timer.h"
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: TcpNice
+// Purpose: Calculator for adaptive TCP window sizing.
+// Created: 11/02/2012
+//
+// --------------------------------------------------------------------------
+
+class TcpNice
+{
+public:
+ TcpNice();
+ int GetNextWindowSize(int bytesChange, box_time_t timeElapsed,
+ int rttEstimateMicros);
+
+private:
+ /**
+ * The previous (last recommended) window size is one of the parameters
+ * used to calculate the next window size.
+ */
+ int mLastWindowSize;
+
+ /**
+ * Controls the speed of adaptation and the variance (random variation)
+ * of the stable state in response to noise. The paper suggests using
+ * 1.0 (100%).
+ */
+ int mGammaPercent;
+
+ /**
+ * Controls the extent to which background flows are allowed to affect
+ * foreground flows. Its detailed meaning is not explained in the paper,
+ * but its units are bytes, and I think it controls how aggressive we
+ * are at increasing window size, potentially at the expense of other
+ * competing flows.
+ */
+ int mAlphaStar;
+
+ /**
+ * Controls the speed of adaptation of the exponential weighted moving
+ * average (EWMA) estimate of the bandwidth available to this flow.
+ * The paper uses 10%.
+ */
+ int mDeltaPercent;
+
+ /**
+ * The stochastic algorithm in the paper uses the rate estimate for the
+ * last-but-one period (rbHat[n-2]) to calculate the next window size.
+ * So we keep both the last (in rateEstimateMovingAverage[1]) and the
+ * last-but-one (in rateEstimateMovingAverage[0]) values.
+ */
+ int mRateEstimateMovingAverage[2];
+};
+
+// --------------------------------------------------------------------------
+//
+// Class
+// Name: NiceSocketStream
+// Purpose: Wrapper around a SocketStream to limit sending rate to
+// avoid interference with higher-priority flows.
+// Created: 11/02/2012
+//
+// --------------------------------------------------------------------------
+
+class NiceSocketStream : public IOStream
+{
+private:
+ std::auto_ptr<SocketStream> mapSocket;
+ TcpNice mTcpNice;
+ std::auto_ptr<Timer> mapTimer;
+ int mBytesWrittenThisPeriod;
+ box_time_t mPeriodStartTime;
+
+ /**
+ * The control interval T from the paper, in milliseconds. The available
+ * bandwidth is estimated over this period, and the window size is
+ * recalculated at the end of each period. It should be long enough for
+ * TCP to adapt to a change in window size; perhaps 10-100 RTTs. One
+ * second (1000) is probably a good first approximation in many cases.
+ */
+ int mTimeIntervalMillis;
+
+ /**
+ * Because our data use is bursty, and tcp nice works on the assumption
+ * that we've always got data to send, we should only enable nice mode
+ * when we're doing a bulk upload, and disable it afterwards.
+ */
+ bool mEnabled;
+
+ void StartTimer()
+ {
+ mapTimer.reset(new Timer(mTimeIntervalMillis, "NiceSocketStream"));
+ }
+
+ void StopTimer()
+ {
+ mapTimer.reset();
+ }
+
+public:
+ NiceSocketStream(std::auto_ptr<SocketStream> apSocket);
+ virtual ~NiceSocketStream()
+ {
+ // Be nice about closing the socket
+ mapSocket->Shutdown();
+ mapSocket->Close();
+ }
+
+ // This is the only magic
+ virtual void Write(const void *pBuffer, int NBytes);
+
+ // Everything else is delegated to the sink
+ virtual int Read(void *pBuffer, int NBytes,
+ int Timeout = IOStream::TimeOutInfinite)
+ {
+ return mapSocket->Read(pBuffer, NBytes, Timeout);
+ }
+ virtual pos_type BytesLeftToRead()
+ {
+ return mapSocket->BytesLeftToRead();
+ }
+ virtual pos_type GetPosition() const
+ {
+ return mapSocket->GetPosition();
+ }
+ virtual void Seek(IOStream::pos_type Offset, int SeekType)
+ {
+ mapSocket->Seek(Offset, SeekType);
+ }
+ virtual void Flush(int Timeout = IOStream::TimeOutInfinite)
+ {
+ mapSocket->Flush(Timeout);
+ }
+ virtual void Close()
+ {
+ mapSocket->Close();
+ }
+ virtual bool StreamDataLeft()
+ {
+ return mapSocket->StreamDataLeft();
+ }
+ virtual bool StreamClosed()
+ {
+ return mapSocket->StreamClosed();
+ }
+ virtual void SetEnabled(bool enabled);
+
+private:
+ NiceSocketStream(const NiceSocketStream &rToCopy)
+ { /* do not call */ }
+};
+
+#endif // TCPNICE__H
diff --git a/lib/server/makeprotocol.pl.in b/lib/server/makeprotocol.pl.in
index 91ba55b0..9caa970d 100755
--- a/lib/server/makeprotocol.pl.in
+++ b/lib/server/makeprotocol.pl.in
@@ -30,6 +30,7 @@ my %log_display_types =
'string' => ['%s', 'VAR.c_str()']
);
+<<<<<<< HEAD
my ($type, $file) = @ARGV;
@@ -48,6 +49,21 @@ my @extra_header_files;
my $implement_syslog = 0;
my $implement_filelog = 0;
+=======
+if (@ARGV != 1)
+{
+ die "Usage: $0 <protocol-txt-file>\n";
+}
+
+my ($file) = @ARGV;
+
+open IN, $file or die "Can't open input file $file\n";
+
+print "Making protocol classes from $file...\n";
+
+my @extra_header_files;
+
+>>>>>>> 0.12
# read attributes
my %attr;
while(<IN>)
@@ -59,6 +75,7 @@ while(<IN>)
my ($k,$v) = split /\s+/,$l,2;
+<<<<<<< HEAD
if($k eq 'ClientType')
{
add_type($v) if $type eq 'Client';
@@ -94,6 +111,20 @@ while(<IN>)
{
$log_display_types{$type_name} = [$printf_format,$arg_template]
}
+=======
+ if($k eq 'AddType')
+ {
+ add_type($v);
+ }
+ elsif($k eq 'ImplementLog')
+ {
+ # Always implement logging
+ }
+ elsif($k eq 'LogTypeToText')
+ {
+ my ($type_name,$printf_format,$arg_template) = split /\s+/,$v;
+ $log_display_types{$type_name} = [$printf_format,$arg_template]
+>>>>>>> 0.12
}
else
{
@@ -169,9 +200,19 @@ close IN;
# open files
+<<<<<<< HEAD
my $h_filename = 'autogen_'.$protocol_name.'Protocol'.$type.'.h';
open CPP,'>autogen_'.$protocol_name.'Protocol'.$type.'.cpp';
open H,">$h_filename";
+=======
+my $filename_base = 'autogen_'.$protocol_name.'Protocol';
+print "Writing $filename_base.cpp\n";
+print "Writing $filename_base.h\n";
+open CPP, "> $filename_base.cpp";
+open H, "> $filename_base.h";
+
+my $guardname = uc 'AUTOGEN_'.$protocol_name.'Protocol_H';
+>>>>>>> 0.12
print CPP <<__E;
@@ -181,6 +222,7 @@ print CPP <<__E;
#include <sstream>
+<<<<<<< HEAD
#include "$h_filename"
#include "IOStream.h"
@@ -199,27 +241,52 @@ EOF
my $guardname = uc 'AUTOGEN_'.$protocol_name.'Protocol'.$type.'_H';
print H <<__E;
+=======
+#include "$filename_base.h"
+#include "IOStream.h"
+__E
+
+print H <<__E;
+>>>>>>> 0.12
// Auto-generated file -- do not edit
#ifndef $guardname
#define $guardname
+<<<<<<< HEAD
#include "Protocol.h"
#include "ProtocolObject.h"
+=======
+#include <cstdio>
+#include <list>
+
+#ifndef WIN32
+#include <syslog.h>
+#endif
+
+#include "Protocol.h"
+#include "Message.h"
+>>>>>>> 0.12
#include "ServerException.h"
class IOStream;
+<<<<<<< HEAD
__E
if($implement_filelog)
{
print H qq~#include <stdio.h>\n~;
}
+=======
+
+__E
+>>>>>>> 0.12
# extra headers
for(@extra_header_files)
{
+<<<<<<< HEAD
print H qq~#include "$_"\n~
}
print H "\n";
@@ -320,12 +387,103 @@ public:
$classname_base$cmd();
$classname_base$cmd(const $classname_base$cmd &rToCopy);
~$classname_base$cmd();
+=======
+ print H qq@#include "$_"\n@;
+}
+
+print H <<__E;
+
+// need utils file for the server
+#include "Utils.h"
+
+__E
+
+my $message_base_class = "${protocol_name}ProtocolMessage";
+my $objects_extra_h = '';
+my $objects_extra_cpp = '';
+
+# define the context
+print H "class $context_class;\n\n";
+print CPP <<__E;
+#include "$context_class_inc"
+#include "MemLeakFindOn.h"
+__E
+
+my $request_base_class = "${protocol_name}ProtocolRequest";
+my $reply_base_class = "${protocol_name}ProtocolReply";
+# the abstract protocol interface
+my $protocol_base_class = $protocol_name."ProtocolBase";
+my $replyable_base_class = $protocol_name."ProtocolReplyable";
+
+print H <<__E;
+class $protocol_base_class;
+class $replyable_base_class;
+class $reply_base_class;
+
+class $message_base_class : public Message
+{
+public:
+ virtual std::auto_ptr<$message_base_class> DoCommand($replyable_base_class &rProtocol,
+ $context_class &rContext) const;
+};
+
+class $reply_base_class
+{
+};
+
+class $request_base_class
+{
+};
+
+__E
+
+print CPP <<__E;
+std::auto_ptr<$message_base_class> $message_base_class\::DoCommand($replyable_base_class &rProtocol,
+ $context_class &rContext) const
+{
+ THROW_EXCEPTION(ConnectionException, Conn_Protocol_TriedToExecuteReplyCommand)
+}
+__E
+
+my %cmd_class;
+
+# output the classes
+foreach my $cmd (@cmd_list)
+{
+ my @cmd_base_classes = ($message_base_class);
+
+ if(obj_is_type($cmd, 'Command'))
+ {
+ push @cmd_base_classes, $request_base_class;
+ }
+
+ if(obj_is_type($cmd, 'Reply'))
+ {
+ push @cmd_base_classes, $reply_base_class;
+ }
+
+ my $cmd_base_class = join(", ", map {"public $_"} @cmd_base_classes);
+ my $cmd_class = $protocol_name."Protocol".$cmd;
+ $cmd_class{$cmd} = $cmd_class;
+
+ print H <<__E;
+class $cmd_class : $cmd_base_class
+{
+public:
+ $cmd_class();
+ $cmd_class(const $cmd_class &rToCopy);
+ ~$cmd_class();
+>>>>>>> 0.12
int GetType() const;
enum
{
TypeID = $cmd_id{$cmd}
};
__E
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
# constants
if(exists $cmd_constants{$cmd})
{
@@ -333,16 +491,25 @@ __E
print H join(",\n\t\t",@{$cmd_constants{$cmd}});
print H "\n\t};\n";
}
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
# flags
if(obj_is_type($cmd,'EndsConversation'))
{
print H "\tbool IsConversationEnd() const;\n";
}
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
if(obj_is_type($cmd,'IsError'))
{
print H "\tbool IsError(int &rTypeOut, int &rSubTypeOut) const;\n";
print H "\tstd::string GetMessage() const;\n";
}
+<<<<<<< HEAD
if($type eq 'Server' && obj_is_type($cmd, 'Command'))
{
print H "\tstd::auto_ptr<ProtocolObject> DoCommand(${protocol_name}ProtocolServer &rProtocol, $context_class &rContext); // IMPLEMENT THIS\n"
@@ -399,6 +566,54 @@ __E
print H "\tvirtual void LogFile(const char *Action, FILE *file) const;\n";
}
+=======
+
+ if(obj_is_type($cmd, 'Command'))
+ {
+ print H <<__E;
+ std::auto_ptr<$message_base_class> DoCommand($replyable_base_class &rProtocol,
+ $context_class &rContext) const; // IMPLEMENT THIS\n
+__E
+ }
+
+ # want to be able to read from streams?
+ print H "\tvoid SetPropertiesFromStreamData(Protocol &rProtocol);\n";
+
+ # write Get functions
+ for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
+ {
+ my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);
+
+ print H "\t".translate_type_to_arg_type($ty)." Get$nm() {return m$nm;}\n";
+ }
+
+ my $param_con_args = '';
+ # extra constructor?
+ if($#{$cmd_contents{$cmd}} >= 0)
+ {
+ my @a;
+ for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
+ {
+ my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);
+
+ push @a,translate_type_to_arg_type($ty)." $nm";
+ }
+ $param_con_args = join(', ',@a);
+ print H "\t$cmd_class(".$param_con_args.");\n";
+ }
+ print H "\tvoid WritePropertiesToStreamData(Protocol &rProtocol) const;\n";
+ # set functions
+ for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
+ {
+ my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);
+
+ print H "\tvoid Set$nm(".translate_type_to_arg_type($ty)." $nm) {m$nm = $nm;}\n";
+ }
+
+ print H "\tvirtual void LogSysLog(const char *Action) const;\n";
+ print H "\tvirtual void LogFile(const char *Action, FILE *file) const;\n";
+ print H "\tvirtual std::string ToString() const;\n";
+>>>>>>> 0.12
# write member variables and setup for cpp file
my @def_constructor_list;
@@ -432,6 +647,7 @@ __E
my $param_con_vars = join(",\n\t ",@param_constructor_list);
$param_con_vars = "\n\t: ".$param_con_vars if $param_con_vars ne '';
+<<<<<<< HEAD
my $class = "$classname_base$cmd".'::';
print CPP <<__E;
$class$classname_base$cmd()$def_con_vars
@@ -444,10 +660,24 @@ $class~$classname_base$cmd()
{
}
int ${class}GetType() const
+=======
+ print CPP <<__E;
+$cmd_class\::$cmd_class()$def_con_vars
+{
+}
+$cmd_class\::$cmd_class(const $cmd_class &rToCopy)$copy_con_vars
+{
+}
+$cmd_class\::~$cmd_class()
+{
+}
+int $cmd_class\::GetType() const
+>>>>>>> 0.12
{
return $cmd_id{$cmd};
}
__E
+<<<<<<< HEAD
if($read_from_streams)
{
print CPP "void ${class}SetPropertiesFromStreamData(Protocol &rProtocol)\n{\n";
@@ -491,18 +721,68 @@ __E
{
print CPP "bool ${class}IsConversationEnd() const\n{\n\treturn true;\n}\n";
}
+=======
+ print CPP "void $cmd_class\::SetPropertiesFromStreamData(Protocol &rProtocol)\n{\n";
+ for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
+ {
+ my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);
+ if($ty =~ m/\Avector/)
+ {
+ print CPP "\trProtocol.ReadVector(m$nm);\n";
+ }
+ else
+ {
+ print CPP "\trProtocol.Read(m$nm);\n";
+ }
+ }
+ print CPP "}\n";
+
+ # implement extra constructor?
+ if($param_con_vars ne '')
+ {
+ print CPP "$cmd_class\::$cmd_class($param_con_args)$param_con_vars\n{\n}\n";
+ }
+ print CPP "void $cmd_class\::WritePropertiesToStreamData(Protocol &rProtocol) const\n{\n";
+ for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
+ {
+ my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);
+ if($ty =~ m/\Avector/)
+ {
+ print CPP "\trProtocol.WriteVector(m$nm);\n";
+ }
+ else
+ {
+ print CPP "\trProtocol.Write(m$nm);\n";
+ }
+ }
+ print CPP "}\n";
+
+ if(obj_is_type($cmd,'EndsConversation'))
+ {
+ print CPP "bool $cmd_class\::IsConversationEnd() const\n{\n\treturn true;\n}\n";
+ }
+
+>>>>>>> 0.12
if(obj_is_type($cmd,'IsError'))
{
# get parameters
my ($mem_type,$mem_subtype) = split /,/,obj_get_type_params($cmd,'IsError');
print CPP <<__E;
+<<<<<<< HEAD
bool ${class}IsError(int &rTypeOut, int &rSubTypeOut) const
+=======
+bool $cmd_class\::IsError(int &rTypeOut, int &rSubTypeOut) const
+>>>>>>> 0.12
{
rTypeOut = m$mem_type;
rSubTypeOut = m$mem_subtype;
return true;
}
+<<<<<<< HEAD
std::string ${class}GetMessage() const
+=======
+std::string $cmd_class\::GetMessage() const
+>>>>>>> 0.12
{
switch(m$mem_subtype)
{
@@ -526,6 +806,7 @@ __E
__E
}
+<<<<<<< HEAD
if($implement_syslog)
{
my ($log) = make_log_strings_framework($cmd);
@@ -667,11 +948,453 @@ const char *${prefix}GetIdentString()
return "$ident_string";
}
std::auto_ptr<ProtocolObject> ${prefix}MakeProtocolObject(int ObjType)
+=======
+ my ($log) = make_log_strings_framework($cmd);
+ print CPP <<__E;
+std::string $cmd_class\::ToString() const
+{
+ std::ostringstream oss;
+ try
+ {
+ oss << $log;
+ }
+ catch(std::exception &e)
+ {
+ oss << "Failed to log command: " << e.what();
+ }
+ return oss.str();
+}
+void $cmd_class\::LogSysLog(const char *Action) const
+{
+ try
+ {
+ BOX_TRACE(Action << " " << $log);
+ }
+ catch(std::exception &e)
+ {
+ BOX_WARNING("Failed to log command: " << Action << ": " <<
+ e.what());
+ }
+}
+void $cmd_class\::LogFile(const char *Action, FILE *File) const
+{
+ ::fprintf(File, "%s %s\\n", Action, ToString().c_str());
+ ::fflush(File);
+}
+__E
+}
+
+my $error_class = $protocol_name."ProtocolError";
+
+# the abstract protocol interface
+print H <<__E;
+class $protocol_base_class
+{
+public:
+ $protocol_base_class();
+ virtual ~$protocol_base_class();
+ virtual const char *GetIdentString();
+ bool GetLastError(int &rTypeOut, int &rSubTypeOut);
+
+protected:
+ void CheckReply(const std::string& requestCommand,
+ const $message_base_class &rReply, int expectedType);
+ void SetLastError(int Type, int SubType)
+ {
+ mLastErrorType = Type;
+ mLastErrorSubType = SubType;
+ }
+
+private:
+ $protocol_base_class(const $protocol_base_class &rToCopy); /* do not call */
+ int mLastErrorType;
+ int mLastErrorSubType;
+};
+
+class $replyable_base_class : public virtual $protocol_base_class
+{
+public:
+ $replyable_base_class();
+ virtual ~$replyable_base_class();
+
+ /*
+ virtual std::auto_ptr<$message_base_class> Receive() = 0;
+ virtual void Send(const ${message_base_class} &rObject) = 0;
+ */
+
+ virtual std::auto_ptr<IOStream> ReceiveStream() = 0;
+ virtual int GetTimeout() = 0;
+ void SendStreamAfterCommand(std::auto_ptr<IOStream> apStream);
+
+protected:
+ std::list<IOStream*> mStreamsToSend;
+ void DeleteStreamsToSend();
+
+private:
+ $replyable_base_class(const $replyable_base_class &rToCopy); /* do not call */
+};
+
+__E
+
+print CPP <<__E;
+$protocol_base_class\::$protocol_base_class()
+: mLastErrorType(Protocol::NoError),
+ mLastErrorSubType(Protocol::NoError)
+{ }
+
+$protocol_base_class\::~$protocol_base_class()
+{ }
+
+const char *$protocol_base_class\::GetIdentString()
+{
+ return "$ident_string";
+}
+
+$replyable_base_class\::$replyable_base_class()
+{ }
+
+$replyable_base_class\::~$replyable_base_class()
+{ }
+
+void $replyable_base_class\::SendStreamAfterCommand(std::auto_ptr<IOStream> apStream)
+{
+ ASSERT(apStream.get() != NULL);
+ mStreamsToSend.push_back(apStream.release());
+}
+
+void $replyable_base_class\::DeleteStreamsToSend()
+{
+ for(std::list<IOStream*>::iterator i(mStreamsToSend.begin()); i != mStreamsToSend.end(); ++i)
+ {
+ delete (*i);
+ }
+
+ mStreamsToSend.clear();
+}
+
+void $protocol_base_class\::CheckReply(const std::string& requestCommand,
+ const $message_base_class &rReply, int expectedType)
+{
+ if(rReply.GetType() == expectedType)
+ {
+ // Correct response, do nothing
+ }
+ else
+ {
+ // Set protocol error
+ int type, subType;
+
+ if(rReply.IsError(type, subType))
+ {
+ SetLastError(type, subType);
+ THROW_EXCEPTION_MESSAGE(ConnectionException,
+ Conn_Protocol_UnexpectedReply,
+ requestCommand << " command failed: "
+ "received error " <<
+ (($error_class&)rReply).GetMessage());
+ }
+ else
+ {
+ SetLastError(Protocol::UnknownError, Protocol::UnknownError);
+ THROW_EXCEPTION_MESSAGE(ConnectionException,
+ Conn_Protocol_UnexpectedReply,
+ requestCommand << " command failed: "
+ "received unexpected response type " <<
+ rReply.GetType());
+ }
+ }
+}
+
+// --------------------------------------------------------------------------
+//
+// Function
+// Name: Protocol::GetLastError(int &, int &)
+// Purpose: Returns true if there was an error, and type and subtype if there was.
+// Created: 2003/08/19
+//
+// --------------------------------------------------------------------------
+bool $protocol_base_class\::GetLastError(int &rTypeOut, int &rSubTypeOut)
+{
+ if(mLastErrorType == Protocol::NoError)
+ {
+ // no error.
+ return false;
+ }
+
+ // Return type and subtype in args
+ rTypeOut = mLastErrorType;
+ rSubTypeOut = mLastErrorSubType;
+
+ // and unset them
+ mLastErrorType = Protocol::NoError;
+ mLastErrorSubType = Protocol::NoError;
+
+ return true;
+}
+
+__E
+
+# the callable protocol interface (implemented by Client and Local classes)
+# with Query methods that don't take a context parameter
+my $callable_base_class = $protocol_name."ProtocolCallable";
+print H <<__E;
+class $callable_base_class : public virtual $protocol_base_class
+{
+public:
+ virtual std::auto_ptr<IOStream> ReceiveStream() = 0;
+ virtual int GetTimeout() = 0;
+__E
+
+# add plain object taking query functions
+my $with_params;
+for my $cmd (@cmd_list)
+{
+ if(obj_is_type($cmd,'Command'))
+ {
+ my $has_stream = obj_is_type($cmd,'StreamWithCommand');
+ my $argextra = $has_stream?', std::auto_ptr<IOStream> apStream':'';
+ my $queryextra = $has_stream?', apStream':'';
+ my $request_class = $cmd_class{$cmd};
+ my $reply_class = $cmd_class{obj_get_type_params($cmd,'Command')};
+
+ print H "\tvirtual std::auto_ptr<$reply_class> Query(const $request_class &rQuery$argextra) = 0;\n";
+ my @a;
+ my @na;
+ for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
+ {
+ my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);
+ push @a,translate_type_to_arg_type($ty)." $nm";
+ push @na,"$nm";
+ }
+ my $ar = join(', ',@a);
+ my $nar = join(', ',@na);
+ $nar = "($nar)" if $nar ne '';
+
+ $with_params .= <<__E;
+ inline std::auto_ptr<$reply_class> Query$cmd($ar$argextra)
+ {
+ $request_class send$nar;
+ return Query(send$queryextra);
+ }
+__E
+ }
+}
+
+# quick hack to correct bad argument lists for commands with zero parameters but with streams
+$with_params =~ s/\(, /(/g;
+
+print H <<__E;
+
+$with_params
+};
+__E
+
+# standard remote protocol objects
+foreach my $type ('Client', 'Server', 'Local')
+{
+ my $writing_client = ($type eq 'Client');
+ my $writing_server = ($type eq 'Server');
+ my $writing_local = ($type eq 'Local');
+
+ my $server_or_client_class = $protocol_name."Protocol".$type;
+ my @base_classes;
+
+ if (not $writing_client)
+ {
+ push @base_classes, $replyable_base_class;
+ }
+ if (not $writing_server)
+ {
+ push @base_classes, $callable_base_class;
+ }
+ if (not $writing_local)
+ {
+ push @base_classes, "Protocol";
+ }
+
+ my $base_classes_str = join(", ", map {"public $_"} @base_classes);
+
+ print H <<__E;
+class $server_or_client_class : $base_classes_str
+{
+public:
+__E
+
+ if($writing_local)
+ {
+ print H <<__E;
+ $server_or_client_class($context_class &rContext);
+__E
+ }
+ else
+ {
+ print H <<__E;
+ $server_or_client_class(IOStream &rStream);
+ std::auto_ptr<$message_base_class> Receive();
+ void Send(const $message_base_class &rObject);
+__E
+ }
+
+ print H <<__E;
+ virtual ~$server_or_client_class();
+__E
+
+ if($writing_server)
+ {
+ # need to put in the conversation function
+ print H <<__E;
+ void DoServer($context_class &rContext);
+
+__E
+ }
+
+ if($writing_client or $writing_local)
+ {
+ # add plain object taking query functions
+ for my $cmd (@cmd_list)
+ {
+ if(obj_is_type($cmd,'Command'))
+ {
+ my $has_stream = obj_is_type($cmd,'StreamWithCommand');
+ my $argextra = $has_stream?', std::auto_ptr<IOStream> apStream':'';
+ my $queryextra = $has_stream?', apStream':'';
+ my $request_class = $cmd_class{$cmd};
+ my $reply_class = $cmd_class{obj_get_type_params($cmd,'Command')};
+ print H "\tstd::auto_ptr<$reply_class> Query(const $request_class &rQuery$argextra);\n";
+ }
+ }
+ }
+
+ if($writing_local)
+ {
+ print H <<__E;
+private:
+ $context_class &mrContext;
+__E
+ }
+
+ print H <<__E;
+
+protected:
+ virtual std::auto_ptr<Message> MakeMessage(int ObjType);
+
+__E
+
+ if($writing_local)
+ {
+ print H <<__E;
+ virtual void InformStreamReceiving(u_int32_t Size) { }
+ virtual void InformStreamSending(u_int32_t Size) { }
+
+public:
+ virtual std::auto_ptr<IOStream> ReceiveStream()
+ {
+ std::auto_ptr<IOStream> apStream(mStreamsToSend.front());
+ mStreamsToSend.pop_front();
+ return apStream;
+ }
+__E
+ }
+ else
+ {
+ print H <<__E;
+ virtual void InformStreamReceiving(u_int32_t Size)
+ {
+ this->Protocol::InformStreamReceiving(Size);
+ }
+ virtual void InformStreamSending(u_int32_t Size)
+ {
+ this->Protocol::InformStreamSending(Size);
+ }
+
+public:
+ virtual std::auto_ptr<IOStream> ReceiveStream()
+ {
+ return this->Protocol::ReceiveStream();
+ }
+__E
+ }
+
+ print H <<__E;
+ virtual const char *GetProtocolIdentString()
+ {
+ return GetIdentString();
+ }
+__E
+
+ if($writing_local)
+ {
+ print H <<__E;
+ virtual int GetTimeout()
+ {
+ return IOStream::TimeOutInfinite;
+ }
+__E
+ }
+ else
+ {
+ print H <<__E;
+ virtual int GetTimeout()
+ {
+ return this->Protocol::GetTimeout();
+ }
+__E
+ }
+
+ print H <<__E;
+ /*
+ virtual void Handshake()
+ {
+ this->Protocol::Handshake();
+ }
+ virtual bool GetLastError(int &rTypeOut, int &rSubTypeOut)
+ {
+ return this->Protocol::GetLastError(rTypeOut, rSubTypeOut);
+ }
+ */
+
+private:
+ $server_or_client_class(const $server_or_client_class &rToCopy); /* no copies */
+};
+
+__E
+
+ my $destructor_extra = ($writing_server) ? "\n\tDeleteStreamsToSend();"
+ : '';
+
+ if($writing_local)
+ {
+ print CPP <<__E;
+$server_or_client_class\::$server_or_client_class($context_class &rContext)
+: mrContext(rContext)
+{ }
+__E
+ }
+ else
+ {
+ print CPP <<__E;
+$server_or_client_class\::$server_or_client_class(IOStream &rStream)
+: Protocol(rStream)
+{ }
+__E
+ }
+
+ print CPP <<__E;
+$server_or_client_class\::~$server_or_client_class()
+{$destructor_extra
+}
+__E
+
+ # write receive and send functions
+ print CPP <<__E;
+std::auto_ptr<Message> $server_or_client_class\::MakeMessage(int ObjType)
+>>>>>>> 0.12
{
switch(ObjType)
{
__E
+<<<<<<< HEAD
# do objects within this
for my $cmd (@cmd_list)
{
@@ -683,11 +1406,25 @@ __E
}
print CPP <<__E;
+=======
+ # do objects within this
+ for my $cmd (@cmd_list)
+ {
+ print CPP <<__E;
+ case $cmd_id{$cmd}:
+ return std::auto_ptr<Message>(new $cmd_class{$cmd}());
+ break;
+__E
+ }
+
+ print CPP <<__E;
+>>>>>>> 0.12
default:
THROW_EXCEPTION(ConnectionException, Conn_Protocol_UnknownCommandRecieved)
}
}
__E
+<<<<<<< HEAD
# write receive and send functions
print CPP <<__E;
std::auto_ptr<$derive_objects_from> ${prefix}Receive()
@@ -714,10 +1451,31 @@ __E
__E
}
print CPP <<__E;
+=======
+
+ if(not $writing_local)
+ {
+ print CPP <<__E;
+std::auto_ptr<$message_base_class> $server_or_client_class\::Receive()
+{
+ std::auto_ptr<$message_base_class> preply(($message_base_class *)
+ Protocol::ReceiveInternal().release());
+
+ if(GetLogToSysLog())
+ {
+ preply->LogSysLog("Receive");
+ }
+
+ if(GetLogToFile() != 0)
+ {
+ preply->LogFile("Receive", GetLogToFile());
+ }
+>>>>>>> 0.12
return preply;
}
+<<<<<<< HEAD
void ${prefix}Send(const ${derive_objects_from} &rObject)
{
__E
@@ -750,6 +1508,31 @@ if($type eq 'Server')
{
print CPP <<__E;
void ${prefix}DoServer($context_class &rContext)
+=======
+void $server_or_client_class\::Send(const $message_base_class &rObject)
+{
+ if(GetLogToSysLog())
+ {
+ rObject.LogSysLog("Send");
+ }
+
+ if(GetLogToFile() != 0)
+ {
+ rObject.LogFile("Send", GetLogToFile());
+ }
+
+ Protocol::SendInternal(rObject);
+}
+
+__E
+ }
+
+ # write server function?
+ if($writing_server)
+ {
+ print CPP <<__E;
+void $server_or_client_class\::DoServer($context_class &rContext)
+>>>>>>> 0.12
{
// Handshake with client
Handshake();
@@ -759,6 +1542,7 @@ void ${prefix}DoServer($context_class &rContext)
while(inProgress)
{
// Get an object from the conversation
+<<<<<<< HEAD
std::auto_ptr<${derive_objects_from}> pobj(Receive());
// Run the command
@@ -773,6 +1557,24 @@ void ${prefix}DoServer($context_class &rContext)
// Send the streams
SendStream(*mStreamsToSend[s]);
}
+=======
+ std::auto_ptr<$message_base_class> pobj = Receive();
+
+ // Run the command
+ std::auto_ptr<$message_base_class> preply = pobj->DoCommand(*this, rContext);
+
+ // Send the reply
+ Send(*preply);
+
+ // Send any streams
+ for(std::list<IOStream*>::iterator
+ i = mStreamsToSend.begin();
+ i != mStreamsToSend.end(); ++i)
+ {
+ SendStream(**i);
+ }
+
+>>>>>>> 0.12
// Delete these streams
DeleteStreamsToSend();
@@ -784,6 +1586,7 @@ void ${prefix}DoServer($context_class &rContext)
}
}
+<<<<<<< HEAD
void ${prefix}SendStreamAfterCommand(IOStream *pStream)
{
ASSERT(pStream != NULL);
@@ -898,10 +1701,45 @@ __E
}
print CPP <<__E;
std::auto_ptr<$classname_base$reply> ${classname_base}::Query(const $classname_base$cmd &rQuery$argextra)
+=======
+__E
+ }
+
+ # write client Query functions?
+ if($writing_client or $writing_local)
+ {
+ for my $cmd (@cmd_list)
+ {
+ if(obj_is_type($cmd,'Command'))
+ {
+ my $request_class = $cmd_class{$cmd};
+ my $reply_msg = obj_get_type_params($cmd,'Command');
+ my $reply_class = $cmd_class{$reply_msg};
+ my $reply_id = $cmd_id{$reply_msg};
+ my $has_stream = obj_is_type($cmd,'StreamWithCommand');
+ my $argextra = $has_stream?', std::auto_ptr<IOStream> apStream':'';
+ my $send_stream_extra = '';
+ my $send_stream_method = $writing_client ? "SendStream"
+ : "SendStreamAfterCommand";
+
+ if($writing_client)
+ {
+ if($has_stream)
+ {
+ $send_stream_extra = <<__E;
+ // Send stream after the command
+ SendStream(*apStream);
+__E
+ }
+
+ print CPP <<__E;
+std::auto_ptr<$reply_class> $server_or_client_class\::Query(const $request_class &rQuery$argextra)
+>>>>>>> 0.12
{
// Send query
Send(rQuery);
$send_stream_extra
+<<<<<<< HEAD
// Wait for the reply
std::auto_ptr<${derive_objects_from}> preply(Receive().release());
@@ -933,12 +1771,53 @@ std::auto_ptr<$classname_base$reply> ${classname_base}::Query(const $classname_b
}
}
__E
+=======
+
+ // Wait for the reply
+ std::auto_ptr<$message_base_class> preply = Receive();
+
+ CheckReply("$cmd", *preply, $reply_id);
+
+ // Correct response, if no exception thrown by CheckReply
+ return std::auto_ptr<$reply_class>(($reply_class *)preply.release());
+}
+__E
+ }
+ elsif($writing_local)
+ {
+ if($has_stream)
+ {
+ $send_stream_extra = <<__E;
+ // Send stream after the command
+ SendStreamAfterCommand(apStream);
+__E
+ }
+
+ print CPP <<__E;
+std::auto_ptr<$reply_class> $server_or_client_class\::Query(const $request_class &rQuery$argextra)
+{
+ // Send query
+ $send_stream_extra
+ std::auto_ptr<$message_base_class> preply = rQuery.DoCommand(*this, mrContext);
+
+ CheckReply("$cmd", *preply, $reply_id);
+
+ // Correct response, if no exception thrown by CheckReply
+ return std::auto_ptr<$reply_class>(($reply_class *)preply.release());
+}
+__E
+ }
+ }
+>>>>>>> 0.12
}
}
}
+<<<<<<< HEAD
+=======
+>>>>>>> 0.12
print H <<__E;
#endif // $guardname
@@ -948,8 +1827,12 @@ __E
close H;
close CPP;
+<<<<<<< HEAD
sub obj_is_type
+=======
+sub obj_is_type ($$)
+>>>>>>> 0.12
{
my ($c,$ty) = @_;
for(@{$cmd_attributes{$c}})
@@ -1003,6 +1886,7 @@ sub translate_type_to_member_type
return $typename
}
+<<<<<<< HEAD
sub make_log_strings
{
my ($cmd) = @_;
@@ -1037,6 +1921,8 @@ sub make_log_strings
return ($cmd.'('.join(',',@str).')', join(',','',@arg));
}
+=======
+>>>>>>> 0.12
sub make_log_strings_framework
{
my ($cmd) = @_;
@@ -1053,7 +1939,11 @@ sub make_log_strings_framework
my ($format,$arg) = @{$log_display_types{$ty}};
$arg =~ s/VAR/m$nm/g;
+<<<<<<< HEAD
if ($format eq '\\"%s\\"')
+=======
+ if ($format eq '"%s"')
+>>>>>>> 0.12
{
$arg = "\"\\\"\" << $arg << \"\\\"\"";
}
@@ -1080,7 +1970,11 @@ sub make_log_strings_framework
}
}
+<<<<<<< HEAD
my $log_cmd = "Action << \" $cmd(\" ";
+=======
+ my $log_cmd = '"'.$cmd.'(" ';
+>>>>>>> 0.12
foreach my $arg (@args)
{
$arg = "<< $arg ";
@@ -1090,4 +1984,7 @@ sub make_log_strings_framework
return $log_cmd;
}
+<<<<<<< HEAD
+=======
+>>>>>>> 0.12
diff --git a/lib/win32/emu.cpp b/lib/win32/emu.cpp
index db9974d2..9b5159cf 100644
--- a/lib/win32/emu.cpp
+++ b/lib/win32/emu.cpp
@@ -1,8 +1,11 @@
// Box Backup Win32 native port by Nick Knight
+<<<<<<< HEAD
// Need at least 0x0500 to use GetFileSizeEx on Cygwin/MinGW
#define WINVER 0x0500
+=======
+>>>>>>> 0.12
#include "emu.h"
#ifdef WIN32
@@ -82,9 +85,12 @@ bool EnableBackupRights()
return true;
}
+<<<<<<< HEAD
// forward declaration
char* ConvertFromWideString(const WCHAR* pString, unsigned int codepage);
+=======
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
// Function
@@ -246,7 +252,11 @@ char* ConvertFromWideString(const WCHAR* pString, unsigned int codepage)
{
::syslog(LOG_WARNING,
"Failed to convert wide string to narrow: "
+<<<<<<< HEAD
"error %d", GetLastError());
+=======
+ "%s", GetErrorMessage(GetLastError()).c_str());
+>>>>>>> 0.12
errno = EINVAL;
return NULL;
}
@@ -278,7 +288,11 @@ char* ConvertFromWideString(const WCHAR* pString, unsigned int codepage)
{
::syslog(LOG_WARNING,
"Failed to convert wide string to narrow: "
+<<<<<<< HEAD
"error %i", GetLastError());
+=======
+ "%s", GetErrorMessage(GetLastError()).c_str());
+>>>>>>> 0.12
errno = EACCES;
delete [] buffer;
return NULL;
@@ -287,6 +301,72 @@ char* ConvertFromWideString(const WCHAR* pString, unsigned int codepage)
return buffer;
}
+<<<<<<< HEAD
+=======
+bool ConvertFromWideString(const std::wstring& rInput,
+ std::string* pOutput, unsigned int codepage)
+{
+ int len = WideCharToMultiByte
+ (
+ codepage, // destination code page
+ 0, // character-type options
+ rInput.c_str(), // string to map
+ rInput.size(), // number of bytes in string - auto detect
+ NULL, // output buffer
+ 0, // size of buffer - work out
+ // how much space we need
+ NULL, // replace unknown chars with system default
+ NULL // don't tell us when that happened
+ );
+
+ if (len == 0)
+ {
+ ::syslog(LOG_WARNING,
+ "Failed to convert wide string to narrow: "
+ "%s", GetErrorMessage(GetLastError()).c_str());
+ errno = EINVAL;
+ return false;
+ }
+
+ char* buffer = new char[len];
+
+ if (buffer == NULL)
+ {
+ ::syslog(LOG_WARNING,
+ "Failed to convert wide string to narrow: "
+ "out of memory");
+ errno = ENOMEM;
+ return false;
+ }
+
+ len = WideCharToMultiByte
+ (
+ codepage, // source code page
+ 0, // character-type options
+ rInput.c_str(), // string to map
+ rInput.size(), // number of bytes in string - auto detect
+ buffer, // output buffer
+ len, // size of buffer
+ NULL, // replace unknown chars with system default
+ NULL // don't tell us when that happened
+ );
+
+ if (len == 0)
+ {
+ ::syslog(LOG_WARNING,
+ "Failed to convert wide string to narrow: "
+ "%s", GetErrorMessage(GetLastError()).c_str());
+ errno = EACCES;
+ delete [] buffer;
+ return false;
+ }
+
+ *pOutput = std::string(buffer, len);
+ delete [] buffer;
+ return true;
+}
+
+>>>>>>> 0.12
// --------------------------------------------------------------------------
//
// Function
@@ -391,7 +471,19 @@ std::string ConvertPathToAbsoluteUnicode(const char *pFileName)
return tmpStr;
}
+<<<<<<< HEAD
if (filename.length() > 2 && filename[0] == '\\' &&
+=======
+ if (filename.length() > 4 && filename[0] == '\\' &&
+ filename[1] == '\\' && filename[2] == '?' &&
+ filename[3] == '\\')
+ {
+ // File is already in absolute utf-8 format, e.g.
+ // \\?\GLOBALROOT\...
+ tmpStr = "";
+ }
+ else if (filename.length() > 2 && filename[0] == '\\' &&
+>>>>>>> 0.12
filename[1] == '\\')
{
tmpStr += "UNC\\";
@@ -401,16 +493,27 @@ std::string ConvertPathToAbsoluteUnicode(const char *pFileName)
}
else if (filename.length() >= 1 && filename[0] == '\\')
{
+<<<<<<< HEAD
// root directory of current drive.
+=======
+ // starts with \, i.e. root directory of current drive.
+>>>>>>> 0.12
tmpStr = wd;
tmpStr.resize(2); // drive letter and colon
}
else if (filename.length() >= 2 && filename[1] != ':')
{
+<<<<<<< HEAD
// Must be relative. We need to get the
// current directory to make it absolute.
tmpStr += wd;
if (tmpStr[tmpStr.length()] != '\\')
+=======
+ // Must be a relative path. We need to get the
+ // current directory to make it absolute.
+ tmpStr += wd;
+ if (tmpStr[tmpStr.length()-1] != '\\')
+>>>>>>> 0.12
{
tmpStr += '\\';
}
@@ -439,7 +542,11 @@ std::string ConvertPathToAbsoluteUnicode(const char *pFileName)
"");
}
+<<<<<<< HEAD
i = lastSlash;
+=======
+ i = lastSlash - 1;
+>>>>>>> 0.12
}
}
@@ -654,7 +761,11 @@ int emu_fstat(HANDLE hdir, struct emu_stat * st)
{
conv.HighPart = fi.nFileSizeHigh;
conv.LowPart = fi.nFileSizeLow;
+<<<<<<< HEAD
st->st_size = (_off_t)conv.QuadPart;
+=======
+ st->st_size = conv.QuadPart;
+>>>>>>> 0.12
}
// at the mo
@@ -988,7 +1099,11 @@ DIR *opendir(const char *name)
std::string dirName(name);
//append a '\' win32 findfirst is sensitive to this
+<<<<<<< HEAD
if ( dirName[dirName.size()] != '\\' || dirName[dirName.size()] != '/' )
+=======
+ if (dirName[dirName.size()-1] != '\\' || dirName[dirName.size()-1] != '/')
+>>>>>>> 0.12
{
dirName += '\\';
}
@@ -1012,15 +1127,26 @@ DIR *opendir(const char *name)
return NULL;
}
+<<<<<<< HEAD
pDir->fd = _wfindfirst((const wchar_t*)pDir->name, &(pDir->info));
if (pDir->fd == -1)
+=======
+ pDir->fd = FindFirstFileW(pDir->name, &pDir->info);
+ DWORD tmp = GetLastError();
+
+ if (pDir->fd == INVALID_HANDLE_VALUE)
+>>>>>>> 0.12
{
delete [] pDir->name;
delete pDir;
return NULL;
}
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
pDir->result.d_name = 0;
return pDir;
}
@@ -1043,6 +1169,7 @@ struct dirent *readdir(DIR *dp)
{
struct dirent *den = NULL;
+<<<<<<< HEAD
if (dp && dp->fd != -1)
{
if (!dp->result.d_name ||
@@ -1052,10 +1179,25 @@ struct dirent *readdir(DIR *dp)
std::wstring input(dp->info.name);
memset(tempbuff, 0, sizeof(tempbuff));
WideCharToMultiByte(CP_UTF8, 0, dp->info.name,
+=======
+ if (dp && dp->fd != INVALID_HANDLE_VALUE)
+ {
+ // first time around, when dp->result.d_name == NULL, use
+ // the values returned by FindFirstFile. After that, call
+ // FindNextFileW to return new ones.
+ if (!dp->result.d_name ||
+ FindNextFileW(dp->fd, &dp->info) != 0)
+ {
+ den = &dp->result;
+ std::wstring input(dp->info.cFileName);
+ memset(tempbuff, 0, sizeof(tempbuff));
+ WideCharToMultiByte(CP_UTF8, 0, dp->info.cFileName,
+>>>>>>> 0.12
-1, &tempbuff[0], sizeof (tempbuff),
NULL, NULL);
//den->d_name = (char *)dp->info.name;
den->d_name = &tempbuff[0];
+<<<<<<< HEAD
if (dp->info.attrib & FILE_ATTRIBUTE_DIRECTORY)
{
den->d_type = S_IFDIR;
@@ -1063,6 +1205,23 @@ struct dirent *readdir(DIR *dp)
else
{
den->d_type = S_IFREG;
+=======
+ den->d_type = dp->info.dwFileAttributes;
+ }
+ else // FindNextFileW failed
+ {
+ // Why did it fail? No more files?
+ winerrno = GetLastError();
+ den = NULL;
+
+ if (winerrno == ERROR_NO_MORE_FILES)
+ {
+ errno = 0; // no more files
+ }
+ else
+ {
+ errno = ENOSYS;
+>>>>>>> 0.12
}
}
}
@@ -1070,6 +1229,10 @@ struct dirent *readdir(DIR *dp)
{
errno = EBADF;
}
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
return den;
}
catch (...)
@@ -1091,24 +1254,44 @@ int closedir(DIR *dp)
{
try
{
+<<<<<<< HEAD
int finres = -1;
if (dp)
{
if(dp->fd != -1)
{
finres = _findclose(dp->fd);
+=======
+ BOOL finres = false;
+
+ if (dp)
+ {
+ if(dp->fd != INVALID_HANDLE_VALUE)
+ {
+ finres = FindClose(dp->fd);
+>>>>>>> 0.12
}
delete [] dp->name;
delete dp;
}
+<<<<<<< HEAD
if (finres == -1) // errors go to EBADF
{
errno = EBADF;
}
return finres;
+=======
+ if (finres == FALSE) // errors go to EBADF
+ {
+ winerrno = GetLastError();
+ errno = EBADF;
+ }
+
+ return (finres == TRUE) ? 0 : -1;
+>>>>>>> 0.12
}
catch (...)
{
diff --git a/lib/win32/emu.h b/lib/win32/emu.h
index f3389590..bc91e2ba 100644
--- a/lib/win32/emu.h
+++ b/lib/win32/emu.h
@@ -1,5 +1,10 @@
// emulates unix syscalls to win32 functions
+<<<<<<< HEAD
+=======
+#include "emu_winver.h"
+
+>>>>>>> 0.12
#ifdef WIN32
#define EMU_STRUCT_STAT struct emu_stat
#define EMU_STAT emu_stat
@@ -15,6 +20,17 @@
#if ! defined EMU_INCLUDE && defined WIN32
#define EMU_INCLUDE
+<<<<<<< HEAD
+=======
+// Need feature detection macros below
+#include "../common/BoxConfig.h"
+
+// Shut up stupid new warnings. Thanks MinGW! Ever heard of "compatibility"?
+#ifdef __MINGW32__
+# define __MINGW_FEATURES__ 0
+#endif
+
+>>>>>>> 0.12
// basic types, may be required by other headers since we
// don't include sys/types.h
@@ -42,6 +58,7 @@
typedef unsigned int pid_t;
#endif
+<<<<<<< HEAD
// set up to include the necessary parts of Windows headers
#define WIN32_LEAN_AND_MEAN
@@ -50,6 +67,8 @@
#define __MSVCRT_VERSION__ 0x0601
#endif
+=======
+>>>>>>> 0.12
// Windows headers
#include <winsock2.h>
@@ -220,17 +239,28 @@ inline int strcasecmp(const char *s1, const char *s2)
struct dirent
{
char *d_name;
+<<<<<<< HEAD
unsigned long d_type;
+=======
+ DWORD d_type; // file attributes
+>>>>>>> 0.12
};
struct DIR
{
+<<<<<<< HEAD
intptr_t fd; // filedescriptor
// struct _finddata_t info;
struct _wfinddata_t info;
// struct _finddata_t info;
struct dirent result; // d_name (first time null)
wchar_t *name; // null-terminated byte string
+=======
+ HANDLE fd; // the HANDLE returned by FindFirstFile
+ WIN32_FIND_DATAW info;
+ struct dirent result; // d_name (first time null)
+ wchar_t* name; // null-terminated byte string
+>>>>>>> 0.12
};
DIR *opendir(const char *name);
@@ -286,9 +316,24 @@ inline unsigned int sleep(unsigned int secs)
}
#define INFTIM -1
+<<<<<<< HEAD
#define POLLIN 0x1
#define POLLERR 0x8
#define POLLOUT 0x4
+=======
+
+#ifndef POLLIN
+# define POLLIN 0x1
+#endif
+
+#ifndef POLLERR
+# define POLLERR 0x8
+#endif
+
+#ifndef POLLOUT
+# define POLLOUT 0x4
+#endif
+>>>>>>> 0.12
#define SHUT_RDWR SD_BOTH
#define SHUT_RD SD_RECEIVE
@@ -307,11 +352,14 @@ inline int ioctl(SOCKET sock, int flag, int * something)
return 0;
}
+<<<<<<< HEAD
extern "C" inline int getpid()
{
return (int)GetCurrentProcessId();
}
+=======
+>>>>>>> 0.12
inline int waitpid(pid_t pid, int *status, int)
{
return 0;
@@ -351,12 +399,21 @@ int emu_chmod (const char* pName, mode_t mode);
char* emu_getcwd (char* pBuffer, int BufSize);
int emu_rename (const char* pOldName, const char* pNewName);
+<<<<<<< HEAD
#define chdir(directory) emu_chdir (directory)
#define mkdir(path, mode) emu_mkdir (path)
#define unlink(file) emu_unlink (file)
#define utimes(buffer, times) emu_utimes (buffer, times)
#define chmod(file, mode) emu_chmod (file, mode)
#define getcwd(buffer, size) emu_getcwd (buffer, size)
+=======
+#define chdir(directory) emu_chdir (directory)
+#define mkdir(path, mode) emu_mkdir (path)
+#define unlink(file) emu_unlink (file)
+#define utimes(buffer, times) emu_utimes (buffer, times)
+#define chmod(file, mode) emu_chmod (file, mode)
+#define getcwd(buffer, size) emu_getcwd (buffer, size)
+>>>>>>> 0.12
#define rename(oldname, newname) emu_rename (oldname, newname)
// Not safe to replace stat/fstat/lstat on mingw at least, as struct stat
@@ -396,6 +453,13 @@ bool ConvertFromUtf8 (const std::string& rSource, std::string& rDest,
int destCodePage);
bool ConvertUtf8ToConsole(const std::string& rSource, std::string& rDest);
bool ConvertConsoleToUtf8(const std::string& rSource, std::string& rDest);
+<<<<<<< HEAD
+=======
+char* ConvertFromWideString(const WCHAR* pString, unsigned int codepage);
+bool ConvertFromWideString(const std::wstring& rInput,
+ std::string* pOutput, unsigned int codepage);
+std::string ConvertPathToAbsoluteUnicode(const char *pFileName);
+>>>>>>> 0.12
// Utility function which returns a default config file name,
// based on the path of the current executable.
@@ -409,6 +473,21 @@ std::string GetErrorMessage(DWORD errorCode);
// relatively recent C runtime lib
int console_read(char* pBuffer, size_t BufferSize);
+<<<<<<< HEAD
+=======
+// Defined thus by MinGW, but missing from MSVC
+// [http://curl.haxx.se/mail/lib-2004-11/0260.html]
+// note: chsize() doesn't work over 2GB:
+// [https://stat.ethz.ch/pipermail/r-devel/2005-May/033339.html]
+#ifndef HAVE_FTRUNCATE
+ extern "C" int ftruncate(int, off_t);
+ inline int ftruncate(int __fd, off_t __length)
+ {
+ return _chsize(__fd, __length);
+ }
+#endif
+
+>>>>>>> 0.12
#ifdef _MSC_VER
/* disable certain compiler warnings to be able to actually see the show-stopper ones */
#pragma warning(disable:4101) // unreferenced local variable
diff --git a/lib/win32/emu_winver.h b/lib/win32/emu_winver.h
new file mode 100644
index 00000000..92060150
--- /dev/null
+++ b/lib/win32/emu_winver.h
@@ -0,0 +1,37 @@
+#ifndef _EMU_WINVER_H
+#define _EMU_WINVER_H
+
+// set up to include the necessary parts of Windows headers
+
+#define WIN32_LEAN_AND_MEAN
+
+#ifndef __MSVCRT_VERSION__
+#define __MSVCRT_VERSION__ 0x0601
+#endif
+
+// We need WINVER at least 0x0500 to use GetFileSizeEx on Cygwin/MinGW,
+// and 0x0501 for FindFirstFile(W) for opendir/readdir.
+//
+// WIN32_WINNT versions 0x0600 (Vista) and higher enable WSAPoll() in
+// winsock2.h, whose struct pollfd conflicts with ours below, so for
+// now we just set it lower than that, to Windows XP (0x0501).
+
+#ifdef WINVER
+# if WINVER != 0x0501
+// provoke a redefinition warning to track down the offender
+# define WINVER 0x0501
+# error Must include emu.h before setting WINVER
+# endif
+#endif
+#define WINVER 0x0501
+
+#ifdef _WIN32_WINNT
+# if _WIN32_WINNT != 0x0501
+// provoke a redefinition warning to track down the offender
+# define _WIN32_WINNT 0x0501
+# error Must include emu.h before setting _WIN32_WINNT
+# endif
+#endif
+#define _WIN32_WINNT 0x0501
+
+#endif // _EMU_WINVER_H
diff --git a/lib/win32/getopt_long.cpp b/lib/win32/getopt_long.cpp
index 5d910e1b..a508d22a 100755
--- a/lib/win32/getopt_long.cpp
+++ b/lib/win32/getopt_long.cpp
@@ -58,6 +58,10 @@
*/
// #include "Box.h"
+<<<<<<< HEAD
+=======
+#include "emu.h"
+>>>>>>> 0.12
#include <errno.h>
#include <stdarg.h>
diff --git a/modules.txt b/modules.txt
index 1be10efa..54c29b0c 100644
--- a/modules.txt
+++ b/modules.txt
@@ -11,7 +11,11 @@
lib/raidfile
lib/crypto
+<<<<<<< HEAD
lib/server
+=======
+lib/server qdbm lib/crypto
+>>>>>>> 0.12
lib/compress
lib/intercept
@@ -25,6 +29,7 @@ test/basicserver lib/server
# Backup system
+<<<<<<< HEAD
lib/backupclient lib/server lib/crypto lib/compress
lib/backupstore lib/server lib/raidfile lib/backupclient
@@ -32,12 +37,25 @@ bin/bbackupobjdump lib/backupclient lib/backupstore
bin/bbstored lib/raidfile lib/server lib/backupstore lib/backupclient
bin/bbstoreaccounts lib/raidfile lib/backupstore
bin/bbackupd lib/server lib/backupclient
+=======
+lib/backupstore lib/server lib/raidfile lib/crypto lib/compress
+lib/backupclient lib/backupstore
+
+bin/bbackupobjdump lib/backupclient lib/backupstore
+bin/bbstored lib/raidfile lib/server lib/backupstore
+bin/bbstoreaccounts lib/raidfile lib/backupstore
+bin/bbackupd lib/server lib/backupclient qdbm
+>>>>>>> 0.12
bin/bbackupquery lib/server lib/backupclient
bin/bbackupctl lib/server lib/backupclient
test/backupstore bin/bbstored bin/bbstoreaccounts lib/server lib/backupstore lib/backupclient lib/raidfile
test/backupstorefix bin/bbstored bin/bbstoreaccounts lib/backupstore lib/raidfile bin/bbackupquery bin/bbackupd bin/bbackupctl
+<<<<<<< HEAD
test/backupstorepatch bin/bbstored bin/bbstoreaccounts lib/backupstore lib/raidfile
+=======
+test/backupstorepatch bin/bbstored bin/bbstoreaccounts lib/backupclient
+>>>>>>> 0.12
test/backupdiff lib/backupclient
test/bbackupd bin/bbackupd bin/bbstored bin/bbstoreaccounts bin/bbackupquery bin/bbackupctl lib/server lib/backupstore lib/backupclient lib/intercept
diff --git a/parcels.txt b/parcels.txt
index 718fb995..396586b1 100644
--- a/parcels.txt
+++ b/parcels.txt
@@ -18,6 +18,11 @@ backup-client
html bbackupd-config
html bbackupd.conf
+<<<<<<< HEAD
+=======
+ subdir qdbm libqdbm.a
+
+>>>>>>> 0.12
EXCEPT:mingw32,mingw32msvc
man bbackupd.8
man bbackupquery.8
@@ -33,10 +38,16 @@ ONLY:mingw32,mingw32msvc
END-ONLY
ONLY:mingw32
+<<<<<<< HEAD
script /bin/mgwz.dll
script /bin/mingwm10.dll
script /usr/i686-pc-mingw32/bin/cygpcreposix-0.dll
script /usr/i686-pc-mingw32/bin/cygpcre-0.dll
+=======
+ script /usr/i686-pc-mingw32/sys-root/mingw/bin/libz-1.dll
+ script /usr/i686-pc-mingw32/sys-root/mingw/bin/mingwm10.dll
+ script /usr/i686-pc-mingw32/sys-root/mingw/bin/libgcc_s_dw2-1.dll
+>>>>>>> 0.12
END-ONLY
ONLY:SunOS
diff --git a/qdbm/COPYING b/qdbm/COPYING
new file mode 100644
index 00000000..b1e3f5a2
--- /dev/null
+++ b/qdbm/COPYING
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/qdbm/ChangeLog b/qdbm/ChangeLog
new file mode 100644
index 00000000..3fae65dd
--- /dev/null
+++ b/qdbm/ChangeLog
@@ -0,0 +1,990 @@
+2007-10-18 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A potential defect related to leaf division of B+ tree was cleared.
+ - Release: 1.8.77
+
+2007-03-07 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The hash function of on-memory map was optimized.
+ - A bug related to large object was fixed.
+ - Release: 1.8.76
+
+2006-11-10 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - A bug related to B+ tree API for Ruby was fixed.
+ - Release: 1.8.75
+
+2006-11-02 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug related to optimization on Intel Mac series was escaped.
+ - Release: 1.8.74
+
+2006-10-20 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug related to BZIP2 encoding was fixed.
+ - Release: 1.8.73
+
+2006-10-06 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Release: 1.8.72
+
+2006-08-24 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Makefile was modified to assure portability.
+ - Silent flag was added to the C++ API, the Java API, and the Ruby API.
+ - Release: 1.8.71
+
+2006-08-18 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug of segmentation fault on BSD was fixed.
+ - A test command for checking multi thread safety was added.
+ - Release: 1.8.70
+
+2006-08-15 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Mutex controll in C++ API became refined.
+ - Release: 1.8.69
+
+2006-08-08 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug about memory alignment was fixed.
+ - A bug of handling meta data on big endian platforms was fixed.
+ - Release: 1.8.68
+
+2006-08-05 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug about memory alignment was fixed.
+ - A bug about parsing MIME was fixed.
+ - Release: 1.8.67
+
+2006-08-05 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Release: 1.8.66
+
+2006-08-03 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The extended API was enhanced.
+ - The extended advanced API was enhanced.
+ - Release: 1.8.65
+
+2006-07-28 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug of Makefile ralated to optimization was fixed.
+ - Release: 1.8.64
+
+2006-07-24 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A lot of functions were replaced by macros.
+ - The utility API was enhanced.
+ - Release: 1.8.63
+
+2006-07-20 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A lot of functions were replaced by macros.
+ - The URL resolver was to allow unescaped meta characters.
+ - The advanced API was enhanced.
+ - Release: 1.8.62
+
+2006-07-14 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug of the size checking function of the extended API was fixed.
+ - The advanced API was enhanced.
+ - Release: 1.8.61
+
+2006-06-03 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The basic API was enhanced.
+ - The extended API was enhanced.
+ - The advanced API was enhanced.
+ - Multiple cursor class is now supported on the java API.
+ - Release: 1.8.60
+
+2006-05-30 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The basic API was enhanced.
+ - The extended API was enhanced.
+ - The advanced API was enhanced.
+ - A bug of Makefile for Mac OS X support was fixed.
+ - Release: 1.8.59
+
+2006-05-28 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The basic API was enhanced.
+ - The advanced API was enhanced.
+ - Release: 1.8.58
+
+2006-05-20 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The basic API was enhanced.
+ - The utility API was enhanced.
+ - Release: 1.8.57
+
+2006-05-17 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug of URL decoder was fixed.
+ - Release: 1.8.56
+
+2006-05-15 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The extended API was enhanced.
+ - The utility API was enhanced.
+ - Release: 1.8.55
+
+2006-05-15 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The basic API was enhanced.
+ - Release: 1.8.54
+
+2006-05-10 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - AIX is now supported.
+ - The utility API was enhanced.
+ - Release: 1.8.53
+
+2006-05-04 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug about evaluating RFC822 date format was fixed.
+ - Release: 1.8.52
+
+2006-05-02 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug about evaluating RFC822 date format was fixed.
+ - Warings from GCC 4.1.x were dealt with.
+ - Missing functions in qdbm.def were supplied.
+ - Release: 1.8.51
+
+2006-04-28 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Release: 1.8.50
+
+2006-04-19 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A parameter of dynamic linking library on Mac OS X was modified.
+ - The utility API was enhanced.
+ - Release: 1.8.49
+
+2006-03-27 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Release: 1.8.48
+
+2006-03-10 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - LTmakefile was modified.
+ - The utility API was enhanced.
+ - Release: 1.8.47
+
+2006-02-20 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Release: 1.8.46
+
+2006-01-28 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Alignment algorithm was improved.
+ - A bug of mmap emulation on Windows was fixed.
+ - Release: 1.8.45
+
+2006-01-24 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug of handling meta data on big endian platforms was fixed.
+ - The advanced API was enhanced.
+ - Release: 1.8.44
+
+2006-01-24 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug of mmap emulation on Windows was fixed.
+ - Release: 1.8.43
+
+2006-01-22 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - mmap emulation on Windows was enhanced.
+ - Release: 1.8.42
+
+2006-01-13 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Compression of pages of B+ tree with LZO and BZIP was added.
+ - Release: 1.8.41
+
+2006-01-08 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Configuration for VC++ was to build DLL.
+ - Release: 1.8.40
+
+2006-01-04 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The advanced API was enhanced.
+ - Release: 1.8.39
+
+2006-01-01 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The advanced API was enhanced.
+ - Release: 1.8.38
+
+2005-12-26 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Functions to set the size of the free block pool were added.
+ - Commands line tools were enhanced.
+ - Release: 1.8.37
+
+2005-12-24 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Free block pool was enhanced.
+ - Commands line tools were enhanced.
+ - Release: 1.8.36
+
+2005-11-30 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - The extended advanced API was enhanced.
+ - All stat calls were replaced with lstat calls.
+ - Release: 1.8.35
+
+2005-11-22 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug of i-node duplication on MinGW was fixed.
+ - Release: 1.8.34
+
+2005-09-09 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Compressing options of ZLIB was changed.
+ - The utility API was enhanced.
+ - Release: 1.8.33
+
+2005-08-30 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug of backward string matching was fixed.
+ - The utility API was enhanced.
+ - Release: 1.8.32
+
+2005-06-19 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug of the MIME parser was fixed.
+ - A bug about database repair on Win32 was fixed.
+ - A bug of Makefile for Mac OS X support was fixed.
+ - The type of error codes of GDBM-compatibility API was changed.
+ - Release: 1.8.31
+
+2005-06-09 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A Bug of APIs for C++ related to namespace was fixed.
+ - The library of shared objects of C++ APIs was added.
+ - The utility API was enhanced.
+ - Release: 1.8.30
+
+2005-06-01 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug about file locking on Win32 was fixed.
+ - Manuals of command line tools were added.
+ - The utility API was enhanced.
+ - Release: 1.8.29
+
+2005-05-27 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A compilation problem on hppa-linux was fixed.
+ - A configuration file for pkg-config was added.
+ - Release: 1.8.28
+
+2005-05-18 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Non-blocking lock was featured.
+ - Release: 1.8.27
+
+2005-05-12 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The inverted API was enhanced.
+ - The utility API was enhanced.
+ - Release: 1.8.26
+
+2005-05-10 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The inverted API was enhanced.
+ - The utility API was enhanced.
+ - Release: 1.8.25
+
+2005-04-25 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - A bug about database repair on Win32 was fixed.
+ - Release: 1.8.24
+
+2005-04-01 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - The extended advanced API was enhanced.
+ - Release: 1.8.23
+
+2005-03-24 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The extended advanced API was enhanced.
+ - The utility API was enhanced.
+ - The inverted API was enhanced.
+ - Test commands were enhanced.
+ - Release: 1.8.22
+
+2005-01-22 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Functions to dump endian independent data was added.
+ - Release: 1.8.21
+
+2005-01-05 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Sparse file support was added.
+ - Release: 1.8.20
+
+2005-01-02 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Building configurations were enhanced.
+ - Release: 1.8.19
+
+2004-11-10 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - The extended advanced API was enhanced.
+ - The inverted API was enhanced.
+ - Release: 1.8.18
+
+2004-09-20 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - The CGI script for file upload was enhanced.
+ - The spec file for RPM packages was enhanced.
+ - Release: 1.8.17
+
+2004-08-17 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug in the extended API was fixed.
+ - A bug about seek on Windows 9x was fixed.
+ - The CGI script for file upload was enhanced.
+ - Release: 1.8.16
+
+2004-08-04 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - A bug about mmap on Mac OS X was fixed.
+ - A CGI script for file upload was added.
+ - Building configurations were enhanced.
+ - Release: 1.8.15
+
+2004-07-19 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The basic API was enhanced.
+ - The extended API was enhanced.
+ - The advanced API was enhanced.
+ - The inverted API was enhanced.
+ - Building configurations were enhanced.
+ - Release: 1.8.14
+
+2004-07-07 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - The inverted API was enhanced.
+ - Release: 1.8.13
+
+2004-05-18 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - The inverted API was enhanced.
+ - Building configurations were enhanced.
+ - Release: 1.8.12
+
+2004-05-12 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Source codes were cleaned up for Visual C++.
+ - Test commands were enhanced.
+ - Building configurations were enhanced.
+ - Release: 1.8.11
+
+2004-04-23 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Utility for performance test was enhanced.
+ - It was escaped from a bug about mmap on FreeBSD and NetBSD.
+ - Release: 1.8.10
+
+2004-04-20 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Logger for debugging is now featured.
+ - It was escaped from a bug about mmap on OpenBSD.
+ - Release: 1.8.9
+
+2004-04-11 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Processing speed was improved.
+ - The basic API was enhanced.
+ - The extended API was enhanced.
+ - The configuration for VC++ was enhanced.
+ - Release: 1.8.8
+
+2004-03-25 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The configuration of C++ API escaped from a bug of pthread.
+ - Release: 1.8.7
+
+2004-03-21 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The extended API was enhanced.
+ - Bugs in the utility API about memory management were fixed.
+ - Release: 1.8.6
+
+2004-03-12 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug in the utility API for 64-bit platforms was fixed.
+ - The utility API was enhanced.
+ - Release: 1.8.5
+
+2004-03-09 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Runtime switching of whether to use ZLIB was featured.
+ - The binary package for Win32 now features ZLIB and ICONV.
+ - The utility API was enhanced.
+ - Release: 1.8.4
+
+2004-03-06 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - A utility class of Java API was enhanced.
+ - Test commands were enhanced.
+ - Release: 1.8.3
+
+2004-03-01 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Release: 1.8.2
+
+2004-02-22 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Processing speed of multi-thread support was improved.
+ - Release: 1.8.1
+
+2004-02-21 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Switch to make API for C thread-safe was added.
+ - Release: 1.8.0
+
+2004-02-18 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Potential bugs in APIs for C++ were fixed.
+ - Release: 1.7.34
+
+2004-01-28 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The extended API was enhanced.
+ - The inverted API was enhanced.
+ - Release: 1.7.33
+
+2004-01-17 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The inverted API was enhanced.
+ - Release: 1.7.32
+
+2004-01-16 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The inverted API was enhanced.
+ - Release: 1.7.31
+
+2004-01-12 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The inverted API was enhanced.
+ - Release: 1.7.30
+
+2004-01-11 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Release: 1.7.29
+
+2004-01-09 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug in the utility API was fixed.
+ - Release: 1.7.28
+
+2004-01-06 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug in the advanced API was fixed.
+ - The utility API was enhanced.
+ - Release: 1.7.27
+
+2004-01-04 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The inverted API was enhanced.
+ - Release: 1.7.26
+
+2004-01-01 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The inverted API was enhanced.
+ - The utility API was enhanced.
+ - Release: 1.7.25
+
+2003-12-26 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - C++ export was supported in header files.
+ - The utility API was enhanced.
+ - Release: 1.7.24
+
+2003-12-21 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Repairing utility was added to the advanced API.
+ - The basic API was enhanced.
+ - The utility API was enhanced.
+ - Release: 1.7.23
+
+2003-12-16 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Repairing utility was added to the extended API.
+ - The utility API was enhanced.
+ - Release: 1.7.22
+
+2003-12-14 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A utility to repair a broken database file was added.
+ - A ulitity command for full-text search was enhanced.
+ - Release: 1.7.21
+
+2003-12-10 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Character encoding converter was added.
+ - Release: 1.7.20
+
+2003-12-10 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Format converter for older versions than 1.7.13 was added.
+ - Tuning paramters of the inverted API were modified.
+ - A CGI script for full-text search was enhanced.
+ - Release: 1.7.19
+
+2003-12-08 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - The GDBM-compatible API was enhanced.
+ - Release: 1.7.18
+
+2003-12-05 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - A CGI script for full-text search was enhanced.
+ - Release: 1.7.17
+
+2003-12-01 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A CGI script for full-text search was enhanced.
+ - A ulitity command for full-text search was enhanced.
+ - Building configuration files were modified.
+ - Release: 1.7.16
+
+2003-11-30 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A CGI script for full-text search was enhanced.
+ - A ulitity command for full-text search was enhanced.
+ - Release: 1.7.15
+
+2003-11-29 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The inverted API was enhanced.
+ - A ulitity command for full-text search was enhanced.
+ - A CGI script for full-text search was enhanced.
+ - Release: 1.7.14
+
+2003-11-27 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Format of headers of database was modified.
+ - Supporting endian conversion was abolished.
+ - Import/export features were added to utility commands.
+ - A ulitity command for full-text search was enhanced.
+ - A CGI script for full-text search was enhanced.
+ - Release: 1.7.13
+
+2003-11-25 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - The inverted API was enhanced.
+ - A ulitity command for full-text search was enhanced.
+ - A CGI script for full-text search was enhanced.
+ - Release: 1.7.12
+
+2003-11-23 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The inverted API was enhanced.
+ - A ulitity command for full-text search was enhanced.
+ - A CGI script for full-text search was enhanced.
+ - A utility class of Java API was enhanced.
+ - Release: 1.7.11
+
+2003-11-20 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A utility class of Java API was enhanced.
+ - Building configuration for CGI scripts was modified.
+ - Release: 1.7.10
+
+2003-11-20 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Tuning paramters of the inverted API were modified.
+ - A ulitity command for full-text search was enhanced.
+ - Release: 1.7.9
+
+2003-11-16 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Optional features of compressing with ZLIB were added.
+ - Release: 1.7.8
+
+2003-11-05 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The extended advanced API, Vista was added.
+ - Release: 1.7.7
+
+2003-11-03 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - C API compilation using Visual C++ was supported.
+ - Odeum and its commands were enhanced.
+ - Release: 1.7.6
+
+2003-10-25 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A ulitity command for full-text search was enhanced.
+ - A CGI script for full-text search was enhanced.
+ - Release: 1.7.5
+
+2003-10-23 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A CGI script for full-text search was added.
+ - A bug of BSD support in Makefile was fixed.
+ - Release: 1.7.4
+
+2003-10-19 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Processing speed of the inverted API was improved.
+ - A ulitity command for full-text search was enhanced.
+ - Release: 1.7.3
+
+2003-10-17 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The license was changed to LGPL.
+ - An indexing command for the inverted API was added.
+ - Release: 1.7.2
+
+2003-10-14 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A test command for the inverted API was added.
+ - A bug about optimization in the inverted API was fixed.
+ - Tuning paramters of the inverted API were modified.
+ - Release: 1.7.1
+
+2003-10-13 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The inverted API, Odeum was added.
+ - Comments in source codes increased.
+ - Release: 1.7.0
+
+2003-10-05 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Executable commands were modified for RISC OS.
+ - Release: 1.6.22
+
+2003-10-05 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Executable commands were modified for RISC OS.
+ - Release: 1.6.21
+
+2003-09-30 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Naming file in RISC OS style was supported.
+ - Hash functions were enhanced.
+ - Release: 1.6.20
+
+2003-09-26 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Test commands were added.
+ - Release: 1.6.19
+
+2003-09-22 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Makefile for RISC OS was added.
+ - C++ API was enhanced.
+ - Release: 1.6.18
+
+2003-09-14 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Test commands were enhanced.
+ - Source codes and Makefile were modified for RISC OS.
+ - The CGI script was enhanced.
+ - Release: 1.6.17
+
+2003-09-13 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The CGI script for administration was added.
+ - C++ API was enhanced.
+ - Release: 1.6.16
+
+2003-08-28 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Release: 1.6.15
+
+2003-08-16 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Release: 1.6.14
+
+2003-08-11 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Release: 1.6.13
+
+2003-08-05 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - A bug in the utility API was fixed.
+ - Test commands were enhanced.
+ - Release: 1.6.12
+
+2003-07-31 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - C and Java APIs support compilation using MinGW.
+ - Release: 1.6.11
+
+2003-07-27 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Perl and Ruby APIs support transaction.
+ - Release: 1.6.10
+
+2003-07-26 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Speed of transaction was improved.
+ - Release: 1.6.9
+
+2003-07-24 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - C++ and Java APIs of B+ tree support transaction.
+ - Release: 1.6.8
+
+2003-07-21 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - C API of B+ tree supports transaction.
+ - Bugs about Mutex in C++ and Java APIs were fixed.
+ - Release: 1.6.7
+
+2003-07-18 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - C++ and Java APIs of database abstraction were simplified.
+ - Ruby API of B+ tree was simplified.
+ - Release: 1.6.6
+
+2003-07-16 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Ruby API of B+ tree was added.
+ - C, C++, Java and Perl APIs of B+ tree were enhanced.
+ - Release: 1.6.5
+
+2003-07-13 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Perl API of B+ tree was added.
+ - C, C++ and Java APIs of B+ tree were enhanced.
+ - Release: 1.6.4
+
+2003-07-08 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Java API of B++ tree was added.
+ - C API of B+ tree was enhanced.
+ - C++ API of B+ tree was enhanced.
+ - Release: 1.6.3
+
+2003-07-05 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Debug routines were removed and speed was improved.
+ - No locking mode was added.
+ - Endian converter of Villa was added.
+ - C++ API of B++ tree was added.
+ - Release: 1.6.2
+
+2003-07-03 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The advanced API was enhanced to improve its speed.
+ - Makefiles were enhanced for HP-UX.
+ - Release: 1.6.1
+
+2003-06-28 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The advanced API for B+ tree database is added.
+ - Release: 1.6.0
+
+2003-06-23 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Makefiles were enhanced for Mac OS X.
+ - Release: 1.5.13
+
+2003-06-18 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Makefile was enhanced for Solaris and Mac OS X.
+ - Release: 1.5.12
+
+2003-06-06 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Release: 1.5.11
+
+2003-05-31 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was enhanced.
+ - Release: 1.5.10
+
+2003-05-29 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was changed.
+ - Release: 1.5.9
+
+2003-05-25 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Alignment mechanism was simplified.
+ - Release: 1.5.8
+
+2003-05-17 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Makefile was updated
+ - Release: 1.5.7
+
+2003-05-16 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Test tools were enhanced.
+ - Release: 1.5.6
+
+2003-05-14 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Makefile using libtool was added.
+ - Release: 1.5.5
+
+2003-05-12 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - API for Ruby was enhanced.
+ - Release: 1.5.4
+
+2003-05-08 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - API for Ruby was added.
+ - Release: 1.5.3
+
+2003-05-04 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - API for Perl was enhanced.
+ - Release: 1.5.2
+
+2003-04-30 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - API for Perl was enhanced.
+ - Release: 1.5.1
+
+2003-04-29 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - API for Perl was added.
+ - Release: 1.5.0
+
+2003-04-25 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The package was reorganized to be a GNU package.
+ - Release: 1.4.11
+
+2003-04-21 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The utility API was added.
+ - Release: 1.4.10
+
+2003-04-15 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Endian converters were added.
+ - Release: 1.4.9
+
+2003-04-12 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The GDBM-compatible API was enhanced.
+ - Release: 1.4.8
+
+2003-04-10 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Some bugs were fixed and C++ API was enhanced.
+ - Release: 1.4.7
+
+2003-04-09 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Coalescence of dead regions and reuse of them were featured.
+ - Release: 1.4.6
+
+2003-04-06 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The GDBM-compatible API and the C++ API were enhanced.
+ - Release: 1.4.5
+
+2003-04-04 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The API for C++ and the API for Java were enhanced.
+ - Release: 1.4.4
+
+2003-04-02 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Documents for C++ was enhanced.
+ - Release: 1.4.3
+
+2003-04-01 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The API for Java was enhanced.
+ - Release: 1.4.2
+
+2003-03-23 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Makefiles were modified.
+ - Release: 1.4.1
+
+2003-03-23 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - APIs for C++ and Java was enhanced.
+ - Release: 1.4.0
+
+2003-03-15 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The API for C++ was added.
+ - Release: 1.3.2
+
+2003-03-11 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The API for Java was supported on Solaris and Windows.
+ - Release: 1.3.1
+
+2003-03-04 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The API for Java was added.
+ - Release: 1.3.0
+
+2003-02-23 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The GDBM-compatible API was added.
+ - Release: 1.2.0
+
+2003-02-21 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Compatibility with NDBM was improved.
+ - Release: 1.1.3
+
+2003-02-18 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Optimizing suppoted on Windows. DLL is supported.
+ - Release: 1.1.2
+
+2003-02-15 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - Windows is now supported.
+ - The compatible API was enhanced.
+ - Release: 1.1.1
+
+2003-02-11 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The NDBM-compatible API was added.
+ - Release: 1.1.0
+
+2003-02-08 Mikio Hirabayashi <mikio@users.sourceforge.net>
+
+ - The initial version.
+ - Release: 1.0.0
+
diff --git a/qdbm/LTmakefile.in b/qdbm/LTmakefile.in
new file mode 100644
index 00000000..547abccf
--- /dev/null
+++ b/qdbm/LTmakefile.in
@@ -0,0 +1,318 @@
+# Makefile to build QDBM using libtool
+
+
+
+#================================================================
+# Setting variables
+#================================================================
+
+
+# Generic settings
+SHELL = @SHELL@
+
+# Packaging
+PACKAGE = @PACKAGE_NAME@
+VERSION = @PACKAGE_VERSION@
+LIBVER = @LIBVER@
+LIBREV = @LIBREV@
+
+# Targets
+MYHEADS = depot.h curia.h relic.h hovel.h cabin.h villa.h vista.h odeum.h
+MYLIBOBJS = depot.lo curia.lo relic.lo hovel.lo cabin.lo villa.lo vista.lo odeum.lo myconf.lo
+MYLIBS = libqdbm.la
+MYBINS = dpmgr dptest dptsv crmgr crtest crtsv rlmgr rltest hvmgr hvtest \
+ cbtest cbcodec vlmgr vltest vltsv odmgr odtest odidx qmttest
+MYMAN1S = dpmgr.1 dptest.1 dptsv.1 crmgr.1 crtest.1 crtsv.1 rlmgr.1 rltest.1 hvmgr.1 hvtest.1 \
+ cbtest.1 cbcodec.1 vlmgr.1 vltest.1 vltsv.1 odmgr.1 odtest.1 odidx.1 qmttest.1
+MYMAN3S = qdbm.3 depot.3 dpopen.3 curia.3 cropen.3 relic.3 hovel.3 \
+ cabin.3 villa.3 vlopen.3 vista.3 odeum.3 odopen.3
+MYDOCS = spex.html spex-ja.html COPYING ChangeLog NEWS THANKS
+MYPCS = qdbm.pc
+
+# Install destinations
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+MYHEADDIR = @includedir@
+MYLIBDIR = @libdir@
+MYBINDIR = @bindir@
+MYMAN1DIR = @mandir@/man1
+MYMAN3DIR = @mandir@/man3
+MYSHAREDIR = $(prefix)/share/$(PACKAGE)
+MYPCDIR = @libdir@/pkgconfig
+
+# Building binaries
+LIBTOOL = libtool
+CC = gcc
+CFLAGS = -I. -I$(MYHEADDIR) -I$(HOME)/include -I/usr/local/include @MYDEFS@ \
+ -D_XOPEN_SOURCE_EXTENDED=1 -D_GNU_SOURCE=1 -D__EXTENSIONS__=1 -D_HPUX_SOURCE=1 \
+ -D_POSIX_MAPPED_FILES=1 -D_POSIX_SYNCHRONIZED_IO=1 \
+ -DPIC=1 -D_THREAD_SAFE=1 -D_REENTRANT=1 -DNDEBUG -O3
+LD = gcc
+LIBLDFLAGS = -rpath $(MYLIBDIR) -R $(MYLIBDIR) \
+ -version-info $$(($(LIBVER)+$(LIBREV))):0:$(LIBREV) @LIBS@
+LDFLAGS = -rpath $(MYLIBDIR) -R $(MYLIBDIR) -L. -lqdbm @LIBS@
+INSTALL = install
+MKDIR = mkdir -p
+CP = cp -rf
+RM = rm -rf
+
+
+
+#================================================================
+# Suffix rules
+#================================================================
+
+
+.SUFFIXES :
+.SUFFIXES : .c .lo
+
+.c.lo :
+ $(LIBTOOL) --mode=compile --tag=CC $(CC) -c $(CFLAGS) $<
+
+
+
+#================================================================
+# Actions
+#================================================================
+
+
+all : $(MYLIBS) $(MYBINS)
+ @printf '\n'
+ @printf '#================================================================\n'
+ @printf '# Ready to install.\n'
+ @printf '#================================================================\n'
+
+
+clean :
+ $(RM) $(MYLIBS) $(MYBINS) *.o *.a *.so *.lo *.la a.out .libs \
+ *.exe *.dll.a *.dll TAGS srcdoc gmon.out leak.log casket casket.* *~
+
+
+install :
+ $(LIBTOOL) --mode=install $(INSTALL) $(MYHEADS) $(MYHEADDIR)
+ $(LIBTOOL) --mode=install $(INSTALL) $(MYLIBS) $(MYLIBDIR)
+ $(LIBTOOL) --mode=install $(INSTALL) $(MYBINS) $(MYBINDIR)
+ $(MKDIR) $(MYMAN1DIR)
+ cd man && $(CP) $(MYMAN1S) $(MYMAN1DIR)
+ $(MKDIR) $(MYMAN3DIR)
+ cd man && $(CP) $(MYMAN3S) $(MYMAN3DIR)
+ $(MKDIR) $(MYSHAREDIR)
+ $(CP) $(MYDOCS) $(MYSHAREDIR)
+ $(MKDIR) $(MYPCDIR)
+ $(CP) $(MYPCS) $(MYPCDIR)
+ @printf '\n'
+ @printf '#================================================================\n'
+ @printf '# Thanks for using QDBM.\n'
+ @printf '#================================================================\n'
+
+
+uninstall :
+ cd $(MYHEADDIR) && $(LIBTOOL) --mode=uninstall $(RM) $(MYHEADS)
+ cd $(MYLIBDIR) && $(LIBTOOL) --mode=uninstall $(RM) $(MYLIBS)
+ cd $(MYBINDIR) && $(LIBTOOL) --mode=uninstall $(RM) $(MYBINS)
+ cd $(MYMAN1DIR) && $(RM) $(MYMAN1S)
+ cd $(MYMAN3DIR) && $(RM) $(MYMAN3S)
+ $(RM) $(MYSHAREDIR)
+ cd $(MYPCDIR) && $(RM) $(MYPCS)
+
+
+distclean : clean
+ $(RM) Makefile LTmakefile rpmspec config.cache config.log config.status autom4te.cache
+
+
+check :
+ $(RM) casket*
+ $(LIBTOOL) --mode=execute ./dptest write -s casket 500 500000
+ $(LIBTOOL) --mode=execute ./dptest write casket 50000 5000
+ $(LIBTOOL) --mode=execute ./dptest read casket
+ $(LIBTOOL) --mode=execute ./dptest read -wb casket
+ $(LIBTOOL) --mode=execute ./dptest rcat -c casket 50000 50 500 32 8
+ $(LIBTOOL) --mode=execute ./dptest combo casket
+ $(LIBTOOL) --mode=execute ./dptest wicked -c casket 5000
+ $(LIBTOOL) --mode=execute ./dptest wicked casket 500
+ $(LIBTOOL) --mode=execute ./dpmgr repair casket
+ $(LIBTOOL) --mode=execute ./dpmgr optimize casket
+ $(LIBTOOL) --mode=execute ./dpmgr list casket
+ $(RM) casket*
+ $(LIBTOOL) --mode=execute ./crtest write -s casket 500 100000 5
+ $(LIBTOOL) --mode=execute ./crtest write casket 50000 500 10
+ $(LIBTOOL) --mode=execute ./crtest read casket
+ $(LIBTOOL) --mode=execute ./crtest read -wb casket
+ $(LIBTOOL) --mode=execute ./crtest rcat -c casket 50000 5 10 500 32 8
+ $(LIBTOOL) --mode=execute ./crtest combo casket
+ $(LIBTOOL) --mode=execute ./crtest wicked -c casket 5000
+ $(LIBTOOL) --mode=execute ./crtest wicked casket 500
+ $(LIBTOOL) --mode=execute ./crmgr repair casket
+ $(LIBTOOL) --mode=execute ./crmgr optimize casket
+ $(LIBTOOL) --mode=execute ./crmgr list casket
+ $(RM) casket*
+ $(LIBTOOL) --mode=execute ./crtest write -lob casket 1000 50 10
+ $(LIBTOOL) --mode=execute ./crtest read -lob casket
+ $(RM) casket*
+ $(LIBTOOL) --mode=execute ./rltest write casket 5000
+ $(LIBTOOL) --mode=execute ./rltest read casket 5000
+ $(LIBTOOL) --mode=execute ./rlmgr list casket
+ $(RM) casket*
+ $(LIBTOOL) --mode=execute ./hvtest write casket 5000
+ $(LIBTOOL) --mode=execute ./hvtest read casket 5000
+ $(LIBTOOL) --mode=execute ./hvmgr optimize casket
+ $(LIBTOOL) --mode=execute ./hvmgr list casket
+ $(RM) casket*
+ $(LIBTOOL) --mode=execute ./hvtest write -qdbm -s casket 500
+ $(LIBTOOL) --mode=execute ./hvtest write -qdbm casket 5000
+ $(LIBTOOL) --mode=execute ./hvtest read -qdbm casket 5000
+ $(RM) casket*
+ $(LIBTOOL) --mode=execute ./cbtest sort 5000
+ $(LIBTOOL) --mode=execute ./cbtest strstr 500
+ $(LIBTOOL) --mode=execute ./cbtest list 50000
+ $(LIBTOOL) --mode=execute ./cbtest list -d 500
+ $(LIBTOOL) --mode=execute ./cbtest map 50000 500
+ $(LIBTOOL) --mode=execute ./cbtest map -d 500 5
+ $(LIBTOOL) --mode=execute ./cbtest heap 50000 500
+ $(LIBTOOL) --mode=execute ./cbtest heap -d 500 50
+ $(LIBTOOL) --mode=execute ./cbtest wicked 5000
+ $(LIBTOOL) --mode=execute ./cbtest misc
+ $(RM) casket*
+ $(LIBTOOL) --mode=execute ./vltest write -tune 32 31 32 32 casket 50000
+ $(LIBTOOL) --mode=execute ./vltest read casket
+ $(LIBTOOL) --mode=execute ./vltest rdup -tune 32 31 512 256 casket 50000 50000
+ $(LIBTOOL) --mode=execute ./vltest combo casket
+ $(LIBTOOL) --mode=execute ./vltest wicked -c casket 5000
+ $(LIBTOOL) --mode=execute ./vltest wicked casket 500
+ $(LIBTOOL) --mode=execute ./vlmgr repair casket
+ $(LIBTOOL) --mode=execute ./vlmgr optimize casket
+ $(LIBTOOL) --mode=execute ./vlmgr list casket
+ $(RM) casket*
+ $(LIBTOOL) --mode=execute ./vltest write -int -cz -tune 32 31 32 32 casket 50000
+ $(LIBTOOL) --mode=execute ./vltest read -int -vc casket
+ $(LIBTOOL) --mode=execute ./vltest rdup -int -cz -cc -tune 32 31 512 256 casket 50000 50000
+ $(LIBTOOL) --mode=execute ./vltest combo -cz casket
+ $(LIBTOOL) --mode=execute ./vltest wicked -cz -c casket 5000
+ $(LIBTOOL) --mode=execute ./vltest combo -cy casket
+ $(LIBTOOL) --mode=execute ./vltest wicked -cy -c casket 5000
+ $(LIBTOOL) --mode=execute ./vltest combo -cx casket
+ $(LIBTOOL) --mode=execute ./vltest wicked -cx -c casket 5000
+ $(RM) casket*
+ $(LIBTOOL) --mode=execute ./odtest write casket 500 50 5000
+ $(LIBTOOL) --mode=execute ./odtest read casket
+ $(LIBTOOL) --mode=execute ./odtest combo casket
+ $(LIBTOOL) --mode=execute ./odtest wicked casket 500
+ $(RM) casket*
+ $(LIBTOOL) --mode=execute ./qmttest casket 50000 10
+ $(RM) casket*
+ @printf '\n'
+ @printf '#================================================================\n'
+ @printf '# Checking completed.\n'
+ @printf '#================================================================\n'
+
+
+.PHONY : all clean install check
+
+
+
+#================================================================
+# Building binaries
+#================================================================
+
+
+libqdbm.la : $(MYLIBOBJS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) $(MYLIBOBJS) -o $@ $(LIBLDFLAGS)
+
+
+dpmgr : dpmgr.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ dpmgr.lo $(LDFLAGS)
+
+
+dptest : dptest.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ dptest.lo $(LDFLAGS)
+
+
+dptsv : dptsv.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ dptsv.lo $(LDFLAGS)
+
+
+crmgr : crmgr.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ crmgr.lo $(LDFLAGS)
+
+
+crtest : crtest.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ crtest.lo $(LDFLAGS)
+
+
+crtsv : crtsv.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ crtsv.lo $(LDFLAGS)
+
+
+rlmgr : rlmgr.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ rlmgr.lo $(LDFLAGS)
+
+
+rltest : rltest.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ rltest.lo $(LDFLAGS)
+
+
+hvmgr : hvmgr.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ hvmgr.lo $(LDFLAGS)
+
+
+hvtest : hvtest.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ hvtest.lo $(LDFLAGS)
+
+
+cbtest : cbtest.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ cbtest.lo $(LDFLAGS)
+
+
+cbcodec : cbcodec.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ cbcodec.lo $(LDFLAGS)
+
+
+vlmgr : vlmgr.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ vlmgr.lo $(LDFLAGS)
+
+
+vltest : vltest.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ vltest.lo $(LDFLAGS)
+
+
+vltsv : vltsv.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ vltsv.lo $(LDFLAGS)
+
+
+odmgr : odmgr.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ odmgr.lo $(LDFLAGS)
+
+
+odtest : odtest.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ odtest.lo $(LDFLAGS)
+
+
+odidx : odidx.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ odidx.lo $(LDFLAGS)
+
+
+qmttest : qmttest.lo $(MYLIBS)
+ $(LIBTOOL) --mode=link --tag=CC $(LD) -o $@ qmttest.lo $(LDFLAGS)
+
+
+depot.lo dpmgr.lo dptest.lo dptsv.lo : depot.h myconf.h
+
+curia.lo crmgr.lo crtest.lo crtsv.lo : curia.h depot.h myconf.h
+
+relic.lo rlmgr.lo rltest.lo : relic.h depot.h myconf.h
+
+hovel.lo hvmgr.lo hvtest.lo : hovel.h depot.h curia.h myconf.h
+
+cabin.lo cbtest.lo : cabin.h myconf.h
+
+villa.lo vlmgr.lo vltest.lo vltsv.lo : villa.h depot.h cabin.h myconf.h
+
+vista.lo : vista.h villa.h depot.h curia.h cabin.h myconf.h villa.c
+
+odeum.lo odmgr.lo odtest.lo odidx.lo : odeum.h depot.h curia.h cabin.h villa.h myconf.h
+
+myconf.lo : myconf.h
+
+
+
+# END OF FILE
diff --git a/qdbm/Makefile.in b/qdbm/Makefile.in
new file mode 100644
index 00000000..f87d7e46
--- /dev/null
+++ b/qdbm/Makefile.in
@@ -0,0 +1,646 @@
+# Makefile for QDBM
+
+
+
+#================================================================
+# Setting variables
+#================================================================
+
+
+# Generic settings
+SHELL = @SHELL@
+srcdir = @srcdir@
+VPATH = @srcdir@
+SUBDIRS =
+
+# Packaging
+PACKAGE = @PACKAGE_NAME@
+VERSION = @PACKAGE_VERSION@
+PACKAGEDIR = $(PACKAGE)-$(VERSION)
+PACKAGETGZ = $(PACKAGE)-$(VERSION).tar.gz
+LIBVER = @LIBVER@
+LIBREV = @LIBREV@
+
+# Targets
+MYHEADS = depot.h curia.h relic.h hovel.h cabin.h villa.h vista.h odeum.h
+MYLIBOBJS = depot.o curia.o relic.o hovel.o cabin.o villa.o vista.o odeum.o myconf.o
+MYLIBS = libqdbm.a libqdbm.so.$(LIBVER).$(LIBREV).0 libqdbm.so.$(LIBVER) libqdbm.so
+MYBINS = dpmgr dptest dptsv crmgr crtest crtsv rlmgr rltest hvmgr hvtest \
+ cbtest cbcodec vlmgr vltest vltsv odmgr odtest odidx qmttest
+MYMAN1S = dpmgr.1 dptest.1 dptsv.1 crmgr.1 crtest.1 crtsv.1 rlmgr.1 rltest.1 hvmgr.1 hvtest.1 \
+ cbtest.1 cbcodec.1 vlmgr.1 vltest.1 vltsv.1 odmgr.1 odtest.1 odidx.1 qmttest.1
+MYMAN3S = qdbm.3 depot.3 dpopen.3 curia.3 cropen.3 relic.3 hovel.3 \
+ cabin.3 villa.3 vlopen.3 vista.3 odeum.3 odopen.3
+MYDOCS = spex.html spex-ja.html COPYING ChangeLog NEWS THANKS
+MYPCS = qdbm.pc
+MYWINLIBS = libqdbm.a libqdbm.dll.a
+MYMACLIBS = libqdbm.a libqdbm.$(LIBVER).$(LIBREV).0.dylib libqdbm.$(LIBVER).dylib libqdbm.dylib
+MYHPUXLIBS = libqdbm.a libqdbm.sl
+
+# Install destinations
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+MYHEADDIR = @includedir@
+MYLIBDIR = @libdir@
+MYBINDIR = @bindir@
+MYMAN1DIR = @mandir@/man1
+MYMAN3DIR = @mandir@/man3
+MYDATADIR = @datadir@/$(PACKAGE)
+MYPCDIR = @libdir@/pkgconfig
+DESTDIR =
+
+# Building binaries
+CC = @CC@
+CPPFLAGS = @CPPFLAGS@ -I$(srcdir) -I$(MYHEADDIR) \
+ -I$(HOME)/include -I/usr/local/include @MYDEFS@ \
+ -D_XOPEN_SOURCE_EXTENDED=1 -D_GNU_SOURCE=1 -D__EXTENSIONS__=1 -D_HPUX_SOURCE=1 \
+ -D_POSIX_MAPPED_FILES=1 -D_POSIX_SYNCHRONIZED_IO=1 \
+ -DPIC=1 -D_THREAD_SAFE=1 -D_REENTRANT=1 -DNDEBUG
+CFLAGS = @CFLAGS@ -Wall -pedantic -fPIC -fsigned-char -O0 -fforce-addr @MYOPTS@
+LD = @LD@
+LIBS = -lqdbm @LIBS@
+LIBLDFLAGS = @LDFLAGS@ -L. -L$(MYLIBDIR) -L$(HOME)/lib -L/usr/local/lib @LIBS@
+LDFLAGS = @LDFLAGS@ -L. -L$(MYLIBDIR) -L$(HOME)/lib -L/usr/local/lib $(LIBS)
+LDENV = LD_RUN_PATH=/lib:/usr/lib:$(MYLIBDIR):$(HOME)/lib:/usr/local/lib
+AR = @AR@
+ARFLAGS = rcsv
+RUNENV = LD_LIBRARY_PATH=.:/lib:/usr/lib:$(MYLIBDIR):$(HOME)/lib:/usr/local/lib
+
+
+
+#================================================================
+# Suffix rules
+#================================================================
+
+
+.SUFFIXES :
+.SUFFIXES : .c .o
+
+.c.o :
+ $(CC) -c $(CPPFLAGS) $(CFLAGS) $<
+
+
+
+#================================================================
+# Actions
+#================================================================
+
+
+targets : @TARGETS@
+
+
+all : $(MYLIBS) $(MYBINS)
+ @printf '\n'
+ @printf '#================================================================\n'
+ @printf '# Ready to install.\n'
+ @printf '#================================================================\n'
+
+
+static :
+ make MYLIBS="$(MYLIBS)" LDFLAGS="-static $(LDFLAGS)" all
+
+
+debug :
+ make MYLIBS="$(MYLIBS)" CPPFLAGS="$(CPPFLAGS) -UNDEBUG" \
+ CFLAGS="-Wall -ansi -pedantic -fPIC -fsigned-char -O2 -g" \
+ LDFLAGS="-static $(LDFLAGS)" all
+
+
+devel :
+ make MYLIBS="$(MYLIBS)" CPPFLAGS="$(CPPFLAGS) -UNDEBUG" \
+ CFLAGS="-Wall -ansi -pedantic -fPIC -fsigned-char -O2 -g -pipe" all
+ sync ; sync
+
+
+stable :
+ make MYLIBS="$(MYLIBS)" CFLAGS="-Wall -ansi -pedantic -fPIC -fsigned-char -O2" all
+
+
+profile :
+ make MYLIBS="$(MYLIBS)" \
+ CFLAGS="-Wall -pedantic -fPIC -fsigned-char -O3 -pg -g -Werror" \
+ LDFLAGS="-static $(LDFLAGS)" all
+
+
+unsigned :
+ make MYLIBS="$(MYLIBS)" CPPFLAGS="$(CPPFLAGS) -UNDEBUG" \
+ CFLAGS="-Wall -ansi -pedantic -fPIC -funsigned-char -g -O2" all
+
+
+m64 :
+ make MYLIBS="$(MYLIBS)" CPPFLAGS="$(CPPFLAGS) -UNDEBUG" \
+ CFLAGS="-Wall -ansi -pedantic -fPIC -fsigned-char -O2 -m64 -g" all
+
+
+pen4 :
+ stdopt="-O3 -fomit-frame-pointer -minline-all-stringops" ; \
+ exopt="-march=pentium4 -minline-all-stringops -fprefetch-loop-arrays" ; \
+ make MYLIBS="$(MYLIBS)" \
+ CFLAGS="-Wall -pedantic -fPIC -fsigned-char $$stdopt $$exopt" all
+
+
+k8 :
+ stdopt="-O3 -fomit-frame-pointer -minline-all-stringops" ; \
+ exopt="-march=k8 -minline-all-stringops -fprefetch-loop-arrays" ; \
+ make MYLIBS="$(MYLIBS)" \
+ CFLAGS="-Wall -pedantic -fPIC -fsigned-char $$stdopt $$exopt" all
+
+
+clean :
+ rm -rf $(MYLIBS) $(MYBINS) *.o *.a *.so *.lo *.la a.out .libs \
+ *.exe *.dll.a *.dll *.dylib *.sl TAGS srcdoc gmon.out leak.log \
+ casket casket.* casket-* *~
+
+
+version :
+ vernum=`expr $(LIBVER)00 + $(LIBREV)` ; \
+ sed -e 's/_QDBM_VERSION.*/_QDBM_VERSION "$(VERSION)"/' \
+ -e "s/_QDBM_LIBVER.*/_QDBM_LIBVER $$vernum/" depot.h > depot.h~
+ [ -f depot.h~ ] && mv -f depot.h~ depot.h
+
+
+install :
+ mkdir -p $(DESTDIR)$(MYHEADDIR)
+ cd $(srcdir) && cp -Rf $(MYHEADS) $(DESTDIR)$(MYHEADDIR)
+ mkdir -p $(DESTDIR)$(MYLIBDIR)
+ cp -Rf $(MYLIBS) $(DESTDIR)$(MYLIBDIR)
+ mkdir -p $(DESTDIR)$(MYBINDIR)
+ cp -Rf $(MYBINS) $(DESTDIR)$(MYBINDIR)
+ mkdir -p $(DESTDIR)$(MYMAN1DIR)
+ cd $(srcdir)/man && cp -Rf $(MYMAN1S) $(DESTDIR)$(MYMAN1DIR)
+ mkdir -p $(DESTDIR)$(MYMAN3DIR)
+ cd $(srcdir)/man && cp -Rf $(MYMAN3S) $(DESTDIR)$(MYMAN3DIR)
+ mkdir -p $(DESTDIR)$(MYDATADIR)
+ cd $(srcdir) && cp -Rf $(MYDOCS) $(DESTDIR)$(MYDATADIR)
+ mkdir -p $(DESTDIR)$(MYPCDIR)
+ cd $(srcdir) && cp -Rf $(MYPCS) $(DESTDIR)$(MYPCDIR)
+ @printf '\n'
+ @printf '#================================================================\n'
+ @printf '# Thanks for using QDBM.\n'
+ @printf '#================================================================\n'
+
+
+install-strip :
+ make DESTDIR=$(DESTDIR) install
+ cd $(DESTDIR)$(MYBINDIR) && strip $(MYBINS)
+
+
+uninstall :
+ cd $(DESTDIR)$(MYHEADDIR) && rm -f $(MYHEADS)
+ cd $(DESTDIR)$(MYLIBDIR) && rm -f $(MYLIBS)
+ cd $(DESTDIR)$(MYBINDIR) && rm -f $(MYBINS)
+ cd $(DESTDIR)$(MYMAN1DIR) && rm -f $(MYMAN1S)
+ cd $(DESTDIR)$(MYMAN3DIR) && rm -f $(MYMAN3S)
+ rm -rf $(DESTDIR)$(MYDATADIR)
+ cd $(DESTDIR)$(MYPCDIR) && rm -f $(MYPCS)
+
+
+dist :
+ sync ; sync
+ for dir in $(SUBDIRS) ; \
+ do \
+ if [ -d $$dir ] ; \
+ then \
+ echo Making $@ in $$dir ; \
+ ( cd $$dir && if [ -f configure.in ] ; then autoconf ; ./configure ; \
+ make dist ; fi ) ; \
+ fi ; \
+ done
+ make version
+ make distclean
+ cd .. && tar cvf - $(PACKAGEDIR) | gzip -c > $(PACKAGETGZ)
+ sync ; sync
+
+
+distclean : clean
+ for dir in $(SUBDIRS) ; \
+ do \
+ if [ -d $$dir ] ; \
+ then \
+ echo Making $@ in $$dir ; \
+ ( cd $$dir && if [ -f Makefile ] ; then make distclean ; fi ) ; \
+ fi ; \
+ done
+ rm -rf Makefile LTmakefile qdbm.spec qdbm.pc config.cache config.log config.status \
+ autom4te.cache rpm-tmp *-win32
+
+
+TAGS :
+ etags -o $@ *.c *.h
+
+
+sdoc :
+ rm -rf srcdoc
+ ./lab/ccdoc -d srcdoc -t "Source Documents of QDBM" *.h *.c
+
+
+check :
+ sync ; sync
+ rm -rf casket*
+ $(RUNENV) $(RUNCMD) ./dptest write -s casket 500 500000
+ $(RUNENV) $(RUNCMD) ./dptest write casket 50000 5000
+ $(RUNENV) $(RUNCMD) ./dptest read casket
+ $(RUNENV) $(RUNCMD) ./dptest read -wb casket
+ $(RUNENV) $(RUNCMD) ./dptest rcat -c casket 50000 50 500 32 8
+ $(RUNENV) $(RUNCMD) ./dptest combo casket
+ $(RUNENV) $(RUNCMD) ./dptest wicked -c casket 5000
+ $(RUNENV) $(RUNCMD) ./dptest wicked casket 500
+ $(RUNENV) $(RUNCMD) ./dpmgr repair casket
+ $(RUNENV) $(RUNCMD) ./dpmgr optimize casket
+ $(RUNENV) $(RUNCMD) ./dpmgr list casket
+ rm -rf casket*
+ $(RUNENV) $(RUNCMD) ./crtest write -s casket 500 100000 5
+ $(RUNENV) $(RUNCMD) ./crtest write casket 50000 500 10
+ $(RUNENV) $(RUNCMD) ./crtest read casket
+ $(RUNENV) $(RUNCMD) ./crtest read -wb casket
+ $(RUNENV) $(RUNCMD) ./crtest rcat -c casket 50000 5 10 500 32 8
+ $(RUNENV) $(RUNCMD) ./crtest combo casket
+ $(RUNENV) $(RUNCMD) ./crtest wicked -c casket 5000
+ $(RUNENV) $(RUNCMD) ./crtest wicked casket 500
+ $(RUNENV) $(RUNCMD) ./crmgr repair casket
+ $(RUNENV) $(RUNCMD) ./crmgr optimize casket
+ $(RUNENV) $(RUNCMD) ./crmgr list casket
+ rm -rf casket*
+ $(RUNENV) $(RUNCMD) ./crtest write -lob casket 1000 50 10
+ $(RUNENV) $(RUNCMD) ./crtest read -lob casket
+ rm -rf casket*
+ $(RUNENV) $(RUNCMD) ./rltest write casket 5000
+ $(RUNENV) $(RUNCMD) ./rltest read casket 5000
+ $(RUNENV) $(RUNCMD) ./rlmgr list casket
+ rm -rf casket*
+ $(RUNENV) $(RUNCMD) ./hvtest write casket 5000
+ $(RUNENV) $(RUNCMD) ./hvtest read casket 5000
+ $(RUNENV) $(RUNCMD) ./hvmgr optimize casket
+ $(RUNENV) $(RUNCMD) ./hvmgr list casket
+ rm -rf casket*
+ $(RUNENV) $(RUNCMD) ./hvtest write -qdbm -s casket 500
+ $(RUNENV) $(RUNCMD) ./hvtest write -qdbm casket 5000
+ $(RUNENV) $(RUNCMD) ./hvtest read -qdbm casket 5000
+ rm -rf casket*
+ $(RUNENV) $(RUNCMD) ./cbtest sort 5000
+ $(RUNENV) $(RUNCMD) ./cbtest strstr 500
+ $(RUNENV) $(RUNCMD) ./cbtest list 50000
+ $(RUNENV) $(RUNCMD) ./cbtest list -d 500
+ $(RUNENV) $(RUNCMD) ./cbtest map 50000 500
+ $(RUNENV) $(RUNCMD) ./cbtest map -d 500 5
+ $(RUNENV) $(RUNCMD) ./cbtest heap 50000 500
+ $(RUNENV) $(RUNCMD) ./cbtest heap -d 500 50
+ $(RUNENV) $(RUNCMD) ./cbtest wicked 5000
+ $(RUNENV) $(RUNCMD) ./cbtest misc
+ rm -rf casket*
+ $(RUNENV) $(RUNCMD) ./vltest write -tune 32 31 32 32 casket 50000
+ $(RUNENV) $(RUNCMD) ./vltest read casket
+ $(RUNENV) $(RUNCMD) ./vltest rdup -tune 32 31 512 256 casket 50000 50000
+ $(RUNENV) $(RUNCMD) ./vltest combo casket
+ $(RUNENV) $(RUNCMD) ./vltest wicked -c casket 5000
+ $(RUNENV) $(RUNCMD) ./vltest wicked casket 500
+ $(RUNENV) $(RUNCMD) ./vlmgr repair casket
+ $(RUNENV) $(RUNCMD) ./vlmgr optimize casket
+ $(RUNENV) $(RUNCMD) ./vlmgr list casket
+ rm -rf casket*
+ $(RUNENV) $(RUNCMD) ./vltest write -int -cz -tune 32 31 32 32 casket 50000
+ $(RUNENV) $(RUNCMD) ./vltest read -int -vc casket
+ $(RUNENV) $(RUNCMD) ./vltest rdup -int -cz -cc -tune 32 31 512 256 casket 50000 50000
+ $(RUNENV) $(RUNCMD) ./vltest combo -cz casket
+ $(RUNENV) $(RUNCMD) ./vltest wicked -cz -c casket 5000
+ $(RUNENV) $(RUNCMD) ./vltest combo -cy casket
+ $(RUNENV) $(RUNCMD) ./vltest wicked -cy -c casket 5000
+ $(RUNENV) $(RUNCMD) ./vltest combo -cx casket
+ $(RUNENV) $(RUNCMD) ./vltest wicked -cx -c casket 5000
+ rm -rf casket*
+ $(RUNENV) $(RUNCMD) ./odtest write casket 500 50 5000
+ $(RUNENV) $(RUNCMD) ./odtest read casket
+ $(RUNENV) $(RUNCMD) ./odtest combo casket
+ $(RUNENV) $(RUNCMD) ./odtest wicked casket 500
+ rm -rf casket*
+ $(RUNENV) $(RUNCMD) ./qmttest casket 50000 10
+ rm -rf casket*
+ @printf '\n'
+ @printf '#================================================================\n'
+ @printf '# Checking completed.\n'
+ @printf '#================================================================\n'
+
+
+check-valgrind :
+ make RUNCMD="valgrind --tool=memcheck --log-fd=1" check | tee leak.log
+ grep ERROR leak.log
+ grep 'at exit' leak.log
+
+
+world :
+ make clean ; make
+ cd plus ; [ -f Makefile ] || ./configure ; make clean ; make
+ cd java ; [ -f Makefile ] || ./configure ; make clean ; make
+ cd perl ; [ -f Makefile ] || ./configure ; make clean ; make
+ cd ruby ; [ -f Makefile ] || ./configure ; make clean ; make
+ cd cgi ; [ -f Makefile ] || ./configure ; make clean ; make
+
+
+install-world :
+ make install
+ cd plus ; [ -f Makefile ] || ./configure ; make install
+ cd java ; [ -f Makefile ] || ./configure ; make install
+ cd perl ; [ -f Makefile ] || ./configure ; make install
+ cd ruby ; [ -f Makefile ] || ./configure ; make install
+ cd cgi ; [ -f Makefile ] || ./configure ; make install
+
+
+uninstall-world :
+ make uninstall
+ cd plus ; [ -f Makefile ] || ./configure ; make uninstall
+ cd java ; [ -f Makefile ] || ./configure ; make uninstall
+ cd perl ; [ -f Makefile ] || ./configure ; make uninstall
+ cd ruby ; [ -f Makefile ] || ./configure ; make uninstall
+ cd cgi ; [ -f Makefile ] || ./configure ; make uninstall
+
+
+check-world :
+ make check
+ cd plus ; [ -f Makefile ] || ./configure ; make check
+ cd java ; [ -f Makefile ] || ./configure ; make check
+ cd perl ; [ -f Makefile ] || ./configure ; make check
+ cd ruby ; [ -f Makefile ] || ./configure ; make check
+
+
+rpm : ../$(PACKAGETGZ) qdbm.spec
+ mkdir -p rpm-tmp/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
+ mkdir -p rpm-tmp/RPMS/i386
+ cp ../$(PACKAGETGZ) rpm-tmp/SOURCES
+ rpmbuild -bb --target i386 --define "_topdir `pwd`/rpm-tmp" qdbm.spec
+ mv -f rpm-tmp/RPMS/i386/$(PACKAGE)-*$(VERSION)*.rpm ..
+ rm -rf rpm-tmp
+
+
+win32pkg :
+ test -f /bin/mgwz.dll
+ test -f /bin/libiconv-2.dll
+ make uninstall && make uninstall-win && make clean
+ make mingw && strip *.exe && make install-win
+ cd java && ./configure
+ cd java && make uninstall && make uninstall-win && make clean
+ cd java && make mingw
+ cd cgi && ./configure
+ cd cgi && make clean
+ cd cgi && make mingw && strip *.cgi
+ mkdir -p $(PACKAGE)-$(VERSION)-win32
+ cp -Rf $(MYHEADS) libqdbm.dll.a qdbm.dll *.exe *.html \
+ misc/README-win32.txt misc/COPYING.txt misc/win32check.bat \
+ /bin/mgwz.dll /bin/libiconv-2.dll \
+ $(PACKAGE)-$(VERSION)-win32
+ cp -Rf java/jqdbm.dll java/qdbm.jar java/*.html java/japidoc $(PACKAGE)-$(VERSION)-win32
+ mkdir -p $(PACKAGE)-$(VERSION)-win32/cgi
+ cp -Rf cgi/*.cgi cgi/*.conf cgi/*.html $(PACKAGE)-$(VERSION)-win32/cgi
+ zip -r $(PACKAGE)-$(VERSION)-win32.zip $(PACKAGE)-$(VERSION)-win32
+ mv -f $(PACKAGE)-$(VERSION)-win32.zip ..
+ rm -rf $(PACKAGE)-$(VERSION)-win32
+ make uninstall && make uninstall-win && make clean
+ cd java ; make uninstall && make uninstall-win && make clean
+
+
+win :
+ make MYLIBS="$(MYWINLIBS)" CFLAGS="-Wall -ansi -pedantic -fsigned-char -O2"
+
+
+mingw :
+ make CC="gcc -mno-cygwin" MYLIBS="$(MYWINLIBS)" \
+ CFLAGS="-Wall -fsigned-char -O2" LIBLDFLAGS="@MGWLIBS@" LDFLAGS="-L. -lqdbm @MGWLIBS@"
+
+
+check-win :
+ make check
+
+
+install-win :
+ make MYBINS="`for file in $(MYBINS) ; do echo $$file.exe ; done | tr '\n' ' '`" \
+ MYLIBS="$(MYWINLIBS)" install
+ cp -Rf qdbm.dll $(DESTDIR)$(MYBINDIR)
+
+
+uninstall-win :
+ make MYBINS="`for file in $(MYBINS) ; do echo $$file.exe ; done | tr '\n' ' '`" \
+ MYLIBS="$(MYWINLIBS)" uninstall
+ rm -f $(DESTDIR)$(MYBINDIR)/qdbm.dll
+
+
+def : libqdbm.a
+ ./misc/makevcdef libqdbm.a > qdbm.def
+
+
+mac :
+ make MYLIBS="$(MYMACLIBS)" CFLAGS="-Wall -fsigned-char -fno-common -O2"
+
+
+check-mac :
+ make RUNENV="DYLD_LIBRARY_PATH=." check
+
+
+install-mac :
+ make MYLIBS="$(MYMACLIBS)" install
+
+
+uninstall-mac :
+ make MYLIBS="$(MYMACLIBS)" uninstall
+
+
+hpux :
+ make MYLIBS="$(MYHPUXLIBS)"
+
+
+check-hpux :
+ make RUNENV="SHLIB_PATH=." check
+
+
+install-hpux :
+ make MYLIBS="$(MYHPUXLIBS)" install
+
+
+uninstall-hpux :
+ make MYLIBS="$(MYHPUXLIBS)" uninstall
+
+
+no-so :
+ make MYLIBS="libqdbm.a" all
+
+
+install-no-so :
+ make MYLIBS="libqdbm.a" install
+
+
+uninstall-no-so :
+ make MYLIBS="libqdbm.a" uninstall
+
+
+.PHONY : all clean install check
+
+
+
+#================================================================
+# Building binaries
+#================================================================
+
+
+libqdbm.a : $(MYLIBOBJS)
+ $(AR) $(ARFLAGS) $@ $(MYLIBOBJS)
+
+
+libqdbm.so.$(LIBVER).$(LIBREV).0 : $(MYLIBOBJS)
+ if uname -a | egrep -i 'SunOS' > /dev/null ; \
+ then \
+ $(CC) -shared -Wl,-G,-h,libqdbm.so.$(LIBVER) -o $@ $(MYLIBOBJS) $(LIBLDFLAGS) ; \
+ else \
+ $(CC) -shared -Wl,-soname,libqdbm.so.$(LIBVER) -o $@ $(MYLIBOBJS) $(LIBLDFLAGS) ; \
+ fi
+
+
+libqdbm.so.$(LIBVER) : libqdbm.so.$(LIBVER).$(LIBREV).0
+ ln -f -s libqdbm.so.$(LIBVER).$(LIBREV).0 $@
+
+
+libqdbm.so : libqdbm.so.$(LIBVER).$(LIBREV).0
+ ln -f -s libqdbm.so.$(LIBVER).$(LIBREV).0 $@
+
+
+libqdbm.dll.a : qdbm.dll
+
+
+qdbm.dll : $(MYLIBOBJS)
+ $(CC) -shared -o $@ \
+ -Wl,--out-implib=lib$@.a \
+ -Wl,--export-all-symbols \
+ -Wl,--enable-auto-import \
+ -Wl,--add-stdcall-alias \
+ -Wl,--whole-archive \
+ -Wl,--no-whole-archive $(MYLIBOBJS) $(LIBLDFLAGS)
+
+
+libqdbm.$(LIBVER).$(LIBREV).0.dylib : $(MYLIBOBJS)
+ $(CC) -dynamiclib -o $@ \
+ -install_name $(MYLIBDIR)/libqdbm.$(LIBVER).dylib \
+ -current_version $(LIBVER).$(LIBREV).0 \
+ -compatibility_version $(LIBVER) \
+ $(MYLIBOBJS) $(LIBLDFLAGS)
+
+
+libqdbm.$(LIBVER).dylib : libqdbm.$(LIBVER).$(LIBREV).0.dylib
+ ln -f -s libqdbm.$(LIBVER).$(LIBREV).0.dylib $@
+
+
+libqdbm.dylib : libqdbm.$(LIBVER).$(LIBREV).0.dylib
+ ln -f -s libqdbm.$(LIBVER).$(LIBREV).0.dylib $@
+
+
+libqdbm.sl : $(MYLIBOBJS)
+ $(CC) -shared -Wl,-b -o $@ $(MYLIBOBJS) $(LIBLDFLAGS)
+
+
+dpmgr : dpmgr.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ dpmgr.o $(LDFLAGS)
+
+
+dptest : dptest.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ dptest.o $(LDFLAGS)
+
+
+dptsv : dptsv.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ dptsv.o $(LDFLAGS)
+
+
+crmgr : crmgr.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ crmgr.o $(LDFLAGS)
+
+
+crtest : crtest.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ crtest.o $(LDFLAGS)
+
+
+crtsv : crtsv.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ crtsv.o $(LDFLAGS)
+
+
+rlmgr : rlmgr.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ rlmgr.o $(LDFLAGS)
+
+
+rltest : rltest.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ rltest.o $(LDFLAGS)
+
+
+hvmgr : hvmgr.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ hvmgr.o $(LDFLAGS)
+
+
+hvtest : hvtest.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ hvtest.o $(LDFLAGS)
+
+
+cbtest : cbtest.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ cbtest.o $(LDFLAGS)
+
+
+cbcodec : cbcodec.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ cbcodec.o $(LDFLAGS)
+
+
+vlmgr : vlmgr.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ vlmgr.o $(LDFLAGS)
+
+
+vltest : vltest.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ vltest.o $(LDFLAGS)
+
+
+vltsv : vltsv.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ vltsv.o $(LDFLAGS)
+
+
+odmgr : odmgr.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ odmgr.o $(LDFLAGS)
+
+
+odtest : odtest.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ odtest.o $(LDFLAGS)
+
+
+odidx : odidx.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ odidx.o $(LDFLAGS)
+
+
+qmttest : qmttest.o $(MYLIBS)
+ $(LDENV) $(CC) $(CFLAGS) -o $@ qmttest.o $(LDFLAGS)
+
+
+depot.o : depot.h myconf.h
+
+curia.o : depot.h curia.h myconf.h
+
+relic.o : depot.h relic.h myconf.h
+
+hovel.o : depot.h curia.h hovel.h myconf.h
+
+cabin.o : cabin.h myconf.h
+
+villa.o : depot.h cabin.h villa.h myconf.h
+
+vista.o : depot.h curia.h cabin.h villa.h vista.h myconf.h villa.c
+
+odeum.o : depot.h curia.h cabin.h villa.h myconf.h
+
+myconf.o : myconf.h
+
+dpmgr.o dptest.o dptsv.o : depot.h cabin.h
+
+crmgr.o crtest.o crtsv.o : depot.h curia.h cabin.h
+
+rlmgr.o rltest.o : depot.h relic.h cabin.h
+
+hvmgr.o hvtest.o : depot.h curia.h hovel.h cabin.h
+
+cbtest.o cbcodec.o : cabin.h
+
+vlmgr.o vltest.o vltsv.o : depot.h cabin.h villa.h
+
+odmgr.o odtest.o odidx.o : depot.h curia.h cabin.h villa.h odeum.h
+
+
+
+# END OF FILE
diff --git a/qdbm/NEWS b/qdbm/NEWS
new file mode 100644
index 00000000..52a27359
--- /dev/null
+++ b/qdbm/NEWS
@@ -0,0 +1,43 @@
+== Thu, 08 Sep 2005 13:13:58 +0900 ==
+
+Compressing options of ZLIB was changed. If you use villa with the option
+`VL_OZCOMP', databases of the earlier versions are not compatible with the
+current version of QDBM. To convert the old database to new format,
+export endian independent data by "vlmgr exportdb" with the old version,
+and then import it by "vlmgr importdb" with the latest version.
+
+
+
+== Wed, 10 Mar 2004 23:24:24 +0900 ==
+
+API of B+ tree was changed a bit. Even if you build QDBM with ZLIB
+enabled, records are not compressed. Instead of it, the function `vlopen'
+has the option `VL_OZCOMP'. If it is specified, records are compressed.
+So, you can switch whether to compress records or not, on runtime.
+
+Users who have used ZLIB feature should modify their source codes to
+specify that option.
+
+
+
+== Wed, 10 Dec 2003 09:24:12 +0900 ==
+
+The database format was changed with QDBM 1.7.13. Newer versions do not
+have backward compatibility to old format. You can convert old databases
+with the command `fmtcnv031127' in the sub directory `lab'. To build it,
+perform the following steps.
+
+ cd lab
+ make fmtcnv031127
+
+About usage of it, see the file `README' in `lab'. Typically, to convert
+a Depot database named as `old' and create a database named as `new',
+perform the following step.
+
+ ./fmtcnv031127 < old > new
+
+I'm sorry for bothering you.
+
+
+
+== END OF FILE ==
diff --git a/qdbm/NO-AUTO-GEN b/qdbm/NO-AUTO-GEN
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/qdbm/NO-AUTO-GEN
diff --git a/qdbm/README b/qdbm/README
new file mode 100644
index 00000000..d7adad4b
--- /dev/null
+++ b/qdbm/README
@@ -0,0 +1,50 @@
+================================================================
+ QDBM: Quick Database Manager
+ Copyright (C) 2000-2007 Mikio Hirabayashi
+================================================================
+
+
+Please read the following documents with a WWW browser.
+How to install QDBM is explained in the specification.
+
+ README - this file
+ COPYING - license
+ ChangeLog - history of enhancement
+ NEWS - news for users
+ THANKS - list of contributors
+ spex.html - specification
+ spex-ja.html - specification in Japanese
+
+
+Contents of the directory tree is below.
+
+ ./ - sources of QDBM
+ ./plus/ - API for C++ (read `./plus/xspex.html')
+ ./java/ - API for Java (read `./java/jspex.html')
+ ./perl/ - API for Perl (read `./perl/plspex.html')
+ ./ruby/ - API for Ruby (read `./ruby/rbspex.html')
+ ./cgi/ - CGI scripts (read `./cgi/cgispex.html')
+ ./man1/ - manual pages for commands
+ ./man3/ - manual pages for C API
+ ./lab/ - for test and experiment
+ ./bros/ - for comparison with other database managers
+ ./misc/ - miscellaneous files
+
+
+QDBM is released under the terms of the GNU Lesser General Public
+License. See the file `COPYING' for details.
+
+QDBM was written by Mikio Hirabayashi. You can contact the author
+by e-mail to `mikio@users.sourceforge.net'. However, as for
+topics which can be shared among other users, pleae send it to
+the mailing list. To join the mailing list, refer to the following
+URL.
+
+ http://lists.sourceforge.net/lists/listinfo/qdbm-users
+
+
+Thanks.
+
+
+
+== END OF FILE ==
diff --git a/qdbm/RISCmakefile b/qdbm/RISCmakefile
new file mode 100644
index 00000000..2cebbb3f
--- /dev/null
+++ b/qdbm/RISCmakefile
@@ -0,0 +1,140 @@
+# Makefile for the RISC OS version of QDBM
+
+
+# Define which compiler to use:
+
+CC = cc
+#CC = gcc
+
+
+#########################################
+# DO NOT EDIT ANYTHING BELOW THIS LINE! #
+#########################################
+
+ifeq (${CC},cc)
+CC = cc
+LD = link
+AR = libfile
+DEPEND = -depend !Depend
+CC_FLAGS = -Wdp -throwback -Otime -I@,Unix: -JUnix
+UNIXLIB = Unix:o.UnixLib
+else
+ifeq (${CC},gcc)
+CC = gcc
+LD = gcc
+AR = ar
+CC_FLAGS = -mthrowback -O3 -I.
+else
+# No other compiler supported!
+endif
+endif
+
+QDBM_OBJS = o.depot o.curia o.relic o.hovel o.cabin o.villa o.vista o.odeum o.myconf
+
+.INIT :
+ @cdir o
+
+## Rule Patterns ##
+
+.SUFFIXES : .c .o
+
+.c.o :
+ $(CC) $(CC_FLAGS) $(DEPEND) -c -o $@ $<
+
+# Static dependencies:
+
+all : libqdbm testcases managers converters
+
+libqdbm : $(QDBM_OBJS)
+ $(AR) $(AR_FLAGS) -c -o libqdbm $(QDBM_OBJS)
+
+testcases : dptest crtest rltest hvtest cbtest vltest odtest
+ create testcases
+
+managers : dpmgr crmgr rlmgr hvmgr vlmgr odmgr
+ create managers
+
+converters : dptsv crtsv cbcodec vltsv odidx
+ create converters
+
+dptest : o.dptest libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+crtest : o.crtest libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+rltest : o.rltest libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+hvtest : o.hvtest libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+cbtest : o.cbtest libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+vltest : o.vltest libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+odtest : o.odtest libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+dpmgr : o.dpmgr libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+crmgr : o.crmgr libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+rlmgr : o.rlmgr libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+hvmgr : o.hvmgr libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+vlmgr : o.vlmgr libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+odmgr : o.odmgr libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+dptsv : o.dptsv libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+crtsv : o.crtsv libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+cbcodec : o.cbcodec libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+vltsv : o.vltsv libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+odidx : o.odidx libqdbm
+ $(LD) $(LD_FLAGS) -o $@ o.$* libqdbm $(UNIXLIB)
+
+clean:
+ -ifthere libqdbm then wipe libqdbm ~CFR~V
+ -ifthere dptest then wipe dptest ~CFR~V
+ -ifthere crtest then wipe crtest ~CFR~V
+ -ifthere rltest then wipe rltest ~CFR~V
+ -ifthere hvtest then wipe hvtest ~CFR~V
+ -ifthere cbtest then wipe cbtest ~CFR~V
+ -ifthere vltest then wipe vltest ~CFR~V
+ -ifthere odtest then wipe odtest ~CFR~V
+ -ifthere dpmgr then wipe dpmgr ~CFR~V
+ -ifthere crmgr then wipe crmgr ~CFR~V
+ -ifthere rlmgr then wipe rlmgr ~CFR~V
+ -ifthere hvmgr then wipe hvmgr ~CFR~V
+ -ifthere cbmgr then wipe cbmgr ~CFR~V
+ -ifthere vlmgr then wipe vlmgr ~CFR~V
+ -ifthere odmgr then wipe odmgr ~CFR~V
+ -ifthere dptsv then wipe dptsv ~CFR~V
+ -ifthere crtsv then wipe crtsv ~CFR~V
+ -ifthere cbcodec then wipe cbcodec ~CFR~V
+ -ifthere vltsv then wipe vltsv ~CFR~V
+ -ifthere odidx then wipe odidx ~CFR~V
+ -ifthere testcases then wipe testcases ~CFR~V
+ -ifthere managers then wipe managers ~CFR~V
+ -ifthere converters then wipe converters ~CFR~V
+ -ifthere o.* then wipe o.* ~CFR~V
+
+# Dynamic dependencies:
diff --git a/qdbm/THANKS b/qdbm/THANKS
new file mode 100644
index 00000000..dc7a0dc6
--- /dev/null
+++ b/qdbm/THANKS
@@ -0,0 +1,45 @@
+================================================================
+ Thanks to all of the following for their valuable suggestions
+ or contributions.
+================================================================
+
+
+Kang-Jin Lee
+ - suggestions about the GDBM-compatible API
+ - contributions about Makefile
+
+Pat Podenski
+ - suggestions about porting to Mac OS X, Solaris, and HP-UX
+
+BERO
+ - contributions about supporting MinGW
+
+Stefan Bellon
+ - contributions about porting to RISC OS
+
+Donald Gobin
+ - contributions about supporting Visual C++
+
+Emanuel Dejanu
+ - contributions about supporting Visual C++
+
+Keith Bostic
+ - suggestions about the performance test suite
+
+William Lachance
+ - contributions about RPM spec file
+
+Zed A. Shaw
+ - contributions about a text analyzer in Odeum
+ - contributions about a query language in Odeum
+
+Chris Bilderback
+ - contributions about cursor functions in Villa.
+
+Fumitoshi Ukai
+ - contributions of troff manuals
+ - making Debian packages
+
+
+
+== END OF FILE ==
diff --git a/qdbm/VCmakefile b/qdbm/VCmakefile
new file mode 100644
index 00000000..210a98ff
--- /dev/null
+++ b/qdbm/VCmakefile
@@ -0,0 +1,248 @@
+# Makefile to build QDBM using Microsoft Visual C++
+
+
+
+#================================================================
+# Setting variables
+#================================================================
+
+
+# VC++ directory
+VCPATH = C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7
+
+# User options
+YOUR_CLFLAGS =
+YOUR_LIBFLAGS =
+YOUR_LINKFLAGS=
+
+# Configurations
+!IF "$(CFG)" == "ld"
+!MESSAGE Build using static debug configuration
+BASE_FLAGS = /MLd /W3 /ZI /Od /FD /GZ /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\"
+BASE_DEFS = /D_DEBUG /D__DEBUG__
+OUTDIR = .\tmp_ld
+LIB_APPEND = _ld
+EXE_APPEND = _ld
+!ELSEIF "$(CFG)" == "l"
+!MESSAGE Build using static release configuration
+BASE_DEFS = /DNDEBUG
+BASE_FLAGS = /ML /W3 /O2 /FD /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\"
+OUTDIR = .\tmp_l
+LIB_APPEND = _l
+EXE_APPEND = _l
+!ELSEIF "$(CFG)" == "td"
+!MESSAGE Build using static threaded debug configuration
+BASE_FLAGS = /MTd /W3 /ZI /Od /FD /GZ /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\"
+BASE_DEFS = /D_DEBUG /D__DEBUG__
+OUTDIR = .\tmp_td
+LIB_APPEND = _td
+EXE_APPEND = _td
+!ELSEIF "$(CFG)" == "t"
+!MESSAGE Build using static threaded release configuration
+BASE_DEFS = /DNDEBUG
+BASE_FLAGS = /MT /W3 /O2 /FD /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\"
+OUTDIR = .\tmp_t
+LIB_APPEND = _t
+EXE_APPEND = _t
+!ELSEIF "$(CFG)" == "dd"
+!MESSAGE Build using dynamic threaded debug configuration
+BASE_FLAGS = /MDd /W3 /ZI /Od /FD /GZ /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\"
+BASE_DEFS = /D_DEBUG /D__DEBUG__
+OUTDIR = .\tmp_dd
+LIB_APPEND = _dd
+EXE_APPEND = _dd
+!ELSE
+!MESSAGE Build using dynamic threaded release configuration
+BASE_DEFS = /DNDEBUG
+BASE_FLAGS = /MD /W3 /O2 /FD /Fo"$(OUTDIR)\\" /Fd"$(OUTDIR)\\"
+OUTDIR = .\tmp
+LIB_APPEND =
+EXE_APPEND =
+!ENDIF
+
+# Building binaries
+CLFLAGS = /I "$(VCPATH)\Include" /I "$(VCPATH)\PlatformSDK\Include" /I "." \
+ /nologo $(YOUR_CLFLAGS) $(BASE_FLAGS) $(BASE_DEFS) /D_CRT_SECURE_NO_DEPRECATE=1
+LIBFLAGS = /libpath:"$(VCPATH)\lib" /libpath:"$(VCPATH)\PlatformSDK\Lib" /libpath:"." \
+ /nologo $(YOUR_LIBFLAGS)
+LINKFLAGS = /libpath:"$(VCPATH)\lib" /libpath:"$(VCPATH)\PlatformSDK\Lib" /libpath:"." \
+ /nologo $(YOUR_LINKFLAGS)
+
+# Targets
+MYLIBS = qdbm$(LIB_APPEND).dll qdbm$(LIB_APPEND).lib qdbm$(LIB_APPEND)_static.lib
+LIBOBJS = $(OUTDIR)\depot.obj $(OUTDIR)\curia.obj $(OUTDIR)\relic.obj \
+ $(OUTDIR)\hovel.obj $(OUTDIR)\cabin.obj $(OUTDIR)\villa.obj \
+ $(OUTDIR)\vista.obj $(OUTDIR)\odeum.obj $(OUTDIR)\myconf.obj
+MYBINS = dpmgr$(EXE_APPEND).exe dptest$(EXE_APPEND).exe dptsv$(EXE_APPEND).exe \
+ crmgr$(EXE_APPEND).exe crtest$(EXE_APPEND).exe crtsv$(EXE_APPEND).exe \
+ rlmgr$(EXE_APPEND).exe rltest$(EXE_APPEND).exe hvmgr$(EXE_APPEND).exe \
+ hvtest$(EXE_APPEND).exe cbtest$(EXE_APPEND).exe cbcodec$(EXE_APPEND).exe \
+ vlmgr$(EXE_APPEND).exe vltest$(EXE_APPEND).exe vltsv$(EXE_APPEND).exe \
+ odmgr$(EXE_APPEND).exe odtest$(EXE_APPEND).exe odidx$(EXE_APPEND).exe
+
+
+
+#================================================================
+# Suffix rules
+#================================================================
+
+
+.SUFFIXES :
+.SUFFIXES : .c .obj
+
+.c{$(OUTDIR)}.obj :
+ cl /c $(CLFLAGS) $<
+
+.c.obj:
+ cl /c $(CLFLAGS) $<
+
+
+
+#================================================================
+# Actions
+#================================================================
+
+
+all : $(OUTDIR) $(MYLIBS) $(MYBINS)
+
+
+allcfg:
+ nmake /NOLOGO /f VCmakefile CFG=ld
+ nmake /NOLOGO /f VCmakefile CFG=l
+ nmake /NOLOGO /f VCmakefile CFG=td
+ nmake /NOLOGO /f VCmakefile CFG=t
+ nmake /NOLOGO /f VCmakefile CFG=dd
+ nmake /NOLOGO /f VCmakefile
+
+
+clean :
+ -rd tmp_ld /S /Q > NUL: 2>&1
+ -rd tmp_l /S /Q > NUL: 2>&1
+ -rd tmp_td /S /Q > NUL: 2>&1
+ -rd tmp_t /S /Q > NUL: 2>&1
+ -rd tmp_dd /S /Q > NUL: 2>&1
+ -rd tmp /S /Q > NUL: 2>&1
+ -del *.obj *.lib *.dll *.exp *.exe casket /F /Q > NUL: 2>&1
+
+
+"$(OUTDIR)" :
+ if not exist "$(OUTDIR)/" mkdir "$(OUTDIR)"
+
+
+
+#================================================================
+# Building binaries
+#================================================================
+
+
+qdbm$(LIB_APPEND).dll : $(LIBOBJS) qdbm.def
+ link /DLL /DEF:qdbm.def $(LINKFLAGS) /OUT:$@ /IMPLIB:qdbm$(LIB_APPEND).lib $(LIBOBJS)
+
+
+qdbm$(LIB_APPEND).lib : qdbm$(LIB_APPEND).dll
+
+
+qdbm$(LIB_APPEND)_static.lib : $(LIBOBJS)
+ lib $(LIBFLAGS) /OUT:$@ $(LIBOBJS)
+
+
+dpmgr$(EXE_APPEND).exe : $(OUTDIR)\dpmgr.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\dpmgr.obj qdbm$(LIB_APPEND).lib
+
+
+dptest$(EXE_APPEND).exe : $(OUTDIR)\dptest.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\dptest.obj qdbm$(LIB_APPEND).lib
+
+
+dptsv$(EXE_APPEND).exe : $(OUTDIR)\dptsv.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\dptsv.obj qdbm$(LIB_APPEND).lib
+
+
+crmgr$(EXE_APPEND).exe : $(OUTDIR)\crmgr.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\crmgr.obj qdbm$(LIB_APPEND).lib
+
+
+crtest$(EXE_APPEND).exe : $(OUTDIR)\crtest.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\crtest.obj qdbm$(LIB_APPEND).lib
+
+
+crtsv$(EXE_APPEND).exe : $(OUTDIR)\crtsv.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\crtsv.obj qdbm$(LIB_APPEND).lib
+
+
+rlmgr$(EXE_APPEND).exe : $(OUTDIR)\rlmgr.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\rlmgr.obj qdbm$(LIB_APPEND).lib
+
+
+rltest$(EXE_APPEND).exe : $(OUTDIR)\rltest.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\rltest.obj qdbm$(LIB_APPEND).lib
+
+
+hvmgr$(EXE_APPEND).exe : $(OUTDIR)\hvmgr.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\hvmgr.obj qdbm$(LIB_APPEND).lib
+
+
+hvtest$(EXE_APPEND).exe : $(OUTDIR)\hvtest.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\hvtest.obj qdbm$(LIB_APPEND).lib
+
+
+cbtest$(EXE_APPEND).exe : $(OUTDIR)\cbtest.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\cbtest.obj qdbm$(LIB_APPEND).lib
+
+
+cbcodec$(EXE_APPEND).exe : $(OUTDIR)\cbcodec.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\cbcodec.obj qdbm$(LIB_APPEND).lib
+
+
+vlmgr$(EXE_APPEND).exe : $(OUTDIR)\vlmgr.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\vlmgr.obj qdbm$(LIB_APPEND).lib
+
+
+vltest$(EXE_APPEND).exe : $(OUTDIR)\vltest.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\vltest.obj qdbm$(LIB_APPEND).lib
+
+
+vltsv$(EXE_APPEND).exe : $(OUTDIR)\vltsv.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\vltsv.obj qdbm$(LIB_APPEND).lib
+
+
+odmgr$(EXE_APPEND).exe : $(OUTDIR)\odmgr.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\odmgr.obj qdbm$(LIB_APPEND).lib
+
+
+odtest$(EXE_APPEND).exe : $(OUTDIR)\odtest.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\odtest.obj qdbm$(LIB_APPEND).lib
+
+
+odidx$(EXE_APPEND).exe : $(OUTDIR)\odidx.obj qdbm$(LIB_APPEND).lib
+ link $(LINKFLAGS) /OUT:$@ $(OUTDIR)\odidx.obj qdbm$(LIB_APPEND).lib
+
+
+$(OUTDIR)\depot.obj $(OUTDIR)\dpmgr.obj $(OUTDIR)\dptest.obj $(OUTDIR)\dptsv.obj : \
+ depot.h myconf.h
+
+$(OUTDIR)\curia.obj $(OUTDIR)\crmgr.obj $(OUTDIR)\crtest.obj $(OUTDIR)\crtsv.obj : \
+ curia.h depot.h myconf.h
+
+$(OUTDIR)\relic.obj $(OUTDIR)\rlmgr.obj $(OUTDIR)\rltest.obj : \
+ relic.h depot.h myconf.h
+
+$(OUTDIR)\hovel.obj $(OUTDIR)\hvmgr.obj $(OUTDIR)\hvtest.obj : \
+ hovel.h depot.h curia.h myconf.h
+
+$(OUTDIR)\cabin.obj $(OUTDIR)\cbtest.obj $(OUTDIR)\cbcodec.obj : \
+ cabin.h myconf.h
+
+$(OUTDIR)\villa.obj $(OUTDIR)\vlmgr.obj $(OUTDIR)\vltest.obj $(OUTDIR)\vltsv.obj : \
+ villa.h depot.h cabin.h myconf.h
+
+$(OUTDIR)\vista.obj : vista.h villa.h depot.h curia.h cabin.h myconf.h
+
+$(OUTDIR)\odeum.obj $(OUTDIR)\odmgr.obj $(OUTDIR)\odtest.obj $(OUTDIR)\odidx.obj : \
+ odeum.h depot.h curia.h cabin.h villa.h myconf.h
+
+$(OUTDIR)\myconf.obj : myconf.h
+
+
+
+# END OF FILE
diff --git a/qdbm/cabin.c b/qdbm/cabin.c
new file mode 100644
index 00000000..262cb3eb
--- /dev/null
+++ b/qdbm/cabin.c
@@ -0,0 +1,3529 @@
+/*************************************************************************************************
+ * Implementation of Cabin
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#define QDBM_INTERNAL 1
+
+#include "cabin.h"
+#include "myconf.h"
+
+#define CB_GCUNIT 64 /* allocation unit size of a buffer in gc */
+#define CB_SPBUFSIZ 32 /* size of a buffer for sprintf */
+#define CB_SPMAXWIDTH 128 /* max width of a column for sprintf */
+#define CB_MAPPBNUM 251 /* bucket size of a petit map handle */
+#define CB_MAPCSUNIT 52 /* small allocation unit size of map concatenation */
+#define CB_MAPCBUNIT 252 /* big allocation unit size of map concatenation */
+#define CB_MSGBUFSIZ 256 /* size of a buffer for log message */
+#define CB_IOBUFSIZ 8192 /* size of an I/O buffer */
+#define CB_FILEMODE 00644 /* permission of a creating file */
+#define CB_NUMBUFSIZ 32 /* size of a buffer for a number */
+#define CB_ENCBUFSIZ 32 /* size of a buffer for encoding name */
+#define CB_DATEBUFSIZ 64 /* size of a buffer for date expression */
+#define CB_VNUMBUFSIZ 8 /* size of a buffer for variable length number */
+
+/* set a buffer for a variable length number */
+#define CB_SETVNUMBUF(CB_len, CB_buf, CB_num) \
+ do { \
+ int _CB_num; \
+ _CB_num = (CB_num); \
+ if(_CB_num == 0){ \
+ ((signed char *)(CB_buf))[0] = 0; \
+ (CB_len) = 1; \
+ } else { \
+ (CB_len) = 0; \
+ while(_CB_num > 0){ \
+ int _CB_rem = _CB_num & 0x7f; \
+ _CB_num >>= 7; \
+ if(_CB_num > 0){ \
+ ((signed char *)(CB_buf))[(CB_len)] = -_CB_rem - 1; \
+ } else { \
+ ((signed char *)(CB_buf))[(CB_len)] = _CB_rem; \
+ } \
+ (CB_len)++; \
+ } \
+ } \
+ } while(FALSE)
+
+/* read a variable length buffer */
+#define CB_READVNUMBUF(CB_buf, CB_size, CB_num, CB_step) \
+ do { \
+ int _CB_i, _CB_base; \
+ CB_num = 0; \
+ _CB_base = 1; \
+ if((size) < 2){ \
+ CB_num = ((signed char *)(CB_buf))[0]; \
+ (CB_step) = 1; \
+ } else { \
+ for(_CB_i = 0; _CB_i < (size); _CB_i++){ \
+ if(((signed char *)(CB_buf))[_CB_i] >= 0){ \
+ CB_num += ((signed char *)(CB_buf))[_CB_i] * _CB_base; \
+ break; \
+ } \
+ CB_num += _CB_base * (((signed char *)(CB_buf))[_CB_i] + 1) * -1; \
+ _CB_base *= 128; \
+ } \
+ (CB_step) = _CB_i + 1; \
+ } \
+ } while(FALSE)
+
+/* get the first hash value */
+#define CB_FIRSTHASH(CB_res, CB_kbuf, CB_ksiz) \
+ do { \
+ const unsigned char *_CB_p; \
+ int _CB_ksiz; \
+ _CB_p = (const unsigned char *)(CB_kbuf); \
+ _CB_ksiz = CB_ksiz; \
+ for((CB_res) = 19780211; _CB_ksiz--;){ \
+ (CB_res) = (CB_res) * 37 + *(_CB_p)++; \
+ } \
+ (CB_res) &= INT_MAX; \
+ } while(FALSE)
+
+/* get the second hash value */
+#define CB_SECONDHASH(CB_res, CB_kbuf, CB_ksiz) \
+ do { \
+ const unsigned char *_CB_p; \
+ int _CB_ksiz; \
+ _CB_p = (const unsigned char *)(CB_kbuf) + CB_ksiz - 1; \
+ _CB_ksiz = CB_ksiz; \
+ for((CB_res) = 0x13579bdf; _CB_ksiz--;){ \
+ (CB_res) = (CB_res) * 31 + *(_CB_p)--; \
+ } \
+ (CB_res) &= INT_MAX; \
+ } while(FALSE)
+
+
+/* private function prototypes */
+static void cbggchandler(void);
+static void cbggckeeper(void *ptr, void (*func)(void *));
+static void cbqsortsub(char *bp, int nmemb, int size, char *pswap, char *vswap,
+ int(*compar)(const void *, const void *));
+static int cblistelemcmp(const void *a, const void *b);
+static int cbkeycmp(const char *abuf, int asiz, const char *bbuf, int bsiz);
+
+
+
+/*************************************************************************************************
+ * public objects
+ *************************************************************************************************/
+
+
+/* Call back function for handling a fatal error. */
+void (*cbfatalfunc)(const char *message) = NULL;
+
+
+/* Allocate a region on memory. */
+void *cbmalloc(size_t size){
+ char *p;
+ assert(size > 0 && size < INT_MAX);
+ if(!(p = malloc(size))) cbmyfatal("out of memory");
+ return p;
+}
+
+
+/* Re-allocate a region on memory. */
+void *cbrealloc(void *ptr, size_t size){
+ char *p;
+ assert(size > 0);
+ if(!(p = realloc(ptr, size))) cbmyfatal("out of memory");
+ return p;
+}
+
+
+/* Duplicate a region on memory. */
+char *cbmemdup(const char *ptr, int size){
+ char *p;
+ assert(ptr);
+ if(size < 0) size = strlen(ptr);
+ CB_MALLOC(p, size + 1);
+ memcpy(p, ptr, size);
+ p[size] = '\0';
+ return p;
+}
+
+
+/* Free a region on memory. */
+void cbfree(void *ptr){
+ free(ptr);
+}
+
+
+/* Register the pointer or handle of an object to the global garbage collector. */
+void cbglobalgc(void *ptr, void (*func)(void *)){
+ assert(ptr && func);
+ cbggckeeper(ptr, func);
+}
+
+
+/* Exercise the global garbage collector explicitly. */
+void cbggcsweep(void){
+ cbggckeeper(NULL, NULL);
+}
+
+
+/* Check availability of allocation of the virtual memory. */
+int cbvmemavail(size_t size){
+ assert(size >= 0);
+ return _qdbm_vmemavail(size);
+}
+
+
+/* Sort an array using insert sort. */
+void cbisort(void *base, int nmemb, int size, int(*compar)(const void *, const void *)){
+ char *bp, *swap;
+ int i, j;
+ assert(base && nmemb >= 0 && size > 0 && compar);
+ bp = (char *)base;
+ CB_MALLOC(swap, size);
+ for(i = 1; i < nmemb; i++){
+ if(compar(bp + (i - 1) * size, bp + i * size) > 0){
+ memcpy(swap, bp + i * size, size);
+ for(j = i; j > 0; j--){
+ if(compar(bp + (j - 1) * size, swap) < 0) break;
+ memcpy(bp + j * size, bp + (j - 1) * size, size);
+ }
+ memcpy(bp + j * size, swap, size);
+ }
+ }
+ free(swap);
+}
+
+
+/* Sort an array using shell sort. */
+void cbssort(void *base, int nmemb, int size, int(*compar)(const void *, const void *)){
+ char *bp, *swap;
+ int step, bottom, i, j;
+ assert(base && nmemb >= 0 && size > 0 && compar);
+ bp = (char *)base;
+ CB_MALLOC(swap, size);
+ for(step = (nmemb - 1) / 3; step >= 0; step = (step - 1) / 3){
+ if(step < 5) step = 1;
+ for(bottom = 0; bottom < step; bottom++){
+ for(i = bottom + step; i < nmemb; i += step){
+ if(compar(bp + (i - step) * size, bp + i * size) > 0){
+ memcpy(swap, bp + i * size, size);
+ for(j = i; j > step - 1; j -= step){
+ if(compar(bp + (j - step) * size, swap) < 0) break;
+ memcpy(bp + j * size, bp + (j - step) * size, size);
+ }
+ memcpy(bp + j * size, swap, size);
+ }
+ }
+ }
+ if(step < 2) break;
+ }
+ free(swap);
+}
+
+
+/* Sort an array using heap sort. */
+void cbhsort(void *base, int nmemb, int size, int(*compar)(const void *, const void *)){
+ char *bp, *swap;
+ int top, bottom, mybot, i;
+ assert(base && nmemb >= 0 && size > 0 && compar);
+ bp = (char *)base;
+ nmemb--;
+ bottom = nmemb / 2 + 1;
+ top = nmemb;
+ CB_MALLOC(swap, size);
+ while(bottom > 0){
+ bottom--;
+ mybot = bottom;
+ i = 2 * mybot;
+ while(i <= top) {
+ if(i < top && compar(bp + (i + 1) * size, bp + i * size) > 0) i++;
+ if(compar(bp + mybot * size, bp + i * size) >= 0) break;
+ memcpy(swap, bp + mybot * size, size);
+ memcpy(bp + mybot * size, bp + i * size, size);
+ memcpy(bp + i * size, swap, size);
+ mybot = i;
+ i = 2 * mybot;
+ }
+ }
+ while(top > 0){
+ memcpy(swap, bp, size);
+ memcpy(bp, bp + top * size, size);
+ memcpy(bp + top * size, swap, size);
+ top--;
+ mybot = bottom;
+ i = 2 * mybot;
+ while(i <= top){
+ if(i < top && compar(bp + (i + 1) * size, bp + i * size) > 0) i++;
+ if(compar(bp + mybot * size, bp + i * size) >= 0) break;
+ memcpy(swap, bp + mybot * size, size);
+ memcpy(bp + mybot * size, bp + i * size, size);
+ memcpy(bp + i * size, swap, size);
+ mybot = i;
+ i = 2 * mybot;
+ }
+ }
+ free(swap);
+}
+
+
+/* Sort an array using quick sort. */
+void cbqsort(void *base, int nmemb, int size, int(*compar)(const void *, const void *)){
+ char *pswap, *vswap;
+ assert(base && nmemb >= 0 && size > 0 && compar);
+ CB_MALLOC(pswap, size);
+ CB_MALLOC(vswap, size);
+ cbqsortsub(base, nmemb, size, pswap, vswap, compar);
+ free(vswap);
+ free(pswap);
+}
+
+
+/* Compare two strings with case insensitive evaluation. */
+int cbstricmp(const char *astr, const char *bstr){
+ int ac, bc;
+ assert(astr && bstr);
+ while(*astr != '\0'){
+ if(*bstr == '\0') return 1;
+ ac = (*astr >= 'A' && *astr <= 'Z') ? *astr + ('a' - 'A') : *(unsigned char *)astr;
+ bc = (*bstr >= 'A' && *bstr <= 'Z') ? *bstr + ('a' - 'A') : *(unsigned char *)bstr;
+ if(ac != bc) return ac - bc;
+ astr++;
+ bstr++;
+ }
+ return *bstr == '\0' ? 0 : -1;
+}
+
+
+/* Check whether a string begins with a key. */
+int cbstrfwmatch(const char *str, const char *key){
+ assert(str && key);
+ while(*key != '\0'){
+ if(*str != *key || *str == '\0') return FALSE;
+ key++;
+ str++;
+ }
+ return TRUE;
+}
+
+
+/* Check whether a string begins with a key, with case insensitive evaluation. */
+int cbstrfwimatch(const char *str, const char *key){
+ int sc, kc;
+ assert(str && key);
+ while(*key != '\0'){
+ if(*str == '\0') return FALSE;
+ sc = *str;
+ if(sc >= 'A' && sc <= 'Z') sc += 'a' - 'A';
+ kc = *key;
+ if(kc >= 'A' && kc <= 'Z') kc += 'a' - 'A';
+ if(sc != kc) return FALSE;
+ key++;
+ str++;
+ }
+ return TRUE;
+}
+
+
+/* Check whether a string ends with a key. */
+int cbstrbwmatch(const char *str, const char *key){
+ int slen, klen, i;
+ assert(str && key);
+ slen = strlen(str);
+ klen = strlen(key);
+ for(i = 1; i <= klen; i++){
+ if(i > slen || str[slen-i] != key[klen-i]) return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Check whether a string ends with a key, with case insensitive evaluation. */
+int cbstrbwimatch(const char *str, const char *key){
+ int slen, klen, i, sc, kc;
+ assert(str && key);
+ slen = strlen(str);
+ klen = strlen(key);
+ for(i = 1; i <= klen; i++){
+ if(i > slen) return FALSE;
+ sc = str[slen-i];
+ if(sc >= 'A' && sc <= 'Z') sc += 'a' - 'A';
+ kc = key[klen-i];
+ if(kc >= 'A' && kc <= 'Z') kc += 'a' - 'A';
+ if(sc != kc) return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Locate a substring in a string using KMP method. */
+char *cbstrstrkmp(const char *haystack, const char *needle){
+ int i, j, hlen, nlen;
+ signed char tbl[0x100];
+ assert(haystack && needle);
+ nlen = strlen(needle);
+ if(nlen >= 0x100) return strstr(haystack, needle);
+ tbl[0] = -1;
+ i = 0;
+ j = -1;
+ while(i < nlen){
+ while((j >= 0) && (needle[i] != needle[j])){
+ j = tbl[j];
+ }
+ i++;
+ j++;
+ tbl[i] = j;
+ }
+ hlen = strlen(haystack);
+ i = 0;
+ j = 0;
+ while(i < hlen && j < nlen){
+ while((j >= 0) && (haystack[i] != needle[j])){
+ j = tbl[j];
+ }
+ i++;
+ j++;
+ }
+ if(j == nlen) return (char *)(haystack + i - nlen);
+ return NULL;
+}
+
+
+/* Locate a substring in a string using BM method. */
+char *cbstrstrbm(const char *haystack, const char *needle){
+ const unsigned char *rp;
+ const char *ep;
+ unsigned char tbl[0x100];
+ int i, j, nlen, len, idx;
+ assert(haystack && needle);
+ nlen = strlen(needle);
+ if(nlen < 3 || nlen >= 0x100) return strstr(haystack, needle);
+ for(i = 0; i < 0x100; i++){
+ tbl[i] = nlen;
+ }
+ len = nlen;
+ rp = (const unsigned char *)needle;
+ while(len > 0){
+ tbl[*rp++] = --len;
+ }
+ nlen--;
+ ep = haystack + strlen(haystack) - nlen;
+ while(haystack < ep){
+ for(i = nlen; haystack[i] == needle[i]; i--){
+ if(i == 0) return (char *)haystack;
+ }
+ idx = ((unsigned char *)haystack)[i];
+ j = tbl[idx] - nlen + i;
+ haystack += j > 0 ? j : 2;
+ }
+ return NULL;
+}
+
+
+/* Convert the letters of a string to upper case. */
+char *cbstrtoupper(char *str){
+ int i;
+ assert(str);
+ for(i = 0; str[i] != '\0'; i++){
+ if(str[i] >= 'a' && str[i] <= 'z') str[i] -= 'a' - 'A';
+ }
+ return str;
+}
+
+
+/* Convert the letters of a string to lower case. */
+char *cbstrtolower(char *str){
+ int i;
+ assert(str);
+ for(i = 0; str[i] != '\0'; i++){
+ if(str[i] >= 'A' && str[i] <= 'Z') str[i] += 'a' - 'A';
+ }
+ return str;
+}
+
+
+/* Cut space characters at head or tail of a string. */
+char *cbstrtrim(char *str){
+ char *wp;
+ int i, head;
+ assert(str);
+ wp = str;
+ head = TRUE;
+ for(i = 0; str[i] != '\0'; i++){
+ if((str[i] >= 0x07 && str[i] <= 0x0d) || str[i] == 0x20){
+ if(!head) *(wp++) = str[i];
+ } else {
+ *(wp++) = str[i];
+ head = FALSE;
+ }
+ }
+ *wp = '\0';
+ while(wp > str && ((wp[-1] >= 0x07 && wp[-1] <= 0x0d) || wp[-1] == 0x20)){
+ *(--wp) = '\0';
+ }
+ return str;
+}
+
+
+/* Squeeze space characters in a string and trim it. */
+char *cbstrsqzspc(char *str){
+ char *wp;
+ int i, spc;
+ assert(str);
+ wp = str;
+ spc = TRUE;
+ for(i = 0; str[i] != '\0'; i++){
+ if(str[i] > 0 && str[i] <= ' '){
+ if(!spc) *(wp++) = str[i];
+ spc = TRUE;
+ } else {
+ *(wp++) = str[i];
+ spc = FALSE;
+ }
+ }
+ *wp = '\0';
+ for(wp--; wp >= str; wp--){
+ if(*wp > 0 && *wp <= ' '){
+ *wp = '\0';
+ } else {
+ break;
+ }
+ }
+ return str;
+}
+
+
+/* Count the number of characters in a string of UTF-8. */
+int cbstrcountutf(const char *str){
+ const unsigned char *rp;
+ int cnt;
+ assert(str);
+ rp = (unsigned char *)str;
+ cnt = 0;
+ while(*rp != '\0'){
+ if((*rp & 0x80) == 0x00 || (*rp & 0xe0) == 0xc0 ||
+ (*rp & 0xf0) == 0xe0 || (*rp & 0xf8) == 0xf0) cnt++;
+ rp++;
+ }
+ return cnt;
+}
+
+
+/* Cut a string of UTF-8 at the specified number of characters. */
+char *cbstrcututf(char *str, int num){
+ unsigned char *wp;
+ int cnt;
+ assert(str && num >= 0);
+ wp = (unsigned char *)str;
+ cnt = 0;
+ while(*wp != '\0'){
+ if((*wp & 0x80) == 0x00 || (*wp & 0xe0) == 0xc0 ||
+ (*wp & 0xf0) == 0xe0 || (*wp & 0xf8) == 0xf0){
+ cnt++;
+ if(cnt > num){
+ *wp = '\0';
+ break;
+ }
+ }
+ wp++;
+ }
+ return str;
+}
+
+
+/* Get a datum handle. */
+CBDATUM *cbdatumopen(const char *ptr, int size){
+ CBDATUM *datum;
+ CB_MALLOC(datum, sizeof(*datum));
+ CB_MALLOC(datum->dptr, CB_DATUMUNIT);
+ datum->dptr[0] = '\0';
+ datum->dsize = 0;
+ datum->asize = CB_DATUMUNIT;
+ if(ptr) CB_DATUMCAT(datum, ptr, (size >= 0 ? size : strlen(ptr)));
+ return datum;
+}
+
+
+/* Copy a datum. */
+CBDATUM *cbdatumdup(const CBDATUM *datum){
+ assert(datum);
+ return cbdatumopen(datum->dptr, datum->dsize);
+}
+
+
+/* Free a datum handle. */
+void cbdatumclose(CBDATUM *datum){
+ assert(datum);
+ free(datum->dptr);
+ free(datum);
+}
+
+
+/* Concatenate a datum and a region. */
+void cbdatumcat(CBDATUM *datum, const char *ptr, int size){
+ assert(datum && ptr);
+ if(size < 0) size = strlen(ptr);
+ if(datum->dsize + size >= datum->asize){
+ datum->asize = datum->asize * 2 + size + 1;
+ CB_REALLOC(datum->dptr, datum->asize);
+ }
+ memcpy(datum->dptr + datum->dsize, ptr, size);
+ datum->dsize += size;
+ datum->dptr[datum->dsize] = '\0';
+}
+
+
+/* Get the pointer of the region of a datum. */
+const char *cbdatumptr(const CBDATUM *datum){
+ assert(datum);
+ return datum->dptr;
+}
+
+
+/* Get the size of the region of a datum. */
+int cbdatumsize(const CBDATUM *datum){
+ assert(datum);
+ return datum->dsize;
+}
+
+
+/* Set the size of the region of a datum. */
+void cbdatumsetsize(CBDATUM *datum, int size){
+ assert(datum && size >= 0);
+ if(size <= datum->dsize){
+ datum->dsize = size;
+ datum->dptr[size] = '\0';
+ } else {
+ if(size >= datum->asize){
+ datum->asize = datum->asize * 2 + size + 1;
+ CB_REALLOC(datum->dptr, datum->asize);
+ }
+ memset(datum->dptr + datum->dsize, 0, (size - datum->dsize) + 1);
+ datum->dsize = size;
+ }
+}
+
+
+/* Perform formatted output into a datum. */
+void cbdatumprintf(CBDATUM *datum, const char *format, ...){
+ va_list ap;
+ char *tmp, cbuf[CB_NUMBUFSIZ], tbuf[CB_NUMBUFSIZ*2];
+ unsigned char c;
+ int cblen, tlen;
+ assert(datum && format);
+ va_start(ap, format);
+ while(*format != '\0'){
+ if(*format == '%'){
+ cbuf[0] = '%';
+ cblen = 1;
+ format++;
+ while(strchr("0123456789 .+-", *format) && *format != '\0' && cblen < CB_NUMBUFSIZ - 1){
+ cbuf[cblen++] = *format;
+ format++;
+ }
+ cbuf[cblen++] = *format;
+ cbuf[cblen] = '\0';
+ switch(*format){
+ case 's':
+ tmp = va_arg(ap, char *);
+ if(!tmp) tmp = "(null)";
+ cbdatumcat(datum, tmp, -1);
+ break;
+ case 'd':
+ tlen = sprintf(tbuf, cbuf, va_arg(ap, int));
+ cbdatumcat(datum, tbuf, tlen);
+ break;
+ case 'o': case 'u': case 'x': case 'X': case 'c':
+ tlen = sprintf(tbuf, cbuf, va_arg(ap, unsigned int));
+ cbdatumcat(datum, tbuf, tlen);
+ break;
+ case 'e': case 'E': case 'f': case 'g': case 'G':
+ tlen = sprintf(tbuf, cbuf, va_arg(ap, double));
+ cbdatumcat(datum, tbuf, tlen);
+ break;
+ case '@':
+ tmp = va_arg(ap, char *);
+ if(!tmp) tmp = "(null)";
+ while(*tmp){
+ switch(*tmp){
+ case '&': cbdatumcat(datum, "&amp;", 5); break;
+ case '<': cbdatumcat(datum, "&lt;", 4); break;
+ case '>': cbdatumcat(datum, "&gt;", 4); break;
+ case '"': cbdatumcat(datum, "&quot;", 6); break;
+ default:
+ if(!((*tmp >= 0 && *tmp <= 0x8) || (*tmp >= 0x0e && *tmp <= 0x1f)))
+ cbdatumcat(datum, tmp, 1);
+ break;
+ }
+ tmp++;
+ }
+ break;
+ case '?':
+ tmp = va_arg(ap, char *);
+ if(!tmp) tmp = "(null)";
+ while(*tmp){
+ c = *(unsigned char *)tmp;
+ if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') || (c != '\0' && strchr("_-.", c))){
+ cbdatumcat(datum, tmp, 1);
+ } else {
+ tlen = sprintf(tbuf, "%%%02X", c);
+ cbdatumcat(datum, tbuf, tlen);
+ }
+ tmp++;
+ }
+ break;
+ case ':':
+ tmp = va_arg(ap, char *);
+ if(!tmp) tmp = "";
+ tmp = cbmimeencode(tmp, "UTF-8", TRUE);
+ cbdatumcat(datum, tmp, -1);
+ free(tmp);
+ break;
+ case '%':
+ cbdatumcat(datum, "%", 1);
+ break;
+ }
+ } else {
+ cbdatumcat(datum, format, 1);
+ }
+ format++;
+ }
+ va_end(ap);
+}
+
+
+/* Convert a datum to an allocated region. */
+char *cbdatumtomalloc(CBDATUM *datum, int *sp){
+ char *ptr;
+ assert(datum);
+ ptr = datum->dptr;
+ if(sp) *sp = datum->dsize;
+ free(datum);
+ return ptr;
+}
+
+
+/* Get a list handle. */
+CBLIST *cblistopen(void){
+ CBLIST *list;
+ CB_MALLOC(list, sizeof(*list));
+ list->anum = CB_LISTUNIT;
+ CB_MALLOC(list->array, sizeof(list->array[0]) * list->anum);
+ list->start = 0;
+ list->num = 0;
+ return list;
+}
+
+
+/* Copy a list. */
+CBLIST *cblistdup(const CBLIST *list){
+ CBLIST *newlist;
+ int i, size;
+ const char *val;
+ assert(list);
+ CB_LISTOPEN2(newlist, CB_LISTNUM(list));
+ for(i = 0; i < CB_LISTNUM(list); i++){
+ val = CB_LISTVAL2(list, i, size);
+ CB_LISTPUSH(newlist, val, size);
+ }
+ return newlist;
+}
+
+
+/* Close a list handle. */
+void cblistclose(CBLIST *list){
+ int i, end;
+ assert(list);
+ end = list->start + list->num;
+ for(i = list->start; i < end; i++){
+ free(list->array[i].dptr);
+ }
+ free(list->array);
+ free(list);
+}
+
+
+/* Get the number of elements of a list. */
+int cblistnum(const CBLIST *list){
+ assert(list);
+ return list->num;
+}
+
+
+/* Get the pointer to the region of an element. */
+const char *cblistval(const CBLIST *list, int index, int *sp){
+ assert(list && index >= 0);
+ if(index >= list->num) return NULL;
+ index += list->start;
+ if(sp) *sp = list->array[index].dsize;
+ return list->array[index].dptr;
+}
+
+
+/* Add an element at the end of a list. */
+void cblistpush(CBLIST *list, const char *ptr, int size){
+ int index;
+ assert(list && ptr);
+ if(size < 0) size = strlen(ptr);
+ index = list->start + list->num;
+ if(index >= list->anum){
+ list->anum *= 2;
+ CB_REALLOC(list->array, list->anum * sizeof(list->array[0]));
+ }
+ CB_MALLOC(list->array[index].dptr, (size < CB_DATUMUNIT ? CB_DATUMUNIT : size) + 1);
+ memcpy(list->array[index].dptr, ptr, size);
+ list->array[index].dptr[size] = '\0';
+ list->array[index].dsize = size;
+ list->num++;
+}
+
+
+/* Remove an element of the end of a list. */
+char *cblistpop(CBLIST *list, int *sp){
+ int index;
+ assert(list);
+ if(list->num < 1) return NULL;
+ index = list->start + list->num - 1;
+ list->num--;
+ if(sp) *sp = list->array[index].dsize;
+ return list->array[index].dptr;
+}
+
+
+/* Add an element at the top of a list. */
+void cblistunshift(CBLIST *list, const char *ptr, int size){
+ int index;
+ assert(list && ptr);
+ if(size < 0) size = strlen(ptr);
+ if(list->start < 1){
+ if(list->start + list->num >= list->anum){
+ list->anum *= 2;
+ CB_REALLOC(list->array, list->anum * sizeof(list->array[0]));
+ }
+ list->start = list->anum - list->num;
+ memmove(list->array + list->start, list->array, list->num * sizeof(list->array[0]));
+ }
+ index = list->start - 1;
+ CB_MALLOC(list->array[index].dptr, (size < CB_DATUMUNIT ? CB_DATUMUNIT : size) + 1);
+ memcpy(list->array[index].dptr, ptr, size);
+ list->array[index].dptr[size] = '\0';
+ list->array[index].dsize = size;
+ list->start--;
+ list->num++;
+}
+
+
+/* Remove an element of the top of a list. */
+char *cblistshift(CBLIST *list, int *sp){
+ int index;
+ assert(list);
+ if(list->num < 1) return NULL;
+ index = list->start;
+ list->start++;
+ list->num--;
+ if(sp) *sp = list->array[index].dsize;
+ return list->array[index].dptr;
+}
+
+
+/* Add an element at the specified location of a list. */
+void cblistinsert(CBLIST *list, int index, const char *ptr, int size){
+ assert(list && index >= 0);
+ if(index > list->num) return;
+ if(size < 0) size = strlen(ptr);
+ index += list->start;
+ if(list->start + list->num >= list->anum){
+ list->anum *= 2;
+ CB_REALLOC(list->array, list->anum * sizeof(list->array[0]));
+ }
+ memmove(list->array + index + 1, list->array + index,
+ sizeof(list->array[0]) * (list->start + list->num - index));
+ CB_MEMDUP(list->array[index].dptr, ptr, size);
+ list->array[index].dsize = size;
+ list->num++;
+}
+
+
+/* Remove an element at the specified location of a list. */
+char *cblistremove(CBLIST *list, int index, int *sp){
+ char *dptr;
+ assert(list && index >= 0);
+ if(index >= list->num) return NULL;
+ index += list->start;
+ dptr = list->array[index].dptr;
+ if(sp) *sp = list->array[index].dsize;
+ list->num--;
+ memmove(list->array + index, list->array + index + 1,
+ sizeof(list->array[0]) * (list->start + list->num - index));
+ return dptr;
+}
+
+
+/* Overwrite an element at the specified location of a list. */
+void cblistover(CBLIST *list, int index, const char *ptr, int size){
+ assert(list && index >= 0);
+ if(index >= list->num) return;
+ if(size < 0) size = strlen(ptr);
+ index += list->start;
+ if(size > list->array[index].dsize)
+ CB_REALLOC(list->array[index].dptr, size + 1);
+ memcpy(list->array[index].dptr, ptr, size);
+ list->array[index].dsize = size;
+ list->array[index].dptr[size] = '\0';
+}
+
+
+/* Sort elements of a list in lexical order. */
+void cblistsort(CBLIST *list){
+ assert(list);
+ qsort(list->array + list->start, list->num, sizeof(list->array[0]), cblistelemcmp);
+}
+
+
+/* Search a list for an element using liner search. */
+int cblistlsearch(const CBLIST *list, const char *ptr, int size){
+ int i, end;
+ assert(list && ptr);
+ if(size < 0) size = strlen(ptr);
+ end = list->start + list->num;
+ for(i = list->start; i < end; i++){
+ if(list->array[i].dsize == size && !memcmp(list->array[i].dptr, ptr, size))
+ return i - list->start;
+ }
+ return -1;
+}
+
+
+/* Search a list for an element using binary search. */
+int cblistbsearch(const CBLIST *list, const char *ptr, int size){
+ CBLISTDATUM key, *res;
+ assert(list && ptr);
+ if(size < 0) size = strlen(ptr);
+ CB_MEMDUP(key.dptr, ptr, size);
+ key.dsize = size;
+ res = bsearch(&key, list->array + list->start, list->num, sizeof(list->array[0]), cblistelemcmp);
+ free(key.dptr);
+ return res ? (res - list->array - list->start) : -1;
+}
+
+
+/* Serialize a list into a byte array. */
+char *cblistdump(const CBLIST *list, int *sp){
+ char *buf, vnumbuf[CB_VNUMBUFSIZ];
+ const char *vbuf;
+ int i, bsiz, vnumsiz, ln, vsiz;
+ assert(list && sp);
+ ln = CB_LISTNUM(list);
+ CB_SETVNUMBUF(vnumsiz, vnumbuf, ln);
+ CB_MALLOC(buf, vnumsiz + 1);
+ memcpy(buf, vnumbuf, vnumsiz);
+ bsiz = vnumsiz;
+ for(i = 0; i < ln; i++){
+ vbuf = CB_LISTVAL2(list, i, vsiz);
+ CB_SETVNUMBUF(vnumsiz, vnumbuf, vsiz);
+ CB_REALLOC(buf, bsiz + vnumsiz + vsiz + 1);
+ memcpy(buf + bsiz, vnumbuf, vnumsiz);
+ bsiz += vnumsiz;
+ memcpy(buf + bsiz, vbuf, vsiz);
+ bsiz += vsiz;
+ }
+ *sp = bsiz;
+ return buf;
+}
+
+
+/* Redintegrate a serialized list. */
+CBLIST *cblistload(const char *ptr, int size){
+ CBLIST *list;
+ const char *rp;
+ int i, anum, step, ln, vsiz;
+ assert(ptr && size >= 0);
+ anum = size / (sizeof(CBLISTDATUM) + 1);
+ CB_LISTOPEN2(list, anum);
+ rp = ptr;
+ CB_READVNUMBUF(rp, size, ln, step);
+ rp += step;
+ size -= step;
+ if(ln > size) return list;
+ for(i = 0; i < ln; i++){
+ if(size < 1) break;
+ CB_READVNUMBUF(rp, size, vsiz, step);
+ rp += step;
+ size -= step;
+ if(vsiz > size) break;
+ CB_LISTPUSH(list, rp, vsiz);
+ rp += vsiz;
+ }
+ return list;
+}
+
+
+/* Get a map handle. */
+CBMAP *cbmapopen(void){
+ return cbmapopenex(CB_MAPBNUM);
+}
+
+
+/* Copy a map. */
+CBMAP *cbmapdup(CBMAP *map){
+ CBMAP *newmap;
+ const char *kbuf, *vbuf;
+ int ksiz, vsiz;
+ assert(map);
+ cbmapiterinit(map);
+ newmap = map->rnum > CB_MAPPBNUM ? cbmapopen() : cbmapopenex(CB_MAPPBNUM);
+ while((kbuf = cbmapiternext(map, &ksiz)) != NULL){
+ CB_MAPITERVAL(vbuf, kbuf, vsiz);
+ cbmapput(newmap, kbuf, ksiz, vbuf, vsiz, FALSE);
+ }
+ cbmapiterinit(map);
+ return newmap;
+}
+
+
+/* Close a map handle. */
+void cbmapclose(CBMAP *map){
+ CBMAPDATUM *datum, *next;
+ datum = map->first;
+ while(datum){
+ next = datum->next;
+ free(datum);
+ datum = next;
+ }
+ free(map->buckets);
+ free(map);
+}
+
+
+/* Store a record. */
+int cbmapput(CBMAP *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int over){
+ CBMAPDATUM *datum, **entp, *old;
+ char *dbuf;
+ int bidx, hash, kcmp, psiz;
+ assert(map && kbuf && vbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(vsiz < 0) vsiz = strlen(vbuf);
+ CB_FIRSTHASH(hash, kbuf, ksiz);
+ bidx = hash % map->bnum;
+ datum = map->buckets[bidx];
+ entp = map->buckets + bidx;
+ CB_SECONDHASH(hash, kbuf, ksiz);
+ while(datum){
+ if(hash > datum->hash){
+ entp = &(datum->left);
+ datum = datum->left;
+ } else if(hash < datum->hash){
+ entp = &(datum->right);
+ datum = datum->right;
+ } else {
+ dbuf = (char *)datum + sizeof(*datum);
+ kcmp = cbkeycmp(kbuf, ksiz, dbuf, datum->ksiz);
+ if(kcmp < 0){
+ entp = &(datum->left);
+ datum = datum->left;
+ } else if(kcmp > 0){
+ entp = &(datum->right);
+ datum = datum->right;
+ } else {
+ if(!over) return FALSE;
+ psiz = CB_ALIGNPAD(ksiz);
+ if(vsiz > datum->vsiz){
+ old = datum;
+ CB_REALLOC(datum, sizeof(*datum) + ksiz + psiz + vsiz + 1);
+ if(datum != old){
+ if(map->first == old) map->first = datum;
+ if(map->last == old) map->last = datum;
+ if(*entp == old) *entp = datum;
+ if(datum->prev) datum->prev->next = datum;
+ if(datum->next) datum->next->prev = datum;
+ dbuf = (char *)datum + sizeof(*datum);
+ }
+ }
+ memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
+ dbuf[ksiz+psiz+vsiz] = '\0';
+ datum->vsiz = vsiz;
+ return TRUE;
+ }
+ }
+ }
+ psiz = CB_ALIGNPAD(ksiz);
+ CB_MALLOC(datum, sizeof(*datum) + ksiz + psiz + vsiz + 1);
+ dbuf = (char *)datum + sizeof(*datum);
+ memcpy(dbuf, kbuf, ksiz);
+ dbuf[ksiz] = '\0';
+ datum->ksiz = ksiz;
+ memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
+ dbuf[ksiz+psiz+vsiz] = '\0';
+ datum->vsiz = vsiz;
+ datum->hash = hash;
+ datum->left = NULL;
+ datum->right = NULL;
+ datum->prev = map->last;
+ datum->next = NULL;
+ *entp = datum;
+ if(!map->first) map->first = datum;
+ if(map->last) map->last->next = datum;
+ map->last = datum;
+ map->rnum++;
+ return TRUE;
+}
+
+
+/* Concatenate a value at the end of the value of the existing record. */
+void cbmapputcat(CBMAP *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz){
+ CBMAPDATUM *datum, **entp, *old;
+ char *dbuf;
+ int bidx, hash, kcmp, psiz, asiz, unit;
+ assert(map && kbuf && vbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(vsiz < 0) vsiz = strlen(vbuf);
+ CB_FIRSTHASH(hash, kbuf, ksiz);
+ bidx = hash % map->bnum;
+ datum = map->buckets[bidx];
+ entp = map->buckets + bidx;
+ CB_SECONDHASH(hash, kbuf, ksiz);
+ while(datum){
+ if(hash > datum->hash){
+ entp = &(datum->left);
+ datum = datum->left;
+ } else if(hash < datum->hash){
+ entp = &(datum->right);
+ datum = datum->right;
+ } else {
+ dbuf = (char *)datum + sizeof(*datum);
+ kcmp = cbkeycmp(kbuf, ksiz, dbuf, datum->ksiz);
+ if(kcmp < 0){
+ entp = &(datum->left);
+ datum = datum->left;
+ } else if(kcmp > 0){
+ entp = &(datum->right);
+ datum = datum->right;
+ } else {
+ psiz = CB_ALIGNPAD(ksiz);
+ asiz = sizeof(*datum) + ksiz + psiz + datum->vsiz + vsiz + 1;
+ unit = asiz <= CB_MAPCSUNIT ? CB_MAPCSUNIT : CB_MAPCBUNIT;
+ asiz = (asiz - 1) + unit - (asiz - 1) % unit;
+ old = datum;
+ CB_REALLOC(datum, asiz);
+ if(datum != old){
+ if(map->first == old) map->first = datum;
+ if(map->last == old) map->last = datum;
+ if(*entp == old) *entp = datum;
+ if(datum->prev) datum->prev->next = datum;
+ if(datum->next) datum->next->prev = datum;
+ dbuf = (char *)datum + sizeof(*datum);
+ }
+ memcpy(dbuf + ksiz + psiz + datum->vsiz, vbuf, vsiz);
+ dbuf[ksiz+psiz+datum->vsiz+vsiz] = '\0';
+ datum->vsiz += vsiz;
+ return;
+ }
+ }
+ }
+ psiz = CB_ALIGNPAD(ksiz);
+ asiz = sizeof(*datum) + ksiz + psiz + vsiz + 1;
+ unit = asiz <= CB_MAPCSUNIT ? CB_MAPCSUNIT : CB_MAPCBUNIT;
+ asiz = (asiz - 1) + unit - (asiz - 1) % unit;
+ CB_MALLOC(datum, asiz);
+ dbuf = (char *)datum + sizeof(*datum);
+ memcpy(dbuf, kbuf, ksiz);
+ dbuf[ksiz] = '\0';
+ datum->ksiz = ksiz;
+ memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
+ dbuf[ksiz+psiz+vsiz] = '\0';
+ datum->vsiz = vsiz;
+ datum->hash = hash;
+ datum->left = NULL;
+ datum->right = NULL;
+ datum->prev = map->last;
+ datum->next = NULL;
+ *entp = datum;
+ if(!map->first) map->first = datum;
+ if(map->last) map->last->next = datum;
+ map->last = datum;
+ map->rnum++;
+}
+
+
+/* Delete a record. */
+int cbmapout(CBMAP *map, const char *kbuf, int ksiz){
+ CBMAPDATUM *datum, **entp, *tmp;
+ char *dbuf;
+ int bidx, hash, kcmp;
+ assert(map && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ CB_FIRSTHASH(hash, kbuf, ksiz);
+ bidx = hash % map->bnum;
+ datum = map->buckets[bidx];
+ entp = map->buckets + bidx;
+ CB_SECONDHASH(hash, kbuf, ksiz);
+ while(datum){
+ if(hash > datum->hash){
+ entp = &(datum->left);
+ datum = datum->left;
+ } else if(hash < datum->hash){
+ entp = &(datum->right);
+ datum = datum->right;
+ } else {
+ dbuf = (char *)datum + sizeof(*datum);
+ kcmp = cbkeycmp(kbuf, ksiz, dbuf, datum->ksiz);
+ if(kcmp < 0){
+ entp = &(datum->left);
+ datum = datum->left;
+ } else if(kcmp > 0){
+ entp = &(datum->right);
+ datum = datum->right;
+ } else {
+ if(datum->prev) datum->prev->next = datum->next;
+ if(datum->next) datum->next->prev = datum->prev;
+ if(datum == map->first) map->first = datum->next;
+ if(datum == map->last) map->last = datum->prev;
+ if(datum->left && !datum->right){
+ *entp = datum->left;
+ } else if(!datum->left && datum->right){
+ *entp = datum->right;
+ } else if(!datum->left && !datum->left){
+ *entp = NULL;
+ } else {
+ *entp = datum->left;
+ tmp = *entp;
+ while(TRUE){
+ if(hash > tmp->hash){
+ if(tmp->left){
+ tmp = tmp->left;
+ } else {
+ tmp->left = datum->right;
+ break;
+ }
+ } else if(hash < tmp->hash){
+ if(tmp->right){
+ tmp = tmp->right;
+ } else {
+ tmp->right = datum->right;
+ break;
+ }
+ } else {
+ kcmp = cbkeycmp(kbuf, ksiz, dbuf, datum->ksiz);
+ if(kcmp < 0){
+ if(tmp->left){
+ tmp = tmp->left;
+ } else {
+ tmp->left = datum->right;
+ break;
+ }
+ } else {
+ if(tmp->right){
+ tmp = tmp->right;
+ } else {
+ tmp->right = datum->right;
+ break;
+ }
+ }
+ }
+ }
+ }
+ free(datum);
+ map->rnum--;
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+
+/* Retrieve a record. */
+const char *cbmapget(const CBMAP *map, const char *kbuf, int ksiz, int *sp){
+ CBMAPDATUM *datum;
+ char *dbuf;
+ int hash, kcmp;
+ assert(map && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ CB_FIRSTHASH(hash, kbuf, ksiz);
+ datum = map->buckets[hash%map->bnum];
+ CB_SECONDHASH(hash, kbuf, ksiz);
+ while(datum){
+ if(hash > datum->hash){
+ datum = datum->left;
+ } else if(hash < datum->hash){
+ datum = datum->right;
+ } else {
+ dbuf = (char *)datum + sizeof(*datum);
+ kcmp = cbkeycmp(kbuf, ksiz, dbuf, datum->ksiz);
+ if(kcmp < 0){
+ datum = datum->left;
+ } else if(kcmp > 0){
+ datum = datum->right;
+ } else {
+ if(sp) *sp = datum->vsiz;
+ return dbuf + datum->ksiz + CB_ALIGNPAD(datum->ksiz);
+ }
+ }
+ }
+ return NULL;
+}
+
+
+/* Move a record to the edge. */
+int cbmapmove(CBMAP *map, const char *kbuf, int ksiz, int head){
+ CBMAPDATUM *datum;
+ char *dbuf;
+ int hash, kcmp;
+ assert(map && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ CB_FIRSTHASH(hash, kbuf, ksiz);
+ datum = map->buckets[hash%map->bnum];
+ CB_SECONDHASH(hash, kbuf, ksiz);
+ while(datum){
+ if(hash > datum->hash){
+ datum = datum->left;
+ } else if(hash < datum->hash){
+ datum = datum->right;
+ } else {
+ dbuf = (char *)datum + sizeof(*datum);
+ kcmp = cbkeycmp(kbuf, ksiz, dbuf, datum->ksiz);
+ if(kcmp < 0){
+ datum = datum->left;
+ } else if(kcmp > 0){
+ datum = datum->right;
+ } else {
+ if(head){
+ if(map->first == datum) return TRUE;
+ if(map->last == datum) map->last = datum->prev;
+ if(datum->prev) datum->prev->next = datum->next;
+ if(datum->next) datum->next->prev = datum->prev;
+ datum->prev = NULL;
+ datum->next = map->first;
+ map->first->prev = datum;
+ map->first = datum;
+ } else {
+ if(map->last == datum) return TRUE;
+ if(map->first == datum) map->first = datum->next;
+ if(datum->prev) datum->prev->next = datum->next;
+ if(datum->next) datum->next->prev = datum->prev;
+ datum->prev = map->last;
+ datum->next = NULL;
+ map->last->next = datum;
+ map->last = datum;
+ }
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+
+/* Initialize the iterator of a map handle. */
+void cbmapiterinit(CBMAP *map){
+ assert(map);
+ map->cur = map->first;
+}
+
+
+/* Get the next key of the iterator. */
+const char *cbmapiternext(CBMAP *map, int *sp){
+ CBMAPDATUM *datum;
+ assert(map);
+ if(!map->cur) return NULL;
+ datum = map->cur;
+ map->cur = datum->next;
+ if(sp) *sp = datum->ksiz;
+ return (char *)datum + sizeof(*datum);
+}
+
+
+/* Get the value binded to the key fetched from the iterator. */
+const char *cbmapiterval(const char *kbuf, int *sp){
+ CBMAPDATUM *datum;
+ assert(kbuf);
+ datum = (CBMAPDATUM *)(kbuf - sizeof(*datum));
+ if(sp) *sp = datum->vsiz;
+ return (char *)datum + sizeof(*datum) + datum->ksiz + CB_ALIGNPAD(datum->ksiz);
+}
+
+
+/* Get the number of the records stored in a map. */
+int cbmaprnum(const CBMAP *map){
+ assert(map);
+ return map->rnum;
+}
+
+
+/* Get the list handle contains all keys in a map. */
+CBLIST *cbmapkeys(CBMAP *map){
+ CBLIST *list;
+ const char *kbuf;
+ int anum, ksiz;
+ assert(map);
+ anum = cbmaprnum(map);
+ CB_LISTOPEN2(list, anum);
+ cbmapiterinit(map);
+ while((kbuf = cbmapiternext(map, &ksiz)) != NULL){
+ CB_LISTPUSH(list, kbuf, ksiz);
+ }
+ return list;
+}
+
+
+/* Get the list handle contains all values in a map. */
+CBLIST *cbmapvals(CBMAP *map){
+ CBLIST *list;
+ const char *kbuf, *vbuf;
+ int anum, ksiz, vsiz;
+ assert(map);
+ anum = cbmaprnum(map);
+ CB_LISTOPEN2(list, anum);
+ cbmapiterinit(map);
+ while((kbuf = cbmapiternext(map, &ksiz)) != NULL){
+ CB_MAPITERVAL(vbuf, kbuf, vsiz);
+ CB_LISTPUSH(list, vbuf, vsiz);
+ }
+ return list;
+}
+
+
+/* Serialize a map into a byte array. */
+char *cbmapdump(CBMAP *map, int *sp){
+ char *buf, vnumbuf[CB_VNUMBUFSIZ];
+ const char *kbuf, *vbuf;
+ int bsiz, vnumsiz, rn, ksiz, vsiz;
+ assert(map && sp);
+ rn = cbmaprnum(map);
+ CB_SETVNUMBUF(vnumsiz, vnumbuf, rn);
+ CB_MALLOC(buf, vnumsiz + 1);
+ memcpy(buf, vnumbuf, vnumsiz);
+ bsiz = vnumsiz;
+ cbmapiterinit(map);
+ while((kbuf = cbmapiternext(map, &ksiz)) != NULL){
+ CB_MAPITERVAL(vbuf, kbuf, vsiz);
+ CB_SETVNUMBUF(vnumsiz, vnumbuf, ksiz);
+ CB_REALLOC(buf, bsiz + vnumsiz + ksiz + 1);
+ memcpy(buf + bsiz, vnumbuf, vnumsiz);
+ bsiz += vnumsiz;
+ memcpy(buf + bsiz, kbuf, ksiz);
+ bsiz += ksiz;
+ CB_SETVNUMBUF(vnumsiz, vnumbuf, vsiz);
+ CB_REALLOC(buf, bsiz + vnumsiz + vsiz + 1);
+ memcpy(buf + bsiz, vnumbuf, vnumsiz);
+ bsiz += vnumsiz;
+ memcpy(buf + bsiz, vbuf, vsiz);
+ bsiz += vsiz;
+ }
+ *sp = bsiz;
+ return buf;
+}
+
+
+/* Redintegrate a serialized map. */
+CBMAP *cbmapload(const char *ptr, int size){
+ CBMAP *map;
+ const char *rp, *kbuf, *vbuf;
+ int i, step, rn, ksiz, vsiz;
+ assert(ptr && size >= 0);
+ map = cbmapopenex(CB_MAPPBNUM);
+ rp = ptr;
+ CB_READVNUMBUF(rp, size, rn, step);
+ rp += step;
+ size -= step;
+ if(rn > size) return map;
+ for(i = 0; i < rn; i++){
+ if(size < 1) break;
+ CB_READVNUMBUF(rp, size, ksiz, step);
+ rp += step;
+ size -= step;
+ if(ksiz > size) break;
+ kbuf = rp;
+ rp += ksiz;
+ if(size < 1) break;
+ CB_READVNUMBUF(rp, size, vsiz, step);
+ rp += step;
+ size -= step;
+ if(vsiz > size) break;
+ vbuf = rp;
+ rp += vsiz;
+ cbmapput(map, kbuf, ksiz, vbuf, vsiz, TRUE);
+ }
+ return map;
+}
+
+
+/* Redintegrate a serialized map and get one of the records. */
+char *cbmaploadone(const char *ptr, int size, const char *kbuf, int ksiz, int *sp){
+ const char *rp, *tkbuf, *vbuf;
+ char *rv;
+ int i, step, rn, tksiz, vsiz;
+ assert(ptr && size >= 0 && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ rp = ptr;
+ CB_READVNUMBUF(rp, size, rn, step);
+ rp += step;
+ size -= step;
+ if(rn > size) return NULL;
+ for(i = 0; i < rn; i++){
+ if(size < 1) break;
+ CB_READVNUMBUF(rp, size, tksiz, step);
+ rp += step;
+ size -= step;
+ if(tksiz > size) break;
+ tkbuf = rp;
+ rp += tksiz;
+ if(size < 1) break;
+ CB_READVNUMBUF(rp, size, vsiz, step);
+ rp += step;
+ size -= step;
+ if(vsiz > size) break;
+ vbuf = rp;
+ rp += vsiz;
+ if(tksiz == ksiz && !memcmp(tkbuf, kbuf, ksiz)){
+ if(sp) *sp = vsiz;
+ CB_MEMDUP(rv, vbuf, vsiz);
+ return rv;
+ }
+ }
+ return NULL;
+}
+
+
+/* Get a heap handle. */
+CBHEAP *cbheapopen(int size, int max, int(*compar)(const void *, const void *)){
+ CBHEAP *heap;
+ assert(size > 0 && max >= 0 && compar);
+ CB_MALLOC(heap, sizeof(*heap));
+ CB_MALLOC(heap->base, size * max + 1);
+ CB_MALLOC(heap->swap, size);
+ heap->size = size;
+ heap->num = 0;
+ heap->max = max;
+ heap->compar = compar;
+ return heap;
+}
+
+
+/* Copy a heap. */
+CBHEAP *cbheapdup(CBHEAP *heap){
+ CBHEAP *newheap;
+ assert(heap);
+ CB_MALLOC(newheap, sizeof(*newheap));
+ CB_MEMDUP(newheap->base, heap->base, heap->size * heap->max);
+ CB_MALLOC(newheap->swap, heap->size);
+ newheap->size = heap->size;
+ newheap->num = heap->num;
+ newheap->max = heap->max;
+ newheap->compar = heap->compar;
+ return newheap;
+}
+
+
+/* Close a heap handle. */
+void cbheapclose(CBHEAP *heap){
+ assert(heap);
+ free(heap->swap);
+ free(heap->base);
+ free(heap);
+}
+
+
+/* Get the number of the records stored in a heap. */
+int cbheapnum(CBHEAP *heap){
+ assert(heap);
+ return heap->num;
+}
+
+
+/* Insert a record into a heap. */
+int cbheapinsert(CBHEAP *heap, const void *ptr){
+ char *base;
+ int size, pidx, cidx, bot;
+ assert(heap && ptr);
+ if(heap->max < 1) return FALSE;
+ base = heap->base;
+ size = heap->size;
+ if(heap->num >= heap->max){
+ if(heap->compar(ptr, base) > 0) return FALSE;
+ memcpy(base, ptr, size);
+ pidx = 0;
+ bot = heap->num / 2;
+ while(pidx < bot){
+ cidx = pidx * 2 + 1;
+ if(cidx < heap->num - 1 && heap->compar(base + cidx * size, base + (cidx + 1) * size) < 0)
+ cidx++;
+ if(heap->compar(base + pidx * size, base + cidx * size) > 0) break;
+ memcpy(heap->swap, base + pidx * size, size);
+ memcpy(base + pidx * size, base + cidx * size, size);
+ memcpy(base + cidx * size, heap->swap, size);
+ pidx = cidx;
+ }
+ } else {
+ memcpy(base + size * heap->num, ptr, size);
+ cidx = heap->num;
+ while(cidx > 0){
+ pidx = (cidx - 1) / 2;
+ if(heap->compar(base + cidx * size, base + pidx * size) <= 0) break;
+ memcpy(heap->swap, base + cidx * size, size);
+ memcpy(base + cidx * size, base + pidx * size, size);
+ memcpy(base + pidx * size, heap->swap, size);
+ cidx = pidx;
+ }
+ heap->num++;
+ }
+ return TRUE;
+}
+
+
+/* Get the pointer to the region of a record in a heap. */
+const void *cbheapval(CBHEAP *heap, int index){
+ assert(heap && index >= 0);
+ if(index >= heap->num) return NULL;
+ return heap->base + index * heap->size;
+}
+
+
+/* Convert a heap to an allocated region. */
+void *cbheaptomalloc(CBHEAP *heap, int *np){
+ char *ptr;
+ assert(heap);
+ qsort(heap->base, heap->num, heap->size, heap->compar);
+ ptr = heap->base;
+ if(np) *np = heap->num;
+ free(heap->swap);
+ free(heap);
+ return ptr;
+}
+
+
+/* Allocate a formatted string on memory. */
+char *cbsprintf(const char *format, ...){
+ va_list ap;
+ char *buf, cbuf[CB_SPBUFSIZ], *str;
+ int len, cblen, num, slen;
+ unsigned int unum;
+ double dnum;
+ va_start(ap, format);
+ assert(format);
+ CB_MALLOC(buf, 1);
+ len = 0;
+ while(*format != '\0'){
+ if(*format == '%'){
+ cbuf[0] = '%';
+ cblen = 1;
+ format++;
+ while(strchr("0123456789 .+-", *format) && *format != '\0' && cblen < CB_SPBUFSIZ - 1){
+ cbuf[cblen++] = *format;
+ format++;
+ }
+ cbuf[cblen] = '\0';
+ if(atoi(cbuf + 1) > CB_SPMAXWIDTH - 16){
+ sprintf(cbuf, "(err)");
+ } else {
+ cbuf[cblen++] = *format;
+ cbuf[cblen] = '\0';
+ }
+ switch(*format){
+ case 'd':
+ num = va_arg(ap, int);
+ CB_REALLOC(buf, len + CB_SPMAXWIDTH + 2);
+ len += sprintf(buf + len, cbuf, num);
+ break;
+ case 'o': case 'u': case 'x': case 'X': case 'c':
+ unum = va_arg(ap, unsigned int);
+ CB_REALLOC(buf, len + CB_SPMAXWIDTH + 2);
+ len += sprintf(buf + len, cbuf, unum);
+ break;
+ case 'e': case 'E': case 'f': case 'g': case 'G':
+ dnum = va_arg(ap, double);
+ CB_REALLOC(buf, len + CB_SPMAXWIDTH + 2);
+ len += sprintf(buf + len, cbuf, dnum);
+ break;
+ case 's':
+ str = va_arg(ap, char *);
+ slen = strlen(str);
+ CB_REALLOC(buf, len + slen + 2);
+ memcpy(buf + len, str, slen);
+ len += slen;
+ break;
+ case '%':
+ CB_REALLOC(buf, len + 2);
+ buf[len++] = '%';
+ break;
+ default:
+ break;
+ }
+ } else {
+ CB_REALLOC(buf, len + 2);
+ buf[len++] = *format;
+ }
+ format++;
+ }
+ buf[len] = '\0';
+ va_end(ap);
+ return buf;
+}
+
+
+/* Replace some patterns in a string. */
+char *cbreplace(const char *str, CBMAP *pairs){
+ int i, bsiz, wi, rep, ksiz, vsiz;
+ char *buf;
+ const char *key, *val;
+ assert(str && pairs);
+ bsiz = CB_DATUMUNIT;
+ CB_MALLOC(buf, bsiz);
+ wi = 0;
+ while(*str != '\0'){
+ rep = FALSE;
+ cbmapiterinit(pairs);
+ while((key = cbmapiternext(pairs, &ksiz)) != NULL){
+ for(i = 0; i < ksiz; i++){
+ if(str[i] == '\0' || str[i] != key[i]) break;
+ }
+ if(i == ksiz){
+ CB_MAPITERVAL(val, key, vsiz);
+ if(wi + vsiz >= bsiz){
+ bsiz = bsiz * 2 + vsiz;
+ CB_REALLOC(buf, bsiz);
+ }
+ memcpy(buf + wi, val, vsiz);
+ wi += vsiz;
+ str += ksiz;
+ rep = TRUE;
+ break;
+ }
+ }
+ if(!rep){
+ if(wi + 1 >= bsiz){
+ bsiz = bsiz * 2 + 1;
+ CB_REALLOC(buf, bsiz);
+ }
+ buf[wi++] = *str;
+ str++;
+ }
+ }
+ CB_REALLOC(buf, wi + 1);
+ buf[wi] = '\0';
+ return buf;
+}
+
+
+/* Make a list by split a serial datum. */
+CBLIST *cbsplit(const char *ptr, int size, const char *delim){
+ CBLIST *list;
+ int bi, step;
+ assert(ptr);
+ CB_LISTOPEN(list);
+ if(size < 0) size = strlen(ptr);
+ if(delim){
+ for(bi = 0; bi < size; bi += step){
+ step = 0;
+ while(bi + step < size && !strchr(delim, ptr[bi+step])){
+ step++;
+ }
+ cblistpush(list, ptr + bi, step);
+ step++;
+ }
+ if(size > 0 && strchr(delim, ptr[size-1])) cblistpush(list, "", 0);
+ } else {
+ for(bi = 0; bi < size; bi += step){
+ step = 0;
+ while(bi + step < size && ptr[bi+step]){
+ step++;
+ }
+ cblistpush(list, ptr + bi, step);
+ step++;
+ }
+ if(size > 0 && ptr[size-1] == 0) cblistpush(list, "", 0);
+ }
+ return list;
+}
+
+
+/* Read whole data of a file. */
+char *cbreadfile(const char *name, int *sp){
+ struct stat sbuf;
+ char iobuf[CB_IOBUFSIZ], *buf;
+ int fd, size, asiz, rv;
+ asiz = CB_IOBUFSIZ * 2;
+ if(name){
+ if((fd = open(name, O_RDONLY, 0)) == -1) return NULL;
+ if(fstat(fd, &sbuf) != -1) asiz = sbuf.st_size + 1;
+ } else {
+ fd = 0;
+ }
+ CB_MALLOC(buf, asiz + 1);
+ size = 0;
+ while((rv = read(fd, iobuf, CB_IOBUFSIZ)) > 0){
+ if(size + rv >= asiz){
+ asiz = asiz * 2 + size;
+ CB_REALLOC(buf, asiz + 1);
+ }
+ memcpy(buf + size, iobuf, rv);
+ size += rv;
+ }
+ buf[size] = '\0';
+ if(close(fd) == -1 || rv == -1){
+ free(buf);
+ return NULL;
+ }
+ if(sp) *sp = size;
+ return buf;
+}
+
+
+/* Write data of a region into a file. */
+int cbwritefile(const char *name, const char *ptr, int size){
+ int fd, err, wb;
+ assert(ptr);
+ if(size < 0) size = strlen(ptr);
+ if(name){
+ if((fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, CB_FILEMODE)) == -1) return FALSE;
+ } else {
+ fd = 1;
+ }
+ err = FALSE;
+ wb = 0;
+ do {
+ wb = write(fd, ptr, size);
+ switch(wb){
+ case -1: err = errno != EINTR ? TRUE : FALSE; break;
+ case 0: break;
+ default:
+ ptr += wb;
+ size -= wb;
+ break;
+ }
+ } while(size > 0);
+ if(close(fd) == -1) err = TRUE;
+ return err ? FALSE : TRUE;
+}
+
+
+/* Read every line of a file. */
+CBLIST *cbreadlines(const char *name){
+ char *buf, *tmp;
+ int vsiz;
+ CBMAP *pairs;
+ CBLIST *list;
+ if(!(buf = cbreadfile(name, NULL))) return NULL;
+ pairs = cbmapopenex(3);
+ cbmapput(pairs, "\r\n", 2, "\n", 1, TRUE);
+ cbmapput(pairs, "\r", 1, "\n", 1, TRUE);
+ tmp = cbreplace(buf, pairs);
+ list = cbsplit(tmp, strlen(tmp), "\n");
+ free(tmp);
+ cbmapclose(pairs);
+ free(buf);
+ if(CB_LISTNUM(list) > 0){
+ cblistval(list, CB_LISTNUM(list) - 1, &vsiz);
+ if(vsiz < 1) CB_LISTDROP(list);
+ }
+ return list;
+}
+
+
+/* Read names of files in a directory. */
+CBLIST *cbdirlist(const char *name){
+ DIR *DD;
+ struct dirent *dp;
+ CBLIST *list;
+ assert(name);
+ if(!(DD = opendir(name))) return NULL;
+ CB_LISTOPEN(list);
+ while((dp = readdir(DD)) != NULL){
+ CB_LISTPUSH(list, dp->d_name, strlen(dp->d_name));
+ }
+ if(closedir(DD) == -1){
+ CB_LISTCLOSE(list);
+ return NULL;
+ }
+ return list;
+}
+
+
+/* Get the status of a file or a directory. */
+int cbfilestat(const char *name, int *isdirp, int *sizep, time_t *mtimep){
+ struct stat sbuf;
+ assert(name);
+ if(lstat(name, &sbuf) == -1) return FALSE;
+ if(isdirp) *isdirp = S_ISDIR(sbuf.st_mode);
+ if(sizep) *sizep = (int)sbuf.st_size;
+ if(mtimep) *mtimep = sbuf.st_mtime;
+ return TRUE;
+}
+
+
+/* Remove a file or a directory and its sub ones recursively. */
+int cbremove(const char *name){
+ CBLIST *list;
+ const char *elem;
+ char *path;
+ int i, err, tail;
+ struct stat sbuf;
+ assert(name);
+ if(lstat(name, &sbuf) == -1) return FALSE;
+ if(unlink(name) == 0) return TRUE;
+ if(!S_ISDIR(sbuf.st_mode) || !(list = cbdirlist(name))) return FALSE;
+ err = FALSE;
+ tail = name[0] != '\0' && name[strlen(name)-1] == MYPATHCHR;
+ for(i = 0; i < CB_LISTNUM(list); i++){
+ elem = CB_LISTVAL(list, i);
+ if(!strcmp(MYCDIRSTR, elem) || !strcmp(MYPDIRSTR, elem)) continue;
+ if(tail){
+ path = cbsprintf("%s%s", name, elem);
+ } else {
+ path = cbsprintf("%s%c%s", name, MYPATHCHR, elem);
+ }
+ if(!cbremove(path)) err = TRUE;
+ free(path);
+ }
+ CB_LISTCLOSE(list);
+ return rmdir(name) == 0 ? TRUE : FALSE;
+}
+
+
+/* Break up a URL into elements. */
+CBMAP *cburlbreak(const char *str){
+ CBMAP *map;
+ char *tmp, *ep;
+ const char *rp;
+ int serv;
+ assert(str);
+ map = cbmapopenex(CB_MAPPBNUM);
+ CB_MEMDUP(tmp, str, strlen(str));
+ rp = cbstrtrim(tmp);
+ cbmapput(map, "self", -1, rp, -1, TRUE);
+ serv = FALSE;
+ if(cbstrfwimatch(rp, "http://")){
+ cbmapput(map, "scheme", -1, "http", -1, TRUE);
+ rp += 7;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "https://")){
+ cbmapput(map, "scheme", -1, "https", -1, TRUE);
+ rp += 8;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "ftp://")){
+ cbmapput(map, "scheme", -1, "ftp", -1, TRUE);
+ rp += 6;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "sftp://")){
+ cbmapput(map, "scheme", -1, "sftp", -1, TRUE);
+ rp += 7;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "ftps://")){
+ cbmapput(map, "scheme", -1, "ftps", -1, TRUE);
+ rp += 7;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "tftp://")){
+ cbmapput(map, "scheme", -1, "tftp", -1, TRUE);
+ rp += 7;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "ldap://")){
+ cbmapput(map, "scheme", -1, "ldap", -1, TRUE);
+ rp += 7;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "ldaps://")){
+ cbmapput(map, "scheme", -1, "ldaps", -1, TRUE);
+ rp += 8;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "file://")){
+ cbmapput(map, "scheme", -1, "file", -1, TRUE);
+ rp += 7;
+ serv = TRUE;
+ }
+ if((ep = strchr(rp, '#')) != NULL){
+ cbmapput(map, "fragment", -1, ep + 1, -1, TRUE);
+ *ep = '\0';
+ }
+ if((ep = strchr(rp, '?')) != NULL){
+ cbmapput(map, "query", -1, ep + 1, -1, TRUE);
+ *ep = '\0';
+ }
+ if(serv){
+ if((ep = strchr(rp, '/')) != NULL){
+ cbmapput(map, "path", -1, ep, -1, TRUE);
+ *ep = '\0';
+ } else {
+ cbmapput(map, "path", -1, "/", -1, TRUE);
+ }
+ if((ep = strchr(rp, '@')) != NULL){
+ *ep = '\0';
+ if(rp[0] != '\0') cbmapput(map, "authority", -1, rp, -1, TRUE);
+ rp = ep + 1;
+ }
+ if((ep = strchr(rp, ':')) != NULL){
+ if(ep[1] != '\0') cbmapput(map, "port", -1, ep + 1, -1, TRUE);
+ *ep = '\0';
+ }
+ if(rp[0] != '\0') cbmapput(map, "host", -1, rp, -1, TRUE);
+ } else {
+ cbmapput(map, "path", -1, rp, -1, TRUE);
+ }
+ free(tmp);
+ if((rp = cbmapget(map, "path", -1, NULL)) != NULL){
+ if((ep = strrchr(rp, '/')) != NULL){
+ if(ep[1] != '\0') cbmapput(map, "file", -1, ep + 1, -1, TRUE);
+ } else {
+ cbmapput(map, "file", -1, rp, -1, TRUE);
+ }
+ }
+ if((rp = cbmapget(map, "file", -1, NULL)) != NULL && (!strcmp(rp, ".") || !strcmp(rp, "..")))
+ cbmapout(map, "file", -1);
+ return map;
+}
+
+
+/* Resolve a relative URL with another absolute URL. */
+char *cburlresolve(const char *base, const char *target){
+ CBMAP *telems, *belems;
+ CBLIST *bpaths, *opaths, *qelems;
+ CBDATUM *rbuf;
+ const char *vbuf, *path;
+ char *tmp, *wp, *enc, numbuf[CB_NUMBUFSIZ];
+ int i, vsiz, port, num;
+ assert(base && target);
+ while(*base > '\0' && *base <= ' '){
+ base++;
+ }
+ while(*target > '\0' && *target <= ' '){
+ target++;
+ }
+ if(*target == '\0') target = base;
+ CB_DATUMOPEN(rbuf);
+ telems = cburlbreak(target);
+ port = 80;
+ belems = cburlbreak(cbmapget(telems, "scheme", -1, &vsiz) ? target : base);
+ if((vbuf = cbmapget(belems, "scheme", -1, &vsiz)) != NULL){
+ CB_DATUMCAT(rbuf, vbuf, vsiz);
+ CB_DATUMCAT(rbuf, "://", 3);
+ if(!cbstricmp(vbuf, "https")){
+ port = 443;
+ } else if(!cbstricmp(vbuf, "ftp")){
+ port = 21;
+ } else if(!cbstricmp(vbuf, "sftp")){
+ port = 115;
+ } else if(!cbstricmp(vbuf, "ftps")){
+ port = 22;
+ } else if(!cbstricmp(vbuf, "tftp")){
+ port = 69;
+ } else if(!cbstricmp(vbuf, "ldap")){
+ port = 389;
+ } else if(!cbstricmp(vbuf, "ldaps")){
+ port = 636;
+ }
+ } else {
+ CB_DATUMCAT(rbuf, "http://", 7);
+ }
+ if((vbuf = cbmapget(belems, "authority", -1, &vsiz)) != NULL){
+ if((wp = strchr(vbuf, ':')) != NULL){
+ *wp = '\0';
+ tmp = cburldecode(vbuf, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ CB_DATUMCAT(rbuf, ":", 1);
+ wp++;
+ tmp = cburldecode(wp, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ } else {
+ tmp = cburldecode(vbuf, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ }
+ CB_DATUMCAT(rbuf, "@", 1);
+ }
+ if((vbuf = cbmapget(belems, "host", -1, &vsiz)) != NULL){
+ tmp = cburldecode(vbuf, NULL);
+ cbstrtolower(tmp);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ } else {
+ CB_DATUMCAT(rbuf, "localhost", 9);
+ }
+ if((vbuf = cbmapget(belems, "port", -1, &vsiz)) != NULL &&
+ (num = atoi(vbuf)) != port && num > 1){
+ sprintf(numbuf, ":%d", num);
+ CB_DATUMCAT(rbuf, numbuf, strlen(numbuf));
+ }
+ if(!(path = cbmapget(telems, "path", -1, NULL))) path = "/";
+ if(path[0] == '\0' && (vbuf = cbmapget(belems, "path", -1, NULL)) != NULL) path = vbuf;
+ if(path[0] == '\0') path = "/";
+ CB_LISTOPEN(bpaths);
+ if(path[0] != '/' && (vbuf = cbmapget(belems, "path", -1, &vsiz)) != NULL){
+ opaths = cbsplit(vbuf, vsiz, "/");
+ } else {
+ opaths = cbsplit("/", 1, "/");
+ }
+ CB_LISTDROP(opaths);
+ for(i = 0; i < CB_LISTNUM(opaths); i++){
+ vbuf = CB_LISTVAL2(opaths, i, vsiz);
+ if(vsiz < 1 || !strcmp(vbuf, ".")) continue;
+ if(!strcmp(vbuf, "..")){
+ CB_LISTDROP(bpaths);
+ } else {
+ CB_LISTPUSH(bpaths, vbuf, vsiz);
+ }
+ }
+ CB_LISTCLOSE(opaths);
+ opaths = cbsplit(path, -1, "/");
+ for(i = 0; i < CB_LISTNUM(opaths); i++){
+ vbuf = CB_LISTVAL2(opaths, i, vsiz);
+ if(vsiz < 1 || !strcmp(vbuf, ".")) continue;
+ if(!strcmp(vbuf, "..")){
+ CB_LISTDROP(bpaths);
+ } else {
+ CB_LISTPUSH(bpaths, vbuf, vsiz);
+ }
+ }
+ CB_LISTCLOSE(opaths);
+ for(i = 0; i < CB_LISTNUM(bpaths); i++){
+ vbuf = CB_LISTVAL(bpaths, i);
+ if(strchr(vbuf, '%')){
+ tmp = cburldecode(vbuf, NULL);
+ } else {
+ CB_MEMDUP(tmp, vbuf, strlen(vbuf));
+ }
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, "/", 1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ }
+ if(cbstrbwmatch(path, "/")) CB_DATUMCAT(rbuf, "/", 1);
+ CB_LISTCLOSE(bpaths);
+ if((vbuf = cbmapget(telems, "query", -1, &vsiz)) != NULL){
+ CB_DATUMCAT(rbuf, "?", 1);
+ qelems = cbsplit(vbuf, vsiz, "&;");
+ for(i = 0; i < CB_LISTNUM(qelems); i++){
+ vbuf = CB_LISTVAL(qelems, i);
+ if(i > 0) CB_DATUMCAT(rbuf, "&", 1);
+ if((wp = strchr(vbuf, '=')) != NULL){
+ *wp = '\0';
+ tmp = cburldecode(vbuf, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ CB_DATUMCAT(rbuf, "=", 1);
+ wp++;
+ tmp = cburldecode(wp, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ } else {
+ tmp = cburldecode(vbuf, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ }
+ }
+ CB_LISTCLOSE(qelems);
+ }
+ if((vbuf = cbmapget(telems, "fragment", -1, &vsiz)) != NULL){
+ tmp = cburldecode(vbuf, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, "#", 1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ }
+ cbmapclose(belems);
+ cbmapclose(telems);
+ return cbdatumtomalloc(rbuf, NULL);
+}
+
+
+/* Encode a serial object with URL encoding. */
+char *cburlencode(const char *ptr, int size){
+ char *buf, *wp;
+ int i, c;
+ assert(ptr);
+ if(size < 0) size = strlen(ptr);
+ CB_MALLOC(buf, size * 3 + 1);
+ wp = buf;
+ for(i = 0; i < size; i++){
+ c = ((unsigned char *)ptr)[i];
+ if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') || (c != '\0' && strchr("_-.!~*'()", c))){
+ *(wp++) = c;
+ } else {
+ wp += sprintf(wp, "%%%02X", c);
+ }
+ }
+ *wp = '\0';
+ return buf;
+}
+
+
+/* Decode a string encoded with URL encoding. */
+char *cburldecode(const char *str, int *sp){
+ char *buf, *wp;
+ unsigned char c;
+ CB_MEMDUP(buf, str, strlen(str));
+ wp = buf;
+ while(*str != '\0'){
+ if(*str == '%'){
+ str++;
+ if(((str[0] >= '0' && str[0] <= '9') || (str[0] >= 'A' && str[0] <= 'F') ||
+ (str[0] >= 'a' && str[0] <= 'f')) &&
+ ((str[1] >= '0' && str[1] <= '9') || (str[1] >= 'A' && str[1] <= 'F') ||
+ (str[1] >= 'a' && str[1] <= 'f'))){
+ c = *str;
+ if(c >= 'A' && c <= 'Z') c += 'a' - 'A';
+ if(c >= 'a' && c <= 'z'){
+ *wp = c - 'a' + 10;
+ } else {
+ *wp = c - '0';
+ }
+ *wp *= 0x10;
+ str++;
+ c = *str;
+ if(c >= 'A' && c <= 'Z') c += 'a' - 'A';
+ if(c >= 'a' && c <= 'z'){
+ *wp += c - 'a' + 10;
+ } else {
+ *wp += c - '0';
+ }
+ str++;
+ wp++;
+ } else {
+ break;
+ }
+ } else if(*str == '+'){
+ *wp = ' ';
+ str++;
+ wp++;
+ } else {
+ *wp = *str;
+ str++;
+ wp++;
+ }
+ }
+ *wp = '\0';
+ if(sp) *sp = wp - buf;
+ return buf;
+}
+
+
+/* Encode a serial object with Base64 encoding. */
+char *cbbaseencode(const char *ptr, int size){
+ char *tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ char *buf, *wp;
+ const unsigned char *obj;
+ int i;
+ assert(ptr);
+ if(size < 0) size = strlen(ptr);
+ CB_MALLOC(buf, 4 * (size + 2) / 3 + 1);
+ obj = (const unsigned char *)ptr;
+ wp = buf;
+ for(i = 0; i < size; i += 3){
+ switch(size - i){
+ case 1:
+ *wp++ = tbl[obj[0] >> 2];
+ *wp++ = tbl[(obj[0] & 3) << 4];
+ *wp++ = '=';
+ *wp++ = '=';
+ break;
+ case 2:
+ *wp++ = tbl[obj[0] >> 2];
+ *wp++ = tbl[((obj[0] & 3) << 4) + (obj[1] >> 4)];
+ *wp++ = tbl[(obj[1] & 0xf) << 2];
+ *wp++ = '=';
+ break;
+ default:
+ *wp++ = tbl[obj[0] >> 2];
+ *wp++ = tbl[((obj[0] & 3) << 4) + (obj[1] >> 4)];
+ *wp++ = tbl[((obj[1] & 0xf) << 2) + (obj[2] >> 6)];
+ *wp++ = tbl[obj[2] & 0x3f];
+ break;
+ }
+ obj += 3;
+ }
+ *wp = '\0';
+ return buf;
+}
+
+
+/* Decode a string encoded with Base64 encoding. */
+char *cbbasedecode(const char *str, int *sp){
+ unsigned char *obj, *wp;
+ int len, cnt, bpos, i, bits, eqcnt;
+ assert(str);
+ cnt = 0;
+ bpos = 0;
+ eqcnt = 0;
+ len = strlen(str);
+ CB_MALLOC(obj, len + 4);
+ wp = obj;
+ while(bpos < len && eqcnt == 0){
+ bits = 0;
+ for(i = 0; bpos < len && i < 4; bpos++){
+ if(str[bpos] >= 'A' && str[bpos] <= 'Z'){
+ bits = (bits << 6) | (str[bpos] - 'A');
+ i++;
+ } else if(str[bpos] >= 'a' && str[bpos] <= 'z'){
+ bits = (bits << 6) | (str[bpos] - 'a' + 26);
+ i++;
+ } else if(str[bpos] >= '0' && str[bpos] <= '9'){
+ bits = (bits << 6) | (str[bpos] - '0' + 52);
+ i++;
+ } else if(str[bpos] == '+'){
+ bits = (bits << 6) | 62;
+ i++;
+ } else if(str[bpos] == '/'){
+ bits = (bits << 6) | 63;
+ i++;
+ } else if(str[bpos] == '='){
+ bits <<= 6;
+ i++;
+ eqcnt++;
+ }
+ }
+ if(i == 0 && bpos >= len) continue;
+ switch(eqcnt){
+ case 0:
+ *wp++ = (bits >> 16) & 0xff;
+ *wp++ = (bits >> 8) & 0xff;
+ *wp++ = bits & 0xff;
+ cnt += 3;
+ break;
+ case 1:
+ *wp++ = (bits >> 16) & 0xff;
+ *wp++ = (bits >> 8) & 0xff;
+ cnt += 2;
+ break;
+ case 2:
+ *wp++ = (bits >> 16) & 0xff;
+ cnt += 1;
+ break;
+ }
+ }
+ obj[cnt] = '\0';
+ if(sp) *sp = cnt;
+ return (char *)obj;
+}
+
+
+/* Encode a serial object with quoted-printable encoding. */
+char *cbquoteencode(const char *ptr, int size){
+ const unsigned char *rp;
+ char *buf, *wp;
+ int i, cols;
+ assert(ptr);
+ if(size < 0) size = strlen(ptr);
+ rp = (const unsigned char *)ptr;
+ CB_MALLOC(buf, size * 3 + 1);
+ wp = buf;
+ cols = 0;
+ for(i = 0; i < size; i++){
+ if(rp[i] == '=' || (rp[i] < 0x20 && rp[i] != '\r' && rp[i] != '\n' && rp[i] != '\t') ||
+ rp[i] > 0x7e){
+ wp += sprintf(wp, "=%02X", rp[i]);
+ cols += 3;
+ } else {
+ *(wp++) = rp[i];
+ cols++;
+ }
+ }
+ *wp = '\0';
+ return buf;
+}
+
+
+/* Decode a string encoded with quoted-printable encoding. */
+char *cbquotedecode(const char *str, int *sp){
+ char *buf, *wp;
+ assert(str);
+ CB_MALLOC(buf, strlen(str) + 1);
+ wp = buf;
+ for(; *str != '\0'; str++){
+ if(*str == '='){
+ str++;
+ if(*str == '\0'){
+ break;
+ } else if(str[0] == '\r' && str[1] == '\n'){
+ str++;
+ } else if(str[0] != '\n' && str[0] != '\r'){
+ if(*str >= 'A' && *str <= 'Z'){
+ *wp = (*str - 'A' + 10) * 16;
+ } else if(*str >= 'a' && *str <= 'z'){
+ *wp = (*str - 'a' + 10) * 16;
+ } else {
+ *wp = (*str - '0') * 16;
+ }
+ str++;
+ if(*str == '\0') break;
+ if(*str >= 'A' && *str <= 'Z'){
+ *wp += *str - 'A' + 10;
+ } else if(*str >= 'a' && *str <= 'z'){
+ *wp += *str - 'a' + 10;
+ } else {
+ *wp += *str - '0';
+ }
+ wp++;
+ }
+ } else {
+ *wp = *str;
+ wp++;
+ }
+ }
+ *wp = '\0';
+ if(sp) *sp = wp - buf;
+ return buf;
+}
+
+
+/* Split a string of MIME into headers and the body. */
+char *cbmimebreak(const char *ptr, int size, CBMAP *attrs, int *sp){
+ CBLIST *list;
+ const char *head, *line, *pv, *ep;
+ char *hbuf, *name, *rv;
+ int i, j, wi, hlen;
+ assert(ptr);
+ if(size < 0) size = strlen(ptr);
+ head = NULL;
+ hlen = 0;
+ for(i = 0; i < size; i++){
+ if(i < size - 4 && ptr[i] == '\r' && ptr[i+1] == '\n' &&
+ ptr[i+2] == '\r' && ptr[i+3] == '\n'){
+ head = ptr;
+ hlen = i;
+ ptr += i + 4;
+ size -= i + 4;
+ break;
+ } else if(i < size - 2 && ptr[i] == '\n' && ptr[i+1] == '\n'){
+ head = ptr;
+ hlen = i;
+ ptr += i + 2;
+ size -= i + 2;
+ break;
+ }
+ }
+ if(head && attrs){
+ CB_MALLOC(hbuf, hlen + 1);
+ wi = 0;
+ for(i = 0; i < hlen; i++){
+ if(head[i] == '\r') continue;
+ if(i < hlen - 1 && head[i] == '\n' && (head[i+1] == ' ' || head[i+1] == '\t')){
+ hbuf[wi++] = ' ';
+ i++;
+ } else {
+ hbuf[wi++] = head[i];
+ }
+ }
+ list = cbsplit(hbuf, wi, "\n");
+ for(i = 0; i < CB_LISTNUM(list); i++){
+ line = CB_LISTVAL(list, i);
+ if((pv = strchr(line, ':')) != NULL){
+ CB_MEMDUP(name, line, pv - line);
+ for(j = 0; name[j] != '\0'; j++){
+ if(name[j] >= 'A' && name[j] <= 'Z') name[j] -= 'A' - 'a';
+ }
+ pv++;
+ while(*pv == ' ' || *pv == '\t'){
+ pv++;
+ }
+ cbmapput(attrs, name, -1, pv, -1, TRUE);
+ free(name);
+ }
+
+ }
+ CB_LISTCLOSE(list);
+ free(hbuf);
+ if((pv = cbmapget(attrs, "content-type", -1, NULL)) != NULL){
+ if((ep = strchr(pv, ';')) != NULL){
+ cbmapput(attrs, "TYPE", -1, pv, ep - pv, TRUE);
+ do {
+ ep++;
+ while(ep[0] == ' '){
+ ep++;
+ }
+ if(cbstrfwimatch(ep, "charset=")){
+ ep += 8;
+ while(*ep > '\0' && *ep <= ' '){
+ ep++;
+ }
+ if(ep[0] == '"') ep++;
+ pv = ep;
+ while(ep[0] != '\0' && ep[0] != ' ' && ep[0] != '"' && ep[0] != ';'){
+ ep++;
+ }
+ cbmapput(attrs, "CHARSET", -1, pv, ep - pv, TRUE);
+ } else if(cbstrfwimatch(ep, "boundary=")){
+ ep += 9;
+ while(*ep > '\0' && *ep <= ' '){
+ ep++;
+ }
+ if(ep[0] == '"'){
+ ep++;
+ pv = ep;
+ while(ep[0] != '\0' && ep[0] != '"'){
+ ep++;
+ }
+ } else {
+ pv = ep;
+ while(ep[0] != '\0' && ep[0] != ' ' && ep[0] != '"' && ep[0] != ';'){
+ ep++;
+ }
+ }
+ cbmapput(attrs, "BOUNDARY", -1, pv, ep - pv, TRUE);
+ }
+ } while((ep = strchr(ep, ';')) != NULL);
+ } else {
+ cbmapput(attrs, "TYPE", -1, pv, -1, TRUE);
+ }
+ }
+ if((pv = cbmapget(attrs, "content-disposition", -1, NULL)) != NULL){
+ if((ep = strchr(pv, ';')) != NULL){
+ cbmapput(attrs, "DISPOSITION", -1, pv, ep - pv, TRUE);
+ do {
+ ep++;
+ while(ep[0] == ' '){
+ ep++;
+ }
+ if(cbstrfwimatch(ep, "filename=")){
+ ep += 9;
+ if(ep[0] == '"') ep++;
+ pv = ep;
+ while(ep[0] != '\0' && ep[0] != '"'){
+ ep++;
+ }
+ cbmapput(attrs, "FILENAME", -1, pv, ep - pv, TRUE);
+ } else if(cbstrfwimatch(ep, "name=")){
+ ep += 5;
+ if(ep[0] == '"') ep++;
+ pv = ep;
+ while(ep[0] != '\0' && ep[0] != '"'){
+ ep++;
+ }
+ cbmapput(attrs, "NAME", -1, pv, ep - pv, TRUE);
+ }
+ } while((ep = strchr(ep, ';')) != NULL);
+ } else {
+ cbmapput(attrs, "DISPOSITION", -1, pv, -1, TRUE);
+ }
+ }
+ }
+ if(sp) *sp = size;
+ CB_MEMDUP(rv, ptr, size);
+ return rv;
+}
+
+
+/* Split multipart data in MIME into its parts. */
+CBLIST *cbmimeparts(const char *ptr, int size, const char *boundary){
+ CBLIST *list;
+ const char *pv, *ep;
+ int i, blen;
+ assert(ptr && boundary);
+ if(size < 0) size = strlen(ptr);
+ CB_LISTOPEN(list);
+ if((blen = strlen(boundary)) < 1) return list;
+ pv = NULL;
+ for(i = 0; i < size; i++){
+ if(ptr[i] == '-' && ptr[i+1] == '-' && i + 2 + blen < size &&
+ cbstrfwmatch(ptr + i + 2, boundary) && strchr("\t\n\v\f\r ", ptr[i+2+blen])){
+ pv = ptr + i + 2 + blen;
+ if(*pv == '\r') pv++;
+ if(*pv == '\n') pv++;
+ size -= pv - ptr;
+ ptr = pv;
+ break;
+ }
+ }
+ if(!pv) return list;
+ for(i = 0; i < size; i++){
+ if(ptr[i] == '-' && ptr[i+1] == '-' && i + 2 + blen < size &&
+ cbstrfwmatch(ptr + i + 2, boundary) && strchr("\t\n\v\f\r -", ptr[i+2+blen])){
+ ep = ptr + i;
+ if(ep > ptr && ep[-1] == '\n') ep--;
+ if(ep > ptr && ep[-1] == '\r') ep--;
+ if(ep > pv) CB_LISTPUSH(list, pv, ep - pv);
+ pv = ptr + i + 2 + blen;
+ if(*pv == '\r') pv++;
+ if(*pv == '\n') pv++;
+ }
+ }
+ return list;
+}
+
+
+/* Encode a string with MIME encoding. */
+char *cbmimeencode(const char *str, const char *encname, int base){
+ char *buf, *wp, *enc;
+ int len;
+ assert(str && encname);
+ len = strlen(str);
+ CB_MALLOC(buf, len * 3 + strlen(encname) + 16);
+ wp = buf;
+ wp += sprintf(wp, "=?%s?%c?", encname, base ? 'B' : 'Q');
+ enc = base ? cbbaseencode(str, len) : cbquoteencode(str, len);
+ wp += sprintf(wp, "%s?=", enc);
+ free(enc);
+ return buf;
+}
+
+
+/* Decode a string encoded with MIME encoding. */
+char *cbmimedecode(const char *str, char *enp){
+ char *buf, *wp, *tmp, *dec;
+ const char *pv, *ep;
+ int quoted;
+ assert(str);
+ if(enp) sprintf(enp, "US-ASCII");
+ CB_MALLOC(buf, strlen(str) + 1);
+ wp = buf;
+ while(*str != '\0'){
+ if(cbstrfwmatch(str, "=?")){
+ str += 2;
+ pv = str;
+ if(!(ep = strchr(str, '?'))) continue;
+ if(enp && ep - pv < CB_ENCBUFSIZ){
+ memcpy(enp, pv, ep - pv);
+ enp[ep-pv] = '\0';
+ }
+ pv = ep + 1;
+ quoted = (*pv == 'Q' || *pv == 'q');
+ if(*pv != '\0') pv++;
+ if(*pv != '\0') pv++;
+ if(!(ep = strchr(pv, '?'))) continue;
+ CB_MEMDUP(tmp, pv, ep - pv);
+ dec = quoted ? cbquotedecode(tmp, NULL) : cbbasedecode(tmp, NULL);
+ wp += sprintf(wp, "%s", dec);
+ free(dec);
+ free(tmp);
+ str = ep + 1;
+ if(*str != '\0') str++;
+ } else {
+ *(wp++) = *str;
+ str++;
+ }
+ }
+ *wp = '\0';
+ return buf;
+}
+
+
+/* Split a string of CSV into rows. */
+CBLIST *cbcsvrows(const char *str){
+ CBLIST *list;
+ const char *pv;
+ int quoted;
+ assert(str);
+ CB_LISTOPEN(list);
+ pv = str;
+ quoted = FALSE;
+ while(TRUE){
+ if(*str == '"') quoted = !quoted;
+ if(!quoted && (*str == '\r' || *str == '\n')){
+ CB_LISTPUSH(list, pv, str - pv);
+ if(str[0] == '\r' && str[1] == '\n') str++;
+ str++;
+ pv = str;
+ } else if(*str == '\0'){
+ if(str > pv) CB_LISTPUSH(list, pv, str - pv);
+ break;
+ } else {
+ str++;
+ }
+ }
+ return list;
+}
+
+
+/* Split a string of a row of CSV into cells. */
+CBLIST *cbcsvcells(const char *str){
+ CBLIST *list, *uelist;
+ const char *pv;
+ char *tmp;
+ int i, quoted;
+ assert(str);
+ CB_LISTOPEN(list);
+ pv = str;
+ quoted = FALSE;
+ while(TRUE){
+ if(*str == '"') quoted = !quoted;
+ if(!quoted && *str == ','){
+ CB_LISTPUSH(list, pv, str - pv);
+ str++;
+ pv = str;
+ } else if(*str == '\0'){
+ CB_LISTPUSH(list, pv, str - pv);
+ break;
+ } else {
+ str++;
+ }
+ }
+ CB_LISTOPEN(uelist);
+ for(i = 0; i < CB_LISTNUM(list); i++){
+ tmp = cbcsvunescape(CB_LISTVAL(list, i));
+ CB_LISTPUSH(uelist, tmp, strlen(tmp));
+ free(tmp);
+ }
+ CB_LISTCLOSE(list);
+ return uelist;
+}
+
+
+/* Escape a string with the meta characters of CSV. */
+char *cbcsvescape(const char *str){
+ char *buf, *wp;
+ int i;
+ assert(str);
+ CB_MALLOC(buf, strlen(str) * 2 + 3);
+ wp = buf;
+ *(wp++) = '"';
+ for(i = 0; str[i] != '\0'; i++){
+ if(str[i] == '"') *(wp++) = '"';
+ *(wp++) = str[i];
+ }
+ *(wp++) = '"';
+ *wp = '\0';
+ return buf;
+}
+
+
+/* Unescape a string with the escaped meta characters of CSV. */
+char *cbcsvunescape(const char *str){
+ char *buf, *wp;
+ int i, len;
+ assert(str);
+ len = strlen(str);
+ if(str[0] == '"'){
+ str++;
+ len--;
+ if(str[len-1] == '"') len--;
+ }
+ CB_MALLOC(buf, len + 1);
+ wp = buf;
+ for(i = 0; i < len; i++){
+ if(str[i] == '"'){
+ if(str[i+1] == '"') *(wp++) = str[i++];
+ } else {
+ *(wp++) = str[i];
+ }
+ }
+ *wp = '\0';
+ return buf;
+}
+
+
+/* Split a string of XML into tags and text sections. */
+CBLIST *cbxmlbreak(const char *str, int cr){
+ CBLIST *list;
+ CBDATUM *datum;
+ int i, pv, tag;
+ char *ep;
+ assert(str);
+ CB_LISTOPEN(list);
+ i = 0;
+ pv = 0;
+ tag = FALSE;
+ while(TRUE){
+ if(str[i] == '\0'){
+ if(i > pv) CB_LISTPUSH(list, str + pv, i - pv);
+ break;
+ } else if(!tag && str[i] == '<'){
+ if(str[i+1] == '!' && str[i+2] == '-' && str[i+3] == '-'){
+ if(i > pv) CB_LISTPUSH(list, str + pv, i - pv);
+ if((ep = strstr(str + i, "-->")) != NULL){
+ if(!cr) CB_LISTPUSH(list, str + i, ep - str - i + 3);
+ i = ep - str + 2;
+ pv = i + 1;
+ }
+ } else if(str[i+1] == '!' && str[i+2] == '[' && cbstrfwimatch(str + i, "<![CDATA[")){
+ if(i > pv) CB_LISTPUSH(list, str + pv, i - pv);
+ if((ep = strstr(str + i, "]]>")) != NULL){
+ i += 9;
+ CB_DATUMOPEN(datum);
+ while(str + i < ep){
+ if(str[i] == '&'){
+ CB_DATUMCAT(datum, "&amp;", 5);
+ } else if(str[i] == '<'){
+ CB_DATUMCAT(datum, "&lt;", 4);
+ } else if(str[i] == '>'){
+ CB_DATUMCAT(datum, "&gt;", 4);
+ } else {
+ CB_DATUMCAT(datum, str + i, 1);
+ }
+ i++;
+ }
+ if(CB_DATUMSIZE(datum) > 0) CB_LISTPUSH(list, CB_DATUMPTR(datum), CB_DATUMSIZE(datum));
+ CB_DATUMCLOSE(datum);
+ i = ep - str + 2;
+ pv = i + 1;
+ }
+ } else {
+ if(i > pv) CB_LISTPUSH(list, str + pv, i - pv);
+ tag = TRUE;
+ pv = i;
+ }
+ } else if(tag && str[i] == '>'){
+ if(i > pv) CB_LISTPUSH(list, str + pv, i - pv + 1);
+ tag = FALSE;
+ pv = i + 1;
+ }
+ i++;
+ }
+ return list;
+}
+
+
+/* Get the map of attributes of a XML tag. */
+CBMAP *cbxmlattrs(const char *str){
+ CBMAP *map;
+ const unsigned char *rp, *key, *val;
+ char *copy, *raw;
+ int ksiz, vsiz;
+ assert(str);
+ map = cbmapopenex(CB_MAPPBNUM);
+ rp = (unsigned char *)str;
+ while(*rp == '<' || *rp == '/' || *rp == '?' || *rp == '!' || *rp == ' '){
+ rp++;
+ }
+ key = rp;
+ while(*rp > 0x20 && *rp != '/' && *rp != '>'){
+ rp++;
+ }
+ cbmapput(map, "", -1, (char *)key, rp - key, FALSE);
+ while(*rp != '\0'){
+ while(*rp != '\0' && (*rp <= 0x20 || *rp == '/' || *rp == '?' || *rp == '>')){
+ rp++;
+ }
+ key = rp;
+ while(*rp > 0x20 && *rp != '/' && *rp != '>' && *rp != '='){
+ rp++;
+ }
+ ksiz = rp - key;
+ while(*rp != '\0' && (*rp == '=' || *rp <= 0x20)){
+ rp++;
+ }
+ if(*rp == '"'){
+ rp++;
+ val = rp;
+ while(*rp != '\0' && *rp != '"'){
+ rp++;
+ }
+ vsiz = rp - val;
+ } else if(*rp == '\''){
+ rp++;
+ val = rp;
+ while(*rp != '\0' && *rp != '\''){
+ rp++;
+ }
+ vsiz = rp - val;
+ } else {
+ val = rp;
+ while(*rp > 0x20 && *rp != '"' && *rp != '\'' && *rp != '>'){
+ rp++;
+ }
+ vsiz = rp - val;
+ }
+ if(*rp != '\0') rp++;
+ if(ksiz > 0){
+ CB_MEMDUP(copy, (char *)val, vsiz);
+ raw = cbxmlunescape(copy);
+ cbmapput(map, (char *)key, ksiz, raw, -1, FALSE);
+ free(raw);
+ free(copy);
+ }
+ }
+ return map;
+}
+
+
+/* Escape a string with the meta characters of XML. */
+char *cbxmlescape(const char *str){
+ CBDATUM *datum;
+ assert(str);
+ CB_DATUMOPEN(datum);
+ while(*str != '\0'){
+ switch(*str){
+ case '&':
+ CB_DATUMCAT(datum, "&amp;", 5);
+ break;
+ case '<':
+ CB_DATUMCAT(datum, "&lt;", 4);
+ break;
+ case '>':
+ CB_DATUMCAT(datum, "&gt;", 4);
+ break;
+ case '"':
+ CB_DATUMCAT(datum, "&quot;", 6);
+ break;
+ case '\'':
+ CB_DATUMCAT(datum, "&apos;", 6);
+ break;
+ default:
+ CB_DATUMCAT(datum, str, 1);
+ break;
+ }
+ str++;
+ }
+ return cbdatumtomalloc(datum, NULL);
+}
+
+
+/* Unescape a string with the entity references of XML. */
+char *cbxmlunescape(const char *str){
+ CBDATUM *datum;
+ assert(str);
+ CB_DATUMOPEN(datum);
+ while(*str != '\0'){
+ if(*str == '&'){
+ if(cbstrfwmatch(str, "&amp;")){
+ CB_DATUMCAT(datum, "&", 1);
+ str += 5;
+ } else if(cbstrfwmatch(str, "&lt;")){
+ CB_DATUMCAT(datum, "<", 1);
+ str += 4;
+ } else if(cbstrfwmatch(str, "&gt;")){
+ CB_DATUMCAT(datum, ">", 1);
+ str += 4;
+ } else if(cbstrfwmatch(str, "&quot;")){
+ CB_DATUMCAT(datum, "\"", 1);
+ str += 6;
+ } else if(cbstrfwmatch(str, "&apos;")){
+ CB_DATUMCAT(datum, "'", 1);
+ str += 6;
+ } else {
+ CB_DATUMCAT(datum, str, 1);
+ str++;
+ }
+ } else {
+ CB_DATUMCAT(datum, str, 1);
+ str++;
+ }
+ }
+ return cbdatumtomalloc(datum, NULL);
+}
+
+
+/* Compress a serial object with ZLIB. */
+char *cbdeflate(const char *ptr, int size, int *sp){
+ assert(ptr && sp);
+ if(!_qdbm_deflate) return NULL;
+ return _qdbm_deflate(ptr, size, sp, _QDBM_ZMZLIB);
+}
+
+
+/* Decompress a serial object compressed with ZLIB. */
+char *cbinflate(const char *ptr, int size, int *sp){
+ assert(ptr && size >= 0);
+ if(!_qdbm_inflate) return NULL;
+ return _qdbm_inflate(ptr, size, sp, _QDBM_ZMZLIB);
+}
+
+
+/* Compress a serial object with GZIP. */
+char *cbgzencode(const char *ptr, int size, int *sp){
+ assert(ptr && sp);
+ if(!_qdbm_deflate) return NULL;
+ return _qdbm_deflate(ptr, size, sp, _QDBM_ZMGZIP);
+}
+
+
+/* Decompress a serial object compressed with GZIP. */
+char *cbgzdecode(const char *ptr, int size, int *sp){
+ assert(ptr && size >= 0);
+ if(!_qdbm_inflate) return NULL;
+ return _qdbm_inflate(ptr, size, sp, _QDBM_ZMGZIP);
+}
+
+
+/* Get the CRC32 checksum of a serial object. */
+unsigned int cbgetcrc(const char *ptr, int size){
+ assert(ptr);
+ if(!_qdbm_inflate) return 0;
+ return _qdbm_getcrc(ptr, size);
+}
+
+
+/* Compress a serial object with LZO. */
+char *cblzoencode(const char *ptr, int size, int *sp){
+ assert(ptr && sp);
+ if(!_qdbm_lzoencode) return NULL;
+ return _qdbm_lzoencode(ptr, size, sp);
+}
+
+
+/* Decompress a serial object compressed with LZO. */
+char *cblzodecode(const char *ptr, int size, int *sp){
+ assert(ptr && size >= 0);
+ if(!_qdbm_lzodecode) return NULL;
+ return _qdbm_lzodecode(ptr, size, sp);
+}
+
+
+/* Compress a serial object with BZIP2. */
+char *cbbzencode(const char *ptr, int size, int *sp){
+ assert(ptr && sp);
+ if(!_qdbm_bzencode) return NULL;
+ return _qdbm_bzencode(ptr, size, sp);
+}
+
+
+/* Decompress a serial object compressed with BZIP2. */
+char *cbbzdecode(const char *ptr, int size, int *sp){
+ assert(ptr && size >= 0);
+ if(!_qdbm_bzdecode) return NULL;
+ return _qdbm_bzdecode(ptr, size, sp);
+}
+
+
+/* Convert the character encoding of a string. */
+char *cbiconv(const char *ptr, int size, const char *icode, const char *ocode, int *sp, int *mp){
+ char *res;
+ assert(ptr && icode && ocode);
+ if(!_qdbm_iconv) return NULL;
+ if((res = _qdbm_iconv(ptr, size, icode, ocode, sp, mp)) != NULL) return res;
+ if(!cbstricmp(icode, ocode)){
+ if(sp) *sp = size;
+ if(mp) *mp = 0;
+ CB_MEMDUP(res, ptr, size < 0 ? strlen(ptr) : size);
+ return res;
+ }
+ return NULL;
+}
+
+
+/* Detect the encoding of a string automatically. */
+const char *cbencname(const char *ptr, int size){
+ assert(ptr);
+ if(!_qdbm_encname) return "ISO-8859-1";
+ return _qdbm_encname(ptr, size);
+}
+
+
+/* Get the jet lag of the local time in seconds. */
+int cbjetlag(void){
+ struct tm ts, *tp;
+ time_t t, gt, lt;
+ if((t = time(NULL)) < 0) return 0;
+ if(!(tp = _qdbm_gmtime(&t, &ts))) return 0;
+ if((gt = mktime(tp)) < 0) return 0;
+ if(!(tp = _qdbm_localtime(&t, &ts))) return 0;
+ if((lt = mktime(tp)) < 0) return 0;
+ return lt - gt;
+}
+
+
+/* Get the Gregorian calendar of a time. */
+void cbcalendar(time_t t, int jl, int *yearp, int *monp, int *dayp,
+ int *hourp, int *minp, int *secp){
+ struct tm ts, *tp;
+ if(t < 0) t = time(NULL);
+ t += jl;
+ if(!(tp = _qdbm_gmtime(&t, &ts))) return;
+ if(yearp) *yearp = tp->tm_year + 1900;
+ if(monp) *monp = tp->tm_mon + 1;
+ if(dayp) *dayp = tp->tm_mday;
+ if(hourp) *hourp = tp->tm_hour;
+ if(minp) *minp = tp->tm_min;
+ if(secp) *secp = tp->tm_sec;
+}
+
+
+/* Get the day of week of a date. */
+int cbdayofweek(int year, int mon, int day){
+ if(mon < 3){
+ year--;
+ mon += 12;
+ }
+ return (day + ((8 + (13 * mon)) / 5) + (year + (year / 4) - (year / 100) + (year / 400))) % 7;
+}
+
+
+/* Get the string for a date in W3CDTF. */
+char *cbdatestrwww(time_t t, int jl){
+ char date[CB_DATEBUFSIZ], tzone[CB_DATEBUFSIZ], *rv;
+ int year, mon, day, hour, min, sec;
+ cbcalendar(t, jl, &year, &mon, &day, &hour, &min, &sec);
+ jl /= 60;
+ if(jl == 0){
+ sprintf(tzone, "Z");
+ } else if(jl < 0){
+ jl *= -1;
+ sprintf(tzone, "-%02d:%02d", jl / 60, jl % 60);
+ } else {
+ sprintf(tzone, "+%02d:%02d", jl / 60, jl % 60);
+ }
+ sprintf(date, "%04d-%02d-%02dT%02d:%02d:%02d%s", year, mon, day, hour, min, sec, tzone);
+ CB_MEMDUP(rv, date, strlen(date));
+ return rv;
+}
+
+
+/* Get the string for a date in RFC 1123 format. */
+char *cbdatestrhttp(time_t t, int jl){
+ char date[CB_DATEBUFSIZ], *wp, *rv;
+ int year, mon, day, hour, min, sec;
+ cbcalendar(t, jl, &year, &mon, &day, &hour, &min, &sec);
+ jl /= 60;
+ wp = date;
+ switch(cbdayofweek(year, mon, day)){
+ case 0: wp += sprintf(wp, "Sun, "); break;
+ case 1: wp += sprintf(wp, "Mon, "); break;
+ case 2: wp += sprintf(wp, "Tue, "); break;
+ case 3: wp += sprintf(wp, "Wed, "); break;
+ case 4: wp += sprintf(wp, "Thu, "); break;
+ case 5: wp += sprintf(wp, "Fri, "); break;
+ case 6: wp += sprintf(wp, "Sat, "); break;
+ }
+ wp += sprintf(wp, "%02d ", day);
+ switch(mon){
+ case 1: wp += sprintf(wp, "Jan "); break;
+ case 2: wp += sprintf(wp, "Feb "); break;
+ case 3: wp += sprintf(wp, "Mar "); break;
+ case 4: wp += sprintf(wp, "Apr "); break;
+ case 5: wp += sprintf(wp, "May "); break;
+ case 6: wp += sprintf(wp, "Jun "); break;
+ case 7: wp += sprintf(wp, "Jul "); break;
+ case 8: wp += sprintf(wp, "Aug "); break;
+ case 9: wp += sprintf(wp, "Sep "); break;
+ case 10: wp += sprintf(wp, "Oct "); break;
+ case 11: wp += sprintf(wp, "Nov "); break;
+ case 12: wp += sprintf(wp, "Dec "); break;
+ }
+ wp += sprintf(wp, "%04d %02d:%02d:%02d ", year, hour, min, sec);
+ if(jl == 0){
+ wp += sprintf(wp, "GMT");
+ } else if(jl < 0){
+ jl *= -1;
+ wp += sprintf(wp, "-%02d%02d", jl / 60, jl % 60);
+ } else {
+ wp += sprintf(wp, "+%02d%02d", jl / 60, jl % 60);
+ }
+ CB_MEMDUP(rv, date, strlen(date));
+ return rv;
+}
+
+
+/* Get the time value of a date string in decimal, W3CDTF, or RFC 1123. */
+time_t cbstrmktime(const char *str){
+ const char *crp;
+ char *pv, *rp;
+ int len, clen;
+ time_t t;
+ struct tm ts;
+ assert(str);
+ while(*str > '\0' && *str <= ' '){
+ str++;
+ }
+ if(*str == '\0') return -1;
+ if(str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) return (time_t)strtol(str + 2, NULL, 16);
+ memset(&ts, 0, sizeof(struct tm));
+ ts.tm_year = 70;
+ ts.tm_mon = 0;
+ ts.tm_mday = 1;
+ ts.tm_hour = 0;
+ ts.tm_min = 0;
+ ts.tm_sec = 0;
+ ts.tm_isdst = 0;
+ len = strlen(str);
+ t = (time_t)strtol(str, &pv, 10);
+ if(*(signed char *)pv >= '\0' && *pv <= ' '){
+ while(*pv > '\0' && *pv <= ' '){
+ pv++;
+ }
+ if(*pv == '\0') return t;
+ }
+ if((pv[0] == 's' || pv[0] == 'S') && ((signed char *)pv)[1] >= '\0' && pv[1] <= ' ')
+ return t;
+ if((pv[0] == 'm' || pv[0] == 'M') && ((signed char *)pv)[1] >= '\0' && pv[1] <= ' ')
+ return t * 60;
+ if((pv[0] == 'h' || pv[0] == 'H') && ((signed char *)pv)[1] >= '\0' && pv[1] <= ' ')
+ return t * 60 * 60;
+ if((pv[0] == 'd' || pv[0] == 'D') && ((signed char *)pv)[1] >= '\0' && pv[1] <= ' ')
+ return t * 60 * 60 * 24;
+ if(len > 4 && str[4] == '-'){
+ ts.tm_year = atoi(str) - 1900;
+ if((pv = strchr(str, '-')) != NULL && pv - str == 4){
+ rp = pv + 1;
+ ts.tm_mon = atoi(rp) - 1;
+ if((pv = strchr(rp, '-')) != NULL && pv - str == 7){
+ rp = pv + 1;
+ ts.tm_mday = atoi(rp);
+ if((pv = strchr(rp, 'T')) != NULL && pv - str == 10){
+ rp = pv + 1;
+ ts.tm_hour = atoi(rp);
+ if((pv = strchr(rp, ':')) != NULL && pv - str == 13){
+ rp = pv + 1;
+ ts.tm_min = atoi(rp);
+ }
+ if((pv = strchr(rp, ':')) != NULL && pv - str == 16){
+ rp = pv + 1;
+ ts.tm_sec = atoi(rp);
+ }
+ if((pv = strchr(rp, '.')) != NULL && pv - str >= 19) rp = pv + 1;
+ strtol(rp, &pv, 10);
+ if((*pv == '+' || *pv == '-') && strlen(pv) >= 6 && pv[3] == ':')
+ ts.tm_sec -= (atoi(pv + 1) * 3600 + atoi(pv + 4) * 60) * (pv[0] == '+' ? 1 : -1);
+ }
+ }
+ }
+ ts.tm_sec += cbjetlag();
+ return mktime(&ts);
+ }
+ if(len > 4 && str[4] == '/'){
+ ts.tm_year = atoi(str) - 1900;
+ if((pv = strchr(str, '/')) != NULL && pv - str == 4){
+ rp = pv + 1;
+ ts.tm_mon = atoi(rp) - 1;
+ if((pv = strchr(rp, '/')) != NULL && pv - str == 7){
+ rp = pv + 1;
+ ts.tm_mday = atoi(rp);
+ if((pv = strchr(rp, ' ')) != NULL && pv - str == 10){
+ rp = pv + 1;
+ ts.tm_hour = atoi(rp);
+ if((pv = strchr(rp, ':')) != NULL && pv - str == 13){
+ rp = pv + 1;
+ ts.tm_min = atoi(rp);
+ }
+ if((pv = strchr(rp, ':')) != NULL && pv - str == 16){
+ rp = pv + 1;
+ ts.tm_sec = atoi(rp);
+ }
+ if((pv = strchr(rp, '.')) != NULL && pv - str >= 19) rp = pv + 1;
+ strtol(rp, &pv, 10);
+ if((*pv == '+' || *pv == '-') && strlen(pv) >= 6 && pv[3] == ':')
+ ts.tm_sec -= (atoi(pv + 1) * 3600 + atoi(pv + 4) * 60) * (pv[0] == '+' ? 1 : -1);
+ }
+ }
+ }
+ ts.tm_sec += cbjetlag();
+ return mktime(&ts);
+ }
+ crp = str;
+ if(len >= 4 && str[3] == ',') crp = str + 4;
+ while(*crp == ' '){
+ crp++;
+ }
+ ts.tm_mday = atoi(crp);
+ while((*crp >= '0' && *crp <= '9') || *crp == ' '){
+ crp++;
+ }
+ if(cbstrfwimatch(crp, "Jan")){
+ ts.tm_mon = 0;
+ } else if(cbstrfwimatch(crp, "Feb")){
+ ts.tm_mon = 1;
+ } else if(cbstrfwimatch(crp, "Mar")){
+ ts.tm_mon = 2;
+ } else if(cbstrfwimatch(crp, "Apr")){
+ ts.tm_mon = 3;
+ } else if(cbstrfwimatch(crp, "May")){
+ ts.tm_mon = 4;
+ } else if(cbstrfwimatch(crp, "Jun")){
+ ts.tm_mon = 5;
+ } else if(cbstrfwimatch(crp, "Jul")){
+ ts.tm_mon = 6;
+ } else if(cbstrfwimatch(crp, "Aug")){
+ ts.tm_mon = 7;
+ } else if(cbstrfwimatch(crp, "Sep")){
+ ts.tm_mon = 8;
+ } else if(cbstrfwimatch(crp, "Oct")){
+ ts.tm_mon = 9;
+ } else if(cbstrfwimatch(crp, "Nov")){
+ ts.tm_mon = 10;
+ } else if(cbstrfwimatch(crp, "Dec")){
+ ts.tm_mon = 11;
+ } else {
+ ts.tm_mon = -1;
+ }
+ if(ts.tm_mon >= 0) crp += 3;
+ while(*crp == ' '){
+ crp++;
+ }
+ ts.tm_year = atoi(crp);
+ if(ts.tm_year >= 1969) ts.tm_year -= 1900;
+ while(*crp >= '0' && *crp <= '9'){
+ crp++;
+ }
+ while(*crp == ' '){
+ crp++;
+ }
+ if(ts.tm_mday > 0 && ts.tm_mon >= 0 && ts.tm_year >= 0){
+ clen = strlen(crp);
+ if(clen >= 8 && crp[2] == ':' && crp[5] == ':'){
+ ts.tm_hour = atoi(crp + 0);
+ ts.tm_min = atoi(crp + 3);
+ ts.tm_sec = atoi(crp + 6);
+ if(clen >= 14 && crp[8] == ' ' && (crp[9] == '+' || crp[9] == '-')){
+ ts.tm_sec -= ((crp[10] - '0') * 36000 + (crp[11] - '0') * 3600 +
+ (crp[12] - '0') * 600 + (crp[13] - '0') * 60) * (crp[9] == '+' ? 1 : -1);
+ } else if(clen > 9){
+ if(!strcmp(crp + 9, "JST")){
+ ts.tm_sec -= 9 * 3600;
+ } else if(!strcmp(crp + 9, "CCT")){
+ ts.tm_sec -= 8 * 3600;
+ } else if(!strcmp(crp + 9, "KST")){
+ ts.tm_sec -= 9 * 3600;
+ } else if(!strcmp(crp + 9, "EDT")){
+ ts.tm_sec -= -4 * 3600;
+ } else if(!strcmp(crp + 9, "EST")){
+ ts.tm_sec -= -5 * 3600;
+ } else if(!strcmp(crp + 9, "CDT")){
+ ts.tm_sec -= -5 * 3600;
+ } else if(!strcmp(crp + 9, "CST")){
+ ts.tm_sec -= -6 * 3600;
+ } else if(!strcmp(crp + 9, "MDT")){
+ ts.tm_sec -= -6 * 3600;
+ } else if(!strcmp(crp + 9, "MST")){
+ ts.tm_sec -= -7 * 3600;
+ } else if(!strcmp(crp + 9, "PDT")){
+ ts.tm_sec -= -7 * 3600;
+ } else if(!strcmp(crp + 9, "PST")){
+ ts.tm_sec -= -8 * 3600;
+ } else if(!strcmp(crp + 9, "HDT")){
+ ts.tm_sec -= -9 * 3600;
+ } else if(!strcmp(crp + 9, "HST")){
+ ts.tm_sec -= -10 * 3600;
+ }
+ }
+ }
+ ts.tm_sec += cbjetlag();
+ return mktime(&ts);
+ }
+ return -1;
+}
+
+
+/* Get user and system processing times. */
+void cbproctime(double *usrp, double *sysp){
+ struct tms buf;
+ times(&buf);
+ if(usrp) *usrp = (double)buf.tms_utime / sysconf(_SC_CLK_TCK);
+ if(sysp) *sysp = (double)buf.tms_stime / sysconf(_SC_CLK_TCK);
+}
+
+
+/* Ensure that the standard I/O is binary mode. */
+void cbstdiobin(void){
+ if(setmode(0, O_BINARY) == -1 || setmode(1, O_BINARY) == -1 || setmode(2, O_BINARY) == -1)
+ cbmyfatal("setmode failed");
+}
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Show error message on the standard error output and exit. */
+void *cbmyfatal(const char *message){
+ char buf[CB_MSGBUFSIZ];
+ assert(message);
+ if(cbfatalfunc){
+ cbfatalfunc(message);
+ } else {
+ sprintf(buf, "fatal error: %s\n", message);
+ write(2, buf, strlen(buf));
+ }
+ exit(1);
+ return NULL;
+}
+
+
+/* Create a datum handle from an allocated region. */
+CBDATUM *cbdatumopenbuf(char *ptr, int size){
+ CBDATUM *datum;
+ assert(ptr && size >= 0);
+ CB_REALLOC(ptr, size + 1);
+ CB_MALLOC(datum, sizeof(*datum));
+ datum->dptr = ptr;
+ datum->dptr[size] = '\0';
+ datum->dsize = size;
+ datum->asize = size;
+ return datum;
+}
+
+
+/* Set a buffer to a datum handle. */
+void cbdatumsetbuf(CBDATUM *datum, char *ptr, int size){
+ assert(datum && ptr && size >= 0);
+ free(datum->dptr);
+ CB_REALLOC(ptr, size + 1);
+ datum->dptr = ptr;
+ datum->dptr[size] = '\0';
+ datum->dsize = size;
+ datum->asize = size;
+}
+
+
+/* Add an allocated element at the end of a list. */
+void cblistpushbuf(CBLIST *list, char *ptr, int size){
+ int index;
+ assert(list && ptr && size >= 0);
+ index = list->start + list->num;
+ if(index >= list->anum){
+ list->anum *= 2;
+ CB_REALLOC(list->array, list->anum * sizeof(list->array[0]));
+ }
+ list->array[index].dptr = ptr;
+ list->array[index].dsize = size;
+ list->num++;
+}
+
+
+/* Get a map handle with specifying the number of buckets. */
+CBMAP *cbmapopenex(int bnum){
+ CBMAP *map;
+ int i;
+ assert(bnum > 0);
+ CB_MALLOC(map, sizeof(*map));
+ CB_MALLOC(map->buckets, sizeof(map->buckets[0]) * bnum);
+ for(i = 0; i < bnum; i++){
+ map->buckets[i] = NULL;
+ }
+ map->first = NULL;
+ map->last = NULL;
+ map->cur = NULL;
+ map->bnum = bnum;
+ map->rnum = 0;
+ return map;
+}
+
+
+
+/*************************************************************************************************
+ * private objects
+ *************************************************************************************************/
+
+
+/* Handler to invoke the global garbage collector. */
+static void cbggchandler(void){
+ cbggckeeper(NULL, NULL);
+}
+
+
+/* Manage resources of the global garbage collector.
+ `ptr' specifies the pointer to add to the collection. If it is `NULL', all resources are
+ released.
+ `func' specifies the pointer to the function to release the resources. */
+static void cbggckeeper(void *ptr, void (*func)(void *)){
+ static void **parray = NULL;
+ static void (**farray)(void *) = NULL;
+ static int onum = 0;
+ static int asiz = CB_GCUNIT;
+ int i;
+ if(!ptr){
+ if(!parray) return;
+ for(i = onum - 1; i >= 0; i--){
+ farray[i](parray[i]);
+ }
+ free(parray);
+ free(farray);
+ parray = NULL;
+ farray = NULL;
+ onum = 0;
+ asiz = CB_GCUNIT;
+ return;
+ }
+ if(!parray){
+ CB_MALLOC(parray, sizeof(void *) * asiz);
+ CB_MALLOC(farray, sizeof(void *) * asiz);
+ if(atexit(cbggchandler) != 0) cbmyfatal("gc failed");
+ }
+ if(onum >= asiz){
+ asiz *= 2;
+ CB_REALLOC(parray, sizeof(void *) * asiz);
+ CB_REALLOC(farray, sizeof(void *) * asiz);
+ }
+ parray[onum] = ptr;
+ farray[onum] = func;
+ onum++;
+}
+
+
+/* Utility function for quick sort.
+ `bp' specifies the pointer to the pointer to an array.
+ `nmemb' specifies the number of elements of the array.
+ `size' specifies the size of each element.
+ `pswap' specifies the pointer to the swap region for a pivot.
+ `vswap' specifies the pointer to the swap region for elements.
+ `compar' specifies the pointer to comparing function. */
+static void cbqsortsub(char *bp, int nmemb, int size, char *pswap, char *vswap,
+ int(*compar)(const void *, const void *)){
+ int top, bottom;
+ assert(bp && nmemb >= 0 && size > 0 && pswap && vswap && compar);
+ if(nmemb < 10){
+ if(nmemb > 1) cbisort(bp, nmemb, size, compar);
+ return;
+ }
+ top = 0;
+ bottom = nmemb - 1;
+ memcpy(pswap, bp + (nmemb / 2) * size, size);
+ while(top - 1 < bottom){
+ if(compar(bp + top * size, pswap) < 0){
+ top++;
+ } else if(compar(bp + bottom * size, pswap) > 0){
+ bottom--;
+ } else {
+ if(top != bottom){
+ memcpy(vswap, bp + top * size, size);
+ memcpy(bp + top * size, bp + bottom * size, size);
+ memcpy(bp + bottom * size, vswap, size);
+ }
+ top++;
+ bottom--;
+ }
+ }
+ cbqsortsub(bp, top, size, pswap, vswap, compar);
+ cbqsortsub(bp + (bottom + 1) * size, nmemb - bottom - 1, size, pswap, vswap, compar);
+}
+
+
+/* Compare two list elements.
+ `a' specifies the pointer to one element.
+ `b' specifies the pointer to the other element.
+ The return value is positive if a is big, negative if b is big, else, it is 0. */
+static int cblistelemcmp(const void *a, const void *b){
+ int i, size;
+ CBLISTDATUM *ap, *bp;
+ char *ao, *bo;
+ assert(a && b);
+ ap = (CBLISTDATUM *)a;
+ bp = (CBLISTDATUM *)b;
+ ao = ap->dptr;
+ bo = bp->dptr;
+ size = ap->dsize < bp->dsize ? ap->dsize : bp->dsize;
+ for(i = 0; i < size; i++){
+ if(ao[i] > bo[i]) return 1;
+ if(ao[i] < bo[i]) return -1;
+ }
+ return ap->dsize - bp->dsize;
+}
+
+
+/* Compare two keys.
+ `abuf' specifies the pointer to the region of the former.
+ `asiz' specifies the size of the region.
+ `bbuf' specifies the pointer to the region of the latter.
+ `bsiz' specifies the size of the region.
+ The return value is 0 if two equals, positive if the formar is big, else, negative. */
+static int cbkeycmp(const char *abuf, int asiz, const char *bbuf, int bsiz){
+ assert(abuf && asiz >= 0 && bbuf && bsiz >= 0);
+ if(asiz > bsiz) return 1;
+ if(asiz < bsiz) return -1;
+ return memcmp(abuf, bbuf, asiz);
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/cabin.h b/qdbm/cabin.h
new file mode 100644
index 00000000..230ec150
--- /dev/null
+++ b/qdbm/cabin.h
@@ -0,0 +1,1544 @@
+/*************************************************************************************************
+ * The utitlity API of QDBM
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#ifndef _CABIN_H /* duplication check */
+#define _CABIN_H
+
+#if defined(__cplusplus) /* export for C++ */
+extern "C" {
+#endif
+
+
+#include <stdlib.h>
+#include <time.h>
+
+
+#if defined(_MSC_VER) && !defined(QDBM_INTERNAL) && !defined(QDBM_STATIC)
+#define MYEXTERN extern __declspec(dllimport)
+#else
+#define MYEXTERN extern
+#endif
+
+
+
+/*************************************************************************************************
+ * API
+ *************************************************************************************************/
+
+
+#define CB_DATUMUNIT 12 /* allocation unit size of a datum handle */
+#define CB_LISTUNIT 64 /* allocation unit number of a list handle */
+#define CB_MAPBNUM 4093 /* bucket size of a map handle */
+
+typedef struct { /* type of structure for a basic datum */
+ char *dptr; /* pointer to the region */
+ int dsize; /* size of the region */
+ int asize; /* size of the allocated region */
+} CBDATUM;
+
+typedef struct { /* type of structure for an element of a list */
+ char *dptr; /* pointer to the region */
+ int dsize; /* size of the effective region */
+} CBLISTDATUM;
+
+typedef struct { /* type of structure for a list */
+ CBLISTDATUM *array; /* array of data */
+ int anum; /* number of the elements of the array */
+ int start; /* start index of using elements */
+ int num; /* number of using elements */
+} CBLIST;
+
+typedef struct _CBMAPDATUM { /* type of structure for an element of a map */
+ int ksiz; /* size of the region of the key */
+ int vsiz; /* size of the region of the value */
+ int hash; /* second hash value */
+ struct _CBMAPDATUM *left; /* pointer to the left child */
+ struct _CBMAPDATUM *right; /* pointer to the right child */
+ struct _CBMAPDATUM *prev; /* pointer to the previous element */
+ struct _CBMAPDATUM *next; /* pointer to the next element */
+} CBMAPDATUM;
+
+typedef struct { /* type of structure for a map */
+ CBMAPDATUM **buckets; /* bucket array */
+ CBMAPDATUM *first; /* pointer to the first element */
+ CBMAPDATUM *last; /* pointer to the last element */
+ CBMAPDATUM *cur; /* pointer to the current element */
+ int bnum; /* number of buckets */
+ int rnum; /* number of records */
+} CBMAP;
+
+typedef struct { /* type of structure for a heap */
+ char *base; /* base pointer */
+ char *swap; /* region for swapping */
+ int size; /* size of each record */
+ int num; /* currnet number of records */
+ int max; /* maximum number of records */
+ int(*compar)(const void *, const void *); /* comparing function */
+} CBHEAP;
+
+
+/* Call back function for handling a fatal error.
+ The argument specifies the error message. The initial value of this variable is `NULL'.
+ If the value is `NULL', the default function is called when a fatal error occurs. A fatal
+ error occurs when memory allocation is failed. */
+MYEXTERN void (*cbfatalfunc)(const char *);
+
+
+/* Allocate a region on memory.
+ `size' specifies the size of the region.
+ The return value is the pointer to the allocated region.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+void *cbmalloc(size_t size);
+
+
+/* Re-allocate a region on memory.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region.
+ The return value is the pointer to the re-allocated region.
+ Because the region of the return value is allocated with the `realloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+void *cbrealloc(void *ptr, size_t size);
+
+
+/* Duplicate a region on memory.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ The return value is the pointer to the allocated region of the duplicate.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if
+ it is no longer in use. */
+char *cbmemdup(const char *ptr, int size);
+
+
+/* Free a region on memory.
+ `ptr' specifies the pointer to a region. If it is `NULL', this function has no effect.
+ Although this function is just a wrapper of `free' call, this is useful in applications using
+ another package of the `malloc' series. */
+void cbfree(void *ptr);
+
+
+/* Register the pointer or handle of an object to the global garbage collector.
+ `ptr' specifies the pointer or handle of an object.
+ `func' specifies the pointer to a function to release resources of the object. Its argument
+ is the pointer or handle of the object to release.
+ This function assures that resources of an object are released when the process exits
+ normally by returning from the `main' function or calling the `exit' function. */
+void cbglobalgc(void *ptr, void (*func)(void *));
+
+
+/* Exercise the global garbage collector explicitly.
+ Note that you should not use objects registered to the global garbage collector any longer
+ after calling this function. Because the global garbage collecter is initialized and you
+ can register new objects into it. */
+void cbggcsweep(void);
+
+
+/* Check availability of allocation of the virtual memory.
+ `size' specifies the size of region to be allocated newly.
+ The return value is true if allocation should be success, or false if not. */
+int cbvmemavail(size_t size);
+
+
+/* Sort an array using insert sort.
+ `base' spacifies the pointer to an array.
+ `nmemb' specifies the number of elements of the array.
+ `size' specifies the size of each element.
+ `compar' specifies the pointer to comparing function. The two arguments specify the pointers
+ of elements. The comparing function should returns positive if the former is big, negative
+ if the latter is big, 0 if both are equal.
+ Insert sort is useful only if most elements have been sorted already. */
+void cbisort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
+
+
+/* Sort an array using shell sort.
+ `base' spacifies the pointer to an array.
+ `nmemb' specifies the number of elements of the array.
+ `size' specifies the size of each element.
+ `compar' specifies the pointer to comparing function. The two arguments specify the pointers
+ of elements. The comparing function should returns positive if the former is big, negative
+ if the latter is big, 0 if both are equal.
+ If most elements have been sorted, shell sort may be faster than heap sort or quick sort. */
+void cbssort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
+
+
+/* Sort an array using heap sort.
+ `base' spacifies the pointer to an array.
+ `nmemb' specifies the number of elements of the array.
+ `size' specifies the size of each element.
+ `compar' specifies the pointer to comparing function. The two arguments specify the pointers
+ of elements. The comparing function should returns positive if the former is big, negative
+ if the latter is big, 0 if both are equal.
+ Although heap sort is robust against bias of input, quick sort is faster in most cases. */
+void cbhsort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
+
+
+/* Sort an array using quick sort.
+ `base' spacifies the pointer to an array.
+ `nmemb' specifies the number of elements of the array.
+ `size' specifies the size of each element.
+ `compar' specifies the pointer to comparing function. The two arguments specify the pointers
+ of elements. The comparing function should returns positive if the former is big, negative
+ if the latter is big, 0 if both are equal.
+ Being sensitive to bias of input, quick sort is the fastest sorting algorithm. */
+void cbqsort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
+
+
+/* Compare two strings with case insensitive evaluation.
+ `astr' specifies the pointer of one string.
+ `astr' specifies the pointer of the other string.
+ The return value is positive if the former is big, negative if the latter is big, 0 if both
+ are equivalent.
+ Upper cases and lower cases of alphabets in ASCII code are not distinguished. */
+int cbstricmp(const char *astr, const char *bstr);
+
+
+/* Check whether a string begins with a key.
+ `str' specifies the pointer of a target string.
+ `key' specifies the pointer of a forward matching key string.
+ The return value is true if the target string begins with the key, else, it is false. */
+int cbstrfwmatch(const char *str, const char *key);
+
+
+/* Check whether a string begins with a key, with case insensitive evaluation.
+ `str' specifies the pointer of a target string.
+ `key' specifies the pointer of a forward matching key string.
+ The return value is true if the target string begins with the key, else, it is false.
+ Upper cases and lower cases of alphabets in ASCII code are not distinguished. */
+int cbstrfwimatch(const char *str, const char *key);
+
+
+/* Check whether a string ends with a key.
+ `str' specifies the pointer of a target string.
+ `key' specifies the pointer of a backward matching key string.
+ The return value is true if the target string ends with the key, else, it is false. */
+int cbstrbwmatch(const char *str, const char *key);
+
+
+/* Check whether a string ends with a key, with case insensitive evaluation.
+ `str' specifies the pointer of a target string.
+ `key' specifies the pointer of a backward matching key string.
+ The return value is true if the target string ends with the key, else, it is false.
+ Upper cases and lower cases of alphabets in ASCII code are not distinguished. */
+int cbstrbwimatch(const char *str, const char *key);
+
+
+/* Locate a substring in a string using KMP method.
+ `haystack' specifies the pointer of a target string.
+ `needle' specifies the pointer of a substring to be found.
+ The return value is the pointer to the beginning of the substring or `NULL' if the substring
+ is not found.
+ In most cases, `strstr' as a built-in function of the compiler is faster than this function. */
+char *cbstrstrkmp(const char *haystack, const char *needle);
+
+
+/* Locate a substring in a string using BM method.
+ `haystack' specifies the pointer of a target string.
+ `needle' specifies the pointer of a substring to be found.
+ The return value is the pointer to the beginning of the substring or `NULL' if the substring
+ is not found.
+ In most cases, `strstr' as a built-in function of the compiler is faster than this function. */
+char *cbstrstrbm(const char *haystack, const char *needle);
+
+
+/* Convert the letters of a string to upper case.
+ `str' specifies the pointer of a string to convert.
+ The return value is the pointer to the string. */
+char *cbstrtoupper(char *str);
+
+
+/* Convert the letters of a string to lower case.
+ `str' specifies the pointer of a string to convert.
+ The return value is the pointer to the string. */
+char *cbstrtolower(char *str);
+
+
+/* Cut space characters at head or tail of a string.
+ `str' specifies the pointer of a string to convert.
+ The return value is the pointer to the string. */
+char *cbstrtrim(char *str);
+
+
+/* Squeeze space characters in a string and trim it.
+ `str' specifies the pointer of a string to convert.
+ The return value is the pointer to the string. */
+char *cbstrsqzspc(char *str);
+
+
+/* Count the number of characters in a string of UTF-8.
+ `str' specifies the pointer of a string of UTF-8.
+ The return value is the number of characters in the string. */
+int cbstrcountutf(const char *str);
+
+
+/* Cut a string of UTF-8 at the specified number of characters.
+ `str' specifies the pointer of a string of UTF-8.
+ `num' specifies the number of characters to be kept.
+ The return value is the pointer to the string. */
+char *cbstrcututf(char *str, int num);
+
+
+/* Get a datum handle.
+ `ptr' specifies the pointer to the region of the initial content. If it is `NULL', an empty
+ datum is created.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ The return value is a datum handle. */
+CBDATUM *cbdatumopen(const char *ptr, int size);
+
+
+/* Copy a datum.
+ `datum' specifies a datum handle.
+ The return value is a new datum handle. */
+CBDATUM *cbdatumdup(const CBDATUM *datum);
+
+
+/* Free a datum handle.
+ `datum' specifies a datum handle.
+ Because the region of a closed handle is released, it becomes impossible to use the handle. */
+void cbdatumclose(CBDATUM *datum);
+
+
+/* Concatenate a datum and a region.
+ `datum' specifies a datum handle.
+ `ptr' specifies the pointer to the region to be appended.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'. */
+void cbdatumcat(CBDATUM *datum, const char *ptr, int size);
+
+
+/* Get the pointer of the region of a datum.
+ `datum' specifies a datum handle.
+ The return value is the pointer of the region of a datum.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. */
+const char *cbdatumptr(const CBDATUM *datum);
+
+
+/* Get the size of the region of a datum.
+ `datum' specifies a datum handle.
+ The return value is the size of the region of a datum. */
+int cbdatumsize(const CBDATUM *datum);
+
+
+/* Change the size of the region of a datum.
+ `datum' specifies a datum handle.
+ `size' specifies the new size of the region.
+ If the new size is bigger than the one of old, the surplus region is filled with zero codes. */
+void cbdatumsetsize(CBDATUM *datum, int size);
+
+
+/* Perform formatted output into a datum.
+ `format' specifies a printf-like format string.
+ The conversion character `%' can be used with such flag characters as `s', `d', `o', `u',
+ `x', `X', `c', `e', `E', `f', `g', `G', `@', `?', `:', `%'. `@' works as with `s' but escapes
+ meta characters of XML. `?' works as with `s' but escapes meta characters of URL. `:' works
+ as with `s' but performs MIME encoding as UTF-8. The other conversion character work as with
+ each original. */
+void cbdatumprintf(CBDATUM *datum, const char *format, ...);
+
+
+/* Convert a datum to an allocated region.
+ `datum' specifies a datum handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ The return value is the pointer to the region of the datum.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. Because the region of the original datam is released, it should not be
+ released again. */
+char *cbdatumtomalloc(CBDATUM *datum, int *sp);
+
+
+/* Get a list handle.
+ The return value is a list handle. */
+CBLIST *cblistopen(void);
+
+
+/* Copy a list.
+ `list' specifies a list handle.
+ The return value is a new list handle. */
+CBLIST *cblistdup(const CBLIST *list);
+
+
+/* Close a list handle.
+ `list' specifies a list handle.
+ Because the region of a closed handle is released, it becomes impossible to use the handle. */
+void cblistclose(CBLIST *list);
+
+
+/* Get the number of elements of a list.
+ `list' specifies a list handle.
+ The return value is the number of elements of the list. */
+int cblistnum(const CBLIST *list);
+
+
+/* Get the pointer to the region of an element of a list.
+ `list' specifies a list handle.
+ `index' specifies the index of an element.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ The return value is the pointer to the region of the value.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. If `index' is equal to or more than
+ the number of elements, the return value is `NULL'. */
+const char *cblistval(const CBLIST *list, int index, int *sp);
+
+
+/* Add an element at the end of a list.
+ `list' specifies a list handle.
+ `ptr' specifies the pointer to the region of an element.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'. */
+void cblistpush(CBLIST *list, const char *ptr, int size);
+
+
+/* Remove an element of the end of a list.
+ `list' specifies a list handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ The return value is the pointer to the region of the value.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. If the list is empty, the return value is `NULL'. */
+char *cblistpop(CBLIST *list, int *sp);
+
+
+/* Add an element at the top of a list.
+ `list' specifies a list handle.
+ `ptr' specifies the pointer to the region of an element.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'. */
+void cblistunshift(CBLIST *list, const char *ptr, int size);
+
+
+/* Remove an element of the top of a list.
+ `list' specifies a list handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ The return value is the pointer to the region of the value.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. If the list is empty, the return value is `NULL'. */
+char *cblistshift(CBLIST *list, int *sp);
+
+
+/* Add an element at the specified location of a list.
+ `list' specifies a list handle.
+ `index' specifies the index of an element.
+ `ptr' specifies the pointer to the region of the element.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'. */
+void cblistinsert(CBLIST *list, int index, const char *ptr, int size);
+
+
+/* Remove an element at the specified location of a list.
+ `list' specifies a list handle.
+ `index' specifies the index of an element.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ The return value is the pointer to the region of the value.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. If `index' is equal to or more than the number of elements, no element
+ is removed and the return value is `NULL'. */
+char *cblistremove(CBLIST *list, int index, int *sp);
+
+
+/* Overwrite an element at the specified location of a list.
+ `list' specifies a list handle.
+ `index' specifies the index of an element.
+ `ptr' specifies the pointer to the region of the new content.
+ `size' specifies the size of the new content. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ If `index' is equal to or more than the number of elements, this function has no effect. */
+void cblistover(CBLIST *list, int index, const char *ptr, int size);
+
+
+/* Sort elements of a list in lexical order.
+ `list' specifies a list handle.
+ Quick sort is used for sorting. */
+void cblistsort(CBLIST *list);
+
+
+/* Search a list for an element using liner search.
+ `list' specifies a list handle.
+ `ptr' specifies the pointer to the region of a key.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ The return value is the index of a corresponding element or -1 if there is no corresponding
+ element. If two or more elements corresponds, the former returns. */
+int cblistlsearch(const CBLIST *list, const char *ptr, int size);
+
+
+/* Search a list for an element using binary search.
+ `list' specifies a list handle. It should be sorted in lexical order.
+ `ptr' specifies the pointer to the region of a key.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ The return value is the index of a corresponding element or -1 if there is no corresponding
+ element. If two or more elements corresponds, which returns is not defined. */
+int cblistbsearch(const CBLIST *list, const char *ptr, int size);
+
+
+/* Serialize a list into a byte array.
+ `list' specifies a list handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned.
+ The return value is the pointer to the region of the result serial region.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *cblistdump(const CBLIST *list, int *sp);
+
+
+/* Redintegrate a serialized list.
+ `ptr' specifies the pointer to a byte array.
+ `size' specifies the size of the region.
+ The return value is a new list handle. */
+CBLIST *cblistload(const char *ptr, int size);
+
+
+/* Get a map handle.
+ The return value is a map handle. */
+CBMAP *cbmapopen(void);
+
+
+/* Copy a map.
+ `map' specifies a map handle.
+ The return value is a new map handle.
+ The iterator of the source map is initialized. */
+CBMAP *cbmapdup(CBMAP *map);
+
+
+/* Close a map handle.
+ `map' specifies a map handle.
+ Because the region of a closed handle is released, it becomes impossible to use the handle. */
+void cbmapclose(CBMAP *map);
+
+
+/* Store a record into a map.
+ `map' specifies a map handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `vbuf' specifies the pointer to the region of a value.
+ `vsiz' specifies the size of the region of the value. If it is negative, the size is
+ assigned with `strlen(vbuf)'.
+ `over' specifies whether the value of the duplicated record is overwritten or not.
+ If `over' is false and the key is duplicated, the return value is false, else, it is true. */
+int cbmapput(CBMAP *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int over);
+
+
+/* Concatenate a value at the end of the value of the existing record.
+ `map' specifies a map handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `vbuf' specifies the pointer to the region of a value.
+ `vsiz' specifies the size of the region of the value. If it is negative, the size is
+ assigned with `strlen(vbuf)'.
+ If there is no corresponding record, a new record is created. */
+void cbmapputcat(CBMAP *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz);
+
+
+/* Delete a record in a map.
+ `map' specifies a map handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ If successful, the return value is true. False is returned when no record corresponds to
+ the specified key. */
+int cbmapout(CBMAP *map, const char *kbuf, int ksiz);
+
+
+/* Retrieve a record in a map.
+ `map' specifies a map handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the value of the
+ corresponding record. `NULL' is returned when no record corresponds.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. */
+const char *cbmapget(const CBMAP *map, const char *kbuf, int ksiz, int *sp);
+
+
+/* Move a record to the edge of a map.
+ `map' specifies a map handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `head' specifies the destination which is head if it is true or tail if else.
+ If successful, the return value is true. False is returned when no record corresponds to
+ the specified key. */
+int cbmapmove(CBMAP *map, const char *kbuf, int ksiz, int head);
+
+
+/* Initialize the iterator of a map.
+ `map' specifies a map handle.
+ The iterator is used in order to access the key of every record stored in a map. */
+void cbmapiterinit(CBMAP *map);
+
+
+/* Get the next key of the iterator of a map.
+ `map' specifies a map handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the next key, else, it is
+ `NULL'. `NULL' is returned when no record is to be get out of the iterator.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. The order of iteration is assured
+ to be the same of the one of storing. */
+const char *cbmapiternext(CBMAP *map, int *sp);
+
+
+/* Get the value binded to the key fetched from the iterator of a map.
+ `kbuf' specifies the pointer to the region of a iteration key.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ The return value is the pointer to the region of the value of the corresponding record.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. */
+const char *cbmapiterval(const char *kbuf, int *sp);
+
+
+/* Get the number of the records stored in a map.
+ `map' specifies a map handle.
+ The return value is the number of the records stored in the map. */
+int cbmaprnum(const CBMAP *map);
+
+
+/* Get the list handle contains all keys in a map.
+ `map' specifies a map handle.
+ The return value is the list handle contains all keys in the map.
+ Because the handle of the return value is opened with the function `cblistopen', it should
+ be closed with the function `cblistclose' if it is no longer in use. */
+CBLIST *cbmapkeys(CBMAP *map);
+
+
+/* Get the list handle contains all values in a map.
+ `map' specifies a map handle.
+ The return value is the list handle contains all values in the map.
+ Because the handle of the return value is opened with the function `cblistopen', it should
+ be closed with the function `cblistclose' if it is no longer in use. */
+CBLIST *cbmapvals(CBMAP *map);
+
+
+/* Serialize a map into a byte array.
+ `map' specifies a map handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned.
+ The return value is the pointer to the region of the result serial region.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *cbmapdump(CBMAP *map, int *sp);
+
+
+/* Redintegrate a serialized map.
+ `ptr' specifies the pointer to a byte array.
+ `size' specifies the size of the region.
+ The return value is a new map handle. */
+CBMAP *cbmapload(const char *ptr, int size);
+
+
+/* Extract a record from a serialized map.
+ `ptr' specifies the pointer to a byte array.
+ `size' specifies the size of the region.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the value of the
+ corresponding record. `NULL' is returned when no record corresponds.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. */
+char *cbmaploadone(const char *ptr, int size, const char *kbuf, int ksiz, int *sp);
+
+
+/* Get a heap handle.
+ `size' specifies the size of each record.
+ `max' specifies the maximum number of records in the heap.
+ `compar' specifies the pointer to comparing function. The two arguments specify the pointers
+ of records. The comparing function should returns positive if the former is big, negative
+ if the latter is big, 0 if both are equal.
+ The return value is a heap handle. */
+CBHEAP *cbheapopen(int size, int max, int(*compar)(const void *, const void *));
+
+
+/* Copy a heap.
+ `heap' specifies a heap handle.
+ The return value is a new heap handle. */
+CBHEAP *cbheapdup(CBHEAP *heap);
+
+
+/* Close a heap handle.
+ `heap' specifies a heap handle.
+ Because the region of a closed handle is released, it becomes impossible to use the handle. */
+void cbheapclose(CBHEAP *heap);
+
+
+/* Get the number of the records stored in a heap.
+ `heap' specifies a heap handle.
+ The return value is the number of the records stored in the heap. */
+int cbheapnum(CBHEAP *heap);
+
+
+/* Insert a record into a heap.
+ `heap' specifies a heap handle.
+ `ptr' specifies the pointer to the region of a record.
+ The return value is true if the record is added, else false.
+ If the new record is bigger than the biggest existing regord, the new record is not added.
+ If the new record is added and the number of records exceeds the maximum number, the biggest
+ existing record is removed. */
+int cbheapinsert(CBHEAP *heap, const void *ptr);
+
+
+/* Get the pointer to the region of a record in a heap.
+ `heap' specifies a heap handle.
+ `index' specifies the index of a record.
+ The return value is the pointer to the region of the record.
+ If `index' is equal to or more than the number of records, the return value is `NULL'. Note
+ that records are organized by the nagative order the comparing function. */
+const void *cbheapval(CBHEAP *heap, int index);
+
+
+/* Convert a heap to an allocated region.
+ `heap' specifies a heap handle.
+ `np' specifies the pointer to a variable to which the number of records of the return value
+ is assigned. If it is `NULL', it is not used.
+ The return value is the pointer to the region of the heap. Records are sorted.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. Because the region of the original
+ heap is released, it should not be released again. */
+void *cbheaptomalloc(CBHEAP *heap, int *np);
+
+
+/* Allocate a formatted string on memory.
+ `format' specifies a printf-like format string. The conversion character `%' can be used
+ with such flag characters as `d', `o', `u', `x', `X', `e', `E', `f', `g', `G', `c', `s', and
+ `%'. Specifiers of the field length and the precision can be put between the conversion
+ characters and the flag characters. The specifiers consist of decimal characters, `.', `+',
+ `-', and the space character.
+ The other arguments are used according to the format string.
+ The return value is the pointer to the allocated region of the result string. Because the
+ region of the return value is allocated with the `malloc' call, it should be released with
+ the `free' call if it is no longer in use. */
+char *cbsprintf(const char *format, ...);
+
+
+/* Replace some patterns in a string.
+ `str' specifies the pointer to a source string.
+ `pairs' specifies the handle of a map composed of pairs of replacement. The key of each pair
+ specifies a pattern before replacement and its value specifies the pattern after replacement.
+ The return value is the pointer to the allocated region of the result string.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *cbreplace(const char *str, CBMAP *pairs);
+
+
+/* Make a list by splitting a serial datum.
+ `ptr' specifies the pointer to the region of the source content.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ `delim' specifies a string containing delimiting characters. If it is `NULL', zero code is
+ used as a delimiter.
+ The return value is a list handle.
+ If two delimiters are successive, it is assumed that an empty element is between the two.
+ Because the handle of the return value is opened with the function `cblistopen', it should
+ be closed with the function `cblistclose'. */
+CBLIST *cbsplit(const char *ptr, int size, const char *delim);
+
+
+/* Read whole data of a file.
+ `name' specifies the name of a file. If it is `NULL', the standard input is specified.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ The return value is the pointer to the allocated region of the read data. Because an
+ additional zero code is appended at the end of the region of the return value, the return
+ value can be treated as a character string. Because the region of the return value is
+ allocated with the `malloc' call, it should be released with the `free' call if it is no
+ longer in use. */
+char *cbreadfile(const char *name, int *sp);
+
+
+/* Write a serial datum into a file.
+ `name specifies the name of a file. If it is `NULL', the standard output is specified.
+ `ptr' specifies the pointer to the region of the source content.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ If successful, the return value is true, else, it is false.
+ If the file exists, it is overwritten. Else, a new file is created. */
+int cbwritefile(const char *name, const char *ptr, int size);
+
+
+/* Read every line of a file.
+ `name' specifies the name of a file. If it is `NULL', the standard input is specified.
+ The return value is a list handle of the lines if successful, else it is NULL. Line
+ separators are cut out. Because the handle of the return value is opened with the function
+ `cblistopen', it should be closed with the function `cblistclose' if it is no longer in use. */
+CBLIST *cbreadlines(const char *name);
+
+
+/* Read names of files in a directory.
+ `name' specifies the name of a directory.
+ The return value is a list handle of names if successful, else it is NULL.
+ Because the handle of the return value is opened with the function `cblistopen', it should
+ be closed with the function `cblistclose' if it is no longer in use. */
+CBLIST *cbdirlist(const char *name);
+
+
+/* Get the status of a file or a directory.
+ `name' specifies the name of a file or a directory.
+ `dirp' specifies the pointer to a variable to which whether the file is a directory is
+ assigned. If it is `NULL', it is not used.
+ `sizep' specifies the pointer to a variable to which the size of the file is assigned. If it
+ is `NULL', it is not used.
+ `mtimep' specifies the pointer to a variable to which the last modified time of the file is
+ assigned. If it is `NULL', it is not used.
+ If successful, the return value is true, else, false. False is returned when the file does
+ not exist or the permission is denied. */
+int cbfilestat(const char *name, int *isdirp, int *sizep, time_t *mtimep);
+
+
+/* Remove a file or a directory and its sub ones recursively.
+ `name' specifies the name of a file or a directory.
+ If successful, the return value is true, else, false. False is returned when the file does
+ not exist or the permission is denied. */
+int cbremove(const char *name);
+
+
+/* Break up a URL into elements.
+ `str' specifies the pointer to a string of URL.
+ The return value is a map handle. Each key of the map is the name of an element. The key
+ "self" specifies the URL itself. The key "scheme" specifies the scheme. The key "host"
+ specifies the host of the server. The key "port" specifies the port number of the server.
+ The key "authority" specifies the authority information. The key "path" specifies the path
+ of the resource. The key "file" specifies the file name without the directory section. The
+ key "query" specifies the query string. The key "fragment" specifies the fragment string.
+ Supported schema are HTTP, HTTPS, FTP, and FILE. Absolute URL and relative URL are supported.
+ Because the handle of the return value is opened with the function `cbmapopen', it should
+ be closed with the function `cbmapclose' if it is no longer in use. */
+CBMAP *cburlbreak(const char *str);
+
+
+/* Resolve a relative URL with another absolute URL.
+ `base' specifies an absolute URL of a base location.
+ `target' specifies a URL to be resolved.
+ The return value is a resolved URL. If the target URL is relative, a new URL of relative
+ location from the base location is returned. Else, a copy of the target URL is returned.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *cburlresolve(const char *base, const char *target);
+
+
+/* Encode a serial object with URL encoding.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ The return value is the pointer to the result string.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *cburlencode(const char *ptr, int size);
+
+
+/* Decode a string encoded with URL encoding.
+ `str' specifies the pointer to an encoded string.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ The return value is the pointer to the region of the result.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if
+ it is no longer in use. */
+char *cburldecode(const char *str, int *sp);
+
+
+/* Encode a serial object with Base64 encoding.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ The return value is the pointer to the result string.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *cbbaseencode(const char *ptr, int size);
+
+
+/* Decode a string encoded with Base64 encoding.
+ `str' specifies the pointer to an encoded string.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ The return value is the pointer to the region of the result.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if
+ it is no longer in use. */
+char *cbbasedecode(const char *str, int *sp);
+
+
+/* Encode a serial object with quoted-printable encoding.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ The return value is the pointer to the result string.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *cbquoteencode(const char *ptr, int size);
+
+
+/* Decode a string encoded with quoted-printable encoding.
+ `str' specifies the pointer to an encoded string.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ The return value is the pointer to the region of the result.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if
+ it is no longer in use. */
+char *cbquotedecode(const char *str, int *sp);
+
+
+/* Split a string of MIME into headers and the body.
+ `ptr' specifies the pointer to the region of MIME data.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ `attrs' specifies a map handle to store attributes. If it is `NULL', it is not used. Each
+ key of the map is an attribute name uncapitalized.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ The return value is the pointer of the body data.
+ If the content type is defined, the attribute map has the key "TYPE" specifying the type. If
+ the character encoding is defined, the key "CHARSET" specifies the encoding name. If the
+ boundary string of multipart is defined, the key "BOUNDARY" specifies the string. If the
+ content disposition is defined, the key "DISPOSITION" specifies the direction. If the file
+ name is defined, the key "FILENAME" specifies the name. If the attribute name is defined,
+ the key "NAME" specifies the name. Because the region of the return value is allocated with
+ the `malloc' call, it should be released with the `free' call if it is no longer in use. */
+char *cbmimebreak(const char *ptr, int size, CBMAP *attrs, int *sp);
+
+
+/* Split multipart data of MIME into its parts.
+ `ptr' specifies the pointer to the region of multipart data of MIME.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ `boundary' specifies the pointer to the region of the boundary string.
+ The return value is a list handle. Each element of the list is the string of a part.
+ Because the handle of the return value is opened with the function `cblistopen', it should
+ be closed with the function `cblistclose' if it is no longer in use. */
+CBLIST *cbmimeparts(const char *ptr, int size, const char *boundary);
+
+
+/* Encode a string with MIME encoding.
+ `str' specifies the pointer to a string.
+ `encname' specifies a string of the name of the character encoding.
+ The return value is the pointer to the result string.
+ `base' specifies whether to use Base64 encoding. If it is false, quoted-printable is used.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *cbmimeencode(const char *str, const char *encname, int base);
+
+
+/* Decode a string encoded with MIME encoding.
+ `str' specifies the pointer to an encoded string.
+ `enp' specifies the pointer to a region into which the name of encoding is written. If it is
+ `NULL', it is not used. The size of the buffer should be equal to or more than 32 bytes.
+ The return value is the pointer to the result string.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *cbmimedecode(const char *str, char *enp);
+
+
+/* Split a string of CSV into rows.
+ `str' specifies the pointer to the region of an CSV string.
+ The return value is a list handle. Each element of the list is a string of a row.
+ Because the handle of the return value is opened with the function `cblistopen', it should
+ be closed with the function `cblistclose' if it is no longer in use. The character encoding
+ of the input string should be US-ASCII, UTF-8, ISO-8859-*, EUC-*, or Shift_JIS. Being
+ compatible with MS-Excel, these functions for CSV can handle cells including such meta
+ characters as comma, between double quotation marks. */
+CBLIST *cbcsvrows(const char *str);
+
+
+/* Split the string of a row of CSV into cells.
+ `str' specifies the pointer to the region of a row of CSV.
+ The return value is a list handle. Each element of the list is the unescaped string of a
+ cell of the row.
+ Because the handle of the return value is opened with the function `cblistopen', it should
+ be closed with the function `cblistclose' if it is no longer in use. */
+CBLIST *cbcsvcells(const char *str);
+
+
+/* Escape a string with the meta characters of CSV.
+ `str' specifies the pointer to the region of a string.
+ The return value is the pointer to the escaped string sanitized of meta characters.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *cbcsvescape(const char *str);
+
+
+/* Unescape a string with the escaped meta characters of CSV.
+ `str' specifies the pointer to the region of a string with meta characters.
+ The return value is the pointer to the unescaped string.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *cbcsvunescape(const char *str);
+
+
+/* Split a string of XML into tags and text sections.
+ `str' specifies the pointer to the region of an XML string.
+ `cr' specifies whether to remove comments.
+ The return value is a list handle. Each element of the list is the string of a tag or a
+ text section.
+ Because the handle of the return value is opened with the function `cblistopen', it should
+ be closed with the function `cblistclose' if it is no longer in use. The character encoding
+ of the input string should be US-ASCII, UTF-8, ISO-8859-*, EUC-*, or Shift_JIS. Because
+ these functions for XML are not XML parser with validation check, it can handle also HTML
+ and SGML. */
+CBLIST *cbxmlbreak(const char *str, int cr);
+
+
+/* Get the map of attributes of an XML tag.
+ `str' specifies the pointer to the region of a tag string.
+ The return value is a map handle. Each key of the map is the name of an attribute. Each
+ value is unescaped. You can get the name of the tag with the key of an empty string.
+ Because the handle of the return value is opened with the function `cbmapopen', it should
+ be closed with the function `cbmapclose' if it is no longer in use. */
+CBMAP *cbxmlattrs(const char *str);
+
+
+/* Escape a string with the meta characters of XML.
+ `str' specifies the pointer to the region of a string.
+ The return value is the pointer to the escaped string sanitized of meta characters.
+ This function converts only `&', `<', `>', and `"'. Because the region of the return value
+ is allocated with the `malloc' call, it should be released with the `free' call if it is no
+ longer in use. */
+char *cbxmlescape(const char *str);
+
+
+/* Unescape a string with the entity references of XML.
+ `str' specifies the pointer to the region of a string with meta characters.
+ The return value is the pointer to the unescaped string.
+ This function restores only `&amp;', `&lt;', `&gt;', and `&quot;'. Because the region of the
+ return value is allocated with the `malloc' call, it should be released with the `free' call
+ if it is no longer in use. */
+char *cbxmlunescape(const char *str);
+
+
+/* Compress a serial object with ZLIB.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned.
+ If successful, the return value is the pointer to the result object, else, it is `NULL'.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. This function is available only if
+ QDBM was built with ZLIB enabled. */
+char *cbdeflate(const char *ptr, int size, int *sp);
+
+
+/* Decompress a serial object compressed with ZLIB.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the result object, else, it is `NULL'.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. This function is available only if QDBM was built with ZLIB enabled. */
+char *cbinflate(const char *ptr, int size, int *sp);
+
+
+/* Compress a serial object with GZIP.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned.
+ If successful, the return value is the pointer to the result object, else, it is `NULL'.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. This function is available only if
+ QDBM was built with ZLIB enabled. */
+char *cbgzencode(const char *ptr, int size, int *sp);
+
+
+/* Decompress a serial object compressed with GZIP.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the result object, else, it is `NULL'.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. This function is available only if QDBM was built with ZLIB enabled. */
+char *cbgzdecode(const char *ptr, int size, int *sp);
+
+
+/* Get the CRC32 checksum of a serial object.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ The return value is the CRC32 checksum of the object.
+ This function is available only if QDBM was built with ZLIB enabled. */
+unsigned int cbgetcrc(const char *ptr, int size);
+
+
+/* Compress a serial object with LZO.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned.
+ If successful, the return value is the pointer to the result object, else, it is `NULL'.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. This function is available only if
+ QDBM was built with LZO enabled. */
+char *cblzoencode(const char *ptr, int size, int *sp);
+
+
+/* Decompress a serial object compressed with LZO.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the result object, else, it is `NULL'.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. This function is available only if QDBM was built with LZO enabled. */
+char *cblzodecode(const char *ptr, int size, int *sp);
+
+
+/* Compress a serial object with BZIP2.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned.
+ If successful, the return value is the pointer to the result object, else, it is `NULL'.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. This function is available only if
+ QDBM was built with LZO enabled. */
+char *cbbzencode(const char *ptr, int size, int *sp);
+
+
+/* Decompress a serial object compressed with BZIP2.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the result object, else, it is `NULL'.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. This function is available only if QDBM was built with LZO enabled. */
+char *cbbzdecode(const char *ptr, int size, int *sp);
+
+
+/* Convert the character encoding of a string.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ `icode' specifies the name of encoding of the input string.
+ `ocode' specifies the name of encoding of the output string.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ `mp' specifies the pointer to a variable to which the number of missing characters by failure
+ of conversion is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the result object, else, it is `NULL'.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. This function is available only if QDBM was built with ICONV enabled. */
+char *cbiconv(const char *ptr, int size, const char *icode, const char *ocode, int *sp, int *mp);
+
+
+/* Detect the encoding of a string automatically.
+ `ptr' specifies the pointer to a region.
+ `size' specifies the size of the region. If it is negative, the size is assigned with
+ `strlen(ptr)'.
+ The return value is the string of the encoding name of the string.
+ As it stands, US-ASCII, ISO-2022-JP, Shift_JIS, CP932, EUC-JP, UTF-8, UTF-16, UTF-16BE,
+ and UTF-16LE are supported. If none of them matches, ISO-8859-1 is selected. This function
+ is available only if QDBM was built with ICONV enabled. */
+const char *cbencname(const char *ptr, int size);
+
+
+/* Get the jet lag of the local time in seconds.
+ The return value is the jet lag of the local time in seconds. */
+int cbjetlag(void);
+
+
+/* Get the Gregorian calendar of a time.
+ `t' specifies a source time. If it is negative, the current time is specified.
+ `jl' specifies the jet lag of a location in seconds.
+ `yearp' specifies the pointer to a variable to which the year is assigned. If it is `NULL',
+ it is not used.
+ `monp' specifies the pointer to a variable to which the month is assigned. If it is `NULL',
+ it is not used. 1 means January and 12 means December.
+ `dayp' specifies the pointer to a variable to which the day of the month is assigned. If it
+ is `NULL', it is not used.
+ `hourp' specifies the pointer to a variable to which the hours is assigned. If it is `NULL',
+ it is not used.
+ `minp' specifies the pointer to a variable to which the minutes is assigned. If it is `NULL',
+ it is not used.
+ `secp' specifies the pointer to a variable to which the seconds is assigned. If it is `NULL',
+ it is not used. */
+void cbcalendar(time_t t, int jl, int *yearp, int *monp, int *dayp,
+ int *hourp, int *minp, int *secp);
+
+
+/* Get the day of week of a date.
+ `year' specifies the year of a date.
+ `mon' specifies the month of the date.
+ `day' specifies the day of the date.
+ The return value is the day of week of the date. 0 means Sunday and 6 means Saturday. */
+int cbdayofweek(int year, int mon, int day);
+
+
+/* Get the string for a date in W3CDTF.
+ `t' specifies a source time. If it is negative, the current time is specified.
+ `jl' specifies the jet lag of a location in seconds.
+ The return value is the string of the date in W3CDTF (YYYY-MM-DDThh:mm:ddTZD).
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *cbdatestrwww(time_t t, int jl);
+
+
+/* Get the string for a date in RFC 1123 format.
+ `t' specifies a source time. If it is negative, the current time is specified.
+ `jl' specifies the jet lag of a location in seconds.
+ The return value is the string of the date in RFC 1123 format (Wdy, DD-Mon-YYYY hh:mm:dd TZD).
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *cbdatestrhttp(time_t t, int jl);
+
+
+/* Get the time value of a date string in decimal, hexadecimal, W3CDTF, or RFC 822 (1123).
+ `str' specifies a date string in decimal, hexadecimal, W3CDTF, or RFC 822 (1123).
+ The return value is the time value of the date or -1 if the format is invalid.
+ Decimal can be trailed by "s" for in seconds, "m" for in minutes, "h" for in hours,
+ and "d" for in days. */
+time_t cbstrmktime(const char *str);
+
+
+/* Get user and system processing times.
+ `usrp' specifies the pointer to a variable to which the user processing time is assigned.
+ If it is `NULL', it is not used. The unit of time is seconds.
+ `sysp' specifies the pointer to a variable to which the system processing time is assigned.
+ If it is `NULL', it is not used. The unit of time is seconds. */
+void cbproctime(double *usrp, double *sysp);
+
+
+/* Ensure that the standard I/O is binary mode.
+ This function is useful for applications on dosish file systems. */
+void cbstdiobin(void);
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Show error message on the standard error output and exit.
+ `message' specifies an error message.
+ This function does not return. */
+void *cbmyfatal(const char *message);
+
+
+/* Create a datum handle from an allocated region.
+ `ptr' specifies the pointer to the region of an element. The region should be allocated with
+ malloc and it is released by the function.
+ `size' specifies the size of the region. */
+CBDATUM *cbdatumopenbuf(char *ptr, int size);
+
+
+/* Set a buffer to a datum handle.
+ `ptr' specifies the pointer to the region of an element. The region should be allocated with
+ malloc and it is released by the function.
+ `size' specifies the size of the region. */
+void cbdatumsetbuf(CBDATUM *datum, char *ptr, int size);
+
+
+/* Add an allocated element at the end of a list.
+ `list' specifies a list handle.
+ `ptr' specifies the pointer to the region of an element. The region should be allocated with
+ malloc and it is released by the function.
+ `size' specifies the size of the region. */
+void cblistpushbuf(CBLIST *list, char *ptr, int size);
+
+
+/* Get a map handle with specifying the number of buckets.
+ `bnum' specifies the number of buckets.
+ The return value is a map handle. */
+CBMAP *cbmapopenex(int bnum);
+
+
+/* Alias of `cbmalloc'. */
+#define CB_MALLOC(CB_ptr, CB_size) \
+ (((CB_ptr) = malloc(CB_size)) ? (CB_ptr) : cbmyfatal("out of memory"))
+
+
+/* Alias of `cbrealloc'. */
+#define CB_REALLOC(CB_ptr, CB_size) \
+ (((CB_ptr) = realloc((CB_ptr), (CB_size))) ? (CB_ptr) : cbmyfatal("out of memory"))
+
+
+/* Alias of `cbmemdup'.
+ However, `size' should not be negative. */
+#define CB_MEMDUP(CB_res, CB_ptr, CB_size) \
+ do { \
+ CB_MALLOC((CB_res), (CB_size) + 1); \
+ memcpy((CB_res), (CB_ptr), (CB_size)); \
+ (CB_res)[(CB_size)] = '\0'; \
+ } while(FALSE)
+
+
+/* Get the size of padding bytes for pointer alignment.
+ `hsiz' specifies the header size of the object.
+ The return value is the size of padding bytes. */
+#define CB_ALIGNPAD(CB_hsiz) \
+ (((CB_hsiz | ~-(int)sizeof(void *)) + 1) - CB_hsiz)
+
+
+/* Alias of `cbdatumopen'.
+ However, no dafault data is specified. */
+#define CB_DATUMOPEN(CB_datum) \
+ do { \
+ CB_MALLOC((CB_datum), sizeof(*(CB_datum))); \
+ CB_MALLOC((CB_datum)->dptr, CB_DATUMUNIT); \
+ (CB_datum)->dptr[0] = '\0'; \
+ (CB_datum)->dsize = 0; \
+ (CB_datum)->asize = CB_DATUMUNIT; \
+ } while(FALSE)
+
+
+/* Alias of `cbdatumopen'.
+ However, `size' should not be negative. */
+#define CB_DATUMOPEN2(CB_datum, CB_ptr, CB_size) \
+ do { \
+ CB_DATUMOPEN((CB_datum)); \
+ CB_DATUMCAT((CB_datum), (CB_ptr), (CB_size)); \
+ } while(FALSE)
+
+
+/* Alias of `cbdatumclose'. */
+#define CB_DATUMCLOSE(CB_datum) \
+ do { \
+ free((CB_datum)->dptr); \
+ free((CB_datum)); \
+ } while(FALSE)
+
+
+/* Alias of `cbdatumcat'.
+ However, `size' should not be negative. */
+#define CB_DATUMCAT(CB_datum, CB_ptr, CB_size) \
+ do { \
+ if((CB_datum)->dsize + (CB_size) >= (CB_datum)->asize){ \
+ (CB_datum)->asize = (CB_datum)->asize * 2 + (CB_size) + 1; \
+ CB_REALLOC((CB_datum)->dptr, (CB_datum)->asize); \
+ } \
+ memcpy((CB_datum)->dptr + (CB_datum)->dsize, (CB_ptr), (CB_size)); \
+ (CB_datum)->dsize += (CB_size); \
+ (CB_datum)->dptr[(CB_datum)->dsize] = '\0'; \
+ } while(FALSE)
+
+
+/* Alias of `cbdatumptr'. */
+#define CB_DATUMPTR(CB_datum) ((const char *)((CB_datum)->dptr))
+
+
+/* Alias of `cbdatumsize'. */
+#define CB_DATUMSIZE(CB_datum) ((int)((CB_datum)->dsize))
+
+
+/* Alias of `cbdatumsetsize'. */
+#define CB_DATUMSETSIZE(CB_datum, CB_size) \
+ do { \
+ if((CB_size) <= (CB_datum)->dsize){ \
+ (CB_datum)->dsize = (CB_size); \
+ (CB_datum)->dptr[(CB_size)] = '\0'; \
+ } else { \
+ if((CB_size) >= (CB_datum)->asize){ \
+ (CB_datum)->asize = (CB_datum)->asize * 2 + (CB_size) + 1; \
+ CB_REALLOC((CB_datum)->dptr, (CB_datum)->asize); \
+ } \
+ memset((CB_datum)->dptr + (CB_datum)->dsize, 0, ((CB_size) - (CB_datum)->dsize) + 1); \
+ (CB_datum)->dsize = (CB_size); \
+ } \
+ } while(FALSE)
+
+
+/* Alias of `cbdatumtomalloc'. */
+#define CB_DATUMTOMALLOC(CB_datum, CB_ptr, CB_size) \
+ do { \
+ (CB_ptr) = (CB_datum)->dptr; \
+ (CB_size) = (CB_datum)->dsize; \
+ free((CB_datum)); \
+ } while(FALSE)
+
+
+/* Alias of `cblistopen'. */
+#define CB_LISTOPEN(CB_list) \
+ do { \
+ CB_MALLOC((CB_list), sizeof(*(CB_list))); \
+ (CB_list)->anum = CB_LISTUNIT; \
+ CB_MALLOC((CB_list)->array, sizeof((CB_list)->array[0]) * (CB_list)->anum); \
+ (CB_list)->start = 0; \
+ (CB_list)->num = 0; \
+ } while(FALSE)
+
+
+/* Alias of `cblistopen'.
+ However, `anum' is specified for the number of initial allocated elements. */
+#define CB_LISTOPEN2(CB_list, CB_anum) \
+ do { \
+ CB_MALLOC((CB_list), sizeof(*(CB_list))); \
+ (CB_list)->anum = (CB_anum) > 4 ? (CB_anum) : 4; \
+ CB_MALLOC((CB_list)->array, sizeof((CB_list)->array[0]) * (CB_list)->anum); \
+ (CB_list)->start = 0; \
+ (CB_list)->num = 0; \
+ } while(FALSE)
+
+
+/* Alias of `cblistclose'. */
+#define CB_LISTCLOSE(CB_list) \
+ do { \
+ int _CB_i, _CB_end; \
+ _CB_end = (CB_list)->start + (CB_list)->num; \
+ for(_CB_i = (CB_list)->start; _CB_i < _CB_end; _CB_i++){ \
+ free((CB_list)->array[_CB_i].dptr); \
+ } \
+ free((CB_list)->array); \
+ free((CB_list)); \
+ } while(FALSE)
+
+
+/* Alias of `cblistnum'. */
+#define CB_LISTNUM(CB_list) \
+ ((int)((CB_list)->num))
+
+
+/* Alias of `cblistval'.
+ However, `sp' is ignored. */
+#define CB_LISTVAL(CB_list, CB_index) \
+ ((const char *)((CB_list)->array[(CB_list)->start+(CB_index)].dptr))
+
+
+/* Alias of `cblistval'.
+ However, `size' is used instead of `sp'. */
+#define CB_LISTVAL2(CB_list, CB_index, CB_size) \
+ ((CB_size) = (CB_list)->array[(CB_list)->start+(CB_index)].dsize, \
+ (const char *)((CB_list)->array[(CB_list)->start+(CB_index)].dptr))
+
+
+/* Alias of `cblistpush'.
+ However, `size' should not be negative. */
+#define CB_LISTPUSH(CB_list, CB_ptr, CB_size) \
+ do { \
+ int _CB_index; \
+ _CB_index = (CB_list)->start + (CB_list)->num; \
+ if(_CB_index >= (CB_list)->anum){ \
+ (CB_list)->anum *= 2; \
+ CB_REALLOC((CB_list)->array, (CB_list)->anum * sizeof((CB_list)->array[0])); \
+ } \
+ CB_MALLOC((CB_list)->array[_CB_index].dptr, \
+ ((CB_size) < CB_DATUMUNIT ? CB_DATUMUNIT : (CB_size)) + 1); \
+ memcpy((CB_list)->array[_CB_index].dptr, (CB_ptr), (CB_size)); \
+ (CB_list)->array[_CB_index].dptr[(CB_size)] = '\0'; \
+ (CB_list)->array[_CB_index].dsize = (CB_size); \
+ (CB_list)->num++; \
+ } while(FALSE)
+
+
+/* Remove and free an element of the end of a list.
+ `list' specifies a list handle. */
+#define CB_LISTDROP(CB_list) \
+ do { \
+ if((CB_list)->num > 0){ \
+ free((CB_list)->array[(CB_list)->start+(CB_list)->num-1].dptr); \
+ (CB_list)->num--; \
+ } \
+ } while(FALSE)
+
+
+/* Alias of `cblistinsert'.
+ However, `index' is not checked and `size' should not be negative. */
+#define CB_LISTINSERT(CB_list, CB_index, CB_ptr, CB_size) \
+ do { \
+ int _CB_index = (CB_index); \
+ _CB_index += (CB_list)->start; \
+ if((CB_list)->start + (CB_list)->num >= (CB_list)->anum){ \
+ (CB_list)->anum *= 2; \
+ CB_REALLOC((CB_list)->array, (CB_list)->anum * sizeof((CB_list)->array[0])); \
+ } \
+ memmove((CB_list)->array + _CB_index + 1, (CB_list)->array + _CB_index, \
+ sizeof((CB_list)->array[0]) * ((CB_list)->start + (CB_list)->num - _CB_index)); \
+ CB_MEMDUP((CB_list)->array[_CB_index].dptr, (CB_ptr), (CB_size)); \
+ (CB_list)->array[_CB_index].dsize = (CB_size); \
+ (CB_list)->num++; \
+ } while(FALSE)
+
+
+/* Alias of `cblistpushbuf'. */
+#define CB_LISTPUSHBUF(CB_list, CB_ptr, CB_size) \
+ do{ \
+ int _CB_index; \
+ _CB_index = (CB_list)->start + (CB_list)->num; \
+ if(_CB_index >= (CB_list)->anum){ \
+ (CB_list)->anum *= 2; \
+ CB_REALLOC((CB_list)->array, (CB_list)->anum * sizeof((CB_list)->array[0])); \
+ } \
+ (CB_list)->array[_CB_index].dptr = (CB_ptr); \
+ (CB_list)->array[_CB_index].dsize = (CB_size); \
+ (CB_list)->num++; \
+ } while(FALSE) \
+
+
+/* Alias of `cbmapiterval'.
+ However, `size' is used instead of `sp'. */
+#define CB_MAPITERVAL(CB_vbuf, CB_kbuf, CB_vsiz) \
+ do { \
+ CBMAPDATUM *_CB_datum; \
+ _CB_datum = (CBMAPDATUM *)((CB_kbuf) - sizeof(*_CB_datum)); \
+ (CB_vsiz) = _CB_datum->vsiz; \
+ (CB_vbuf) = (char *)_CB_datum + sizeof(*_CB_datum) + \
+ _CB_datum->ksiz + CB_ALIGNPAD(_CB_datum->ksiz); \
+ } while(FALSE)
+
+
+
+#undef MYEXTERN
+
+#if defined(__cplusplus) /* export for C++ */
+}
+#endif
+
+#endif /* duplication check */
+
+
+/* END OF FILE */
diff --git a/qdbm/cbcodec.c b/qdbm/cbcodec.c
new file mode 100644
index 00000000..1ab45153
--- /dev/null
+++ b/qdbm/cbcodec.c
@@ -0,0 +1,1079 @@
+/*************************************************************************************************
+ * Popular encoders and decoders
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <cabin.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define DEFCODE "UTF-8" /* default encoding */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+char *readstdin(int *sp);
+int runurl(int argc, char **argv);
+int runbase(int argc, char **argv);
+int runquote(int argc, char **argv);
+int runmime(int argc, char **argv);
+int runcsv(int argc, char **argv);
+int runxml(int argc, char **argv);
+int runzlib(int argc, char **argv);
+int runlzo(int argc, char **argv);
+int runbzip(int argc, char **argv);
+int runiconv(int argc, char **argv);
+int rundate(int argc, char **argv);
+void shouucsmap(void);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "url")){
+ rv = runurl(argc, argv);
+ } else if(!strcmp(argv[1], "base")){
+ rv = runbase(argc, argv);
+ } else if(!strcmp(argv[1], "quote")){
+ rv = runquote(argc, argv);
+ } else if(!strcmp(argv[1], "mime")){
+ rv = runmime(argc, argv);
+ } else if(!strcmp(argv[1], "csv")){
+ rv = runcsv(argc, argv);
+ } else if(!strcmp(argv[1], "xml")){
+ rv = runxml(argc, argv);
+ } else if(!strcmp(argv[1], "zlib")){
+ rv = runzlib(argc, argv);
+ } else if(!strcmp(argv[1], "lzo")){
+ rv = runlzo(argc, argv);
+ } else if(!strcmp(argv[1], "bzip")){
+ rv = runbzip(argc, argv);
+ } else if(!strcmp(argv[1], "iconv")){
+ rv = runiconv(argc, argv);
+ } else if(!strcmp(argv[1], "date")){
+ rv = rundate(argc, argv);
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ char *tmp;
+ int tsiz;
+ fprintf(stderr, "%s: popular encoders and decoders\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s url [-d] [-br] [-rs base target] [-l] [-e expr] [file]\n", progname);
+ fprintf(stderr, " %s base [-d] [-l] [-c num] [-e expr] [file]\n", progname);
+ fprintf(stderr, " %s quote [-d] [-l] [-c num] [-e expr] [file]\n", progname);
+ fprintf(stderr, " %s mime [-d] [-hd] [-bd] [-part num] [-l] [-ec code] [-qp] [-dc] [-e expr]"
+ " [file]\n", progname);
+ fprintf(stderr, " %s csv [-d] [-t] [-l] [-e expr] [-html] [file]\n", progname);
+ fprintf(stderr, " %s xml [-d] [-p] [-l] [-e expr] [-tsv] [file]\n", progname);
+ if((tmp = cbdeflate("", 0, &tsiz)) != NULL){
+ fprintf(stderr, " %s zlib [-d] [-gz] [-crc] [file]\n", progname);
+ free(tmp);
+ }
+ if((tmp = cblzoencode("", 0, &tsiz)) != NULL){
+ fprintf(stderr, " %s lzo [-d] [file]\n", progname);
+ free(tmp);
+ }
+ if((tmp = cbbzencode("", 0, &tsiz)) != NULL){
+ fprintf(stderr, " %s bzip [-d] [file]\n", progname);
+ free(tmp);
+ }
+ if((tmp = cbiconv("", 0, "US-ASCII", "US-ASCII", NULL, NULL)) != NULL){
+ fprintf(stderr, " %s iconv [-ic code] [-oc code] [-ol ltype] [-cn] [-wc] [-um] [file]\n",
+ progname);
+ free(tmp);
+ }
+ fprintf(stderr, " %s date [-wf] [-rf] [-utc] [str]\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* read the standard input */
+char *readstdin(int *sp){
+ char *buf;
+ int i, blen, c;
+ blen = 256;
+ buf = cbmalloc(blen);
+ for(i = 0; (c = getchar()) != EOF; i++){
+ if(i >= blen - 1) buf = cbrealloc(buf, blen *= 2);
+ buf[i] = c;
+ }
+ buf[i] = '\0';
+ if(sp) *sp = i;
+ return buf;
+}
+
+
+/* parse arguments of url command */
+int runurl(int argc, char **argv){
+ CBMAP *map;
+ int i, size, dec, br, line;
+ const char *val;
+ char *base, *target, *expr, *file, *buf, *res;
+ dec = FALSE;
+ br = FALSE;
+ line = FALSE;
+ base = NULL;
+ target = NULL;
+ expr = NULL;
+ file = NULL;
+ for(i = 2; i < argc; i++){
+ if(!file && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ dec = TRUE;
+ } else if(!strcmp(argv[i], "-br")){
+ br = TRUE;
+ } else if(!strcmp(argv[i], "-rs")){
+ if(++i >= argc) usage();
+ base = argv[i];
+ if(++i >= argc) usage();
+ target = argv[i];
+ } else if(!strcmp(argv[i], "-l")){
+ line = TRUE;
+ } else if(!strcmp(argv[i], "-e")){
+ if(++i >= argc) usage();
+ expr = argv[i];
+ } else {
+ usage();
+ }
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ buf = NULL;
+ if(base){
+ size = strlen(base);
+ buf = cbmemdup(base, size);
+ } else if(expr){
+ size = strlen(expr);
+ buf = cbmemdup(expr, size);
+ } else if(file){
+ if(!(buf = cbreadfile(file, &size))){
+ fprintf(stderr, "%s: %s: cannot open\n", progname, file);
+ return 1;
+ }
+ } else {
+ buf = readstdin(&size);
+ }
+ if(target){
+ res = cburlresolve(base, target);
+ printf("%s", res);
+ free(res);
+ } else if(br){
+ map = cburlbreak(buf);
+ if((val = cbmapget(map, "self", -1, NULL))) printf("self\t%s\n", val);
+ if((val = cbmapget(map, "scheme", -1, NULL))) printf("scheme\t%s\n", val);
+ if((val = cbmapget(map, "host", -1, NULL))) printf("host\t%s\n", val);
+ if((val = cbmapget(map, "port", -1, NULL))) printf("port\t%s\n", val);
+ if((val = cbmapget(map, "authority", -1, NULL))) printf("authority\t%s\n", val);
+ if((val = cbmapget(map, "path", -1, NULL))) printf("path\t%s\n", val);
+ if((val = cbmapget(map, "file", -1, NULL))) printf("file\t%s\n", val);
+ if((val = cbmapget(map, "query", -1, NULL))) printf("query\t%s\n", val);
+ if((val = cbmapget(map, "fragment", -1, NULL))) printf("fragment\t%s\n", val);
+ cbmapclose(map);
+ } else if(dec){
+ res = cburldecode(buf, &size);
+ for(i = 0; i < size; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ } else {
+ res = cburlencode(buf, size);
+ for(i = 0; res[i] != '\0'; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ }
+ if(line) putchar('\n');
+ free(buf);
+ return 0;
+}
+
+
+/* parse arguments of base command */
+int runbase(int argc, char **argv){
+ int i, ci, size, dec, line, cols;
+ char *expr, *file, *buf, *res;
+ dec = FALSE;
+ line = FALSE;
+ cols = -1;
+ expr = NULL;
+ file = NULL;
+ for(i = 2; i < argc; i++){
+ if(!file && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ dec = TRUE;
+ } else if(!strcmp(argv[i], "-l")){
+ line = TRUE;
+ } else if(!strcmp(argv[i], "-c")){
+ if(++i >= argc) usage();
+ cols = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-e")){
+ if(++i >= argc) usage();
+ expr = argv[i];
+ } else {
+ usage();
+ }
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ buf = NULL;
+ if(expr){
+ size = strlen(expr);
+ buf = cbmemdup(expr, size);
+ } else if(file){
+ if(!(buf = cbreadfile(file, &size))){
+ fprintf(stderr, "%s: %s: cannot open\n", progname, file);
+ return 1;
+ }
+ } else {
+ buf = readstdin(&size);
+ }
+ if(dec){
+ res = cbbasedecode(buf, &size);
+ for(i = 0; i < size; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ } else {
+ res = cbbaseencode(buf, size);
+ ci = 0;
+ for(i = 0; res[i] != '\0'; i++){
+ if(cols > 0 && ci >= cols){
+ putchar('\n');
+ ci = 0;
+ }
+ putchar(res[i]);
+ ci++;
+ }
+ free(res);
+ }
+ if(line) putchar('\n');
+ free(buf);
+ return 0;
+}
+
+
+/* parse arguments of quote command */
+int runquote(int argc, char **argv){
+ int i, ci, size, dec, line, cols;
+ char *expr, *file, *buf, *res;
+ dec = FALSE;
+ line = FALSE;
+ cols = -1;
+ expr = NULL;
+ file = NULL;
+ for(i = 2; i < argc; i++){
+ if(!file && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ dec = TRUE;
+ } else if(!strcmp(argv[i], "-l")){
+ line = TRUE;
+ } else if(!strcmp(argv[i], "-c")){
+ if(++i >= argc) usage();
+ cols = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-e")){
+ if(++i >= argc) usage();
+ expr = argv[i];
+ } else {
+ usage();
+ }
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ buf = NULL;
+ if(expr){
+ size = strlen(expr);
+ buf = cbmemdup(expr, size);
+ } else if(file){
+ if(!(buf = cbreadfile(file, &size))){
+ fprintf(stderr, "%s: %s: cannot open\n", progname, file);
+ return 1;
+ }
+ } else {
+ buf = readstdin(&size);
+ }
+ if(dec){
+ res = cbquotedecode(buf, &size);
+ for(i = 0; i < size; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ } else {
+ res = cbquoteencode(buf, size);
+ ci = 0;
+ for(i = 0; res[i] != '\0'; i++){
+ if(cols > 0 && (ci >= cols || (ci >= cols - 2 && res[i] == '='))){
+ printf("=\n");
+ ci = 0;
+ }
+ if(res[i] == '\r' || res[i] == '\n') ci = 0;
+ putchar(res[i]);
+ ci++;
+ }
+ free(res);
+ }
+ if(line) putchar('\n');
+ free(buf);
+ return 0;
+}
+
+
+/* parse arguments of mime command */
+int runmime(int argc, char **argv){
+ CBMAP *attrs;
+ CBLIST *parts;
+ int i, size, dec, line, qp, dc, hd, bd, pnum, rsiz, bsiz;
+ const char *key, *body;
+ char *code, *expr, *file, *buf, *res, renc[64];
+ dec = FALSE;
+ hd = FALSE;
+ bd = FALSE;
+ pnum = 0;
+ line = FALSE;
+ dc = FALSE;
+ qp = FALSE;
+ code = NULL;
+ expr = NULL;
+ file = NULL;
+ for(i = 2; i < argc; i++){
+ if(!file && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ dec = TRUE;
+ } else if(!strcmp(argv[i], "-hd")){
+ hd = TRUE;
+ } else if(!strcmp(argv[i], "-bd")){
+ bd = TRUE;
+ } else if(!strcmp(argv[i], "-part")){
+ if(++i >= argc) usage();
+ pnum = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-l")){
+ line = TRUE;
+ } else if(!strcmp(argv[i], "-ec")){
+ if(++i >= argc) usage();
+ code = argv[i];
+ } else if(!strcmp(argv[i], "-qp")){
+ qp = TRUE;
+ } else if(!strcmp(argv[i], "-dc")){
+ dc = TRUE;
+ } else if(!strcmp(argv[i], "-e")){
+ if(++i >= argc) usage();
+ expr = argv[i];
+ } else {
+ usage();
+ }
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ buf = NULL;
+ if(expr){
+ size = strlen(expr);
+ buf = cbmemdup(expr, size);
+ } else if(file){
+ if(!(buf = cbreadfile(file, &size))){
+ fprintf(stderr, "%s: %s: cannot open\n", progname, file);
+ return 1;
+ }
+ } else {
+ buf = readstdin(&size);
+ }
+ if(hd || bd || pnum > 0){
+ attrs = cbmapopen();
+ res = cbmimebreak(buf, size, attrs, &rsiz);
+ if(pnum > 0){
+ parts = NULL;
+ if(!(key = cbmapget(attrs, "TYPE", -1, NULL)) || !cbstrfwimatch(key, "multipart/") ||
+ !(key = cbmapget(attrs, "BOUNDARY", -1, NULL)) ||
+ !(parts = cbmimeparts(res, rsiz, key)) || cblistnum(parts) < pnum){
+ fprintf(stderr, "%s: not multipart or no such part\n", progname);
+ if(parts) cblistclose(parts);
+ free(res);
+ cbmapclose(attrs);
+ free(buf);
+ return 1;
+ }
+ body = cblistval(parts, pnum - 1, &bsiz);
+ for(i = 0; i < bsiz; i++){
+ putchar(body[i]);
+ }
+ cblistclose(parts);
+ } else if(hd){
+ cbmapiterinit(attrs);
+ while((key = cbmapiternext(attrs, NULL)) != NULL){
+ printf("%s\t%s\n", key, cbmapget(attrs, key, -1, NULL));
+ }
+ } else {
+ for(i = 0; i < rsiz; i++){
+ putchar(res[i]);
+ }
+ }
+ free(res);
+ cbmapclose(attrs);
+ } else if(dec){
+ res = cbmimedecode(buf, renc);
+ printf("%s", dc ? renc : res);
+ free(res);
+ } else {
+ res = cbmimeencode(buf, code ? code : DEFCODE, !qp);
+ printf("%s", res);
+ free(res);
+ }
+ if(line) putchar('\n');
+ free(buf);
+ return 0;
+}
+
+
+/* parse arguments of csv command */
+int runcsv(int argc, char **argv){
+ CBLIST *rows, *cells;
+ int i, j, k, dec, tb, line, html;
+ const char *row, *cell;
+ char *expr, *file, *buf, *res;
+ dec = FALSE;
+ tb = FALSE;
+ line = FALSE;
+ html = FALSE;
+ expr = NULL;
+ file = NULL;
+ for(i = 2; i < argc; i++){
+ if(!file && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ dec = TRUE;
+ } else if(!strcmp(argv[i], "-t")){
+ tb = TRUE;
+ } else if(!strcmp(argv[i], "-l")){
+ line = TRUE;
+ } else if(!strcmp(argv[i], "-e")){
+ if(++i >= argc) usage();
+ expr = argv[i];
+ } else if(!strcmp(argv[i], "-html")){
+ html = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ buf = NULL;
+ if(expr){
+ buf = cbmemdup(expr, -1);
+ } else if(file){
+ if(!(buf = cbreadfile(file, NULL))){
+ fprintf(stderr, "%s: %s: cannot open\n", progname, file);
+ return 1;
+ }
+ } else {
+ buf = readstdin(NULL);
+ }
+ if(tb || html){
+ if(html) printf("<table border=\"1\">\n");
+ rows = cbcsvrows(buf);
+ for(i = 0; i < cblistnum(rows); i++){
+ if(html) printf("<tr>");
+ row = cblistval(rows, i, NULL);
+ cells = cbcsvcells(row);
+ for(j = 0; j < cblistnum(cells); j++){
+ cell = cblistval(cells, j, NULL);
+ if(html){
+ printf("<td>");
+ for(k = 0; cell[k] != '\0'; k++){
+ if(cell[k] == '\r' || cell[k] == '\n'){
+ printf("<br>");
+ if(cell[k] == '\r' && cell[k] == '\n') k++;
+ } else {
+ switch(cell[k]){
+ case '&': printf("&amp;"); break;
+ case '<': printf("&lt;"); break;
+ case '>': printf("&gt;"); break;
+ default: putchar(cell[k]); break;
+ }
+ }
+ }
+ printf("</td>");
+ } else {
+ if(j > 0) putchar('\t');
+ for(k = 0; cell[k] != '\0'; k++){
+ if(((unsigned char *)cell)[k] >= 0x20) putchar(cell[k]);
+ }
+ }
+ }
+ cblistclose(cells);
+ if(html) printf("</tr>");
+ putchar('\n');
+ }
+ cblistclose(rows);
+ if(html) printf("</table>\n");
+ } else if(dec){
+ res = cbcsvunescape(buf);
+ for(i = 0; res[i] != '\0'; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ } else {
+ res = cbcsvescape(buf);
+ for(i = 0; res[i] != '\0'; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ }
+ if(line) putchar('\n');
+ free(buf);
+ return 0;
+}
+
+
+/* parse arguments of xml command */
+int runxml(int argc, char **argv){
+ CBLIST *elems;
+ CBMAP *attrs;
+ int i, j, dec, pb, line, tsv, div;
+ const char *elem, *attr;
+ char *expr, *file, *buf, *res;
+ dec = FALSE;
+ pb = FALSE;
+ line = FALSE;
+ tsv = FALSE;
+ expr = NULL;
+ file = NULL;
+ for(i = 2; i < argc; i++){
+ if(!file && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ dec = TRUE;
+ } else if(!strcmp(argv[i], "-p")){
+ pb = TRUE;
+ } else if(!strcmp(argv[i], "-l")){
+ line = TRUE;
+ } else if(!strcmp(argv[i], "-e")){
+ if(++i >= argc) usage();
+ expr = argv[i];
+ } else if(!strcmp(argv[i], "-tsv")){
+ tsv = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ buf = NULL;
+ if(expr){
+ buf = cbmemdup(expr, -1);
+ } else if(file){
+ if(!(buf = cbreadfile(file, NULL))){
+ fprintf(stderr, "%s: %s: cannot open\n", progname, file);
+ return 1;
+ }
+ } else {
+ buf = readstdin(NULL);
+ }
+ if(pb || tsv){
+ elems = cbxmlbreak(buf, FALSE);
+ for(i = 0; i < cblistnum(elems); i++){
+ elem = cblistval(elems, i, NULL);
+ div = FALSE;
+ if(elem[0] == '<'){
+ if(cbstrfwimatch(elem, "<?xml")){
+ printf("XMLDECL");
+ div = TRUE;
+ } else if(cbstrfwimatch(elem, "<!DOCTYPE")){
+ printf("DOCTYPE");
+ } else if(cbstrfwimatch(elem, "<!--")){
+ printf("COMMENT");
+ } else if(cbstrfwimatch(elem, "</")){
+ printf("ENDTAG");
+ div = TRUE;
+ } else if(cbstrbwimatch(elem, "/>")){
+ printf("EMPTAG");
+ div = TRUE;
+ } else {
+ printf("BEGTAG");
+ div = TRUE;
+ }
+ } else {
+ printf("TEXT");
+ }
+ putchar('\t');
+ if(tsv){
+ if(div){
+ attrs = cbxmlattrs(elem);
+ cbmapiterinit(attrs);
+ for(j = 0; (attr = cbmapiternext(attrs, NULL)) != NULL; j++){
+ if(j < 1){
+ printf("%s", cbmapget(attrs, attr, -1, NULL));
+ } else {
+ printf("\t%s\t%s", attr, cbmapget(attrs, attr, -1, NULL));
+ }
+ }
+ cbmapclose(attrs);
+ } else {
+ res = cbxmlunescape(elem);
+ for(j = 0; elem[j] != '\0'; j++){
+ if(((unsigned char *)elem)[j] < 0x20 || elem[j] == '%'){
+ printf("%%%02X", elem[j]);
+ } else {
+ putchar(elem[j]);
+ }
+ }
+ free(res);
+ }
+ } else {
+ printf("%s", elem);
+ }
+ putchar('\n');
+ }
+ cblistclose(elems);
+ } else if(dec){
+ res = cbxmlunescape(buf);
+ for(i = 0; res[i] != '\0'; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ } else {
+ res = cbxmlescape(buf);
+ for(i = 0; res[i] != '\0'; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ }
+ if(line) putchar('\n');
+ free(buf);
+ return 0;
+}
+
+
+/* parse arguments of zlib command */
+int runzlib(int argc, char **argv){
+ unsigned int sum;
+ int i, bsiz, rsiz, dec, gz, crc;
+ char *file, *buf, *res;
+ dec = FALSE;
+ gz = FALSE;
+ crc = FALSE;
+ file = NULL;
+ for(i = 2; i < argc; i++){
+ if(!file && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ dec = TRUE;
+ } else if(!strcmp(argv[i], "-gz")){
+ gz = TRUE;
+ } else if(!strcmp(argv[i], "-crc")){
+ crc = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ buf = NULL;
+ if(file){
+ if(!(buf = cbreadfile(file, &bsiz))){
+ fprintf(stderr, "%s: %s: cannot open\n", progname, file);
+ return 1;
+ }
+ } else {
+ buf = readstdin(&bsiz);
+ }
+ if(crc){
+ sum = cbgetcrc(buf, bsiz);
+ for(i = 0; i < sizeof(int); i++){
+ printf("%02x", sum / 0x1000000);
+ sum <<= 8;
+ }
+ putchar('\n');
+ } else if(dec){
+ if(!(res = gz ? cbgzdecode(buf, bsiz, &rsiz) : cbinflate(buf, bsiz, &rsiz))){
+ fprintf(stderr, "%s: inflate failed\n", progname);
+ free(buf);
+ return 1;
+ }
+ for(i = 0; i < rsiz; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ } else {
+ if(!(res = gz ? cbgzencode(buf, bsiz, &rsiz) : cbdeflate(buf, bsiz, &rsiz))){
+ fprintf(stderr, "%s: deflate failed\n", progname);
+ free(buf);
+ return 1;
+ }
+ for(i = 0; i < rsiz; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ }
+ free(buf);
+ return 0;
+}
+
+
+/* parse arguments of lzo command */
+int runlzo(int argc, char **argv){
+ int i, bsiz, rsiz, dec;
+ char *file, *buf, *res;
+ dec = FALSE;
+ file = NULL;
+ for(i = 2; i < argc; i++){
+ if(!file && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ dec = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ buf = NULL;
+ if(file){
+ if(!(buf = cbreadfile(file, &bsiz))){
+ fprintf(stderr, "%s: %s: cannot open\n", progname, file);
+ return 1;
+ }
+ } else {
+ buf = readstdin(&bsiz);
+ }
+ if(dec){
+ if(!(res = cblzodecode(buf, bsiz, &rsiz))){
+ fprintf(stderr, "%s: decode failed\n", progname);
+ free(buf);
+ return 1;
+ }
+ for(i = 0; i < rsiz; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ } else {
+ if(!(res = cblzoencode(buf, bsiz, &rsiz))){
+ fprintf(stderr, "%s: encode failed\n", progname);
+ free(buf);
+ return 1;
+ }
+ for(i = 0; i < rsiz; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ }
+ free(buf);
+ return 0;
+}
+
+
+/* parse arguments of bzip command */
+int runbzip(int argc, char **argv){
+ int i, bsiz, rsiz, dec;
+ char *file, *buf, *res;
+ dec = FALSE;
+ file = NULL;
+ for(i = 2; i < argc; i++){
+ if(!file && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ dec = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ buf = NULL;
+ if(file){
+ if(!(buf = cbreadfile(file, &bsiz))){
+ fprintf(stderr, "%s: %s: cannot open\n", progname, file);
+ return 1;
+ }
+ } else {
+ buf = readstdin(&bsiz);
+ }
+ if(dec){
+ if(!(res = cbbzdecode(buf, bsiz, &rsiz))){
+ fprintf(stderr, "%s: decode failed\n", progname);
+ free(buf);
+ return 1;
+ }
+ for(i = 0; i < rsiz; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ } else {
+ if(!(res = cbbzencode(buf, bsiz, &rsiz))){
+ fprintf(stderr, "%s: encode failed\n", progname);
+ free(buf);
+ return 1;
+ }
+ for(i = 0; i < rsiz; i++){
+ putchar(res[i]);
+ }
+ free(res);
+ }
+ free(buf);
+ return 0;
+}
+
+
+/* parse arguments of iconv command */
+int runiconv(int argc, char **argv){
+ CBDATUM *datum;
+ const char *rcode;
+ char *icode, *ocode, *ltype, *file, *buf, *res, *norm, *orig;
+ int i, cn, wc, bsiz, rsiz, nsiz, osiz, miss;
+ icode = NULL;
+ ocode = NULL;
+ ltype = NULL;
+ file = NULL;
+ cn = FALSE;
+ wc = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!file && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-ic")){
+ if(++i >= argc) usage();
+ icode = argv[i];
+ } else if(!strcmp(argv[i], "-oc")){
+ if(++i >= argc) usage();
+ ocode = argv[i];
+ } else if(!strcmp(argv[i], "-ol")){
+ if(++i >= argc) usage();
+ ltype = argv[i];
+ } else if(!strcmp(argv[i], "-cn")){
+ cn = TRUE;
+ } else if(!strcmp(argv[i], "-wc")){
+ wc = TRUE;
+ } else if(!strcmp(argv[i], "-um")){
+ shouucsmap();
+ } else {
+ usage();
+ }
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ buf = NULL;
+ if(file){
+ if(!(buf = cbreadfile(file, &bsiz))){
+ fprintf(stderr, "%s: %s: cannot open\n", progname, file);
+ return 1;
+ }
+ } else {
+ buf = readstdin(&bsiz);
+ }
+ miss = 0;
+ if(cn){
+ printf("%s\n", cbencname(buf, bsiz));
+ } else if(wc){
+ printf("%d\n", cbstrcountutf(buf));
+ } else {
+ rcode = icode ? icode : cbencname(buf, bsiz);
+ if(!(res = cbiconv(buf, bsiz, rcode, ocode ? ocode : DEFCODE,
+ &rsiz, &miss))){
+ fprintf(stderr, "%s: iconv failed\n", progname);
+ free(buf);
+ return 1;
+ }
+ if(miss > 0) fprintf(stderr, "%s: missing %d characters\n", progname, miss);
+ if(ltype && (!cbstricmp(ltype, "u") || !cbstricmp(ltype, "unix") ||
+ !cbstricmp(ltype, "lf"))){
+ ltype = "\n";
+ } else if(ltype && (!cbstricmp(ltype, "d") || !cbstricmp(ltype, "dos") ||
+ !cbstricmp(ltype, "crlf"))){
+ ltype = "\r\n";
+ } else if(ltype && (!cbstricmp(ltype, "m") || !cbstricmp(ltype, "mac") ||
+ !cbstricmp(ltype, "cr"))){
+ ltype = "\r";
+ } else {
+ ltype = NULL;
+ }
+ if(ltype){
+ if(!(norm = cbiconv(res, rsiz, ocode, "UTF-8", &nsiz, NULL))){
+ fprintf(stderr, "%s: iconv failed\n", progname);
+ free(res);
+ free(buf);
+ return 1;
+ }
+ datum = cbdatumopen(NULL, -1);
+ for(i = 0; i < nsiz; i++){
+ if(norm[i] == '\r'){
+ if(norm[i+1] == '\n') i++;
+ cbdatumcat(datum, ltype, -1);
+ } else if(norm[i] == '\n'){
+ cbdatumcat(datum, ltype, -1);
+ } else {
+ cbdatumcat(datum, norm + i, 1);
+ }
+ }
+ if(!(orig = cbiconv(cbdatumptr(datum), cbdatumsize(datum), "UTF-8", ocode, &osiz, NULL))){
+ fprintf(stderr, "%s: iconv failed\n", progname);
+ cbdatumclose(datum);
+ free(norm);
+ free(res);
+ free(buf);
+ return 1;
+ }
+ for(i = 0; i < osiz; i++){
+ putchar(orig[i]);
+ }
+ free(orig);
+ cbdatumclose(datum);
+ free(norm);
+ } else {
+ for(i = 0; i < rsiz; i++){
+ putchar(res[i]);
+ }
+ }
+ free(res);
+ }
+ free(buf);
+ return miss > 0 ? 1 : 0;
+}
+
+
+/* parse arguments of date command */
+int rundate(int argc, char **argv){
+ int i, wb, rb, utc, jl;
+ char *date, *res;
+ time_t t;
+ wb = FALSE;
+ rb = FALSE;
+ utc = FALSE;
+ date = NULL;
+ for(i = 2; i < argc; i++){
+ if(!date && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-wf")){
+ wb = TRUE;
+ } else if(!strcmp(argv[i], "-rf")){
+ rb = TRUE;
+ } else if(!strcmp(argv[i], "-utc")){
+ utc = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!date){
+ date = argv[i];
+ } else {
+ usage();
+ }
+ }
+ jl = utc ? 0 : cbjetlag();
+ if(date){
+ t = cbstrmktime(date);
+ } else {
+ t = time(NULL);
+ }
+ if(wb){
+ res = cbdatestrwww(t, jl);
+ } else if(rb){
+ res = cbdatestrhttp(t, jl);
+ } else {
+ res = cbsprintf("%d", (int)t);
+ }
+ if(t >= 0){
+ printf("%s\n", res);
+ } else {
+ if(date){
+ fprintf(stderr, "%s: %s: invalid date format\n", progname, date);
+ } else {
+ fprintf(stderr, "%s: invalid time setting\n", progname);
+ }
+ }
+ free(res);
+ return 0;
+}
+
+
+/* show mapping of UCS-2 and exit. */
+void shouucsmap(void){
+ unsigned char buf[2], *tmp;
+ int i, j, tsiz;
+ for(i = 0; i < 65536; i++){
+ buf[0] = i / 256;
+ buf[1] = i % 256;
+ printf("%d\t", i);
+ printf("U+%02X%02X\t", buf[0], buf[1]);
+ printf("\"\\x%x\\x%x\"\t", buf[0], buf[1]);
+ if((tmp = (unsigned char *)cbiconv((char *)buf, 2, "UTF-16BE", "UTF-8",
+ &tsiz, NULL)) != NULL){
+ if(tsiz > 0){
+ printf("\"");
+ for(j = 0; j < tsiz; j++){
+ printf("\\x%x", tmp[j]);
+ }
+ printf("\"");
+ } else {
+ printf("NULL");
+ }
+ free(tmp);
+ }
+ printf("\n");
+ }
+ exit(0);
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/cbtest.c b/qdbm/cbtest.c
new file mode 100644
index 00000000..d1c84e0d
--- /dev/null
+++ b/qdbm/cbtest.c
@@ -0,0 +1,924 @@
+/*************************************************************************************************
+ * Test cases of Cabin
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <cabin.h>
+#include <stdio.h>
+#include <cabin.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <time.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define RECBUFSIZ 32 /* buffer for records */
+#define TEXTBUFSIZ 262144 /* buffer for text */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runsort(int argc, char **argv);
+int runstrstr(int argc, char **argv);
+int runlist(int argc, char **argv);
+int runmap(int argc, char **argv);
+int runheap(int argc, char **argv);
+int runwicked(int argc, char **argv);
+int runmisc(int argc, char **argv);
+int printfflush(const char *format, ...);
+int strpcmp(const void *ap, const void *bp);
+int intpcmp(const void *ap, const void *bp);
+int myrand(void);
+int dosort(int rnum, int disp);
+int dostrstr(int rnum, int disp);
+int dolist(int rnum, int disp);
+int domap(int rnum, int bnum, int disp);
+int doheap(int rnum, int max, int disp);
+int dowicked(int rnum);
+int domisc(void);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "sort")){
+ rv = runsort(argc, argv);
+ } else if(!strcmp(argv[1], "strstr")){
+ rv = runstrstr(argc, argv);
+ } else if(!strcmp(argv[1], "list")){
+ rv = runlist(argc, argv);
+ } else if(!strcmp(argv[1], "map")){
+ rv = runmap(argc, argv);
+ } else if(!strcmp(argv[1], "heap")){
+ rv = runheap(argc, argv);
+ } else if(!strcmp(argv[1], "wicked")){
+ rv = runwicked(argc, argv);
+ } else if(!strcmp(argv[1], "misc")){
+ rv = runmisc(argc, argv);
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: test cases for Cabin\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s sort [-d] rnum\n", progname);
+ fprintf(stderr, " %s strstr [-d] rnum\n", progname);
+ fprintf(stderr, " %s list [-d] rnum\n", progname);
+ fprintf(stderr, " %s map [-d] rnum [bnum]\n", progname);
+ fprintf(stderr, " %s heap [-d] rnum [top]\n", progname);
+ fprintf(stderr, " %s wicked rnum\n", progname);
+ fprintf(stderr, " %s misc\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* parse arguments of sort command */
+int runsort(int argc, char **argv){
+ int i, rnum, disp, rv;
+ char *rstr;
+ rstr = NULL;
+ rnum = 0;
+ disp = FALSE;
+ for(i = 2; i < argc; i++){
+ if(argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ disp = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!rstr){
+ rstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ rv = dosort(rnum, disp);
+ return rv;
+}
+
+
+/* parse arguments of strstr command */
+int runstrstr(int argc, char **argv){
+ int i, rnum, disp, rv;
+ char *rstr;
+ rstr = NULL;
+ rnum = 0;
+ disp = FALSE;
+ for(i = 2; i < argc; i++){
+ if(argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ disp = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!rstr){
+ rstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ rv = dostrstr(rnum, disp);
+ return rv;
+}
+
+
+/* parse arguments of list command */
+int runlist(int argc, char **argv){
+ int i, rnum, disp, rv;
+ char *rstr;
+ rstr = NULL;
+ rnum = 0;
+ disp = FALSE;
+ for(i = 2; i < argc; i++){
+ if(argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ disp = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!rstr){
+ rstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ rv = dolist(rnum, disp);
+ return rv;
+}
+
+
+/* parse arguments of map command */
+int runmap(int argc, char **argv){
+ int i, rnum, bnum, disp, rv;
+ char *rstr, *bstr;
+ rstr = NULL;
+ bstr = NULL;
+ rnum = 0;
+ bnum = -1;
+ disp = FALSE;
+ for(i = 2; i < argc; i++){
+ if(argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ disp = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!rstr){
+ rstr = argv[i];
+ } else if(!bstr){
+ bstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ if(bstr) bnum = atoi(bstr);
+ rv = domap(rnum, bnum, disp);
+ return rv;
+}
+
+
+/* parse arguments of heap command */
+int runheap(int argc, char **argv){
+ int i, rnum, max, disp, rv;
+ char *rstr, *mstr;
+ rstr = NULL;
+ mstr = NULL;
+ rnum = 0;
+ max = -1;
+ disp = FALSE;
+ for(i = 2; i < argc; i++){
+ if(argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-d")){
+ disp = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!rstr){
+ rstr = argv[i];
+ } else if(!mstr){
+ mstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ if(mstr) max = atoi(mstr);
+ if(max < 0) max = rnum;
+ rv = doheap(rnum, max, disp);
+ rv = 0;
+ return rv;
+}
+
+
+/* parse arguments of wicked command */
+int runwicked(int argc, char **argv){
+ int i, rnum, rv;
+ char *rstr;
+ rstr = NULL;
+ rnum = 0;
+ for(i = 2; i < argc; i++){
+ if(argv[i][0] == '-'){
+ usage();
+ } else if(!rstr){
+ rstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ rv = dowicked(rnum);
+ return rv;
+}
+
+
+/* parse arguments of misc command */
+int runmisc(int argc, char **argv){
+ int rv;
+ rv = domisc();
+ return rv;
+}
+
+
+/* print formatted string and flush the buffer */
+int printfflush(const char *format, ...){
+ va_list ap;
+ int rv;
+ va_start(ap, format);
+ rv = vprintf(format, ap);
+ if(fflush(stdout) == EOF) rv = -1;
+ va_end(ap);
+ return rv;
+}
+
+
+/* comparing function for strings */
+int strpcmp(const void *ap, const void *bp){
+ return strcmp(*(char **)ap, *(char **)bp);
+}
+
+
+/* comparing function for integers */
+int intpcmp(const void *ap, const void *bp){
+ return *(int *)ap - *(int *)bp;
+}
+
+
+/* pseudo random number generator */
+int myrand(void){
+ static int cnt = 0;
+ if(cnt == 0) srand(time(NULL));
+ return (rand() * rand() + (rand() >> (sizeof(int) * 4)) + (cnt++)) & INT_MAX;
+}
+
+
+/* perform sort command */
+int dosort(int rnum, int disp){
+ char **ivector1, **ivector2, **ivector3, **ivector4, **ivector5;
+ char buf[RECBUFSIZ];
+ int i, len, err;
+ if(!disp) printfflush("<Sorting Test>\n rnum=%d\n\n", rnum);
+ ivector1 = cbmalloc(rnum * sizeof(ivector1[0]));
+ ivector2 = cbmalloc(rnum * sizeof(ivector2[0]));
+ ivector3 = cbmalloc(rnum * sizeof(ivector3[0]));
+ ivector4 = cbmalloc(rnum * sizeof(ivector4[0]));
+ ivector5 = cbmalloc(rnum * sizeof(ivector5[0]));
+ err = FALSE;
+ for(i = 0; i < rnum; i++){
+ len = sprintf(buf, "%08d", myrand() % rnum + 1);
+ ivector1[i] = cbmemdup(buf, len);
+ ivector2[i] = cbmemdup(buf, len);
+ ivector3[i] = cbmemdup(buf, len);
+ ivector4[i] = cbmemdup(buf, len);
+ ivector5[i] = cbmemdup(buf, len);
+ }
+ if(!disp) printfflush("Sorting with insert sort ... ");
+ cbisort(ivector1, rnum, sizeof(ivector1[0]), strpcmp);
+ if(!disp) printfflush("ok\n");
+ if(!disp) printfflush("Sorting with shell sort ... ");
+ cbssort(ivector2, rnum, sizeof(ivector2[0]), strpcmp);
+ if(!disp) printfflush("ok\n");
+ if(!disp) printfflush("Sorting with heap sort ... ");
+ cbhsort(ivector3, rnum, sizeof(ivector3[0]), strpcmp);
+ if(!disp) printfflush("ok\n");
+ if(!disp) printfflush("Sorting with quick sort ... ");
+ cbqsort(ivector4, rnum, sizeof(ivector4[0]), strpcmp);
+ if(!disp) printfflush("ok\n");
+ for(i = 0; i < rnum; i++){
+ if(disp) printfflush("%s\t%s\t%s\t%s\t[%s]\n",
+ ivector1[i], ivector2[i], ivector3[i], ivector4[i], ivector5[i]);
+ if(strcmp(ivector1[i], ivector2[i])) err = TRUE;
+ if(strcmp(ivector1[i], ivector3[i])) err = TRUE;
+ if(strcmp(ivector1[i], ivector4[i])) err = TRUE;
+ free(ivector1[i]);
+ free(ivector2[i]);
+ free(ivector3[i]);
+ free(ivector4[i]);
+ free(ivector5[i]);
+ }
+ free(ivector1);
+ free(ivector2);
+ free(ivector3);
+ free(ivector4);
+ free(ivector5);
+ if(err) fprintf(stderr, "%s: sorting failed\n", progname);
+ if(!disp && !err) printfflush("all ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform strstr command */
+int dostrstr(int rnum, int disp){
+ char *text, buf[RECBUFSIZ], *std, *kmp, *bm;
+ int i, j, len, err;
+ text = cbmalloc(TEXTBUFSIZ);
+ for(i = 0; i < TEXTBUFSIZ - 1; i++){
+ text[i] = 'a' + myrand() % ('z' - 'a');
+ }
+ text[i] = '\0';
+ err = FALSE;
+ if(!disp) printfflush("Locating substrings ... ");
+ for(i = 0; i < rnum; i++){
+ len = myrand() % (RECBUFSIZ - 1);
+ for(j = 0; j < len; j++){
+ buf[j] = 'a' + myrand() % ('z' - 'a');
+ }
+ buf[j] = 0;
+ std = strstr(text, buf);
+ kmp = cbstrstrkmp(text, buf);
+ bm = cbstrstrbm(text, buf);
+ if(kmp != std || bm != std){
+ err = TRUE;
+ break;
+ }
+ if(disp && std) printf("%s\n", buf);
+ }
+ if(err) fprintf(stderr, "%s: string scanning failed\n", progname);
+ if(!disp && !err){
+ printfflush("ok\n");
+ printfflush("all ok\n\n");
+ }
+ free(text);
+ return err ? 1 : 0;
+}
+
+
+/* perform list command */
+int dolist(int rnum, int disp){
+ CBLIST *list;
+ const char *vbuf;
+ char buf[RECBUFSIZ], *tmp;
+ int i, err, len, vsiz;
+ if(!disp) printfflush("<List Writing Test>\n rnum=%d\n\n", rnum);
+ list = cblistopen();
+ err = FALSE;
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", i);
+ cblistpush(list, buf, len);
+ if(!disp && rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ if(disp){
+ for(i = 0; i < cblistnum(list); i++){
+ if((vbuf = cblistval(list, i, &vsiz)) != NULL){
+ printfflush("%s:%d\n", vbuf, vsiz);
+ } else {
+ fprintf(stderr, "%s: val error\n", progname);
+ err = TRUE;
+ break;
+ }
+ }
+ printfflush("\n");
+ while((tmp = cblistpop(list, &vsiz)) != NULL){
+ printfflush("%s:%d\n", tmp, vsiz);
+ free(tmp);
+ }
+ }
+ cblistclose(list);
+ if(!disp && !err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform list command */
+int domap(int rnum, int bnum, int disp){
+ CBMAP *map;
+ const char *kbuf, *vbuf;
+ char buf[RECBUFSIZ];
+ int i, err, len, ksiz, vsiz;
+ if(!disp) printfflush("<Map Writing Test>\n rnum=%d bnum=%d\n\n", rnum, bnum);
+ map = bnum > 0 ? cbmapopenex(bnum) : cbmapopen();
+ err = FALSE;
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!cbmapput(map, buf, len, buf, len, FALSE)){
+ fprintf(stderr, "%s: put error\n", progname);
+ err = TRUE;
+ break;
+ }
+ if(!disp && rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ if(disp){
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", i);
+ if((vbuf = cbmapget(map, buf, len, &vsiz)) != NULL){
+ printfflush("%s:%d\t%s:%d\n", buf, len, vbuf, vsiz);
+ } else {
+ fprintf(stderr, "%s: get error\n", progname);
+ }
+ }
+ printfflush("\n");
+ cbmapiterinit(map);
+ while((kbuf = cbmapiternext(map, &ksiz)) != NULL){
+ vbuf = cbmapiterval(kbuf, &vsiz);
+ printfflush("%s:%d\t%s:%d\n", kbuf, ksiz, vbuf, vsiz);
+ }
+ }
+ cbmapclose(map);
+ if(!disp && !err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform heap command */
+int doheap(int rnum, int max, int disp){
+ CBHEAP *heap;
+ int *orig, *ary;
+ int i, err, num, anum;
+ if(!disp) printfflush("<Heap Writing Test>\n rnum=%d max=%d\n\n", rnum, max);
+ orig = disp ? cbmalloc(rnum * sizeof(int) + 1) : NULL;
+ heap = cbheapopen(sizeof(int), max, intpcmp);
+ err = FALSE;
+ for(i = 1; i <= rnum; i++){
+ num = myrand() % rnum + 1;
+ if(orig) orig[i-1] = num;
+ cbheapinsert(heap, &num);
+ if(!disp && rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ if(disp){
+ for(i = 0; i < cbheapnum(heap); i++){
+ printf("%d\n", *(int *)cbheapval(heap, i));
+ }
+ printf("\n");
+ qsort(orig, rnum, sizeof(int), intpcmp);
+ ary = (int *)cbheaptomalloc(heap, &anum);
+ if(anum != rnum && anum != max) err = TRUE;
+ for(i = 0; i < anum; i++){
+ printf("%d\t%d\n", ary[i], orig[i]);
+ if(ary[i] != orig[i]) err = TRUE;
+ }
+ free(ary);
+ } else {
+ cbheapclose(heap);
+ }
+ free(orig);
+ if(!disp && !err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform wicked command */
+int dowicked(int rnum){
+ CBLIST *list;
+ CBMAP *map;
+ int i, len;
+ char buf[RECBUFSIZ], *tmp;
+ printfflush("<Wicked Writing Test>\n rnum=%d\n\n", rnum);
+ list = cblistopen();
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%d", myrand() % rnum + 1);
+ switch(myrand() % 16){
+ case 0:
+ free(cblistpop(list, NULL));
+ putchar('O');
+ break;
+ case 1:
+ cblistunshift(list, buf, len);
+ putchar('U');
+ break;
+ case 2:
+ free(cblistshift(list, NULL));
+ putchar('S');
+ break;
+ case 3:
+ cblistinsert(list, myrand() % (cblistnum(list) + 1), buf, len);
+ putchar('I');
+ break;
+ case 4:
+ free(cblistremove(list, myrand() % (cblistnum(list) + 1), NULL));
+ putchar('R');
+ break;
+ case 5:
+ cblistover(list, myrand() % (cblistnum(list) + 1), buf, len);
+ putchar('V');
+ break;
+ case 6:
+ tmp = cbmemdup(buf, len);
+ cblistpushbuf(list, tmp, len);
+ putchar('B');
+ break;
+ default:
+ cblistpush(list, buf, len);
+ putchar('P');
+ break;
+ }
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ }
+ cblistclose(list);
+ map = cbmapopen();
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%d", myrand() % rnum + 1);
+ switch(myrand() % 16){
+ case 0:
+ cbmapput(map, buf, len, buf, len, FALSE);
+ putchar('I');
+ break;
+ case 1:
+ cbmapputcat(map, buf, len, buf, len);
+ putchar('C');
+ break;
+ case 2:
+ cbmapget(map, buf, len, NULL);
+ putchar('V');
+ break;
+ case 3:
+ cbmapout(map, buf, len);
+ putchar('D');
+ break;
+ case 4:
+ cbmapmove(map, buf, len, myrand() % 2);
+ putchar('M');
+ break;
+ default:
+ cbmapput(map, buf, len, buf, len, TRUE);
+ putchar('O');
+ break;
+ }
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ }
+ cbmapclose(map);
+ printfflush("ok\n\n");
+ return 0;
+}
+
+
+/* perform misc command */
+int domisc(void){
+ CBDATUM *odatum, *ndatum;
+ CBLIST *olist, *nlist, *elems, *glist;
+ CBMAP *omap, *nmap, *pairs, *gmap;
+ int i, j, ssiz, osiz, tsiz, jl;
+ char kbuf[RECBUFSIZ], vbuf[RECBUFSIZ], *sbuf, spbuf[1024], *tmp, *orig, renc[64];
+ const char *op, *np;
+ time_t t;
+ printfflush("<Miscellaneous Test>\n\n");
+ printfflush("Checking memory allocation ... ");
+ tmp = cbmalloc(1024);
+ for(i = 1; i <= 65536; i *= 2){
+ tmp = cbrealloc(tmp, i);
+ }
+ cbfree(tmp);
+ printfflush("ok\n");
+ printfflush("Checking basic datum ... ");
+ odatum = cbdatumopen("x", -1);
+ for(i = 0; i < 1000; i++){
+ cbdatumcat(odatum, "x", 1);
+ }
+ cbdatumclose(odatum);
+ tmp = cbmalloc(3);
+ memcpy(tmp, "abc", 3);
+ odatum = cbdatumopenbuf(tmp, 3);
+ for(i = 0; i < 1000; i++){
+ cbdatumcat(odatum, ".", 1);
+ }
+ ndatum = cbdatumdup(odatum);
+ for(i = 0; i < 1000; i++){
+ cbdatumcat(ndatum, "*", 1);
+ }
+ for(i = 0; i < 1000; i++){
+ tmp = cbmalloc(3);
+ memcpy(tmp, "123", 3);
+ cbdatumsetbuf(ndatum, tmp, 3);
+ }
+ cbdatumprintf(ndatum, "[%s\t%08d\t%08o\t%08x\t%08.1f\t%@\t%?\t%:]",
+ "mikio", 1978, 1978, 1978, 1978.0211, "<>&#!+-*/%", "<>&#!+-*/%", "<>&#!+-*/%");
+ cbdatumclose(ndatum);
+ cbdatumclose(odatum);
+ printfflush("ok\n");
+ printfflush("Checking serialization of list ... ");
+ olist = cblistopen();
+ for(i = 1; i <= 1000; i++){
+ sprintf(vbuf, "%d", i);
+ cblistpush(olist, vbuf, -1);
+ }
+ sbuf = cblistdump(olist, &ssiz);
+ nlist = cblistload(sbuf, ssiz);
+ free(sbuf);
+ for(i = 0; i < cblistnum(olist); i++){
+ op = cblistval(olist, i, NULL);
+ np = cblistval(nlist, i, NULL);
+ if(!op || !np || strcmp(op, np)){
+ cblistclose(nlist);
+ cblistclose(olist);
+ fprintf(stderr, "%s: validation failed\n", progname);
+ return 1;
+ }
+ }
+ cblistclose(nlist);
+ cblistclose(olist);
+ printfflush("ok\n");
+ printfflush("Checking serialization of map ... ");
+ omap = cbmapopen();
+ for(i = 1; i <= 1000; i++){
+ sprintf(kbuf, "%X", i);
+ sprintf(vbuf, "[%d]", i);
+ cbmapput(omap, kbuf, -1, vbuf, -1, TRUE);
+ }
+ sbuf = cbmapdump(omap, &ssiz);
+ nmap = cbmapload(sbuf, ssiz);
+ free(cbmaploadone(sbuf, ssiz, "1", 2, &tsiz));
+ free(cbmaploadone(sbuf, ssiz, "33", 2, &tsiz));
+ free(sbuf);
+ cbmapiterinit(omap);
+ while((op = cbmapiternext(omap, NULL)) != NULL){
+ if(!(np = cbmapget(nmap, op, -1, NULL))){
+ cbmapclose(nmap);
+ cbmapclose(omap);
+ fprintf(stderr, "%s: validation failed\n", progname);
+ return 1;
+ }
+ }
+ cbmapclose(nmap);
+ cbmapclose(omap);
+ printfflush("ok\n");
+ printfflush("Checking string utilities ... ");
+ sprintf(spbuf, "[%08d/%08o/%08u/%08x/%08X/%08.3e/%08.3E/%08.3f/%08.3g/%08.3G/%c/%s/%%]",
+ 123456, 123456, 123456, 123456, 123456,
+ 123456.789, 123456.789, 123456.789, 123456.789, 123456.789,
+ 'A', "hoge");
+ tmp = cbsprintf("[%08d/%08o/%08u/%08x/%08X/%08.3e/%08.3E/%08.3f/%08.3g/%08.3G/%c/%s/%%]",
+ 123456, 123456, 123456, 123456, 123456,
+ 123456.789, 123456.789, 123456.789, 123456.789, 123456.789,
+ 'A', "hoge");
+ while(strcmp(spbuf, tmp)){
+ free(tmp);
+ fprintf(stderr, "%s: cbsprintf is invalid\n", progname);
+ return 1;
+ }
+ free(tmp);
+ pairs = cbmapopen();
+ cbmapput(pairs, "aa", -1, "AAA", -1, TRUE);
+ cbmapput(pairs, "bb", -1, "BBB", -1, TRUE);
+ cbmapput(pairs, "cc", -1, "CCC", -1, TRUE);
+ cbmapput(pairs, "ZZZ", -1, "z", -1, TRUE);
+ tmp = cbreplace("[aaaaabbbbbcccccdddddZZZZ]", pairs);
+ if(strcmp(tmp, "[AAAAAAaBBBBBBbCCCCCCcdddddzZ]")){
+ free(tmp);
+ cbmapclose(pairs);
+ fprintf(stderr, "%s: cbreplace is invalid\n", progname);
+ return 1;
+ }
+ free(tmp);
+ cbmapclose(pairs);
+ elems = cbsplit("aa bb,ccc-dd,", -1, " ,-");
+ if(cblistnum(elems) != 5 || strcmp(cblistval(elems, 0, NULL), "aa") ||
+ strcmp(cblistval(elems, 1, NULL), "bb") || strcmp(cblistval(elems, 2, NULL), "ccc") ||
+ strcmp(cblistval(elems, 3, NULL), "dd") || strcmp(cblistval(elems, 4, NULL), "")){
+ cblistclose(elems);
+ fprintf(stderr, "%s: cbsplit is invalid\n", progname);
+ return 1;
+ }
+ cblistclose(elems);
+ if(cbstricmp("abc", "ABC") || !cbstricmp("abc", "abcd")){
+ fprintf(stderr, "%s: cbstricmp is invalid\n", progname);
+ return 1;
+ }
+ if(!cbstrfwmatch("abcd", "abc") || cbstrfwmatch("abc", "abcd")){
+ fprintf(stderr, "%s: cbstrfwmatch is invalid\n", progname);
+ return 1;
+ }
+ if(!cbstrfwimatch("abcd", "ABC") || cbstrfwmatch("abc", "ABCD")){
+ fprintf(stderr, "%s: cbstrfwimatch is invalid\n", progname);
+ return 1;
+ }
+ if(!cbstrbwmatch("dcba", "cba") || cbstrbwmatch("cba", "dcba")){
+ fprintf(stderr, "%s: cbstrbwmatch is invalid\n", progname);
+ return 1;
+ }
+ if(!cbstrbwimatch("dcba", "CBA") || cbstrbwimatch("cba", "DCBA")){
+ fprintf(stderr, "%s: cbstrbwimatch is invalid\n", progname);
+ return 1;
+ }
+ tmp = cbmemdup(" \r\n[Quick Database Manager]\r\n ", -1);
+ if(cbstrtoupper(tmp) != tmp || strcmp(tmp, " \r\n[QUICK DATABASE MANAGER]\r\n ")){
+ free(tmp);
+ fprintf(stderr, "%s: cbstrtoupper is invalid\n", progname);
+ return 1;
+ }
+ if(cbstrtolower(tmp) != tmp || strcmp(tmp, " \r\n[quick database manager]\r\n ")){
+ free(tmp);
+ fprintf(stderr, "%s: cbstrtolower is invalid\n", progname);
+ return 1;
+ }
+ if(cbstrtrim(tmp) != tmp || strcmp(tmp, "[quick database manager]")){
+ free(tmp);
+ fprintf(stderr, "%s: cbstrtrim is invalid\n", progname);
+ return 1;
+ }
+ if(cbstrsqzspc(tmp) != tmp || strcmp(tmp, "[quick database manager]")){
+ free(tmp);
+ fprintf(stderr, "%s: cbstrsqzspc is invalid\n", progname);
+ return 1;
+ }
+ cbstrcututf(tmp, 5);
+ if(cbstrcountutf(tmp) != 5){
+ free(tmp);
+ fprintf(stderr, "%s: cbstrcututf or cbstrcountutf is invalid\n", progname);
+ return 1;
+ }
+ free(tmp);
+ printfflush("ok\n");
+ printfflush("Checking encoding utilities ... ");
+ strcpy(spbuf, "My name is \xca\xbf\xce\xd3\xb4\xb4\xcd\xba.\n\n<Love & Peace!>\n");
+ tmp = cbbaseencode(spbuf, -1);
+ orig = cbbasedecode(tmp, &osiz);
+ if(osiz != strlen(spbuf) || strcmp(orig, spbuf)){
+ free(orig);
+ free(tmp);
+ fprintf(stderr, "%s: Base64 encoding is invalid\n", progname);
+ return 1;
+ }
+ free(orig);
+ free(tmp);
+ strcpy(spbuf, "My name is \xca\xbf\xce\xd3\xb4\xb4\xcd\xba.\n\n<Love & Peace!>\n");
+ tmp = cbquoteencode(spbuf, -1);
+ orig = cbquotedecode(tmp, &osiz);
+ if(osiz != strlen(spbuf) || strcmp(orig, spbuf)){
+ free(orig);
+ free(tmp);
+ fprintf(stderr, "%s: quoted-printable encoding is invalid\n", progname);
+ return 1;
+ }
+ free(orig);
+ free(tmp);
+ strcpy(spbuf, "My name is \xca\xbf\xce\xd3\xb4\xb4\xcd\xba.\n\n<Love & Peace!>\n");
+ tmp = cbmimeencode(spbuf, "ISO-8859-1", TRUE);
+ orig = cbmimedecode(tmp, renc);
+ if(osiz != strlen(spbuf) || strcmp(orig, spbuf) || strcmp(renc, "ISO-8859-1")){
+ free(orig);
+ free(tmp);
+ fprintf(stderr, "%s: MIME encoding is invalid\n", progname);
+ return 1;
+ }
+ free(orig);
+ free(tmp);
+ strcpy(spbuf, "\"He says...\r\n\"\"What PROGRAM are they watching?\"\"\"");
+ tmp = cbcsvunescape(spbuf);
+ orig = cbcsvescape(tmp);
+ if(strcmp(orig, spbuf)){
+ free(orig);
+ free(tmp);
+ fprintf(stderr, "%s: CSV escaping is invalid\n", progname);
+ return 1;
+ }
+ free(orig);
+ free(tmp);
+ strcpy(spbuf, "&lt;Nuts&amp;Milk&gt; is &quot;very&quot; surfin&apos;!");
+ tmp = cbxmlunescape(spbuf);
+ orig = cbxmlescape(tmp);
+ if(strcmp(orig, spbuf)){
+ free(orig);
+ free(tmp);
+ fprintf(stderr, "%s: XML escaping is invalid\n", progname);
+ return 1;
+ }
+ free(orig);
+ free(tmp);
+ printfflush("ok\n");
+ printfflush("Checking date utilities ... ");
+ for(i = 0; i < 200; i++){
+ jl = (myrand() % 23) * 1800;
+ if(myrand() % 2 == 0) jl *= -1;
+ t = myrand() % (INT_MAX - 3600 * 24 * 365 * 6) + 3600 * 24 * 365 * 5;
+ tmp = cbdatestrwww(t, jl);
+ t = cbstrmktime(tmp);
+ orig = cbdatestrwww(t, jl);
+ if(strcmp(orig, tmp)){
+ free(orig);
+ free(tmp);
+ fprintf(stderr, "%s: W3CDTF formatter is invalid\n", progname);
+ return 1;
+ }
+ free(orig);
+ free(tmp);
+ tmp = cbdatestrhttp(t, jl);
+ t = cbstrmktime(tmp);
+ orig = cbdatestrhttp(t, jl);
+ if(strcmp(orig, tmp)){
+ free(orig);
+ free(tmp);
+ fprintf(stderr, "%s: RFC 822 date formatter is invalid\n", progname);
+ return 1;
+ }
+ free(orig);
+ free(tmp);
+ }
+ printfflush("ok\n");
+ printfflush("Checking the global garbage collector ... ");
+ for(i = 0; i < 512; i++){
+ glist = cblistopen();
+ cbglobalgc(glist, (void (*)(void *))cblistclose);
+ for(j = 0; j < 10; j++){
+ sprintf(kbuf, "%08d", j);
+ cblistpush(glist, kbuf, -1);
+ }
+ gmap = cbmapopen();
+ cbglobalgc(gmap, (void (*)(void *))cbmapclose);
+ for(j = 0; j < 10; j++){
+ sprintf(kbuf, "%08d", j);
+ cbmapput(gmap, kbuf, -1, kbuf, -1, TRUE);
+ }
+ if(myrand() % 64 == 0){
+ cbvmemavail(100);
+ cbggcsweep();
+ }
+ }
+ printfflush("ok\n");
+ printfflush("all ok\n\n");
+ return 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/configure b/qdbm/configure
new file mode 100755
index 00000000..60e3d2fa
--- /dev/null
+++ b/qdbm/configure
@@ -0,0 +1,3913 @@
+#! /bin/sh
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.59 for qdbm 1.8.77.
+#
+# Copyright (C) 2003 Free Software Foundation, Inc.
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## --------------------- ##
+## M4sh Initialization. ##
+## --------------------- ##
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
+ set -o posix
+fi
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# Support unset when possible.
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+ as_unset=unset
+else
+ as_unset=false
+fi
+
+
+# Work around bugs in pre-3.0 UWIN ksh.
+$as_unset ENV MAIL MAILPATH
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+for as_var in \
+ LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \
+ LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \
+ LC_TELEPHONE LC_TIME
+do
+ if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then
+ eval $as_var=C; export $as_var
+ else
+ $as_unset $as_var
+ fi
+done
+
+# Required to use basename.
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+if (basename /) >/dev/null 2>&1 && test "X`basename / 2>&1`" = "X/"; then
+ as_basename=basename
+else
+ as_basename=false
+fi
+
+
+# Name of the executable.
+as_me=`$as_basename "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)$' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; }
+ /^X\/\(\/\/\)$/{ s//\1/; q; }
+ /^X\/\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+
+
+# PATH needs CR, and LINENO needs CR and PATH.
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ echo "#! /bin/sh" >conf$$.sh
+ echo "exit 0" >>conf$$.sh
+ chmod +x conf$$.sh
+ if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
+ PATH_SEPARATOR=';'
+ else
+ PATH_SEPARATOR=:
+ fi
+ rm -f conf$$.sh
+fi
+
+
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" || {
+ # Find who we are. Look in the path if we contain no path at all
+ # relative or not.
+ case $0 in
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+
+ ;;
+ esac
+ # We did not find ourselves, most probably we were run as `sh COMMAND'
+ # in which case we are not to be found in the path.
+ if test "x$as_myself" = x; then
+ as_myself=$0
+ fi
+ if test ! -f "$as_myself"; then
+ { echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2
+ { (exit 1); exit 1; }; }
+ fi
+ case $CONFIG_SHELL in
+ '')
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for as_base in sh bash ksh sh5; do
+ case $as_dir in
+ /*)
+ if ("$as_dir/$as_base" -c '
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" ') 2>/dev/null; then
+ $as_unset BASH_ENV || test "${BASH_ENV+set}" != set || { BASH_ENV=; export BASH_ENV; }
+ $as_unset ENV || test "${ENV+set}" != set || { ENV=; export ENV; }
+ CONFIG_SHELL=$as_dir/$as_base
+ export CONFIG_SHELL
+ exec "$CONFIG_SHELL" "$0" ${1+"$@"}
+ fi;;
+ esac
+ done
+done
+;;
+ esac
+
+ # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
+ # uniformly replaced by the line number. The first 'sed' inserts a
+ # line-number line before each line; the second 'sed' does the real
+ # work. The second script uses 'N' to pair each line-number line
+ # with the numbered line, and appends trailing '-' during
+ # substitution so that $LINENO is not a special case at line end.
+ # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
+ # second 'sed' script. Blame Lee E. McMahon for sed's syntax. :-)
+ sed '=' <$as_myself |
+ sed '
+ N
+ s,$,-,
+ : loop
+ s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3,
+ t loop
+ s,-$,,
+ s,^['$as_cr_digits']*\n,,
+ ' >$as_me.lineno &&
+ chmod +x $as_me.lineno ||
+ { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2
+ { (exit 1); exit 1; }; }
+
+ # Don't try to exec as it changes $[0], causing all sort of problems
+ # (the dirname of $[0] is not the place where we might find the
+ # original and so on. Autoconf is especially sensible to this).
+ . ./$as_me.lineno
+ # Exit status is that of the last command.
+ exit
+}
+
+
+case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
+ *c*,-n*) ECHO_N= ECHO_C='
+' ECHO_T=' ' ;;
+ *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;;
+ *) ECHO_N= ECHO_C='\c' ECHO_T= ;;
+esac
+
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+rm -f conf$$ conf$$.exe conf$$.file
+echo >conf$$.file
+if ln -s conf$$.file conf$$ 2>/dev/null; then
+ # We could just check for DJGPP; but this test a) works b) is more generic
+ # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04).
+ if test -f conf$$.exe; then
+ # Don't use ln at all; we don't have any links
+ as_ln_s='cp -p'
+ else
+ as_ln_s='ln -s'
+ fi
+elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+else
+ as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.file
+
+if mkdir -p . 2>/dev/null; then
+ as_mkdir_p=:
+else
+ test -d ./-p && rmdir ./-p
+ as_mkdir_p=false
+fi
+
+as_executable_p="test -f"
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.
+as_nl='
+'
+IFS=" $as_nl"
+
+# CDPATH.
+$as_unset CDPATH
+
+
+# Name of the host.
+# hostname on some systems (SVR3.2, Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+exec 6>&1
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_config_libobj_dir=.
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+SHELL=${CONFIG_SHELL-/bin/sh}
+
+# Maximum number of lines to put in a shell here document.
+# This variable seems obsolete. It should probably be removed, and
+# only ac_max_sed_lines should be used.
+: ${ac_max_here_lines=38}
+
+# Identity of this package.
+PACKAGE_NAME='qdbm'
+PACKAGE_TARNAME='qdbm'
+PACKAGE_VERSION='1.8.77'
+PACKAGE_STRING='qdbm 1.8.77'
+PACKAGE_BUGREPORT=''
+
+ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT LIBVER LIBREV TARGETS MYDEFS MYOPTS MGWLIBS LD AR LIBOBJS LTLIBOBJS'
+ac_subst_files=''
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datadir='${prefix}/share'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+libdir='${exec_prefix}/lib'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+infodir='${prefix}/info'
+mandir='${prefix}/man'
+
+ac_prev=
+for ac_option
+do
+ # If the previous option needs an argument, assign it.
+ if test -n "$ac_prev"; then
+ eval "$ac_prev=\$ac_option"
+ ac_prev=
+ continue
+ fi
+
+ ac_optarg=`expr "x$ac_option" : 'x[^=]*=\(.*\)'`
+
+ # Accept the important Cygnus configure options, so we can diagnose typos.
+
+ case $ac_option in
+
+ -bindir | --bindir | --bindi | --bind | --bin | --bi)
+ ac_prev=bindir ;;
+ -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+ bindir=$ac_optarg ;;
+
+ -build | --build | --buil | --bui | --bu)
+ ac_prev=build_alias ;;
+ -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+ build_alias=$ac_optarg ;;
+
+ -cache-file | --cache-file | --cache-fil | --cache-fi \
+ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+ ac_prev=cache_file ;;
+ -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+ cache_file=$ac_optarg ;;
+
+ --config-cache | -C)
+ cache_file=config.cache ;;
+
+ -datadir | --datadir | --datadi | --datad | --data | --dat | --da)
+ ac_prev=datadir ;;
+ -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \
+ | --da=*)
+ datadir=$ac_optarg ;;
+
+ -disable-* | --disable-*)
+ ac_feature=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid feature name: $ac_feature" >&2
+ { (exit 1); exit 1; }; }
+ ac_feature=`echo $ac_feature | sed 's/-/_/g'`
+ eval "enable_$ac_feature=no" ;;
+
+ -enable-* | --enable-*)
+ ac_feature=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_feature" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid feature name: $ac_feature" >&2
+ { (exit 1); exit 1; }; }
+ ac_feature=`echo $ac_feature | sed 's/-/_/g'`
+ case $ac_option in
+ *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "enable_$ac_feature='$ac_optarg'" ;;
+
+ -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+ | --exec | --exe | --ex)
+ ac_prev=exec_prefix ;;
+ -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+ | --exec=* | --exe=* | --ex=*)
+ exec_prefix=$ac_optarg ;;
+
+ -gas | --gas | --ga | --g)
+ # Obsolete; use --with-gas.
+ with_gas=yes ;;
+
+ -help | --help | --hel | --he | -h)
+ ac_init_help=long ;;
+ -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+ ac_init_help=recursive ;;
+ -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+ ac_init_help=short ;;
+
+ -host | --host | --hos | --ho)
+ ac_prev=host_alias ;;
+ -host=* | --host=* | --hos=* | --ho=*)
+ host_alias=$ac_optarg ;;
+
+ -includedir | --includedir | --includedi | --included | --include \
+ | --includ | --inclu | --incl | --inc)
+ ac_prev=includedir ;;
+ -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+ | --includ=* | --inclu=* | --incl=* | --inc=*)
+ includedir=$ac_optarg ;;
+
+ -infodir | --infodir | --infodi | --infod | --info | --inf)
+ ac_prev=infodir ;;
+ -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+ infodir=$ac_optarg ;;
+
+ -libdir | --libdir | --libdi | --libd)
+ ac_prev=libdir ;;
+ -libdir=* | --libdir=* | --libdi=* | --libd=*)
+ libdir=$ac_optarg ;;
+
+ -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+ | --libexe | --libex | --libe)
+ ac_prev=libexecdir ;;
+ -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+ | --libexe=* | --libex=* | --libe=*)
+ libexecdir=$ac_optarg ;;
+
+ -localstatedir | --localstatedir | --localstatedi | --localstated \
+ | --localstate | --localstat | --localsta | --localst \
+ | --locals | --local | --loca | --loc | --lo)
+ ac_prev=localstatedir ;;
+ -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+ | --localstate=* | --localstat=* | --localsta=* | --localst=* \
+ | --locals=* | --local=* | --loca=* | --loc=* | --lo=*)
+ localstatedir=$ac_optarg ;;
+
+ -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+ ac_prev=mandir ;;
+ -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+ mandir=$ac_optarg ;;
+
+ -nfp | --nfp | --nf)
+ # Obsolete; use --without-fp.
+ with_fp=no ;;
+
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c | -n)
+ no_create=yes ;;
+
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+ no_recursion=yes ;;
+
+ -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+ | --oldin | --oldi | --old | --ol | --o)
+ ac_prev=oldincludedir ;;
+ -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+ oldincludedir=$ac_optarg ;;
+
+ -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+ ac_prev=prefix ;;
+ -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+ prefix=$ac_optarg ;;
+
+ -program-prefix | --program-prefix | --program-prefi | --program-pref \
+ | --program-pre | --program-pr | --program-p)
+ ac_prev=program_prefix ;;
+ -program-prefix=* | --program-prefix=* | --program-prefi=* \
+ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+ program_prefix=$ac_optarg ;;
+
+ -program-suffix | --program-suffix | --program-suffi | --program-suff \
+ | --program-suf | --program-su | --program-s)
+ ac_prev=program_suffix ;;
+ -program-suffix=* | --program-suffix=* | --program-suffi=* \
+ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+ program_suffix=$ac_optarg ;;
+
+ -program-transform-name | --program-transform-name \
+ | --program-transform-nam | --program-transform-na \
+ | --program-transform-n | --program-transform- \
+ | --program-transform | --program-transfor \
+ | --program-transfo | --program-transf \
+ | --program-trans | --program-tran \
+ | --progr-tra | --program-tr | --program-t)
+ ac_prev=program_transform_name ;;
+ -program-transform-name=* | --program-transform-name=* \
+ | --program-transform-nam=* | --program-transform-na=* \
+ | --program-transform-n=* | --program-transform-=* \
+ | --program-transform=* | --program-transfor=* \
+ | --program-transfo=* | --program-transf=* \
+ | --program-trans=* | --program-tran=* \
+ | --progr-tra=* | --program-tr=* | --program-t=*)
+ program_transform_name=$ac_optarg ;;
+
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ silent=yes ;;
+
+ -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+ ac_prev=sbindir ;;
+ -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+ | --sbi=* | --sb=*)
+ sbindir=$ac_optarg ;;
+
+ -sharedstatedir | --sharedstatedir | --sharedstatedi \
+ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+ | --sharedst | --shareds | --shared | --share | --shar \
+ | --sha | --sh)
+ ac_prev=sharedstatedir ;;
+ -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+ | --sha=* | --sh=*)
+ sharedstatedir=$ac_optarg ;;
+
+ -site | --site | --sit)
+ ac_prev=site ;;
+ -site=* | --site=* | --sit=*)
+ site=$ac_optarg ;;
+
+ -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+ ac_prev=srcdir ;;
+ -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+ srcdir=$ac_optarg ;;
+
+ -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+ | --syscon | --sysco | --sysc | --sys | --sy)
+ ac_prev=sysconfdir ;;
+ -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+ sysconfdir=$ac_optarg ;;
+
+ -target | --target | --targe | --targ | --tar | --ta | --t)
+ ac_prev=target_alias ;;
+ -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+ target_alias=$ac_optarg ;;
+
+ -v | -verbose | --verbose | --verbos | --verbo | --verb)
+ verbose=yes ;;
+
+ -version | --version | --versio | --versi | --vers | -V)
+ ac_init_version=: ;;
+
+ -with-* | --with-*)
+ ac_package=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid package name: $ac_package" >&2
+ { (exit 1); exit 1; }; }
+ ac_package=`echo $ac_package| sed 's/-/_/g'`
+ case $ac_option in
+ *=*) ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "with_$ac_package='$ac_optarg'" ;;
+
+ -without-* | --without-*)
+ ac_package=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_package" : ".*[^-_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid package name: $ac_package" >&2
+ { (exit 1); exit 1; }; }
+ ac_package=`echo $ac_package | sed 's/-/_/g'`
+ eval "with_$ac_package=no" ;;
+
+ --x)
+ # Obsolete; use --with-x.
+ with_x=yes ;;
+
+ -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+ | --x-incl | --x-inc | --x-in | --x-i)
+ ac_prev=x_includes ;;
+ -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+ x_includes=$ac_optarg ;;
+
+ -x-libraries | --x-libraries | --x-librarie | --x-librari \
+ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+ ac_prev=x_libraries ;;
+ -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+ x_libraries=$ac_optarg ;;
+
+ -*) { echo "$as_me: error: unrecognized option: $ac_option
+Try \`$0 --help' for more information." >&2
+ { (exit 1); exit 1; }; }
+ ;;
+
+ *=*)
+ ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+ # Reject names that are not valid shell variable names.
+ expr "x$ac_envvar" : ".*[^_$as_cr_alnum]" >/dev/null &&
+ { echo "$as_me: error: invalid variable name: $ac_envvar" >&2
+ { (exit 1); exit 1; }; }
+ ac_optarg=`echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"`
+ eval "$ac_envvar='$ac_optarg'"
+ export $ac_envvar ;;
+
+ *)
+ # FIXME: should be removed in autoconf 3.0.
+ echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+ expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+ echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+ : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}
+ ;;
+
+ esac
+done
+
+if test -n "$ac_prev"; then
+ ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+ { echo "$as_me: error: missing argument to $ac_option" >&2
+ { (exit 1); exit 1; }; }
+fi
+
+# Be sure to have absolute paths.
+for ac_var in exec_prefix prefix
+do
+ eval ac_val=$`echo $ac_var`
+ case $ac_val in
+ [\\/$]* | ?:[\\/]* | NONE | '' ) ;;
+ *) { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2
+ { (exit 1); exit 1; }; };;
+ esac
+done
+
+# Be sure to have absolute paths.
+for ac_var in bindir sbindir libexecdir datadir sysconfdir sharedstatedir \
+ localstatedir libdir includedir oldincludedir infodir mandir
+do
+ eval ac_val=$`echo $ac_var`
+ case $ac_val in
+ [\\/$]* | ?:[\\/]* ) ;;
+ *) { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2
+ { (exit 1); exit 1; }; };;
+ esac
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+ if test "x$build_alias" = x; then
+ cross_compiling=maybe
+ echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host.
+ If a cross compiler is detected then cross compile mode will be used." >&2
+ elif test "x$build_alias" != "x$host_alias"; then
+ cross_compiling=yes
+ fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+ ac_srcdir_defaulted=yes
+ # Try the directory containing this script, then its parent.
+ ac_confdir=`(dirname "$0") 2>/dev/null ||
+$as_expr X"$0" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$0" : 'X\(//\)[^/]' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X"$0" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+ /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+ /^X\(\/\/\)$/{ s//\1/; q; }
+ /^X\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+ srcdir=$ac_confdir
+ if test ! -r $srcdir/$ac_unique_file; then
+ srcdir=..
+ fi
+else
+ ac_srcdir_defaulted=no
+fi
+if test ! -r $srcdir/$ac_unique_file; then
+ if test "$ac_srcdir_defaulted" = yes; then
+ { echo "$as_me: error: cannot find sources ($ac_unique_file) in $ac_confdir or .." >&2
+ { (exit 1); exit 1; }; }
+ else
+ { echo "$as_me: error: cannot find sources ($ac_unique_file) in $srcdir" >&2
+ { (exit 1); exit 1; }; }
+ fi
+fi
+(cd $srcdir && test -r ./$ac_unique_file) 2>/dev/null ||
+ { echo "$as_me: error: sources are in $srcdir, but \`cd $srcdir' does not work" >&2
+ { (exit 1); exit 1; }; }
+srcdir=`echo "$srcdir" | sed 's%\([^\\/]\)[\\/]*$%\1%'`
+ac_env_build_alias_set=${build_alias+set}
+ac_env_build_alias_value=$build_alias
+ac_cv_env_build_alias_set=${build_alias+set}
+ac_cv_env_build_alias_value=$build_alias
+ac_env_host_alias_set=${host_alias+set}
+ac_env_host_alias_value=$host_alias
+ac_cv_env_host_alias_set=${host_alias+set}
+ac_cv_env_host_alias_value=$host_alias
+ac_env_target_alias_set=${target_alias+set}
+ac_env_target_alias_value=$target_alias
+ac_cv_env_target_alias_set=${target_alias+set}
+ac_cv_env_target_alias_value=$target_alias
+ac_env_CC_set=${CC+set}
+ac_env_CC_value=$CC
+ac_cv_env_CC_set=${CC+set}
+ac_cv_env_CC_value=$CC
+ac_env_CFLAGS_set=${CFLAGS+set}
+ac_env_CFLAGS_value=$CFLAGS
+ac_cv_env_CFLAGS_set=${CFLAGS+set}
+ac_cv_env_CFLAGS_value=$CFLAGS
+ac_env_LDFLAGS_set=${LDFLAGS+set}
+ac_env_LDFLAGS_value=$LDFLAGS
+ac_cv_env_LDFLAGS_set=${LDFLAGS+set}
+ac_cv_env_LDFLAGS_value=$LDFLAGS
+ac_env_CPPFLAGS_set=${CPPFLAGS+set}
+ac_env_CPPFLAGS_value=$CPPFLAGS
+ac_cv_env_CPPFLAGS_set=${CPPFLAGS+set}
+ac_cv_env_CPPFLAGS_value=$CPPFLAGS
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+ # Omit some internal or obsolete options to make the list less imposing.
+ # This message is too long to be a string in the A/UX 3.1 sh.
+ cat <<_ACEOF
+\`configure' configures qdbm 1.8.77 to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE. See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+ -h, --help display this help and exit
+ --help=short display options specific to this package
+ --help=recursive display the short help of all the included packages
+ -V, --version display version information and exit
+ -q, --quiet, --silent do not print \`checking...' messages
+ --cache-file=FILE cache test results in FILE [disabled]
+ -C, --config-cache alias for \`--cache-file=config.cache'
+ -n, --no-create do not create output files
+ --srcdir=DIR find the sources in DIR [configure dir or \`..']
+
+_ACEOF
+
+ cat <<_ACEOF
+Installation directories:
+ --prefix=PREFIX install architecture-independent files in PREFIX
+ [$ac_default_prefix]
+ --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
+ [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+ --bindir=DIR user executables [EPREFIX/bin]
+ --sbindir=DIR system admin executables [EPREFIX/sbin]
+ --libexecdir=DIR program executables [EPREFIX/libexec]
+ --datadir=DIR read-only architecture-independent data [PREFIX/share]
+ --sysconfdir=DIR read-only single-machine data [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data [PREFIX/var]
+ --libdir=DIR object code libraries [EPREFIX/lib]
+ --includedir=DIR C header files [PREFIX/include]
+ --oldincludedir=DIR C header files for non-gcc [/usr/include]
+ --infodir=DIR info documentation [PREFIX/info]
+ --mandir=DIR man documentation [PREFIX/man]
+_ACEOF
+
+ cat <<\_ACEOF
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+ case $ac_init_help in
+ short | recursive ) echo "Configuration of qdbm 1.8.77:";;
+ esac
+ cat <<\_ACEOF
+
+Optional Features:
+ --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
+ --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
+ --enable-debug build for debugging
+ --enable-devel build for development
+ --enable-stable build for stable release
+ --enable-pthread use POSIX thread and make APIs thread-safe
+ --disable-lock build for environments without file locking
+ --disable-mmap build for environments without memory mapping
+ --enable-zlib feature ZLIB for B+ tree and inverted index
+ --enable-lzo feature LZO for B+ tree and inverted index
+ --enable-bzip feature BZIP2 for B+ tree and inverted index
+ --enable-iconv feature ICONV utilities
+ --disable-warn hide warnings in the configuration
+
+Some influential environment variables:
+ CC C compiler command
+ CFLAGS C compiler flags
+ LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a
+ nonstandard directory <lib dir>
+ CPPFLAGS C/C++ preprocessor flags, e.g. -I<include dir> if you have
+ headers in a nonstandard directory <include dir>
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+_ACEOF
+fi
+
+if test "$ac_init_help" = "recursive"; then
+ # If there are subdirs, report their specific --help.
+ ac_popdir=`pwd`
+ for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+ test -d $ac_dir || continue
+ ac_builddir=.
+
+if test "$ac_dir" != .; then
+ ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'`
+ # A "../" for each directory in $ac_dir_suffix.
+ ac_top_builddir=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,../,g'`
+else
+ ac_dir_suffix= ac_top_builddir=
+fi
+
+case $srcdir in
+ .) # No --srcdir option. We are building in place.
+ ac_srcdir=.
+ if test -z "$ac_top_builddir"; then
+ ac_top_srcdir=.
+ else
+ ac_top_srcdir=`echo $ac_top_builddir | sed 's,/$,,'`
+ fi ;;
+ [\\/]* | ?:[\\/]* ) # Absolute path.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir ;;
+ *) # Relative path.
+ ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_builddir$srcdir ;;
+esac
+
+# Do not use `cd foo && pwd` to compute absolute paths, because
+# the directories may not exist.
+case `pwd` in
+.) ac_abs_builddir="$ac_dir";;
+*)
+ case "$ac_dir" in
+ .) ac_abs_builddir=`pwd`;;
+ [\\/]* | ?:[\\/]* ) ac_abs_builddir="$ac_dir";;
+ *) ac_abs_builddir=`pwd`/"$ac_dir";;
+ esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_builddir=${ac_top_builddir}.;;
+*)
+ case ${ac_top_builddir}. in
+ .) ac_abs_top_builddir=$ac_abs_builddir;;
+ [\\/]* | ?:[\\/]* ) ac_abs_top_builddir=${ac_top_builddir}.;;
+ *) ac_abs_top_builddir=$ac_abs_builddir/${ac_top_builddir}.;;
+ esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_srcdir=$ac_srcdir;;
+*)
+ case $ac_srcdir in
+ .) ac_abs_srcdir=$ac_abs_builddir;;
+ [\\/]* | ?:[\\/]* ) ac_abs_srcdir=$ac_srcdir;;
+ *) ac_abs_srcdir=$ac_abs_builddir/$ac_srcdir;;
+ esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_srcdir=$ac_top_srcdir;;
+*)
+ case $ac_top_srcdir in
+ .) ac_abs_top_srcdir=$ac_abs_builddir;;
+ [\\/]* | ?:[\\/]* ) ac_abs_top_srcdir=$ac_top_srcdir;;
+ *) ac_abs_top_srcdir=$ac_abs_builddir/$ac_top_srcdir;;
+ esac;;
+esac
+
+ cd $ac_dir
+ # Check for guested configure; otherwise get Cygnus style configure.
+ if test -f $ac_srcdir/configure.gnu; then
+ echo
+ $SHELL $ac_srcdir/configure.gnu --help=recursive
+ elif test -f $ac_srcdir/configure; then
+ echo
+ $SHELL $ac_srcdir/configure --help=recursive
+ elif test -f $ac_srcdir/configure.ac ||
+ test -f $ac_srcdir/configure.in; then
+ echo
+ $ac_configure --help
+ else
+ echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+ fi
+ cd $ac_popdir
+ done
+fi
+
+test -n "$ac_init_help" && exit 0
+if $ac_init_version; then
+ cat <<\_ACEOF
+qdbm configure 1.8.77
+generated by GNU Autoconf 2.59
+
+Copyright (C) 2003 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+ exit 0
+fi
+exec 5>config.log
+cat >&5 <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by qdbm $as_me 1.8.77, which was
+generated by GNU Autoconf 2.59. Invocation command line was
+
+ $ $0 $@
+
+_ACEOF
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown`
+
+/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown`
+/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+hostinfo = `(hostinfo) 2>/dev/null || echo unknown`
+/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown`
+/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown`
+/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ echo "PATH: $as_dir"
+done
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_sep=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+ for ac_arg
+ do
+ case $ac_arg in
+ -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ continue ;;
+ *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?\"\']*)
+ ac_arg=`echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+ esac
+ case $ac_pass in
+ 1) ac_configure_args0="$ac_configure_args0 '$ac_arg'" ;;
+ 2)
+ ac_configure_args1="$ac_configure_args1 '$ac_arg'"
+ if test $ac_must_keep_next = true; then
+ ac_must_keep_next=false # Got value, back to normal.
+ else
+ case $ac_arg in
+ *=* | --config-cache | -C | -disable-* | --disable-* \
+ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+ | -with-* | --with-* | -without-* | --without-* | --x)
+ case "$ac_configure_args0 " in
+ "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+ esac
+ ;;
+ -* ) ac_must_keep_next=true ;;
+ esac
+ fi
+ ac_configure_args="$ac_configure_args$ac_sep'$ac_arg'"
+ # Get rid of the leading space.
+ ac_sep=" "
+ ;;
+ esac
+ done
+done
+$as_unset ac_configure_args0 || test "${ac_configure_args0+set}" != set || { ac_configure_args0=; export ac_configure_args0; }
+$as_unset ac_configure_args1 || test "${ac_configure_args1+set}" != set || { ac_configure_args1=; export ac_configure_args1; }
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log. We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Be sure not to use single quotes in there, as some shells,
+# such as our DU 5.0 friend, will then `close' the trap.
+trap 'exit_status=$?
+ # Save into config.log some information that might help in debugging.
+ {
+ echo
+
+ cat <<\_ASBOX
+## ---------------- ##
+## Cache variables. ##
+## ---------------- ##
+_ASBOX
+ echo
+ # The following way of writing the cache mishandles newlines in values,
+{
+ (set) 2>&1 |
+ case `(ac_space='"'"' '"'"'; set | grep ac_space) 2>&1` in
+ *ac_space=\ *)
+ sed -n \
+ "s/'"'"'/'"'"'\\\\'"'"''"'"'/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='"'"'\\2'"'"'/p"
+ ;;
+ *)
+ sed -n \
+ "s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1=\\2/p"
+ ;;
+ esac;
+}
+ echo
+
+ cat <<\_ASBOX
+## ----------------- ##
+## Output variables. ##
+## ----------------- ##
+_ASBOX
+ echo
+ for ac_var in $ac_subst_vars
+ do
+ eval ac_val=$`echo $ac_var`
+ echo "$ac_var='"'"'$ac_val'"'"'"
+ done | sort
+ echo
+
+ if test -n "$ac_subst_files"; then
+ cat <<\_ASBOX
+## ------------- ##
+## Output files. ##
+## ------------- ##
+_ASBOX
+ echo
+ for ac_var in $ac_subst_files
+ do
+ eval ac_val=$`echo $ac_var`
+ echo "$ac_var='"'"'$ac_val'"'"'"
+ done | sort
+ echo
+ fi
+
+ if test -s confdefs.h; then
+ cat <<\_ASBOX
+## ----------- ##
+## confdefs.h. ##
+## ----------- ##
+_ASBOX
+ echo
+ sed "/^$/d" confdefs.h | sort
+ echo
+ fi
+ test "$ac_signal" != 0 &&
+ echo "$as_me: caught signal $ac_signal"
+ echo "$as_me: exit $exit_status"
+ } >&5
+ rm -f core *.core &&
+ rm -rf conftest* confdefs* conf$$* $ac_clean_files &&
+ exit $exit_status
+ ' 0
+for ac_signal in 1 2 13 15; do
+ trap 'ac_signal='$ac_signal'; { (exit 1); exit 1; }' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -rf conftest* confdefs.h
+# AIX cpp loses on an empty file, so make sure it contains at least a newline.
+echo >confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer explicitly selected file to automatically selected ones.
+if test -z "$CONFIG_SITE"; then
+ if test "x$prefix" != xNONE; then
+ CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
+ else
+ CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+ fi
+fi
+for ac_site_file in $CONFIG_SITE; do
+ if test -r "$ac_site_file"; then
+ { echo "$as_me:$LINENO: loading site script $ac_site_file" >&5
+echo "$as_me: loading site script $ac_site_file" >&6;}
+ sed 's/^/| /' "$ac_site_file" >&5
+ . "$ac_site_file"
+ fi
+done
+
+if test -r "$cache_file"; then
+ # Some versions of bash will fail to source /dev/null (special
+ # files actually), so we avoid doing that.
+ if test -f "$cache_file"; then
+ { echo "$as_me:$LINENO: loading cache $cache_file" >&5
+echo "$as_me: loading cache $cache_file" >&6;}
+ case $cache_file in
+ [\\/]* | ?:[\\/]* ) . $cache_file;;
+ *) . ./$cache_file;;
+ esac
+ fi
+else
+ { echo "$as_me:$LINENO: creating cache $cache_file" >&5
+echo "$as_me: creating cache $cache_file" >&6;}
+ >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in `(set) 2>&1 |
+ sed -n 's/^ac_env_\([a-zA-Z_0-9]*\)_set=.*/\1/p'`; do
+ eval ac_old_set=\$ac_cv_env_${ac_var}_set
+ eval ac_new_set=\$ac_env_${ac_var}_set
+ eval ac_old_val="\$ac_cv_env_${ac_var}_value"
+ eval ac_new_val="\$ac_env_${ac_var}_value"
+ case $ac_old_set,$ac_new_set in
+ set,)
+ { echo "$as_me:$LINENO: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,set)
+ { echo "$as_me:$LINENO: error: \`$ac_var' was not set in the previous run" >&5
+echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+ ac_cache_corrupted=: ;;
+ ,);;
+ *)
+ if test "x$ac_old_val" != "x$ac_new_val"; then
+ { echo "$as_me:$LINENO: error: \`$ac_var' has changed since the previous run:" >&5
+echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+ { echo "$as_me:$LINENO: former value: $ac_old_val" >&5
+echo "$as_me: former value: $ac_old_val" >&2;}
+ { echo "$as_me:$LINENO: current value: $ac_new_val" >&5
+echo "$as_me: current value: $ac_new_val" >&2;}
+ ac_cache_corrupted=:
+ fi;;
+ esac
+ # Pass precious variables to config.status.
+ if test "$ac_new_set" = set; then
+ case $ac_new_val in
+ *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?\"\']*)
+ ac_arg=$ac_var=`echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+ *) ac_arg=$ac_var=$ac_new_val ;;
+ esac
+ case " $ac_configure_args " in
+ *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy.
+ *) ac_configure_args="$ac_configure_args '$ac_arg'" ;;
+ esac
+ fi
+done
+if $ac_cache_corrupted; then
+ { echo "$as_me:$LINENO: error: changes in the environment can compromise the build" >&5
+echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+ { { echo "$as_me:$LINENO: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&5
+echo "$as_me: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&2;}
+ { (exit 1); exit 1; }; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# Export variables
+LIBVER=14
+LIBREV=13
+TARGETS="all"
+MYDEFS=""
+MYOPTS=""
+MGWLIBS=""
+LD="ld"
+AR="ar"
+
+# Building paths
+pathtmp="$PATH"
+PATH="$HOME/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
+PATH="$PATH:/usr/ccs/bin:/usr/ucb:/usr/xpg4/bin:/usr/xpg6/bin:$pathtmp"
+LIBRARY_PATH="$HOME/lib:/usr/local/lib:$LIBRARY_PATH"
+LD_LIBRARY_PATH="$HOME/lib:/usr/local/lib:$LD_LIBRARY_PATH"
+CPATH="$HOME/include:/usr/local/include:$CPATH"
+export PATH LIBRARY_PATH LD_LIBRARY_PATH CPATH
+
+
+
+#================================================================
+# Options
+#================================================================
+
+
+# Internal variables
+enables=""
+ispthread=""
+iszlib=""
+isiconv=""
+isnowarn=""
+
+# Debug mode
+# Check whether --enable-debug or --disable-debug was given.
+if test "${enable_debug+set}" = set; then
+ enableval="$enable_debug"
+
+fi;
+if test "$enable_debug" = "yes"
+then
+ TARGETS="debug"
+ enables="$enables (debug)"
+fi
+
+# Developping mode
+# Check whether --enable-devel or --disable-devel was given.
+if test "${enable_devel+set}" = set; then
+ enableval="$enable_devel"
+
+fi;
+if test "$enable_devel" = "yes"
+then
+ TARGETS="devel"
+ enables="$enables (devel)"
+fi
+
+# Stable mode
+# Check whether --enable-stable or --disable-stable was given.
+if test "${enable_stable+set}" = set; then
+ enableval="$enable_stable"
+
+fi;
+if test "$enable_stable" = "yes"
+then
+ TARGETS="stable"
+ enables="$enables (stable)"
+fi
+
+# Enable POSIX thread
+# Check whether --enable-pthread or --disable-pthread was given.
+if test "${enable_pthread+set}" = set; then
+ enableval="$enable_pthread"
+
+fi;
+if test "$enable_pthread" = "yes"
+then
+ MYDEFS="$MYDEFS -DMYPTHREAD"
+ enables="$enables (pthread)"
+ ispthread="yes"
+fi
+
+# Disable file locking
+# Check whether --enable-lock or --disable-lock was given.
+if test "${enable_lock+set}" = set; then
+ enableval="$enable_lock"
+
+fi;
+if test "$enable_lock" = "no"
+then
+ MYDEFS="$MYDEFS -DMYNOLOCK"
+ enables="$enables (no-lock)"
+fi
+
+# Disable memory mapping
+# Check whether --enable-mmap or --disable-mmap was given.
+if test "${enable_mmap+set}" = set; then
+ enableval="$enable_mmap"
+
+fi;
+if test "$enable_mmap" = "no"
+then
+ MYDEFS="$MYDEFS -DMYNOMMAP"
+ enables="$enables (no-mmap)"
+fi
+
+# Enable ZLIB compression
+# Check whether --enable-zlib or --disable-zlib was given.
+if test "${enable_zlib+set}" = set; then
+ enableval="$enable_zlib"
+
+fi;
+if test "$enable_zlib" = "yes"
+then
+ MYDEFS="$MYDEFS -DMYZLIB"
+ MGWLIBS="-lz $MGWLIBS"
+ enables="$enables (zlib)"
+ iszlib="yes"
+fi
+
+# Enable LZO compression
+# Check whether --enable-lzo or --disable-lzo was given.
+if test "${enable_lzo+set}" = set; then
+ enableval="$enable_lzo"
+
+fi;
+if test "$enable_lzo" = "yes"
+then
+ MYDEFS="$MYDEFS -DMYLZO"
+ MGWLIBS="-llzo2 $MGWLIBS"
+ enables="$enables (lzo)"
+ islzo="yes"
+fi
+
+# Enable BZIP2 compression
+# Check whether --enable-bzip or --disable-bzip was given.
+if test "${enable_bzip+set}" = set; then
+ enableval="$enable_bzip"
+
+fi;
+if test "$enable_bzip" = "yes"
+then
+ MYDEFS="$MYDEFS -DMYBZIP"
+ MGWLIBS="-lbz2 $MGWLIBS"
+ enables="$enables (bzip)"
+ isbzip="yes"
+fi
+
+# Enable ICONV utilities
+# Check whether --enable-iconv or --disable-iconv was given.
+if test "${enable_iconv+set}" = set; then
+ enableval="$enable_iconv"
+
+fi;
+if test "$enable_iconv" = "yes"
+then
+ MYDEFS="$MYDEFS -DMYICONV"
+ MGWLIBS="-liconv $MGWLIBS"
+ enables="$enables (iconv)"
+ isiconv="yes"
+fi
+
+# No warning configuration
+# Check whether --enable-warn or --disable-warn was given.
+if test "${enable_warn+set}" = set; then
+ enableval="$enable_warn"
+
+fi;
+if test "$enable_warn" = "no"
+then
+ isnowarn="yes"
+fi
+
+# Messages
+printf '#================================================================\n'
+printf '# Configuring QDBM version %s%s.\n' "$PACKAGE_VERSION" "$enables"
+printf '#================================================================\n'
+
+
+
+#================================================================
+# Checking Commands to Build with
+#================================================================
+
+
+# C compiler
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="${ac_tool_prefix}gcc"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+ ac_ct_CC=$CC
+ # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CC="gcc"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ echo "$as_me:$LINENO: result: $ac_ct_CC" >&5
+echo "${ECHO_T}$ac_ct_CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+ CC=$ac_ct_CC
+else
+ CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+ if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="${ac_tool_prefix}cc"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+ ac_ct_CC=$CC
+ # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CC="cc"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ echo "$as_me:$LINENO: result: $ac_ct_CC" >&5
+echo "${ECHO_T}$ac_ct_CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+ CC=$ac_ct_CC
+else
+ CC="$ac_cv_prog_CC"
+fi
+
+fi
+if test -z "$CC"; then
+ # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+ ac_prog_rejected=yes
+ continue
+ fi
+ ac_cv_prog_CC="cc"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+if test $ac_prog_rejected = yes; then
+ # We found a bogon in the path, so make sure we never use it.
+ set dummy $ac_cv_prog_CC
+ shift
+ if test $# != 0; then
+ # We chose a different compiler from the bogus one.
+ # However, it has the same basename, so the bogon will be chosen
+ # first if we set CC to just the basename; use the full file name.
+ shift
+ ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+ fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+fi
+if test -z "$CC"; then
+ if test -n "$ac_tool_prefix"; then
+ for ac_prog in cl
+ do
+ # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+ echo "$as_me:$LINENO: result: $CC" >&5
+echo "${ECHO_T}$CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+ test -n "$CC" && break
+ done
+fi
+if test -z "$CC"; then
+ ac_ct_CC=$CC
+ for ac_prog in cl
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
+if test "${ac_cv_prog_ac_ct_CC+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ if test -n "$ac_ct_CC"; then
+ ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CC="$ac_prog"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+ echo "$as_me:$LINENO: result: $ac_ct_CC" >&5
+echo "${ECHO_T}$ac_ct_CC" >&6
+else
+ echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6
+fi
+
+ test -n "$ac_ct_CC" && break
+done
+
+ CC=$ac_ct_CC
+fi
+
+fi
+
+
+test -z "$CC" && { { echo "$as_me:$LINENO: error: no acceptable C compiler found in \$PATH
+See \`config.log' for more details." >&5
+echo "$as_me: error: no acceptable C compiler found in \$PATH
+See \`config.log' for more details." >&2;}
+ { (exit 1); exit 1; }; }
+
+# Provide some information about the compiler.
+echo "$as_me:$LINENO:" \
+ "checking for C compiler version" >&5
+ac_compiler=`set X $ac_compile; echo $2`
+{ (eval echo "$as_me:$LINENO: \"$ac_compiler --version </dev/null >&5\"") >&5
+ (eval $ac_compiler --version </dev/null >&5) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }
+{ (eval echo "$as_me:$LINENO: \"$ac_compiler -v </dev/null >&5\"") >&5
+ (eval $ac_compiler -v </dev/null >&5) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }
+{ (eval echo "$as_me:$LINENO: \"$ac_compiler -V </dev/null >&5\"") >&5
+ (eval $ac_compiler -V </dev/null >&5) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }
+
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+echo "$as_me:$LINENO: checking for C compiler default output file name" >&5
+echo $ECHO_N "checking for C compiler default output file name... $ECHO_C" >&6
+ac_link_default=`echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+if { (eval echo "$as_me:$LINENO: \"$ac_link_default\"") >&5
+ (eval $ac_link_default) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; then
+ # Find the output, starting from the most likely. This scheme is
+# not robust to junk in `.', hence go to wildcards (a.*) only as a last
+# resort.
+
+# Be careful to initialize this variable, since it used to be cached.
+# Otherwise an old cache value of `no' led to `EXEEXT = no' in a Makefile.
+ac_cv_exeext=
+# b.out is created by i960 compilers.
+for ac_file in a_out.exe a.exe conftest.exe a.out conftest a.* conftest.* b.out
+do
+ test -f "$ac_file" || continue
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.o | *.obj )
+ ;;
+ conftest.$ac_ext )
+ # This is the source file.
+ ;;
+ [ab].out )
+ # We found the default executable, but exeext='' is most
+ # certainly right.
+ break;;
+ *.* )
+ ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+ # FIXME: I believe we export ac_cv_exeext for Libtool,
+ # but it would be cool to find out if it's true. Does anybody
+ # maintain Libtool? --akim.
+ export ac_cv_exeext
+ break;;
+ * )
+ break;;
+ esac
+done
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { echo "$as_me:$LINENO: error: C compiler cannot create executables
+See \`config.log' for more details." >&5
+echo "$as_me: error: C compiler cannot create executables
+See \`config.log' for more details." >&2;}
+ { (exit 77); exit 77; }; }
+fi
+
+ac_exeext=$ac_cv_exeext
+echo "$as_me:$LINENO: result: $ac_file" >&5
+echo "${ECHO_T}$ac_file" >&6
+
+# Check the compiler produces executables we can run. If not, either
+# the compiler is broken, or we cross compile.
+echo "$as_me:$LINENO: checking whether the C compiler works" >&5
+echo $ECHO_N "checking whether the C compiler works... $ECHO_C" >&6
+# FIXME: These cross compiler hacks should be removed for Autoconf 3.0
+# If not cross compiling, check that we can run a simple program.
+if test "$cross_compiling" != yes; then
+ if { ac_try='./$ac_file'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ cross_compiling=no
+ else
+ if test "$cross_compiling" = maybe; then
+ cross_compiling=yes
+ else
+ { { echo "$as_me:$LINENO: error: cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details." >&2;}
+ { (exit 1); exit 1; }; }
+ fi
+ fi
+fi
+echo "$as_me:$LINENO: result: yes" >&5
+echo "${ECHO_T}yes" >&6
+
+rm -f a.out a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+# Check the compiler produces executables we can run. If not, either
+# the compiler is broken, or we cross compile.
+echo "$as_me:$LINENO: checking whether we are cross compiling" >&5
+echo $ECHO_N "checking whether we are cross compiling... $ECHO_C" >&6
+echo "$as_me:$LINENO: result: $cross_compiling" >&5
+echo "${ECHO_T}$cross_compiling" >&6
+
+echo "$as_me:$LINENO: checking for suffix of executables" >&5
+echo $ECHO_N "checking for suffix of executables... $ECHO_C" >&6
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+ (eval $ac_link) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; then
+ # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+ test -f "$ac_file" || continue
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.o | *.obj ) ;;
+ *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+ export ac_cv_exeext
+ break;;
+ * ) break;;
+ esac
+done
+else
+ { { echo "$as_me:$LINENO: error: cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details." >&2;}
+ { (exit 1); exit 1; }; }
+fi
+
+rm -f conftest$ac_cv_exeext
+echo "$as_me:$LINENO: result: $ac_cv_exeext" >&5
+echo "${ECHO_T}$ac_cv_exeext" >&6
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+echo "$as_me:$LINENO: checking for suffix of object files" >&5
+echo $ECHO_N "checking for suffix of object files... $ECHO_C" >&6
+if test "${ac_cv_objext+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; then
+ for ac_file in `(ls conftest.o conftest.obj; ls conftest.*) 2>/dev/null`; do
+ case $ac_file in
+ *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg ) ;;
+ *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+ break;;
+ esac
+done
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { echo "$as_me:$LINENO: error: cannot compute suffix of object files: cannot compile
+See \`config.log' for more details." >&5
+echo "$as_me: error: cannot compute suffix of object files: cannot compile
+See \`config.log' for more details." >&2;}
+ { (exit 1); exit 1; }; }
+fi
+
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+echo "$as_me:$LINENO: result: $ac_cv_objext" >&5
+echo "${ECHO_T}$ac_cv_objext" >&6
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+echo "$as_me:$LINENO: checking whether we are using the GNU C compiler" >&5
+echo $ECHO_N "checking whether we are using the GNU C compiler... $ECHO_C" >&6
+if test "${ac_cv_c_compiler_gnu+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+int
+main ()
+{
+#ifndef __GNUC__
+ choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_compiler_gnu=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_compiler_gnu=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+echo "$as_me:$LINENO: result: $ac_cv_c_compiler_gnu" >&5
+echo "${ECHO_T}$ac_cv_c_compiler_gnu" >&6
+GCC=`test $ac_compiler_gnu = yes && echo yes`
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+CFLAGS="-g"
+echo "$as_me:$LINENO: checking whether $CC accepts -g" >&5
+echo $ECHO_N "checking whether $CC accepts -g... $ECHO_C" >&6
+if test "${ac_cv_prog_cc_g+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_cv_prog_cc_g=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_prog_cc_g=no
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+echo "$as_me:$LINENO: result: $ac_cv_prog_cc_g" >&5
+echo "${ECHO_T}$ac_cv_prog_cc_g" >&6
+if test "$ac_test_CFLAGS" = set; then
+ CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+ if test "$GCC" = yes; then
+ CFLAGS="-g -O2"
+ else
+ CFLAGS="-g"
+ fi
+else
+ if test "$GCC" = yes; then
+ CFLAGS="-O2"
+ else
+ CFLAGS=
+ fi
+fi
+echo "$as_me:$LINENO: checking for $CC option to accept ANSI C" >&5
+echo $ECHO_N "checking for $CC option to accept ANSI C... $ECHO_C" >&6
+if test "${ac_cv_prog_cc_stdc+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_cv_prog_cc_stdc=no
+ac_save_CC=$CC
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+ char **p;
+ int i;
+{
+ return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+ char *s;
+ va_list v;
+ va_start (v,p);
+ s = g (p, va_arg (v,int));
+ va_end (v);
+ return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has
+ function prototypes and stuff, but not '\xHH' hex character constants.
+ These don't provoke an error unfortunately, instead are silently treated
+ as 'x'. The following induces an error, until -std1 is added to get
+ proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an
+ array size at least. It's necessary to write '\x00'==0 to get something
+ that's true only with -std1. */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1];
+ ;
+ return 0;
+}
+_ACEOF
+# Don't try gcc -ansi; that turns off useful extensions and
+# breaks some systems' header files.
+# AIX -qlanglvl=ansi
+# Ultrix and OSF/1 -std1
+# HP-UX 10.20 and later -Ae
+# HP-UX older versions -Aa -D_HPUX_SOURCE
+# SVR4 -Xc -D__EXTENSIONS__
+for ac_arg in "" -qlanglvl=ansi -std1 -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+ CC="$ac_save_CC $ac_arg"
+ rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_cv_prog_cc_stdc=$ac_arg
+break
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+fi
+rm -f conftest.err conftest.$ac_objext
+done
+rm -f conftest.$ac_ext conftest.$ac_objext
+CC=$ac_save_CC
+
+fi
+
+case "x$ac_cv_prog_cc_stdc" in
+ x|xno)
+ echo "$as_me:$LINENO: result: none needed" >&5
+echo "${ECHO_T}none needed" >&6 ;;
+ *)
+ echo "$as_me:$LINENO: result: $ac_cv_prog_cc_stdc" >&5
+echo "${ECHO_T}$ac_cv_prog_cc_stdc" >&6
+ CC="$CC $ac_cv_prog_cc_stdc" ;;
+esac
+
+# Some people use a C++ compiler to compile C. Since we use `exit',
+# in C++ we need to declare it. In case someone uses the same compiler
+# for both compiling C and C++ we need to have the C++ compiler decide
+# the declaration of exit, since it's the most demanding environment.
+cat >conftest.$ac_ext <<_ACEOF
+#ifndef __cplusplus
+ choke me
+#endif
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ for ac_declaration in \
+ '' \
+ 'extern "C" void std::exit (int) throw (); using std::exit;' \
+ 'extern "C" void std::exit (int); using std::exit;' \
+ 'extern "C" void exit (int) throw ();' \
+ 'extern "C" void exit (int);' \
+ 'void exit (int);'
+do
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+$ac_declaration
+#include <stdlib.h>
+int
+main ()
+{
+exit (42);
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ :
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+continue
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+ cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+$ac_declaration
+int
+main ()
+{
+exit (42);
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
+ (eval $ac_compile) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest.$ac_objext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ break
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+done
+rm -f conftest*
+if test -n "$ac_declaration"; then
+ echo '#ifdef __cplusplus' >>confdefs.h
+ echo $ac_declaration >>confdefs.h
+ echo '#endif' >>confdefs.h
+fi
+
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+fi
+rm -f conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test "$GCC" != "yes"
+then
+ if test "$isnowarn" != "yes"
+ then
+ printf '#================================================================\n' 1>&2
+ printf '# WARNING: GCC is required to build this package.\n' 1>&2
+ printf '#================================================================\n' 1>&2
+ fi
+fi
+if uname | egrep -i 'Linux' > /dev/null 2>&1 &&
+ uname -m | egrep '(x|i)(3|4|5|6|7|8|9)?86' > /dev/null 2>&1
+then
+ MYOPTS="-minline-all-stringops"
+fi
+if uname | egrep -i 'SunOS' > /dev/null 2>&1
+then
+ MYOPTS="-O1 -fno-omit-frame-pointer -fno-force-addr"
+fi
+if uname | egrep -i 'BSD' > /dev/null 2>&1
+then
+ MYOPTS="-O1 -fno-omit-frame-pointer -fno-force-addr"
+fi
+if gcc --version | egrep -i '^2\.(8|9)' > /dev/null 2>&1
+then
+ MYOPTS="-O1 -fno-omit-frame-pointer -fno-force-addr"
+fi
+
+# Linker
+printf 'checking for ld... '
+if which ld | grep '/ld$' > /dev/null 2>&1
+then
+ LD=`which ld`
+ printf '%s\n' "$LD"
+else
+ printf 'no\n'
+ if test "$isnowarn" != "yes"
+ then
+ printf '#================================================================\n' 1>&2
+ printf '# WARNING: ld is not found in PATH.\n' 1>&2
+ printf '#================================================================\n' 1>&2
+ fi
+fi
+
+# Archiver
+printf 'checking for ar... '
+if which ar | grep '/ar$' > /dev/null 2>&1
+then
+ AR=`which ar`
+ printf '%s\n' "$AR"
+else
+ printf 'no\n'
+ if test "$isnowarn" != "yes"
+ then
+ printf '#================================================================\n' 1>&2
+ printf '# WARNING: ar is not found in PATH.\n' 1>&2
+ printf '#================================================================\n' 1>&2
+ fi
+fi
+
+
+
+#================================================================
+# Checking Libraries
+#================================================================
+
+
+# Underlying libraries
+
+
+echo "$as_me:$LINENO: checking for main in -lc" >&5
+echo $ECHO_N "checking for main in -lc... $ECHO_C" >&6
+if test "${ac_cv_lib_c_main+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lc $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+main ();
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+ (eval $ac_link) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest$ac_exeext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_cv_lib_c_main=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_lib_c_main=no
+fi
+rm -f conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+echo "$as_me:$LINENO: result: $ac_cv_lib_c_main" >&5
+echo "${ECHO_T}$ac_cv_lib_c_main" >&6
+if test $ac_cv_lib_c_main = yes; then
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBC 1
+_ACEOF
+
+ LIBS="-lc $LIBS"
+
+fi
+
+
+# for pthread
+if test "$ispthread" = "yes"
+then
+
+echo "$as_me:$LINENO: checking for main in -lpthread" >&5
+echo $ECHO_N "checking for main in -lpthread... $ECHO_C" >&6
+if test "${ac_cv_lib_pthread_main+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lpthread $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+main ();
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+ (eval $ac_link) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest$ac_exeext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_cv_lib_pthread_main=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_lib_pthread_main=no
+fi
+rm -f conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+echo "$as_me:$LINENO: result: $ac_cv_lib_pthread_main" >&5
+echo "${ECHO_T}$ac_cv_lib_pthread_main" >&6
+if test $ac_cv_lib_pthread_main = yes; then
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBPTHREAD 1
+_ACEOF
+
+ LIBS="-lpthread $LIBS"
+
+fi
+
+fi
+
+# for zlib
+if test "$iszlib" = "yes"
+then
+
+echo "$as_me:$LINENO: checking for main in -lz" >&5
+echo $ECHO_N "checking for main in -lz... $ECHO_C" >&6
+if test "${ac_cv_lib_z_main+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lz $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+main ();
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+ (eval $ac_link) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest$ac_exeext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_cv_lib_z_main=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_lib_z_main=no
+fi
+rm -f conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+echo "$as_me:$LINENO: result: $ac_cv_lib_z_main" >&5
+echo "${ECHO_T}$ac_cv_lib_z_main" >&6
+if test $ac_cv_lib_z_main = yes; then
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBZ 1
+_ACEOF
+
+ LIBS="-lz $LIBS"
+
+fi
+
+fi
+
+# for lzo
+if test "$islzo" = "yes"
+then
+
+echo "$as_me:$LINENO: checking for main in -llzo2" >&5
+echo $ECHO_N "checking for main in -llzo2... $ECHO_C" >&6
+if test "${ac_cv_lib_lzo2_main+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-llzo2 $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+main ();
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+ (eval $ac_link) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest$ac_exeext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_cv_lib_lzo2_main=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_lib_lzo2_main=no
+fi
+rm -f conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+echo "$as_me:$LINENO: result: $ac_cv_lib_lzo2_main" >&5
+echo "${ECHO_T}$ac_cv_lib_lzo2_main" >&6
+if test $ac_cv_lib_lzo2_main = yes; then
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBLZO2 1
+_ACEOF
+
+ LIBS="-llzo2 $LIBS"
+
+fi
+
+fi
+
+# for bzip
+if test "$isbzip" = "yes"
+then
+
+echo "$as_me:$LINENO: checking for main in -lbz2" >&5
+echo $ECHO_N "checking for main in -lbz2... $ECHO_C" >&6
+if test "${ac_cv_lib_bz2_main+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lbz2 $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+main ();
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+ (eval $ac_link) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest$ac_exeext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_cv_lib_bz2_main=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_lib_bz2_main=no
+fi
+rm -f conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+echo "$as_me:$LINENO: result: $ac_cv_lib_bz2_main" >&5
+echo "${ECHO_T}$ac_cv_lib_bz2_main" >&6
+if test $ac_cv_lib_bz2_main = yes; then
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBBZ2 1
+_ACEOF
+
+ LIBS="-lbz2 $LIBS"
+
+fi
+
+fi
+
+# for iconv
+if test "$isiconv" = "yes"
+then
+
+echo "$as_me:$LINENO: checking for main in -liconv" >&5
+echo $ECHO_N "checking for main in -liconv... $ECHO_C" >&6
+if test "${ac_cv_lib_iconv_main+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-liconv $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+main ();
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+ (eval $ac_link) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest$ac_exeext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_cv_lib_iconv_main=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_lib_iconv_main=no
+fi
+rm -f conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+echo "$as_me:$LINENO: result: $ac_cv_lib_iconv_main" >&5
+echo "${ECHO_T}$ac_cv_lib_iconv_main" >&6
+if test $ac_cv_lib_iconv_main = yes; then
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBICONV 1
+_ACEOF
+
+ LIBS="-liconv $LIBS"
+
+fi
+
+fi
+
+# For old BSDs
+if test "$ispthread" = "yes" && uname -a | grep BSD > /dev/null &&
+ test -f /usr/lib/libc_r.a && test ! -f /usr/lib/libpthread.a
+then
+ LIBS=`printf '%s' "$LIBS" | sed 's/-lc/-lc_r/g'`
+fi
+
+# Duplication of QDBM for C
+echo "$as_me:$LINENO: checking for main in -lqdbm" >&5
+echo $ECHO_N "checking for main in -lqdbm... $ECHO_C" >&6
+if test "${ac_cv_lib_qdbm_main+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lqdbm $LIBS"
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+main ();
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+ (eval $ac_link) 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } &&
+ { ac_try='test -z "$ac_c_werror_flag"
+ || test ! -s conftest.err'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; } &&
+ { ac_try='test -s conftest$ac_exeext'
+ { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+ (eval $ac_try) 2>&5
+ ac_status=$?
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); }; }; then
+ ac_cv_lib_qdbm_main=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ac_cv_lib_qdbm_main=no
+fi
+rm -f conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+echo "$as_me:$LINENO: result: $ac_cv_lib_qdbm_main" >&5
+echo "${ECHO_T}$ac_cv_lib_qdbm_main" >&6
+if test $ac_cv_lib_qdbm_main = yes; then
+ if test "$isnowarn" != "yes"
+ then
+ printf '#================================================================\n' 1>&2
+ printf '# WARNING: The existing library was detected.\n' 1>&2
+ printf '#================================================================\n' 1>&2
+ fi
+
+fi
+
+
+
+
+#================================================================
+# Generic Settings
+#================================================================
+
+
+# Export variables
+
+
+
+
+
+
+
+
+
+
+
+# Targets
+ ac_config_files="$ac_config_files Makefile LTmakefile qdbm.spec qdbm.pc"
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems. If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, don't put newlines in cache variables' values.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+{
+ (set) 2>&1 |
+ case `(ac_space=' '; set | grep ac_space) 2>&1` in
+ *ac_space=\ *)
+ # `set' does not quote correctly, so add quotes (double-quote
+ # substitution turns \\\\ into \\, and sed turns \\ into \).
+ sed -n \
+ "s/'/'\\\\''/g;
+ s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+ ;;
+ *)
+ # `set' quotes correctly as required by POSIX, so do not add quotes.
+ sed -n \
+ "s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1=\\2/p"
+ ;;
+ esac;
+} |
+ sed '
+ t clear
+ : clear
+ s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+ t end
+ /^ac_cv_env/!s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+ : end' >>confcache
+if diff $cache_file confcache >/dev/null 2>&1; then :; else
+ if test -w $cache_file; then
+ test "x$cache_file" != "x/dev/null" && echo "updating cache $cache_file"
+ cat confcache >$cache_file
+ else
+ echo "not updating unwritable cache $cache_file"
+ fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# VPATH may cause trouble with some makes, so we remove $(srcdir),
+# ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+ ac_vpsub='/^[ ]*VPATH[ ]*=/{
+s/:*\$(srcdir):*/:/;
+s/:*\${srcdir}:*/:/;
+s/:*@srcdir@:*/:/;
+s/^\([^=]*=[ ]*\):*/\1/;
+s/:*$//;
+s/^[^=]*=[ ]*$//;
+}'
+fi
+
+# Transform confdefs.h into DEFS.
+# Protect against shell expansion while executing Makefile rules.
+# Protect against Makefile macro expansion.
+#
+# If the first sed substitution is executed (which looks for macros that
+# take arguments), then we branch to the quote section. Otherwise,
+# look for a macro that doesn't take arguments.
+cat >confdef2opt.sed <<\_ACEOF
+t clear
+: clear
+s,^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\),-D\1=\2,g
+t quote
+s,^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\),-D\1=\2,g
+t quote
+d
+: quote
+s,[ `~#$^&*(){}\\|;'"<>?],\\&,g
+s,\[,\\&,g
+s,\],\\&,g
+s,\$,$$,g
+p
+_ACEOF
+# We use echo to avoid assuming a particular line-breaking character.
+# The extra dot is to prevent the shell from consuming trailing
+# line-breaks from the sub-command output. A line-break within
+# single-quotes doesn't work because, if this script is created in a
+# platform that uses two characters for line-breaks (e.g., DOS), tr
+# would break.
+ac_LF_and_DOT=`echo; echo .`
+DEFS=`sed -n -f confdef2opt.sed confdefs.h | tr "$ac_LF_and_DOT" ' .'`
+rm -f confdef2opt.sed
+
+
+ac_libobjs=
+ac_ltlibobjs=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+ # 1. Remove the extension, and $U if already installed.
+ ac_i=`echo "$ac_i" |
+ sed 's/\$U\././;s/\.o$//;s/\.obj$//'`
+ # 2. Add them.
+ ac_libobjs="$ac_libobjs $ac_i\$U.$ac_objext"
+ ac_ltlibobjs="$ac_ltlibobjs $ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+
+: ${CONFIG_STATUS=./config.status}
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ echo "$as_me:$LINENO: creating $CONFIG_STATUS" >&5
+echo "$as_me: creating $CONFIG_STATUS" >&6;}
+cat >$CONFIG_STATUS <<_ACEOF
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+SHELL=\${CONFIG_SHELL-$SHELL}
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+## --------------------- ##
+## M4sh Initialization. ##
+## --------------------- ##
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
+ set -o posix
+fi
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# Support unset when possible.
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
+ as_unset=unset
+else
+ as_unset=false
+fi
+
+
+# Work around bugs in pre-3.0 UWIN ksh.
+$as_unset ENV MAIL MAILPATH
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+for as_var in \
+ LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \
+ LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \
+ LC_TELEPHONE LC_TIME
+do
+ if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then
+ eval $as_var=C; export $as_var
+ else
+ $as_unset $as_var
+ fi
+done
+
+# Required to use basename.
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+if (basename /) >/dev/null 2>&1 && test "X`basename / 2>&1`" = "X/"; then
+ as_basename=basename
+else
+ as_basename=false
+fi
+
+
+# Name of the executable.
+as_me=`$as_basename "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+ X"$0" : 'X\(//\)$' \| \
+ X"$0" : 'X\(/\)$' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X/"$0" |
+ sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/; q; }
+ /^X\/\(\/\/\)$/{ s//\1/; q; }
+ /^X\/\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+
+
+# PATH needs CR, and LINENO needs CR and PATH.
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+ echo "#! /bin/sh" >conf$$.sh
+ echo "exit 0" >>conf$$.sh
+ chmod +x conf$$.sh
+ if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
+ PATH_SEPARATOR=';'
+ else
+ PATH_SEPARATOR=:
+ fi
+ rm -f conf$$.sh
+fi
+
+
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" || {
+ # Find who we are. Look in the path if we contain no path at all
+ # relative or not.
+ case $0 in
+ *[\\/]* ) as_myself=$0 ;;
+ *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+done
+
+ ;;
+ esac
+ # We did not find ourselves, most probably we were run as `sh COMMAND'
+ # in which case we are not to be found in the path.
+ if test "x$as_myself" = x; then
+ as_myself=$0
+ fi
+ if test ! -f "$as_myself"; then
+ { { echo "$as_me:$LINENO: error: cannot find myself; rerun with an absolute path" >&5
+echo "$as_me: error: cannot find myself; rerun with an absolute path" >&2;}
+ { (exit 1); exit 1; }; }
+ fi
+ case $CONFIG_SHELL in
+ '')
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for as_base in sh bash ksh sh5; do
+ case $as_dir in
+ /*)
+ if ("$as_dir/$as_base" -c '
+ as_lineno_1=$LINENO
+ as_lineno_2=$LINENO
+ as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
+ test "x$as_lineno_1" != "x$as_lineno_2" &&
+ test "x$as_lineno_3" = "x$as_lineno_2" ') 2>/dev/null; then
+ $as_unset BASH_ENV || test "${BASH_ENV+set}" != set || { BASH_ENV=; export BASH_ENV; }
+ $as_unset ENV || test "${ENV+set}" != set || { ENV=; export ENV; }
+ CONFIG_SHELL=$as_dir/$as_base
+ export CONFIG_SHELL
+ exec "$CONFIG_SHELL" "$0" ${1+"$@"}
+ fi;;
+ esac
+ done
+done
+;;
+ esac
+
+ # Create $as_me.lineno as a copy of $as_myself, but with $LINENO
+ # uniformly replaced by the line number. The first 'sed' inserts a
+ # line-number line before each line; the second 'sed' does the real
+ # work. The second script uses 'N' to pair each line-number line
+ # with the numbered line, and appends trailing '-' during
+ # substitution so that $LINENO is not a special case at line end.
+ # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the
+ # second 'sed' script. Blame Lee E. McMahon for sed's syntax. :-)
+ sed '=' <$as_myself |
+ sed '
+ N
+ s,$,-,
+ : loop
+ s,^\(['$as_cr_digits']*\)\(.*\)[$]LINENO\([^'$as_cr_alnum'_]\),\1\2\1\3,
+ t loop
+ s,-$,,
+ s,^['$as_cr_digits']*\n,,
+ ' >$as_me.lineno &&
+ chmod +x $as_me.lineno ||
+ { { echo "$as_me:$LINENO: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&5
+echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2;}
+ { (exit 1); exit 1; }; }
+
+ # Don't try to exec as it changes $[0], causing all sort of problems
+ # (the dirname of $[0] is not the place where we might find the
+ # original and so on. Autoconf is especially sensible to this).
+ . ./$as_me.lineno
+ # Exit status is that of the last command.
+ exit
+}
+
+
+case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
+ *c*,-n*) ECHO_N= ECHO_C='
+' ECHO_T=' ' ;;
+ *c*,* ) ECHO_N=-n ECHO_C= ECHO_T= ;;
+ *) ECHO_N= ECHO_C='\c' ECHO_T= ;;
+esac
+
+if expr a : '\(a\)' >/dev/null 2>&1; then
+ as_expr=expr
+else
+ as_expr=false
+fi
+
+rm -f conf$$ conf$$.exe conf$$.file
+echo >conf$$.file
+if ln -s conf$$.file conf$$ 2>/dev/null; then
+ # We could just check for DJGPP; but this test a) works b) is more generic
+ # and c) will remain valid once DJGPP supports symlinks (DJGPP 2.04).
+ if test -f conf$$.exe; then
+ # Don't use ln at all; we don't have any links
+ as_ln_s='cp -p'
+ else
+ as_ln_s='ln -s'
+ fi
+elif ln conf$$.file conf$$ 2>/dev/null; then
+ as_ln_s=ln
+else
+ as_ln_s='cp -p'
+fi
+rm -f conf$$ conf$$.exe conf$$.file
+
+if mkdir -p . 2>/dev/null; then
+ as_mkdir_p=:
+else
+ test -d ./-p && rmdir ./-p
+ as_mkdir_p=false
+fi
+
+as_executable_p="test -f"
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.
+as_nl='
+'
+IFS=" $as_nl"
+
+# CDPATH.
+$as_unset CDPATH
+
+exec 6>&1
+
+# Open the log real soon, to keep \$[0] and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling. Logging --version etc. is OK.
+exec 5>>config.log
+{
+ echo
+ sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+} >&5
+cat >&5 <<_CSEOF
+
+This file was extended by qdbm $as_me 1.8.77, which was
+generated by GNU Autoconf 2.59. Invocation command line was
+
+ CONFIG_FILES = $CONFIG_FILES
+ CONFIG_HEADERS = $CONFIG_HEADERS
+ CONFIG_LINKS = $CONFIG_LINKS
+ CONFIG_COMMANDS = $CONFIG_COMMANDS
+ $ $0 $@
+
+_CSEOF
+echo "on `(hostname || uname -n) 2>/dev/null | sed 1q`" >&5
+echo >&5
+_ACEOF
+
+# Files that config.status was made for.
+if test -n "$ac_config_files"; then
+ echo "config_files=\"$ac_config_files\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_headers"; then
+ echo "config_headers=\"$ac_config_headers\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_links"; then
+ echo "config_links=\"$ac_config_links\"" >>$CONFIG_STATUS
+fi
+
+if test -n "$ac_config_commands"; then
+ echo "config_commands=\"$ac_config_commands\"" >>$CONFIG_STATUS
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+ac_cs_usage="\
+\`$as_me' instantiates files from templates according to the
+current configuration.
+
+Usage: $0 [OPTIONS] [FILE]...
+
+ -h, --help print this help, then exit
+ -V, --version print version number, then exit
+ -q, --quiet do not print progress messages
+ -d, --debug don't remove temporary files
+ --recheck update $as_me by reconfiguring in the same conditions
+ --file=FILE[:TEMPLATE]
+ instantiate the configuration file FILE
+
+Configuration files:
+$config_files
+
+Report bugs to <bug-autoconf@gnu.org>."
+_ACEOF
+
+cat >>$CONFIG_STATUS <<_ACEOF
+ac_cs_version="\\
+qdbm config.status 1.8.77
+configured by $0, generated by GNU Autoconf 2.59,
+ with options \\"`echo "$ac_configure_args" | sed 's/[\\""\`\$]/\\\\&/g'`\\"
+
+Copyright (C) 2003 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+srcdir=$srcdir
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+# If no file are specified by the user, then we need to provide default
+# value. By we need to know if files were specified by the user.
+ac_need_defaults=:
+while test $# != 0
+do
+ case $1 in
+ --*=*)
+ ac_option=`expr "x$1" : 'x\([^=]*\)='`
+ ac_optarg=`expr "x$1" : 'x[^=]*=\(.*\)'`
+ ac_shift=:
+ ;;
+ -*)
+ ac_option=$1
+ ac_optarg=$2
+ ac_shift=shift
+ ;;
+ *) # This is not an option, so the user has probably given explicit
+ # arguments.
+ ac_option=$1
+ ac_need_defaults=false;;
+ esac
+
+ case $ac_option in
+ # Handling of the options.
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+ -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+ ac_cs_recheck=: ;;
+ --version | --vers* | -V )
+ echo "$ac_cs_version"; exit 0 ;;
+ --he | --h)
+ # Conflict between --help and --header
+ { { echo "$as_me:$LINENO: error: ambiguous option: $1
+Try \`$0 --help' for more information." >&5
+echo "$as_me: error: ambiguous option: $1
+Try \`$0 --help' for more information." >&2;}
+ { (exit 1); exit 1; }; };;
+ --help | --hel | -h )
+ echo "$ac_cs_usage"; exit 0 ;;
+ --debug | --d* | -d )
+ debug=: ;;
+ --file | --fil | --fi | --f )
+ $ac_shift
+ CONFIG_FILES="$CONFIG_FILES $ac_optarg"
+ ac_need_defaults=false;;
+ --header | --heade | --head | --hea )
+ $ac_shift
+ CONFIG_HEADERS="$CONFIG_HEADERS $ac_optarg"
+ ac_need_defaults=false;;
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil | --si | --s)
+ ac_cs_silent=: ;;
+
+ # This is an error.
+ -*) { { echo "$as_me:$LINENO: error: unrecognized option: $1
+Try \`$0 --help' for more information." >&5
+echo "$as_me: error: unrecognized option: $1
+Try \`$0 --help' for more information." >&2;}
+ { (exit 1); exit 1; }; } ;;
+
+ *) ac_config_targets="$ac_config_targets $1" ;;
+
+ esac
+ shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+ exec 6>/dev/null
+ ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+if \$ac_cs_recheck; then
+ echo "running $SHELL $0 " $ac_configure_args \$ac_configure_extra_args " --no-create --no-recursion" >&6
+ exec $SHELL $0 $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+fi
+
+_ACEOF
+
+
+
+
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+for ac_config_target in $ac_config_targets
+do
+ case "$ac_config_target" in
+ # Handling of arguments.
+ "Makefile" ) CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+ "LTmakefile" ) CONFIG_FILES="$CONFIG_FILES LTmakefile" ;;
+ "qdbm.spec" ) CONFIG_FILES="$CONFIG_FILES qdbm.spec" ;;
+ "qdbm.pc" ) CONFIG_FILES="$CONFIG_FILES qdbm.pc" ;;
+ *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5
+echo "$as_me: error: invalid argument: $ac_config_target" >&2;}
+ { (exit 1); exit 1; }; };;
+ esac
+done
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used. Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+ test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+fi
+
+# Have a temporary directory for convenience. Make it in the build tree
+# simply because there is no reason to put it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Create a temporary directory, and hook for its removal unless debugging.
+$debug ||
+{
+ trap 'exit_status=$?; rm -rf $tmp && exit $exit_status' 0
+ trap '{ (exit 1); exit 1; }' 1 2 13 15
+}
+
+# Create a (secure) tmp directory for tmp files.
+
+{
+ tmp=`(umask 077 && mktemp -d -q "./confstatXXXXXX") 2>/dev/null` &&
+ test -n "$tmp" && test -d "$tmp"
+} ||
+{
+ tmp=./confstat$$-$RANDOM
+ (umask 077 && mkdir $tmp)
+} ||
+{
+ echo "$me: cannot create a temporary directory in ." >&2
+ { (exit 1); exit 1; }
+}
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<_ACEOF
+
+#
+# CONFIG_FILES section.
+#
+
+# No need to generate the scripts if there are no CONFIG_FILES.
+# This happens for instance when ./config.status config.h
+if test -n "\$CONFIG_FILES"; then
+ # Protect against being on the right side of a sed subst in config.status.
+ sed 's/,@/@@/; s/@,/@@/; s/,;t t\$/@;t t/; /@;t t\$/s/[\\\\&,]/\\\\&/g;
+ s/@@/,@/; s/@@/@,/; s/@;t t\$/,;t t/' >\$tmp/subs.sed <<\\CEOF
+s,@SHELL@,$SHELL,;t t
+s,@PATH_SEPARATOR@,$PATH_SEPARATOR,;t t
+s,@PACKAGE_NAME@,$PACKAGE_NAME,;t t
+s,@PACKAGE_TARNAME@,$PACKAGE_TARNAME,;t t
+s,@PACKAGE_VERSION@,$PACKAGE_VERSION,;t t
+s,@PACKAGE_STRING@,$PACKAGE_STRING,;t t
+s,@PACKAGE_BUGREPORT@,$PACKAGE_BUGREPORT,;t t
+s,@exec_prefix@,$exec_prefix,;t t
+s,@prefix@,$prefix,;t t
+s,@program_transform_name@,$program_transform_name,;t t
+s,@bindir@,$bindir,;t t
+s,@sbindir@,$sbindir,;t t
+s,@libexecdir@,$libexecdir,;t t
+s,@datadir@,$datadir,;t t
+s,@sysconfdir@,$sysconfdir,;t t
+s,@sharedstatedir@,$sharedstatedir,;t t
+s,@localstatedir@,$localstatedir,;t t
+s,@libdir@,$libdir,;t t
+s,@includedir@,$includedir,;t t
+s,@oldincludedir@,$oldincludedir,;t t
+s,@infodir@,$infodir,;t t
+s,@mandir@,$mandir,;t t
+s,@build_alias@,$build_alias,;t t
+s,@host_alias@,$host_alias,;t t
+s,@target_alias@,$target_alias,;t t
+s,@DEFS@,$DEFS,;t t
+s,@ECHO_C@,$ECHO_C,;t t
+s,@ECHO_N@,$ECHO_N,;t t
+s,@ECHO_T@,$ECHO_T,;t t
+s,@LIBS@,$LIBS,;t t
+s,@CC@,$CC,;t t
+s,@CFLAGS@,$CFLAGS,;t t
+s,@LDFLAGS@,$LDFLAGS,;t t
+s,@CPPFLAGS@,$CPPFLAGS,;t t
+s,@ac_ct_CC@,$ac_ct_CC,;t t
+s,@EXEEXT@,$EXEEXT,;t t
+s,@OBJEXT@,$OBJEXT,;t t
+s,@LIBVER@,$LIBVER,;t t
+s,@LIBREV@,$LIBREV,;t t
+s,@TARGETS@,$TARGETS,;t t
+s,@MYDEFS@,$MYDEFS,;t t
+s,@MYOPTS@,$MYOPTS,;t t
+s,@MGWLIBS@,$MGWLIBS,;t t
+s,@LD@,$LD,;t t
+s,@AR@,$AR,;t t
+s,@LIBOBJS@,$LIBOBJS,;t t
+s,@LTLIBOBJS@,$LTLIBOBJS,;t t
+CEOF
+
+_ACEOF
+
+ cat >>$CONFIG_STATUS <<\_ACEOF
+ # Split the substitutions into bite-sized pieces for seds with
+ # small command number limits, like on Digital OSF/1 and HP-UX.
+ ac_max_sed_lines=48
+ ac_sed_frag=1 # Number of current file.
+ ac_beg=1 # First line for current file.
+ ac_end=$ac_max_sed_lines # Line after last line for current file.
+ ac_more_lines=:
+ ac_sed_cmds=
+ while $ac_more_lines; do
+ if test $ac_beg -gt 1; then
+ sed "1,${ac_beg}d; ${ac_end}q" $tmp/subs.sed >$tmp/subs.frag
+ else
+ sed "${ac_end}q" $tmp/subs.sed >$tmp/subs.frag
+ fi
+ if test ! -s $tmp/subs.frag; then
+ ac_more_lines=false
+ else
+ # The purpose of the label and of the branching condition is to
+ # speed up the sed processing (if there are no `@' at all, there
+ # is no need to browse any of the substitutions).
+ # These are the two extra sed commands mentioned above.
+ (echo ':t
+ /@[a-zA-Z_][a-zA-Z_0-9]*@/!b' && cat $tmp/subs.frag) >$tmp/subs-$ac_sed_frag.sed
+ if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds="sed -f $tmp/subs-$ac_sed_frag.sed"
+ else
+ ac_sed_cmds="$ac_sed_cmds | sed -f $tmp/subs-$ac_sed_frag.sed"
+ fi
+ ac_sed_frag=`expr $ac_sed_frag + 1`
+ ac_beg=$ac_end
+ ac_end=`expr $ac_end + $ac_max_sed_lines`
+ fi
+ done
+ if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds=cat
+ fi
+fi # test -n "$CONFIG_FILES"
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+for ac_file in : $CONFIG_FILES; do test "x$ac_file" = x: && continue
+ # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+ case $ac_file in
+ - | *:- | *:-:* ) # input from stdin
+ cat >$tmp/stdin
+ ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+ ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+ *:* ) ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+ ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+ * ) ac_file_in=$ac_file.in ;;
+ esac
+
+ # Compute @srcdir@, @top_srcdir@, and @INSTALL@ for subdirectories.
+ ac_dir=`(dirname "$ac_file") 2>/dev/null ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$ac_file" : 'X\(//\)[^/]' \| \
+ X"$ac_file" : 'X\(//\)$' \| \
+ X"$ac_file" : 'X\(/\)' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X"$ac_file" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+ /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+ /^X\(\/\/\)$/{ s//\1/; q; }
+ /^X\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+ { if $as_mkdir_p; then
+ mkdir -p "$ac_dir"
+ else
+ as_dir="$ac_dir"
+ as_dirs=
+ while test ! -d "$as_dir"; do
+ as_dirs="$as_dir $as_dirs"
+ as_dir=`(dirname "$as_dir") 2>/dev/null ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+ X"$as_dir" : 'X\(//\)[^/]' \| \
+ X"$as_dir" : 'X\(//\)$' \| \
+ X"$as_dir" : 'X\(/\)' \| \
+ . : '\(.\)' 2>/dev/null ||
+echo X"$as_dir" |
+ sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+ /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+ /^X\(\/\/\)$/{ s//\1/; q; }
+ /^X\(\/\).*/{ s//\1/; q; }
+ s/.*/./; q'`
+ done
+ test ! -n "$as_dirs" || mkdir $as_dirs
+ fi || { { echo "$as_me:$LINENO: error: cannot create directory \"$ac_dir\"" >&5
+echo "$as_me: error: cannot create directory \"$ac_dir\"" >&2;}
+ { (exit 1); exit 1; }; }; }
+
+ ac_builddir=.
+
+if test "$ac_dir" != .; then
+ ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'`
+ # A "../" for each directory in $ac_dir_suffix.
+ ac_top_builddir=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,../,g'`
+else
+ ac_dir_suffix= ac_top_builddir=
+fi
+
+case $srcdir in
+ .) # No --srcdir option. We are building in place.
+ ac_srcdir=.
+ if test -z "$ac_top_builddir"; then
+ ac_top_srcdir=.
+ else
+ ac_top_srcdir=`echo $ac_top_builddir | sed 's,/$,,'`
+ fi ;;
+ [\\/]* | ?:[\\/]* ) # Absolute path.
+ ac_srcdir=$srcdir$ac_dir_suffix;
+ ac_top_srcdir=$srcdir ;;
+ *) # Relative path.
+ ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix
+ ac_top_srcdir=$ac_top_builddir$srcdir ;;
+esac
+
+# Do not use `cd foo && pwd` to compute absolute paths, because
+# the directories may not exist.
+case `pwd` in
+.) ac_abs_builddir="$ac_dir";;
+*)
+ case "$ac_dir" in
+ .) ac_abs_builddir=`pwd`;;
+ [\\/]* | ?:[\\/]* ) ac_abs_builddir="$ac_dir";;
+ *) ac_abs_builddir=`pwd`/"$ac_dir";;
+ esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_builddir=${ac_top_builddir}.;;
+*)
+ case ${ac_top_builddir}. in
+ .) ac_abs_top_builddir=$ac_abs_builddir;;
+ [\\/]* | ?:[\\/]* ) ac_abs_top_builddir=${ac_top_builddir}.;;
+ *) ac_abs_top_builddir=$ac_abs_builddir/${ac_top_builddir}.;;
+ esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_srcdir=$ac_srcdir;;
+*)
+ case $ac_srcdir in
+ .) ac_abs_srcdir=$ac_abs_builddir;;
+ [\\/]* | ?:[\\/]* ) ac_abs_srcdir=$ac_srcdir;;
+ *) ac_abs_srcdir=$ac_abs_builddir/$ac_srcdir;;
+ esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_srcdir=$ac_top_srcdir;;
+*)
+ case $ac_top_srcdir in
+ .) ac_abs_top_srcdir=$ac_abs_builddir;;
+ [\\/]* | ?:[\\/]* ) ac_abs_top_srcdir=$ac_top_srcdir;;
+ *) ac_abs_top_srcdir=$ac_abs_builddir/$ac_top_srcdir;;
+ esac;;
+esac
+
+
+
+ if test x"$ac_file" != x-; then
+ { echo "$as_me:$LINENO: creating $ac_file" >&5
+echo "$as_me: creating $ac_file" >&6;}
+ rm -f "$ac_file"
+ fi
+ # Let's still pretend it is `configure' which instantiates (i.e., don't
+ # use $as_me), people would be surprised to read:
+ # /* config.h. Generated by config.status. */
+ if test x"$ac_file" = x-; then
+ configure_input=
+ else
+ configure_input="$ac_file. "
+ fi
+ configure_input=$configure_input"Generated from `echo $ac_file_in |
+ sed 's,.*/,,'` by configure."
+
+ # First look for the input files in the build tree, otherwise in the
+ # src tree.
+ ac_file_inputs=`IFS=:
+ for f in $ac_file_in; do
+ case $f in
+ -) echo $tmp/stdin ;;
+ [\\/$]*)
+ # Absolute (can't be DOS-style, as IFS=:)
+ test -f "$f" || { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+ { (exit 1); exit 1; }; }
+ echo "$f";;
+ *) # Relative
+ if test -f "$f"; then
+ # Build tree
+ echo "$f"
+ elif test -f "$srcdir/$f"; then
+ # Source tree
+ echo "$srcdir/$f"
+ else
+ # /dev/null tree
+ { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+echo "$as_me: error: cannot find input file: $f" >&2;}
+ { (exit 1); exit 1; }; }
+ fi;;
+ esac
+ done` || { (exit 1); exit 1; }
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF
+ sed "$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s,@configure_input@,$configure_input,;t t
+s,@srcdir@,$ac_srcdir,;t t
+s,@abs_srcdir@,$ac_abs_srcdir,;t t
+s,@top_srcdir@,$ac_top_srcdir,;t t
+s,@abs_top_srcdir@,$ac_abs_top_srcdir,;t t
+s,@builddir@,$ac_builddir,;t t
+s,@abs_builddir@,$ac_abs_builddir,;t t
+s,@top_builddir@,$ac_top_builddir,;t t
+s,@abs_top_builddir@,$ac_abs_top_builddir,;t t
+" $ac_file_inputs | (eval "$ac_sed_cmds") >$tmp/out
+ rm -f $tmp/stdin
+ if test x"$ac_file" != x-; then
+ mv $tmp/out $ac_file
+ else
+ cat $tmp/out
+ rm -f $tmp/out
+ fi
+
+done
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF
+
+{ (exit 0); exit 0; }
+_ACEOF
+chmod +x $CONFIG_STATUS
+ac_clean_files=$ac_clean_files_save
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded. So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status. When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+ ac_cs_success=:
+ ac_config_status_args=
+ test "$silent" = yes &&
+ ac_config_status_args="$ac_config_status_args --quiet"
+ exec 5>/dev/null
+ $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+ exec 5>>config.log
+ # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+ # would make configure fail if this is the last instruction.
+ $ac_cs_success || { (exit 1); exit 1; }
+fi
+
+
+# Messages
+printf '#================================================================\n'
+printf '# Ready to make.\n'
+printf '#================================================================\n'
+
+
+
+# END OF FILE
diff --git a/qdbm/configure.in b/qdbm/configure.in
new file mode 100644
index 00000000..9a6f4c40
--- /dev/null
+++ b/qdbm/configure.in
@@ -0,0 +1,312 @@
+# Source of configuration for QDBM
+
+
+
+#================================================================
+# Generic Settings
+#================================================================
+
+
+# Targets
+AC_INIT(qdbm, 1.8.77)
+
+# Export variables
+LIBVER=14
+LIBREV=13
+TARGETS="all"
+MYDEFS=""
+MYOPTS=""
+MGWLIBS=""
+LD="ld"
+AR="ar"
+
+# Building paths
+pathtmp="$PATH"
+PATH="$HOME/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
+PATH="$PATH:/usr/ccs/bin:/usr/ucb:/usr/xpg4/bin:/usr/xpg6/bin:$pathtmp"
+LIBRARY_PATH="$HOME/lib:/usr/local/lib:$LIBRARY_PATH"
+LD_LIBRARY_PATH="$HOME/lib:/usr/local/lib:$LD_LIBRARY_PATH"
+CPATH="$HOME/include:/usr/local/include:$CPATH"
+export PATH LIBRARY_PATH LD_LIBRARY_PATH CPATH
+
+
+
+#================================================================
+# Options
+#================================================================
+
+
+# Internal variables
+enables=""
+ispthread=""
+iszlib=""
+isiconv=""
+isnowarn=""
+
+# Debug mode
+AC_ARG_ENABLE(debug,
+ AC_HELP_STRING([--enable-debug], [build for debugging]))
+if test "$enable_debug" = "yes"
+then
+ TARGETS="debug"
+ enables="$enables (debug)"
+fi
+
+# Developping mode
+AC_ARG_ENABLE(devel,
+ AC_HELP_STRING([--enable-devel], [build for development]))
+if test "$enable_devel" = "yes"
+then
+ TARGETS="devel"
+ enables="$enables (devel)"
+fi
+
+# Stable mode
+AC_ARG_ENABLE(stable,
+ AC_HELP_STRING([--enable-stable], [build for stable release]))
+if test "$enable_stable" = "yes"
+then
+ TARGETS="stable"
+ enables="$enables (stable)"
+fi
+
+# Enable POSIX thread
+AC_ARG_ENABLE(pthread,
+ AC_HELP_STRING([--enable-pthread], [use POSIX thread and make APIs thread-safe]))
+if test "$enable_pthread" = "yes"
+then
+ MYDEFS="$MYDEFS -DMYPTHREAD"
+ enables="$enables (pthread)"
+ ispthread="yes"
+fi
+
+# Disable file locking
+AC_ARG_ENABLE(lock,
+ AC_HELP_STRING([--disable-lock], [build for environments without file locking]))
+if test "$enable_lock" = "no"
+then
+ MYDEFS="$MYDEFS -DMYNOLOCK"
+ enables="$enables (no-lock)"
+fi
+
+# Disable memory mapping
+AC_ARG_ENABLE(mmap,
+ AC_HELP_STRING([--disable-mmap], [build for environments without memory mapping]))
+if test "$enable_mmap" = "no"
+then
+ MYDEFS="$MYDEFS -DMYNOMMAP"
+ enables="$enables (no-mmap)"
+fi
+
+# Enable ZLIB compression
+AC_ARG_ENABLE(zlib,
+ AC_HELP_STRING([--enable-zlib], [feature ZLIB for B+ tree and inverted index]))
+if test "$enable_zlib" = "yes"
+then
+ MYDEFS="$MYDEFS -DMYZLIB"
+ MGWLIBS="-lz $MGWLIBS"
+ enables="$enables (zlib)"
+ iszlib="yes"
+fi
+
+# Enable LZO compression
+AC_ARG_ENABLE(lzo,
+ AC_HELP_STRING([--enable-lzo], [feature LZO for B+ tree and inverted index]))
+if test "$enable_lzo" = "yes"
+then
+ MYDEFS="$MYDEFS -DMYLZO"
+ MGWLIBS="-llzo2 $MGWLIBS"
+ enables="$enables (lzo)"
+ islzo="yes"
+fi
+
+# Enable BZIP2 compression
+AC_ARG_ENABLE(bzip,
+ AC_HELP_STRING([--enable-bzip], [feature BZIP2 for B+ tree and inverted index]))
+if test "$enable_bzip" = "yes"
+then
+ MYDEFS="$MYDEFS -DMYBZIP"
+ MGWLIBS="-lbz2 $MGWLIBS"
+ enables="$enables (bzip)"
+ isbzip="yes"
+fi
+
+# Enable ICONV utilities
+AC_ARG_ENABLE(iconv,
+ AC_HELP_STRING([--enable-iconv], [feature ICONV utilities]))
+if test "$enable_iconv" = "yes"
+then
+ MYDEFS="$MYDEFS -DMYICONV"
+ MGWLIBS="-liconv $MGWLIBS"
+ enables="$enables (iconv)"
+ isiconv="yes"
+fi
+
+# No warning configuration
+AC_ARG_ENABLE(warn,
+ AC_HELP_STRING([--disable-warn], [hide warnings in the configuration]))
+if test "$enable_warn" = "no"
+then
+ isnowarn="yes"
+fi
+
+# Messages
+printf '#================================================================\n'
+printf '# Configuring QDBM version %s%s.\n' "$PACKAGE_VERSION" "$enables"
+printf '#================================================================\n'
+
+
+
+#================================================================
+# Checking Commands to Build with
+#================================================================
+
+
+# C compiler
+AC_PROG_CC
+if test "$GCC" != "yes"
+then
+ if test "$isnowarn" != "yes"
+ then
+ printf '#================================================================\n' 1>&2
+ printf '# WARNING: GCC is required to build this package.\n' 1>&2
+ printf '#================================================================\n' 1>&2
+ fi
+fi
+if uname | egrep -i 'Linux' > /dev/null 2>&1 &&
+ uname -m | egrep '(x|i)(3|4|5|6|7|8|9)?86' > /dev/null 2>&1
+then
+ MYOPTS="-minline-all-stringops"
+fi
+if uname | egrep -i 'SunOS' > /dev/null 2>&1
+then
+ MYOPTS="-O1 -fno-omit-frame-pointer -fno-force-addr"
+fi
+if uname | egrep -i 'BSD' > /dev/null 2>&1
+then
+ MYOPTS="-O1 -fno-omit-frame-pointer -fno-force-addr"
+fi
+if gcc --version | egrep -i '^2\.(8|9)' > /dev/null 2>&1
+then
+ MYOPTS="-O1 -fno-omit-frame-pointer -fno-force-addr"
+fi
+
+# Linker
+printf 'checking for ld... '
+if which ld | grep '/ld$' > /dev/null 2>&1
+then
+ LD=`which ld`
+ printf '%s\n' "$LD"
+else
+ printf 'no\n'
+ if test "$isnowarn" != "yes"
+ then
+ printf '#================================================================\n' 1>&2
+ printf '# WARNING: ld is not found in PATH.\n' 1>&2
+ printf '#================================================================\n' 1>&2
+ fi
+fi
+
+# Archiver
+printf 'checking for ar... '
+if which ar | grep '/ar$' > /dev/null 2>&1
+then
+ AR=`which ar`
+ printf '%s\n' "$AR"
+else
+ printf 'no\n'
+ if test "$isnowarn" != "yes"
+ then
+ printf '#================================================================\n' 1>&2
+ printf '# WARNING: ar is not found in PATH.\n' 1>&2
+ printf '#================================================================\n' 1>&2
+ fi
+fi
+
+
+
+#================================================================
+# Checking Libraries
+#================================================================
+
+
+# Underlying libraries
+AC_CHECK_LIB(c, main)
+
+# for pthread
+if test "$ispthread" = "yes"
+then
+ AC_CHECK_LIB(pthread, main)
+fi
+
+# for zlib
+if test "$iszlib" = "yes"
+then
+ AC_CHECK_LIB(z, main)
+fi
+
+# for lzo
+if test "$islzo" = "yes"
+then
+ AC_CHECK_LIB(lzo2, main)
+fi
+
+# for bzip
+if test "$isbzip" = "yes"
+then
+ AC_CHECK_LIB(bz2, main)
+fi
+
+# for iconv
+if test "$isiconv" = "yes"
+then
+ AC_CHECK_LIB(iconv, main)
+fi
+
+# For old BSDs
+if test "$ispthread" = "yes" && uname -a | grep BSD > /dev/null &&
+ test -f /usr/lib/libc_r.a && test ! -f /usr/lib/libpthread.a
+then
+ LIBS=`printf '%s' "$LIBS" | sed 's/-lc/-lc_r/g'`
+fi
+
+# Duplication of QDBM for C
+AC_CHECK_LIB(qdbm, main,
+ if test "$isnowarn" != "yes"
+ then
+ printf '#================================================================\n' 1>&2
+ printf '# WARNING: The existing library was detected.\n' 1>&2
+ printf '#================================================================\n' 1>&2
+ fi
+)
+
+
+
+#================================================================
+# Generic Settings
+#================================================================
+
+
+# Export variables
+AC_SUBST(LIBVER)
+AC_SUBST(LIBREV)
+AC_SUBST(TARGETS)
+AC_SUBST(MYDEFS)
+AC_SUBST(MYOPTS)
+AC_SUBST(MGWLIBS)
+AC_SUBST(LD)
+AC_SUBST(AR)
+AC_SUBST(CPPFLAGS)
+AC_SUBST(LDFLAGS)
+
+# Targets
+AC_OUTPUT(Makefile LTmakefile qdbm.spec qdbm.pc)
+
+# Messages
+printf '#================================================================\n'
+printf '# Ready to make.\n'
+printf '#================================================================\n'
+
+
+
+# END OF FILE
diff --git a/qdbm/crmgr.c b/qdbm/crmgr.c
new file mode 100644
index 00000000..8d9baf34
--- /dev/null
+++ b/qdbm/crmgr.c
@@ -0,0 +1,956 @@
+/*************************************************************************************************
+ * Utility for debugging Curia and its applications
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <curia.h>
+#include <cabin.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define ALIGNSIZ 32 /* basic size of alignment */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+char *hextoobj(const char *str, int *sp);
+char *dectoiobj(const char *str, int *sp);
+int runcreate(int argc, char **argv);
+int runput(int argc, char **argv);
+int runout(int argc, char **argv);
+int runget(int argc, char **argv);
+int runlist(int argc, char **argv);
+int runoptimize(int argc, char **argv);
+int runinform(int argc, char **argv);
+int runremove(int argc, char **argv);
+int runrepair(int argc, char **argv);
+int runexportdb(int argc, char **argv);
+int runimportdb(int argc, char **argv);
+int runsnaffle(int argc, char **argv);
+void pdperror(const char *name);
+void printobj(const char *obj, int size);
+void printobjhex(const char *obj, int size);
+int docreate(const char *name, int bnum, int dnum, int sparse);
+int doput(const char *name, const char *kbuf, int ksiz,
+ const char *vbuf, int vsiz, int dmode, int lob, int align);
+int doout(const char *name, const char *kbuf, int ksiz, int lob);
+int doget(const char *name, int opts, const char *kbuf, int ksiz,
+ int start, int max, int ox, int lob, int nb);
+int dolist(const char *name, int opts, int kb, int vb, int ox);
+int dooptimize(const char *name, int bnum, int align);
+int doinform(const char *name, int opts);
+int doremove(const char *name);
+int dorepair(const char *name);
+int doexportdb(const char *name, const char *dir);
+int doimportdb(const char *name, const char *dir, int bnum, int dnum);
+int dosnaffle(const char *name, const char *kbuf, int ksiz, int ox, int nb);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ char *env;
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ if((env = getenv("QDBMDBGFD")) != NULL) dpdbgfd = atoi(env);
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "create")){
+ rv = runcreate(argc, argv);
+ } else if(!strcmp(argv[1], "put")){
+ rv = runput(argc, argv);
+ } else if(!strcmp(argv[1], "out")){
+ rv = runout(argc, argv);
+ } else if(!strcmp(argv[1], "get")){
+ rv = runget(argc, argv);
+ } else if(!strcmp(argv[1], "list")){
+ rv = runlist(argc, argv);
+ } else if(!strcmp(argv[1], "optimize")){
+ rv = runoptimize(argc, argv);
+ } else if(!strcmp(argv[1], "inform")){
+ rv = runinform(argc, argv);
+ } else if(!strcmp(argv[1], "remove")){
+ rv = runremove(argc, argv);
+ } else if(!strcmp(argv[1], "repair")){
+ rv = runrepair(argc, argv);
+ } else if(!strcmp(argv[1], "exportdb")){
+ rv = runexportdb(argc, argv);
+ } else if(!strcmp(argv[1], "importdb")){
+ rv = runimportdb(argc, argv);
+ } else if(!strcmp(argv[1], "snaffle")){
+ rv = runsnaffle(argc, argv);
+ } else if(!strcmp(argv[1], "version") || !strcmp(argv[1], "--version")){
+ printf("Powered by QDBM version %s on %s%s\n",
+ dpversion, dpsysname, dpisreentrant ? " (reentrant)" : "");
+ printf("Copyright (c) 2000-2007 Mikio Hirabayashi\n");
+ rv = 0;
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: administration utility for Curia\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s create [-s] [-bnum num] [-dnum num] name\n", progname);
+ fprintf(stderr, " %s put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-lob] [-na] "
+ "name key val\n", progname);
+ fprintf(stderr, " %s out [-kx|-ki] [-lob] name key\n", progname);
+ fprintf(stderr, " %s get [-nl] [-kx|-ki] [-start num] [-max num] [-ox] [-lob] [-n] name key\n",
+ progname);
+ fprintf(stderr, " %s list [-nl] [-k|-v] [-ox] name\n", progname);
+ fprintf(stderr, " %s optimize [-bnum num] [-na] name\n", progname);
+ fprintf(stderr, " %s inform [-nl] name\n", progname);
+ fprintf(stderr, " %s remove name\n", progname);
+ fprintf(stderr, " %s repair name\n", progname);
+ fprintf(stderr, " %s exportdb name dir\n", progname);
+ fprintf(stderr, " %s importdb [-bnum num] [-dnum num] name dir\n", progname);
+ fprintf(stderr, " %s snaffle [-kx|-ki] [-ox] [-n] name key\n", progname);
+ fprintf(stderr, " %s version\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* create a binary object from a hexadecimal string */
+char *hextoobj(const char *str, int *sp){
+ char *buf, mbuf[3];
+ int len, i, j;
+ len = strlen(str);
+ if(!(buf = malloc(len + 1))) return NULL;
+ j = 0;
+ for(i = 0; i < len; i += 2){
+ while(strchr(" \n\r\t\f\v", str[i])){
+ i++;
+ }
+ if((mbuf[0] = str[i]) == '\0') break;
+ if((mbuf[1] = str[i+1]) == '\0') break;
+ mbuf[2] = '\0';
+ buf[j++] = (char)strtol(mbuf, NULL, 16);
+ }
+ buf[j] = '\0';
+ *sp = j;
+ return buf;
+}
+
+
+/* create a integer object from a decimal string */
+char *dectoiobj(const char *str, int *sp){
+ char *buf;
+ int num;
+ num = atoi(str);
+ if(!(buf = malloc(sizeof(int)))) return NULL;
+ *(int *)buf = num;
+ *sp = sizeof(int);
+ return buf;
+}
+
+
+/* parse arguments of create command */
+int runcreate(int argc, char **argv){
+ char *name;
+ int i, sb, bnum, dnum, rv;
+ name = NULL;
+ sb = FALSE;
+ bnum = -1;
+ dnum = -1;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-s")){
+ sb = TRUE;
+ } else if(!strcmp(argv[i], "-bnum")){
+ if(++i >= argc) usage();
+ bnum = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-dnum")){
+ if(++i >= argc) usage();
+ dnum = atoi(argv[i]);
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = docreate(name, bnum, dnum, sb);
+ return rv;
+}
+
+
+/* parse arguments of put command */
+int runput(int argc, char **argv){
+ char *name, *key, *val, *kbuf, *vbuf;
+ int i, kx, ki, vx, vi, vf, lob, ksiz, vsiz, align, rv;
+ int dmode;
+ name = NULL;
+ kx = FALSE;
+ ki = FALSE;
+ vx = FALSE;
+ vi = FALSE;
+ vf = FALSE;
+ lob = FALSE;
+ align = ALIGNSIZ;
+ key = NULL;
+ val = NULL;
+ dmode = CR_DOVER;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else if(!strcmp(argv[i], "-vx")){
+ vx = TRUE;
+ } else if(!strcmp(argv[i], "-vi")){
+ vi = TRUE;
+ } else if(!strcmp(argv[i], "-vf")){
+ vf = TRUE;
+ } else if(!strcmp(argv[i], "-keep")){
+ dmode = CR_DKEEP;
+ } else if(!strcmp(argv[i], "-cat")){
+ dmode = CR_DCAT;
+ } else if(!strcmp(argv[i], "-lob")){
+ lob = TRUE;
+ } else if(!strcmp(argv[i], "-na")){
+ align = 0;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else if(!val){
+ val = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key || !val) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else if(ki){
+ kbuf = dectoiobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = -1;
+ }
+ if(vx){
+ vbuf = hextoobj(val, &vsiz);
+ } else if(vi){
+ vbuf = dectoiobj(val, &vsiz);
+ } else if(vf){
+ vbuf = cbreadfile(val, &vsiz);
+ } else {
+ vbuf = cbmemdup(val, -1);
+ vsiz = -1;
+ }
+ if(kbuf && vbuf){
+ rv = doput(name, kbuf, ksiz, vbuf, vsiz, dmode, lob, align);
+ } else {
+ if(vf){
+ fprintf(stderr, "%s: %s: cannot read\n", progname, val);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ }
+ rv = 1;
+ }
+ free(kbuf);
+ free(vbuf);
+ return rv;
+}
+
+
+/* parse arguments of out command */
+int runout(int argc, char **argv){
+ char *name, *key, *kbuf;
+ int i, kx, ki, lob, ksiz, rv;
+ name = NULL;
+ kx = FALSE;
+ ki = FALSE;
+ lob = FALSE;
+ key = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else if(!strcmp(argv[i], "-lob")){
+ lob = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else if(ki){
+ kbuf = dectoiobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = -1;
+ }
+ if(kbuf){
+ rv = doout(name, kbuf, ksiz, lob);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ rv = 1;
+ }
+ free(kbuf);
+ return rv;
+}
+
+
+/* parse arguments of get command */
+int runget(int argc, char **argv){
+ char *name, *key, *kbuf;
+ int i, opts, kx, ki, ox, lob, nb, start, max, ksiz, rv;
+ name = NULL;
+ opts = 0;
+ kx = FALSE;
+ ki = FALSE;
+ ox = FALSE;
+ lob = FALSE;
+ nb = FALSE;
+ start = 0;
+ max = -1;
+ key = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-nl")){
+ opts |= CR_ONOLCK;
+ } else if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else if(!strcmp(argv[i], "-ox")){
+ ox = TRUE;
+ } else if(!strcmp(argv[i], "-lob")){
+ lob = TRUE;
+ } else if(!strcmp(argv[i], "-n")){
+ nb = TRUE;
+ } else if(!strcmp(argv[i], "-start")){
+ if(++i >= argc) usage();
+ start = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-max")){
+ if(++i >= argc) usage();
+ max = atoi(argv[i]);
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key || start < 0) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else if(ki){
+ kbuf = dectoiobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = -1;
+ }
+ if(kbuf){
+ rv = doget(name, opts, kbuf, ksiz, start, max, ox, lob, nb);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ rv = 1;
+ }
+ free(kbuf);
+ return rv;
+}
+
+
+/* parse arguments of list command */
+int runlist(int argc, char **argv){
+ char *name;
+ int i, opts, kb, vb, ox, rv;
+ name = NULL;
+ opts = 0;
+ kb = FALSE;
+ vb = FALSE;
+ ox = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-nl")){
+ opts |= CR_ONOLCK;
+ } else if(!strcmp(argv[i], "-k")){
+ kb = TRUE;
+ } else if(!strcmp(argv[i], "-v")){
+ vb = TRUE;
+ } else if(!strcmp(argv[i], "-ox")){
+ ox = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = dolist(name, opts, kb, vb, ox);
+ return rv;
+}
+
+
+/* parse arguments of optimize command */
+int runoptimize(int argc, char **argv){
+ char *name;
+ int i, bnum, align, rv;
+ name = NULL;
+ bnum = -1;
+ align = ALIGNSIZ;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-bnum")){
+ if(++i >= argc) usage();
+ bnum = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-na")){
+ align = 0;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = dooptimize(name, bnum, align);
+ return rv;
+}
+
+
+/* parse arguments of inform command */
+int runinform(int argc, char **argv){
+ char *name;
+ int i, opts, rv;
+ name = NULL;
+ opts = 0;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-nl")){
+ opts |= CR_ONOLCK;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doinform(name, opts);
+ return rv;
+}
+
+
+/* parse arguments of remove command */
+int runremove(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doremove(name);
+ return rv;
+}
+
+
+/* parse arguments of repair command */
+int runrepair(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = dorepair(name);
+ return rv;
+}
+
+
+/* parse arguments of exportdb command */
+int runexportdb(int argc, char **argv){
+ char *name, *dir;
+ int i, rv;
+ name = NULL;
+ dir = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else if(!dir){
+ dir = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !dir) usage();
+ rv = doexportdb(name, dir);
+ return rv;
+}
+
+
+/* parse arguments of importdb command */
+int runimportdb(int argc, char **argv){
+ char *name, *dir;
+ int i, bnum, dnum, rv;
+ name = NULL;
+ dir = NULL;
+ bnum = -1;
+ dnum = -1;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-bnum")){
+ if(++i >= argc) usage();
+ bnum = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-dnum")){
+ if(++i >= argc) usage();
+ dnum = atoi(argv[i]);
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!dir){
+ dir = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !dir) usage();
+ rv = doimportdb(name, dir, bnum, dnum);
+ return rv;
+}
+
+
+/* parse arguments of snaffle command */
+int runsnaffle(int argc, char **argv){
+ char *name, *key, *kbuf;
+ int i, kx, ki, ox, nb, start, max, ksiz, rv;
+ name = NULL;
+ kx = FALSE;
+ ki = FALSE;
+ ox = FALSE;
+ nb = FALSE;
+ start = 0;
+ max = -1;
+ key = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else if(!strcmp(argv[i], "-ox")){
+ ox = TRUE;
+ } else if(!strcmp(argv[i], "-n")){
+ nb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key || start < 0) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else if(ki){
+ kbuf = dectoiobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = -1;
+ }
+ if(kbuf){
+ rv = dosnaffle(name, kbuf, ksiz, ox, nb);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ rv = 1;
+ }
+ free(kbuf);
+ return rv;
+}
+
+
+/* print an error message */
+void pdperror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, dperrmsg(dpecode));
+}
+
+
+/* print an object */
+void printobj(const char *obj, int size){
+ int i;
+ for(i = 0; i < size; i++){
+ putchar(obj[i]);
+ }
+}
+
+
+/* print an object as a hexadecimal string */
+void printobjhex(const char *obj, int size){
+ int i;
+ for(i = 0; i < size; i++){
+ printf("%s%02X", i > 0 ? " " : "", ((const unsigned char *)obj)[i]);
+ }
+}
+
+
+/* perform create command */
+int docreate(const char *name, int bnum, int dnum, int sparse){
+ CURIA *curia;
+ int omode;
+ omode = CR_OWRITER | CR_OCREAT | CR_OTRUNC | (sparse ? CR_OSPARSE : 0);
+ if(!(curia = cropen(name, omode, bnum, dnum))){
+ pdperror(name);
+ return 1;
+ }
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform put command */
+int doput(const char *name, const char *kbuf, int ksiz,
+ const char *vbuf, int vsiz, int dmode, int lob, int align){
+ CURIA *curia;
+ if(!(curia = cropen(name, CR_OWRITER, -1, -1))){
+ pdperror(name);
+ return 1;
+ }
+ if(align > 0 && !crsetalign(curia, ALIGNSIZ)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ if(lob){
+ if(!crputlob(curia, kbuf, ksiz, vbuf, vsiz, dmode)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ } else {
+ if(!crput(curia, kbuf, ksiz, vbuf, vsiz, dmode)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform out command */
+int doout(const char *name, const char *kbuf, int ksiz, int lob){
+ CURIA *curia;
+ if(!(curia = cropen(name, CR_OWRITER, -1, -1))){
+ pdperror(name);
+ return 1;
+ }
+ if(lob){
+ if(!croutlob(curia, kbuf, ksiz)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ } else {
+ if(!crout(curia, kbuf, ksiz)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform get command */
+int doget(const char *name, int opts, const char *kbuf, int ksiz,
+ int start, int max, int ox, int lob, int nb){
+ CURIA *curia;
+ char *vbuf;
+ int vsiz;
+ if(!(curia = cropen(name, CR_OREADER | opts, -1, -1))){
+ pdperror(name);
+ return 1;
+ }
+ if(lob){
+ if(!(vbuf = crgetlob(curia, kbuf, ksiz, start, max, &vsiz))){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ } else {
+ if(!(vbuf = crget(curia, kbuf, ksiz, start, max, &vsiz))){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ if(ox){
+ printobjhex(vbuf, vsiz);
+ } else {
+ printobj(vbuf, vsiz);
+ }
+ free(vbuf);
+ if(!nb) putchar('\n');
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform list command */
+int dolist(const char *name, int opts, int kb, int vb, int ox){
+ CURIA *curia;
+ char *kbuf, *vbuf;
+ int ksiz, vsiz;
+ if(!(curia = cropen(name, CR_OREADER | opts, -1, -1))){
+ pdperror(name);
+ return 1;
+ }
+ criterinit(curia);
+ while((kbuf = criternext(curia, &ksiz)) != NULL){
+ if(!(vbuf = crget(curia, kbuf, ksiz, 0, -1, &vsiz))){
+ pdperror(name);
+ free(kbuf);
+ break;
+ }
+ if(ox){
+ if(!vb) printobjhex(kbuf, ksiz);
+ if(!kb && !vb) putchar('\t');
+ if(!kb) printobjhex(vbuf, vsiz);
+ } else {
+ if(!vb) printobj(kbuf, ksiz);
+ if(!kb && !vb) putchar('\t');
+ if(!kb) printobj(vbuf, vsiz);
+ }
+ putchar('\n');
+ free(vbuf);
+ free(kbuf);
+ }
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform optimize command */
+int dooptimize(const char *name, int bnum, int align){
+ CURIA *curia;
+ if(!(curia = cropen(name, CR_OWRITER, -1, -1))){
+ pdperror(name);
+ return 1;
+ }
+ if(align > 0 && !crsetalign(curia, ALIGNSIZ)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ if(!croptimize(curia, bnum)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform inform command */
+int doinform(const char *name, int opts){
+ CURIA *curia;
+ if(!(curia = cropen(name, CR_OREADER | opts, -1, -1))){
+ pdperror(name);
+ return 1;
+ }
+ printf("name: %s\n", crname(curia));
+ printf("file size: %.0f\n", crfsizd(curia));
+ printf("all buckets: %d\n", crbnum(curia));
+ printf("used buckets: %d\n", crbusenum(curia));
+ printf("records: %d\n", crrnum(curia));
+ printf("inode number: %d\n", crinode(curia));
+ printf("modified time: %.0f\n", (double)crmtime(curia));
+ printf("LOB records: %d\n", crrnumlob(curia));
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform remove command */
+int doremove(const char *name){
+ if(!crremove(name)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform repair command */
+int dorepair(const char *name){
+ if(!crrepair(name)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform exportdb command */
+int doexportdb(const char *name, const char *dir){
+ CURIA *curia;
+ if(!(curia = cropen(name, CR_OREADER, -1, -1))){
+ pdperror(name);
+ return 1;
+ }
+ if(!crexportdb(curia, dir)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform importdb command */
+int doimportdb(const char *name, const char *dir, int bnum, int dnum){
+ CURIA *curia;
+ if(!(curia = cropen(name, CR_OWRITER | CR_OCREAT | CR_OTRUNC, bnum, dnum))){
+ pdperror(name);
+ return 1;
+ }
+ if(!crimportdb(curia, dir)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform snaffle command */
+int dosnaffle(const char *name, const char *kbuf, int ksiz, int ox, int nb){
+ char *vbuf;
+ int vsiz;
+ if(!(vbuf = crsnaffle(name, kbuf, ksiz, &vsiz))){
+ pdperror(name);
+ return 1;
+ }
+ if(ox){
+ printobjhex(vbuf, vsiz);
+ } else {
+ printobj(vbuf, vsiz);
+ }
+ free(vbuf);
+ if(!nb) putchar('\n');
+ return 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/crtest.c b/qdbm/crtest.c
new file mode 100644
index 00000000..6239841b
--- /dev/null
+++ b/qdbm/crtest.c
@@ -0,0 +1,873 @@
+/*************************************************************************************************
+ * Test cases of Curia
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <curia.h>
+#include <cabin.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <time.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define RECBUFSIZ 32 /* buffer for records */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int runrcat(int argc, char **argv);
+int runcombo(int argc, char **argv);
+int runwicked(int argc, char **argv);
+int printfflush(const char *format, ...);
+void pdperror(const char *name);
+int myrand(void);
+int dowrite(const char *name, int rnum, int bnum, int dnum, int sparse, int lob);
+int doread(const char *name, int wb, int lob);
+int dorcat(const char *name, int rnum, int bnum, int dnum, int pnum,
+ int align, int fbpsiz, int cb);
+int docombo(const char *name);
+int dowicked(const char *name, int rnum, int cb);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ char *env;
+ int rv;
+ cbstdiobin();
+ if((env = getenv("QDBMDBGFD")) != NULL) dpdbgfd = atoi(env);
+ progname = argv[0];
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "write")){
+ rv = runwrite(argc, argv);
+ } else if(!strcmp(argv[1], "read")){
+ rv = runread(argc, argv);
+ } else if(!strcmp(argv[1], "rcat")){
+ rv = runrcat(argc, argv);
+ } else if(!strcmp(argv[1], "combo")){
+ rv = runcombo(argc, argv);
+ } else if(!strcmp(argv[1], "wicked")){
+ rv = runwicked(argc, argv);
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: test cases for Curia\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s write [-s] [-lob] name rnum bnum dnum\n", progname);
+ fprintf(stderr, " %s read [-wb] [-lob] name\n", progname);
+ fprintf(stderr, " %s rcat [-c] name rnum bnum dnum pnum align fbpsiz\n", progname);
+ fprintf(stderr, " %s combo name\n", progname);
+ fprintf(stderr, " %s wicked [-c] name rnum\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+ char *name, *rstr, *bstr, *dstr;
+ int i, rnum, bnum, dnum, sb, lob, rv;
+ name = NULL;
+ rstr = NULL;
+ bstr = NULL;
+ dstr = NULL;
+ rnum = 0;
+ bnum = 0;
+ dnum = 0;
+ sb = FALSE;
+ lob = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-s")){
+ sb = TRUE;
+ } else if(!strcmp(argv[i], "-lob")){
+ lob = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!rstr){
+ rstr = argv[i];
+ } else if(!bstr){
+ bstr = argv[i];
+ } else if(!dstr){
+ dstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !rstr || !bstr || !dstr) usage();
+ rnum = atoi(rstr);
+ bnum = atoi(bstr);
+ dnum = atoi(dstr);
+ if(rnum < 1 || bnum < 1 || dnum < 1) usage();
+ rv = dowrite(name, rnum, bnum, dnum, sb, lob);
+ return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+ char *name;
+ int i, wb, lob, rv;
+ name = NULL;
+ wb = FALSE;
+ lob = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-wb")){
+ wb = TRUE;
+ } else if(!strcmp(argv[i], "-lob")){
+ lob = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doread(name, wb, lob);
+ return rv;
+}
+
+
+/* parse arguments of rcat command */
+int runrcat(int argc, char **argv){
+ char *name, *rstr, *bstr, *dstr, *pstr, *astr, *fstr;
+ int i, rnum, bnum, dnum, pnum, align, fbpsiz, cb, rv;
+ name = NULL;
+ rstr = NULL;
+ bstr = NULL;
+ dstr = NULL;
+ pstr = NULL;
+ astr = NULL;
+ fstr = NULL;
+ rnum = 0;
+ bnum = 0;
+ pnum = 0;
+ align = 0;
+ fbpsiz = 0;
+ cb = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-c")){
+ cb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!rstr){
+ rstr = argv[i];
+ } else if(!bstr){
+ bstr = argv[i];
+ } else if(!dstr){
+ dstr = argv[i];
+ } else if(!pstr){
+ pstr = argv[i];
+ } else if(!astr){
+ astr = argv[i];
+ } else if(!fstr){
+ fstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !rstr || !bstr || !dstr || !pstr || !astr || !fstr) usage();
+ rnum = atoi(rstr);
+ bnum = atoi(bstr);
+ dnum = atoi(dstr);
+ pnum = atoi(pstr);
+ align = atoi(astr);
+ fbpsiz = atoi(fstr);
+ if(rnum < 1 || bnum < 1 || dnum < 1 || pnum < 1 || fbpsiz < 0) usage();
+ rv = dorcat(name, rnum, bnum, dnum, pnum, align, fbpsiz, cb);
+ return rv;
+}
+
+
+/* parse arguments of combo command */
+int runcombo(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = docombo(name);
+ return rv;
+}
+
+
+/* parse arguments of wicked command */
+int runwicked(int argc, char **argv){
+ char *name, *rstr;
+ int i, rnum, cb, rv;
+ name = NULL;
+ rstr = NULL;
+ rnum = 0;
+ cb = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-c")){
+ cb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!rstr){
+ rstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ rv = dowicked(name, rnum, cb);
+ return rv;
+}
+
+
+/* print formatted string and flush the buffer */
+int printfflush(const char *format, ...){
+ va_list ap;
+ int rv;
+ va_start(ap, format);
+ rv = vprintf(format, ap);
+ if(fflush(stdout) == EOF) rv = -1;
+ va_end(ap);
+ return rv;
+}
+
+
+/* print an error message */
+void pdperror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, dperrmsg(dpecode));
+}
+
+
+/* pseudo random number generator */
+int myrand(void){
+ static int cnt = 0;
+ if(cnt == 0) srand(time(NULL));
+ return (rand() * rand() + (rand() >> (sizeof(int) * 4)) + (cnt++)) & INT_MAX;
+}
+
+
+/* perform write command */
+int dowrite(const char *name, int rnum, int bnum, int dnum, int sparse, int lob){
+ CURIA *curia;
+ int i, omode, err, len;
+ char buf[RECBUFSIZ];
+ printfflush("<Writing Test>\n name=%s rnum=%d bnum=%d dnum=%d s=%d lob=%d\n\n",
+ name, rnum, bnum, dnum, sparse, lob);
+ /* open a database */
+ omode = CR_OWRITER | CR_OCREAT | CR_OTRUNC | (sparse ? CR_OSPARSE : 0);
+ if(!(curia = cropen(name, omode, bnum, dnum))){
+ pdperror(name);
+ return 1;
+ }
+ err = FALSE;
+ /* loop for each record */
+ for(i = 1; i <= rnum; i++){
+ /* store a record */
+ len = sprintf(buf, "%08d", i);
+ if(lob){
+ if(!crputlob(curia, buf, len, buf, len, CR_DOVER)){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ } else {
+ if(!crput(curia, buf, len, buf, len, CR_DOVER)){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ }
+ /* print progression */
+ if(rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ /* close the database */
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(const char *name, int wb, int lob){
+ CURIA *curia;
+ int i, rnum, err, len;
+ char buf[RECBUFSIZ], vbuf[RECBUFSIZ], *val;
+ printfflush("<Reading Test>\n name=%s wb=%d lob=%d\n\n", name, wb, lob);
+ /* open a database */
+ if(!(curia = cropen(name, CR_OREADER, -1, -1))){
+ pdperror(name);
+ return 1;
+ }
+ /* get the number of records */
+ if(lob){
+ rnum = crrnumlob(curia);
+ } else {
+ rnum = crrnum(curia);
+ }
+ err = FALSE;
+ /* loop for each record */
+ for(i = 1; i <= rnum; i++){
+ /* retrieve a record */
+ len = sprintf(buf, "%08d", i);
+ if(lob){
+ if(!(val = crgetlob(curia, buf, len, 0, -1, NULL))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ free(val);
+ } else if(wb){
+ if(crgetwb(curia, buf, len, 0, RECBUFSIZ, vbuf) == -1){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ } else {
+ if(!(val = crget(curia, buf, len, 0, -1, NULL))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ free(val);
+ }
+ /* print progression */
+ if(rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ /* close the database */
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform rcat command */
+int dorcat(const char *name, int rnum, int bnum, int dnum, int pnum,
+ int align, int fbpsiz, int cb){
+ CURIA *curia;
+ CBMAP *map;
+ int i, err, len, ksiz, vsiz, rsiz;
+ const char *kbuf, *vbuf;
+ char buf[RECBUFSIZ], *rbuf;
+ printfflush("<Random Writing Test>\n name=%s rnum=%d bnum=%d dnum=%d pnum=%d"
+ " align=%d fbpsiz=%d c=%d\n\n", name, rnum, bnum, dnum, pnum, align, fbpsiz, cb);
+ if(!(curia = cropen(name, CR_OWRITER | CR_OCREAT | CR_OTRUNC, bnum, dnum))){
+ pdperror(name);
+ return 1;
+ }
+ if(!crsetalign(curia, align) || !crsetfbpsiz(curia, fbpsiz)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ map = NULL;
+ if(cb) map = cbmapopen();
+ err = FALSE;
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", myrand() % pnum + 1);
+ if(!crput(curia, buf, len, buf, len, CR_DCAT)){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ if(rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d: fsiz=%d rnum=%d)\n", i, crfsiz(curia), crrnum(curia));
+ }
+ }
+ }
+ if(map){
+ printfflush("Matching records ... ");
+ cbmapiterinit(map);
+ while((kbuf = cbmapiternext(map, &ksiz)) != NULL){
+ vbuf = cbmapget(map, kbuf, ksiz, &vsiz);
+ if(!(rbuf = crget(curia, kbuf, ksiz, 0, -1, &rsiz))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+ fprintf(stderr, "%s: %s: unmatched record\n", progname, name);
+ free(rbuf);
+ err = TRUE;
+ break;
+ }
+ free(rbuf);
+ }
+ cbmapclose(map);
+ if(!err) printfflush("ok\n");
+ }
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform combo command */
+int docombo(const char *name){
+ CURIA *curia;
+ char buf[RECBUFSIZ], wbuf[RECBUFSIZ], *vbuf;
+ int i, len, wlen, vsiz;
+ printfflush("<Combination Test>\n name=%s\n\n", name);
+ printfflush("Creating a database with bnum 3 ... ");
+ if(!(curia = cropen(name, CR_OWRITER | CR_OCREAT | CR_OTRUNC, 3, 3))){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Setting alignment as 16 ... ");
+ if(!crsetalign(curia, 16)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Adding 20 records ... ");
+ for(i = 1; i <= 20; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!crput(curia, buf, len, buf, len, CR_DOVER)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Checking records ... ");
+ for(i = 1; i <= 20; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!(vbuf = crget(curia, buf, len, 0, -1, &vsiz))){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ free(vbuf);
+ if(vsiz != crvsiz(curia, buf, len)){
+ fprintf(stderr, "%s: %s: invalid vsiz\n", progname, name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Overwriting top 10 records without moving rooms ... ");
+ for(i = 1; i <= 10; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!crput(curia, buf, len, buf, len, CR_DOVER)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Overwriting top 5 records with moving rooms ... ");
+ for(i = 1; i <= 5; i++){
+ len = sprintf(buf, "%08d", i);
+ wlen = sprintf(wbuf, "%024d", i);
+ if(!crput(curia, buf, len, wbuf, wlen, CR_DOVER)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Overwriting top 15 records in concatenation with moving rooms ... ");
+ for(i = 1; i <= 15; i++){
+ len = sprintf(buf, "%08d", i);
+ wlen = sprintf(wbuf, "========================");
+ if(!crput(curia, buf, len, wbuf, wlen, CR_DCAT)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Checking records ... ");
+ for(i = 1; i <= 20; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!(vbuf = crget(curia, buf, len, 0, -1, &vsiz))){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ free(vbuf);
+ if(vsiz != crvsiz(curia, buf, len)){
+ fprintf(stderr, "%s: %s: invalid vsiz\n", progname, name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Deleting top 10 records ... ");
+ for(i = 1; i <= 10; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!crout(curia, buf, len)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Checking deleted records ... ");
+ for(i = 1; i <= 10; i++){
+ len = sprintf(buf, "%08d", i);
+ vbuf = crget(curia, buf, len, 0, -1, &vsiz);
+ free(vbuf);
+ if(vbuf || dpecode != DP_ENOITEM){
+ fprintf(stderr, "%s: %s: deleting failed\n", progname, name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Overwriting top 15 records in concatenation with moving rooms ... ");
+ for(i = 1; i <= 15; i++){
+ len = sprintf(buf, "%08d", i);
+ wlen = sprintf(wbuf, "========================");
+ if(!crput(curia, buf, len, wbuf, wlen, CR_DCAT)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Checking records ... ");
+ for(i = 1; i <= 20; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!(vbuf = crget(curia, buf, len, 0, -1, &vsiz))){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ free(vbuf);
+ if(vsiz != crvsiz(curia, buf, len)){
+ fprintf(stderr, "%s: %s: invalid vsiz\n", progname, name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Optimizing the database ... ");
+ if(!croptimize(curia, -1)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Checking records ... ");
+ for(i = 1; i <= 20; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!(vbuf = crget(curia, buf, len, 0, -1, &vsiz))){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ free(vbuf);
+ if(vsiz != crvsiz(curia, buf, len)){
+ fprintf(stderr, "%s: %s: invalid vsiz\n", progname, name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Closing database ... ");
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Creating a database with bnum 1000000 ... ");
+ if(!(curia = cropen(name, DP_OWRITER | DP_OCREAT | DP_OTRUNC, 1000000, -1))){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Adding 1000 records ... ");
+ for(i = 1; i <= 1000; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!crput(curia, buf, len, buf, len, DP_DOVER)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Adding 64 records ... ");
+ for(i = 1; i <= 64; i++){
+ len = sprintf(buf, "%o", i);
+ if(!crput(curia, buf, len, buf, len, DP_DOVER)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Syncing the database ... ");
+ if(!crsync(curia)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Retrieving records directly ... ");
+ for(i = 1; i <= 64; i++){
+ len = sprintf(buf, "%o", i);
+ if(!(vbuf = crsnaffle(name, buf, len, &vsiz))){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ if(strcmp(vbuf, buf)){
+ fprintf(stderr, "%s: %s: invalid content\n", progname, name);
+ free(vbuf);
+ crclose(curia);
+ return 1;
+ }
+ free(vbuf);
+ if(vsiz != crvsiz(curia, buf, len)){
+ fprintf(stderr, "%s: %s: invalid vsiz\n", progname, name);
+ crclose(curia);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Optimizing the database ... ");
+ if(!croptimize(curia, -1)){
+ pdperror(name);
+ crclose(curia);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Closing the database ... ");
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("all ok\n\n");
+ return 0;
+}
+
+
+/* perform wicked command */
+int dowicked(const char *name, int rnum, int cb){
+ CURIA *curia;
+ CBMAP *map;
+ int i, len, err, align, mksiz, mvsiz, rsiz;
+ const char *mkbuf, *mvbuf;
+ char buf[RECBUFSIZ], vbuf[RECBUFSIZ], *val;
+ printfflush("<Wicked Writing Test>\n name=%s rnum=%d\n\n", name, rnum);
+ err = FALSE;
+ if(!(curia = cropen(name, CR_OWRITER | CR_OCREAT | CR_OTRUNC, rnum / 10, 5))){
+ pdperror(name);
+ return 1;
+ }
+ if(!crsetalign(curia, 16) || !crsetfbpsiz(curia, 256)){
+ pdperror(name);
+ err = TRUE;
+ }
+ map = NULL;
+ if(cb) map = cbmapopen();
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", myrand() % rnum + 1);
+ switch(myrand() % 16){
+ case 0:
+ putchar('O');
+ if(!crput(curia, buf, len, buf, len, CR_DOVER)) err = TRUE;
+ if(map) cbmapput(map, buf, len, buf, len, TRUE);
+ break;
+ case 1:
+ putchar('K');
+ if(!crput(curia, buf, len, buf, len, CR_DKEEP) && dpecode != DP_EKEEP) err = TRUE;
+ if(map) cbmapput(map, buf, len, buf, len, FALSE);
+ break;
+ case 2:
+ putchar('D');
+ if(!crout(curia, buf, len) && dpecode != DP_ENOITEM) err = TRUE;
+ if(map) cbmapout(map, buf, len);
+ break;
+ case 3:
+ putchar('G');
+ if(crgetwb(curia, buf, len, 2, RECBUFSIZ, vbuf) == -1 && dpecode != DP_ENOITEM) err = TRUE;
+ break;
+ case 4:
+ putchar('V');
+ if(crvsiz(curia, buf, len) == -1 && dpecode != DP_ENOITEM) err = TRUE;
+ break;
+ default:
+ putchar('C');
+ if(!crput(curia, buf, len, buf, len, CR_DCAT)) err = TRUE;
+ if(map) cbmapputcat(map, buf, len, buf, len);
+ break;
+ }
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ if(!err && rnum > 100 && myrand() % (rnum / 100) == 0){
+ if(myrand() % 10 == 0){
+ align = (myrand() % 4 + 1) * -1;
+ } else {
+ align = myrand() % 32;
+ }
+ if(!crsetalign(curia, align)) err = TRUE;
+ }
+ if(err){
+ pdperror(name);
+ break;
+ }
+ }
+ if(!croptimize(curia, -1)){
+ pdperror(name);
+ err = TRUE;
+ }
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!crput(curia, buf, len, ":", -1, CR_DCAT)){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ if(map) cbmapputcat(map, buf, len, ":", -1);
+ putchar(':');
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ }
+ if(!croptimize(curia, -1)){
+ pdperror(name);
+ err = TRUE;
+ }
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!(val = crget(curia, buf, len, 0, -1, NULL))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ free(val);
+ putchar('=');
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ }
+ if(!criterinit(curia)){
+ pdperror(name);
+ err = TRUE;
+ }
+ for(i = 1; i <= rnum; i++){
+ if(!(val = criternext(curia, NULL))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ free(val);
+ putchar('@');
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ }
+ if(map){
+ printfflush("Matching records ... ");
+ cbmapiterinit(map);
+ while((mkbuf = cbmapiternext(map, &mksiz)) != NULL){
+ mvbuf = cbmapget(map, mkbuf, mksiz, &mvsiz);
+ if(!(val = crget(curia, mkbuf, mksiz, 0, -1, &rsiz))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ if(rsiz != mvsiz || memcmp(val, mvbuf, rsiz)){
+ fprintf(stderr, "%s: %s: unmatched record\n", progname, name);
+ free(val);
+ err = TRUE;
+ break;
+ }
+ free(val);
+ }
+ cbmapclose(map);
+ if(!err) printfflush("ok\n");
+ }
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/crtsv.c b/qdbm/crtsv.c
new file mode 100644
index 00000000..5b52488d
--- /dev/null
+++ b/qdbm/crtsv.c
@@ -0,0 +1,266 @@
+/*************************************************************************************************
+ * Mutual converter between a database of Curia and a TSV text
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <curia.h>
+#include <cabin.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runimport(int argc, char **argv);
+int runexport(int argc, char **argv);
+void pdperror(const char *name);
+char *getl(void);
+int doimport(const char *name, int bnum, int dnum, int bin);
+int doexport(const char *name, int bin);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "import")){
+ rv = runimport(argc, argv);
+ } else if(!strcmp(argv[1], "export")){
+ rv = runexport(argc, argv);
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: mutual converter between TSV and Curia database\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s import [-bnum num] [-dnum num] [-bin] name\n", progname);
+ fprintf(stderr, " %s export [-bin] name\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* parse arguments of import command */
+int runimport(int argc, char **argv){
+ char *name;
+ int i, bnum, dnum, bin, rv;
+ name = NULL;
+ bnum = -1;
+ dnum = -1;
+ bin = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-bnum")){
+ if(++i >= argc) usage();
+ bnum = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-dnum")){
+ if(++i >= argc) usage();
+ dnum = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-bin")){
+ bin = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doimport(name, bnum, dnum, bin);
+ return rv;
+}
+
+
+/* parse arguments of export command */
+int runexport(int argc, char **argv){
+ char *name;
+ int i, bin, rv;
+ name = NULL;
+ bin = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-bin")){
+ bin = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doexport(name, bin);
+ return rv;
+}
+
+
+/* print an error message */
+void pdperror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, dperrmsg(dpecode));
+}
+
+
+/* read a line */
+char *getl(void){
+ char *buf;
+ int c, len, blen;
+ buf = NULL;
+ len = 0;
+ blen = 256;
+ while((c = getchar()) != EOF){
+ if(blen <= len) blen *= 2;
+ buf = cbrealloc(buf, blen + 1);
+ if(c == '\n') c = '\0';
+ buf[len++] = c;
+ if(c == '\0') break;
+ }
+ if(!buf) return NULL;
+ buf[len] = '\0';
+ return buf;
+}
+
+
+/* perform import command */
+int doimport(const char *name, int bnum, int dnum, int bin){
+ CURIA *curia;
+ char *buf, *kbuf, *vbuf, *ktmp, *vtmp;
+ int i, err, ktsiz, vtsiz;
+ /* open a database */
+ if(!(curia = cropen(name, CR_OWRITER | CR_OCREAT, bnum, dnum))){
+ pdperror(name);
+ return 1;
+ }
+ /* loop for each line */
+ err = FALSE;
+ for(i = 1; (buf = getl()) != NULL; i++){
+ kbuf = buf;
+ if((vbuf = strchr(buf, '\t')) != NULL){
+ *vbuf = '\0';
+ vbuf++;
+ /* store a record */
+ if(bin){
+ ktmp = cbbasedecode(kbuf, &ktsiz);
+ vtmp = cbbasedecode(vbuf, &vtsiz);
+ if(!crput(curia, ktmp, ktsiz, vtmp, vtsiz, CR_DOVER)){
+ pdperror(name);
+ err = TRUE;
+ }
+ free(vtmp);
+ free(ktmp);
+ } else {
+ if(!crput(curia, kbuf, -1, vbuf, -1, CR_DOVER)){
+ pdperror(name);
+ err = TRUE;
+ }
+ }
+ } else {
+ fprintf(stderr, "%s: %s: invalid format in line %d\n", progname, name, i);
+ }
+ free(buf);
+ if(err) break;
+ }
+ /* close the database */
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ return err ? 1 : 0;
+}
+
+
+/* perform export command */
+int doexport(const char *name, int bin){
+ CURIA *curia;
+ char *kbuf, *vbuf, *tmp;
+ int err, ksiz, vsiz;
+ /* open a database */
+ if(!(curia = cropen(name, CR_OREADER, -1, -1))){
+ pdperror(name);
+ return 1;
+ }
+ /* initialize the iterator */
+ criterinit(curia);
+ /* loop for each key */
+ err = FALSE;
+ while((kbuf = criternext(curia, &ksiz)) != NULL){
+ /* retrieve a value with a key */
+ if(!(vbuf = crget(curia, kbuf, ksiz, 0, -1, &vsiz))){
+ pdperror(name);
+ free(kbuf);
+ err = TRUE;
+ break;
+ }
+ /* output data */
+ if(bin){
+ tmp = cbbaseencode(kbuf, ksiz);
+ printf("%s\t", tmp);
+ free(tmp);
+ tmp = cbbaseencode(vbuf, vsiz);
+ printf("%s\n", tmp);
+ free(tmp);
+ } else {
+ printf("%s\t%s\n", kbuf, vbuf);
+ }
+ /* free resources */
+ free(vbuf);
+ free(kbuf);
+ }
+ /* check whether all records were retrieved */
+ if(dpecode != DP_ENOITEM){
+ pdperror(name);
+ err = TRUE;
+ }
+ /* close the database */
+ if(!crclose(curia)){
+ pdperror(name);
+ return 1;
+ }
+ return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/curia.c b/qdbm/curia.c
new file mode 100644
index 00000000..100529d0
--- /dev/null
+++ b/qdbm/curia.c
@@ -0,0 +1,1192 @@
+/*************************************************************************************************
+ * Implementation of Curia
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#define QDBM_INTERNAL 1
+
+#include "curia.h"
+#include "myconf.h"
+
+#define CR_NAMEMAX 512 /* max size of a database name */
+#define CR_DPMAX 512 /* max number of division of a database */
+#define CR_DIRMODE 00755 /* permission of a creating directory */
+#define CR_FILEMODE 00644 /* permission of a creating file */
+#define CR_PATHBUFSIZ 1024 /* size of a path buffer */
+#define CR_DEFDNUM 5 /* default number of division of a database */
+#define CR_ATTRBNUM 16 /* bucket number of attrubute database */
+#define CR_DPNAME "depot" /* name of each sub database */
+#define CR_KEYDNUM "dnum" /* key of division number */
+#define CR_KEYLRNUM "lrnum" /* key of the number of large objects */
+#define CR_TMPFSUF MYEXTSTR "crtmp" /* suffix of a temporary directory */
+#define CR_LOBDIR "lob" /* name of the directory of large objects */
+#define CR_LOBDDEPTH 2 /* depth of the directories of large objects */
+#define CR_NUMBUFSIZ 32 /* size of a buffer for a number */
+#define CR_IOBUFSIZ 8192 /* size of an I/O buffer */
+
+
+/* private function prototypes */
+static char *crstrdup(const char *str);
+static int crdpgetnum(DEPOT *depot, const char *kbuf, int ksiz);
+static char *crgetlobpath(CURIA *curia, const char *kbuf, int ksiz);
+static int crmklobdir(const char *path);
+static int crrmlobdir(const char *path);
+static int crcplobdir(CURIA *curia, const char *path);
+static int crwrite(int fd, const void *buf, int size);
+static int crread(int fd, void *buf, int size);
+
+
+
+/*************************************************************************************************
+ * public objects
+ *************************************************************************************************/
+
+
+/* Get a database handle. */
+CURIA *cropen(const char *name, int omode, int bnum, int dnum){
+ DEPOT *attr, **depots;
+ char path[CR_PATHBUFSIZ], *tname;
+ int i, j, dpomode, inode, lrnum;
+ struct stat sbuf;
+ CURIA *curia;
+ assert(name);
+ if(dnum < 1) dnum = CR_DEFDNUM;
+ if(dnum > CR_DPMAX) dnum = CR_DPMAX;
+ if(strlen(name) > CR_NAMEMAX){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return NULL;
+ }
+ dpomode = DP_OREADER;
+ if(omode & CR_OWRITER){
+ dpomode = DP_OWRITER;
+ if(omode & CR_OCREAT) dpomode |= DP_OCREAT;
+ if(omode & CR_OTRUNC) dpomode |= DP_OTRUNC;
+ if(omode & CR_OSPARSE) dpomode |= DP_OSPARSE;
+ }
+ if(omode & CR_ONOLCK) dpomode |= DP_ONOLCK;
+ if(omode & CR_OLCKNB) dpomode |= DP_OLCKNB;
+ attr = NULL;
+ lrnum = 0;
+ if((omode & CR_OWRITER) && (omode & CR_OCREAT)){
+ if(mkdir(name, CR_DIRMODE) == -1 && errno != EEXIST){
+ dpecodeset(DP_EMKDIR, __FILE__, __LINE__);
+ return NULL;
+ }
+ sprintf(path, "%s%c%s", name, MYPATHCHR, CR_DPNAME);
+ if(!(attr = dpopen(path, dpomode, CR_ATTRBNUM))) return NULL;
+ if(dprnum(attr) > 0){
+ if((dnum = crdpgetnum(attr, CR_KEYDNUM, -1)) < 1 ||
+ (lrnum = crdpgetnum(attr, CR_KEYLRNUM, -1)) < 0){
+ dpclose(attr);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ } else {
+ if(!dpput(attr, CR_KEYDNUM, -1, (char *)&dnum, sizeof(int), DP_DOVER) ||
+ !dpput(attr, CR_KEYLRNUM, -1, (char *)&lrnum, sizeof(int), DP_DOVER)){
+ dpclose(attr);
+ return NULL;
+ }
+ for(i = 0; i < dnum; i++){
+ sprintf(path, "%s%c%04d", name, MYPATHCHR, i + 1);
+ if(mkdir(path, CR_DIRMODE) == -1 && errno != EEXIST){
+ dpclose(attr);
+ dpecodeset(DP_EMKDIR, __FILE__, __LINE__);
+ return NULL;
+ }
+ }
+ }
+ }
+ if(!attr){
+ sprintf(path, "%s%c%s", name, MYPATHCHR, CR_DPNAME);
+ if(!(attr = dpopen(path, dpomode, 1))) return NULL;
+ if(!(omode & CR_OTRUNC)){
+ if((dnum = crdpgetnum(attr, CR_KEYDNUM, -1)) < 1 ||
+ (lrnum = crdpgetnum(attr, CR_KEYLRNUM, -1)) < 0){
+ dpclose(attr);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ }
+ }
+ if(omode & CR_OTRUNC){
+ for(i = 0; i < CR_DPMAX; i++){
+ sprintf(path, "%s%c%04d%c%s", name, MYPATHCHR, i + 1, MYPATHCHR, CR_DPNAME);
+ if(unlink(path) == -1 && errno != ENOENT){
+ dpclose(attr);
+ dpecodeset(DP_EUNLINK, __FILE__, __LINE__);
+ return NULL;
+ }
+ sprintf(path, "%s%c%04d%c%s", name, MYPATHCHR, i + 1, MYPATHCHR, CR_LOBDIR);
+ if(!crrmlobdir(path)){
+ dpclose(attr);
+ return NULL;
+ }
+ if(i >= dnum){
+ sprintf(path, "%s%c%04d", name, MYPATHCHR, i + 1);
+ if(rmdir(path) == -1 && errno != ENOENT){
+ dpclose(attr);
+ dpecodeset(DP_ERMDIR, __FILE__, __LINE__);
+ return NULL;
+ }
+ }
+ }
+ errno = 0;
+ }
+ if(lstat(name, &sbuf) == -1){
+ dpclose(attr);
+ dpecodeset(DP_ESTAT, __FILE__, __LINE__);
+ return NULL;
+ }
+ inode = sbuf.st_ino;
+ if(!(depots = malloc(dnum * sizeof(DEPOT *)))){
+ dpclose(attr);
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ return NULL;
+ }
+ for(i = 0; i < dnum; i++){
+ sprintf(path, "%s%c%04d%c%s", name, MYPATHCHR, i + 1, MYPATHCHR, CR_DPNAME);
+ if(!(depots[i] = dpopen(path, dpomode, bnum))){
+ for(j = 0; j < i; j++){
+ dpclose(depots[j]);
+ }
+ free(depots);
+ dpclose(attr);
+ return NULL;
+ }
+ }
+ curia = malloc(sizeof(CURIA));
+ tname = crstrdup(name);
+ if(!curia || !tname){
+ free(curia);
+ free(tname);
+ for(i = 0; i < dnum; i++){
+ dpclose(depots[i]);
+ }
+ free(depots);
+ dpclose(attr);
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ return NULL;
+ }
+ curia->name = tname;
+ curia->wmode = (omode & CR_OWRITER);
+ curia->inode = inode;
+ curia->attr = attr;
+ curia->depots = depots;
+ curia->dnum = dnum;
+ curia->inum = 0;
+ curia->lrnum = lrnum;
+ return curia;
+}
+
+
+/* Close a database handle. */
+int crclose(CURIA *curia){
+ int i, err;
+ assert(curia);
+ err = FALSE;
+ for(i = 0; i < curia->dnum; i++){
+ if(!dpclose(curia->depots[i])) err = TRUE;
+ }
+ free(curia->depots);
+ if(curia->wmode){
+ if(!dpput(curia->attr, CR_KEYLRNUM, -1, (char *)&(curia->lrnum), sizeof(int), DP_DOVER))
+ err = TRUE;
+ }
+ if(!dpclose(curia->attr)) err = TRUE;
+ free(curia->name);
+ free(curia);
+ return err ? FALSE : TRUE;
+}
+
+
+/* Store a record. */
+int crput(CURIA *curia, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode){
+ int dpdmode;
+ int tnum;
+ assert(curia && kbuf && vbuf);
+ if(!curia->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ switch(dmode){
+ case CR_DKEEP: dpdmode = DP_DKEEP; break;
+ case CR_DCAT: dpdmode = DP_DCAT; break;
+ default: dpdmode = DP_DOVER; break;
+ }
+ tnum = dpouterhash(kbuf, ksiz) % curia->dnum;
+ return dpput(curia->depots[tnum], kbuf, ksiz, vbuf, vsiz, dpdmode);
+}
+
+
+/* Delete a record. */
+int crout(CURIA *curia, const char *kbuf, int ksiz){
+ int tnum;
+ assert(curia && kbuf);
+ if(!curia->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ tnum = dpouterhash(kbuf, ksiz) % curia->dnum;
+ return dpout(curia->depots[tnum], kbuf, ksiz);
+}
+
+
+/* Retrieve a record. */
+char *crget(CURIA *curia, const char *kbuf, int ksiz, int start, int max, int *sp){
+ int tnum;
+ assert(curia && kbuf && start >= 0);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ tnum = dpouterhash(kbuf, ksiz) % curia->dnum;
+ return dpget(curia->depots[tnum], kbuf, ksiz, start, max, sp);
+}
+
+
+/* Retrieve a record and write the value into a buffer. */
+int crgetwb(CURIA *curia, const char *kbuf, int ksiz, int start, int max, char *vbuf){
+ int tnum;
+ assert(curia && kbuf && start >= 0 && max >= 0 && vbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ tnum = dpouterhash(kbuf, ksiz) % curia->dnum;
+ return dpgetwb(curia->depots[tnum], kbuf, ksiz, start, max, vbuf);
+}
+
+
+/* Get the size of the value of a record. */
+int crvsiz(CURIA *curia, const char *kbuf, int ksiz){
+ int tnum;
+ assert(curia && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ tnum = dpouterhash(kbuf, ksiz) % curia->dnum;
+ return dpvsiz(curia->depots[tnum], kbuf, ksiz);
+}
+
+
+/* Initialize the iterator of a database handle. */
+int criterinit(CURIA *curia){
+ int i, err;
+ assert(curia);
+ err = FALSE;
+ for(i = 0; i < curia->dnum; i++){
+ if(!dpiterinit(curia->depots[i])){
+ err = TRUE;
+ break;
+ }
+ }
+ curia->inum = 0;
+ return err ? FALSE : TRUE;
+}
+
+
+/* Get the next key of the iterator. */
+char *criternext(CURIA *curia, int *sp){
+ char *kbuf;
+ assert(curia);
+ kbuf = NULL;
+ while(curia->inum < curia->dnum && !(kbuf = dpiternext(curia->depots[curia->inum], sp))){
+ if(dpecode != DP_ENOITEM) return NULL;
+ (curia->inum)++;
+ }
+ return kbuf;
+}
+
+
+/* Set alignment of a database handle. */
+int crsetalign(CURIA *curia, int align){
+ int i, err;
+ assert(curia);
+ if(!curia->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ for(i = 0; i < curia->dnum; i++){
+ if(!dpsetalign(curia->depots[i], align)){
+ err = TRUE;
+ break;
+ }
+ }
+ return err ? FALSE : TRUE;
+}
+
+
+/* Set the size of the free block pool of a database handle. */
+int crsetfbpsiz(CURIA *curia, int size){
+ int i, err;
+ assert(curia && size >= 0);
+ if(!curia->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ for(i = 0; i < curia->dnum; i++){
+ if(!dpsetfbpsiz(curia->depots[i], size)){
+ err = TRUE;
+ break;
+ }
+ }
+ return err ? FALSE : TRUE;
+}
+
+
+/* Synchronize contents of updating a database with the files and the devices. */
+int crsync(CURIA *curia){
+ int i, err;
+ assert(curia);
+ if(!curia->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ if(!dpput(curia->attr, CR_KEYLRNUM, -1, (char *)&(curia->lrnum), sizeof(int), DP_DOVER) ||
+ !dpsync(curia->attr)) err = TRUE;
+ for(i = 0; i < curia->dnum; i++){
+ if(!dpsync(curia->depots[i])){
+ err = TRUE;
+ break;
+ }
+ }
+ return err ? FALSE : TRUE;
+}
+
+
+/* Optimize a database. */
+int croptimize(CURIA *curia, int bnum){
+ int i, err;
+ assert(curia);
+ if(!curia->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ for(i = 0; i < curia->dnum; i++){
+ if(!dpoptimize(curia->depots[i], bnum)){
+ err = TRUE;
+ break;
+ }
+ }
+ curia->inum = 0;
+ return err ? FALSE : TRUE;
+}
+
+
+/* Get the name of a database. */
+char *crname(CURIA *curia){
+ char *name;
+ assert(curia);
+ if(!(name = crstrdup(curia->name))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ return NULL;
+ }
+ return name;
+}
+
+
+/* Get the total size of database files. */
+int crfsiz(CURIA *curia){
+ int i, sum, rv;
+ assert(curia);
+ if((sum = dpfsiz(curia->attr)) == -1) return -1;
+ for(i = 0; i < curia->dnum; i++){
+ if((rv = dpfsiz(curia->depots[i])) == -1) return -1;
+ sum += rv;
+ }
+ return sum;
+}
+
+
+/* Get the total size of database files as double-precision value. */
+double crfsizd(CURIA *curia){
+ double sum;
+ int i, rv;
+ assert(curia);
+ sum = 0.0;
+ if((sum = dpfsiz(curia->attr)) < 0) return -1.0;
+ for(i = 0; i < curia->dnum; i++){
+ if((rv = dpfsiz(curia->depots[i])) == -1) return -1.0;
+ sum += rv;
+ }
+ return sum;
+}
+
+
+/* Get the total number of the elements of each bucket array. */
+int crbnum(CURIA *curia){
+ int i, sum, rv;
+ assert(curia);
+ sum = 0;
+ for(i = 0; i < curia->dnum; i++){
+ rv = dpbnum(curia->depots[i]);
+ if(rv == -1) return -1;
+ sum += rv;
+ }
+ return sum;
+}
+
+
+/* Get the total number of the used elements of each bucket array. */
+int crbusenum(CURIA *curia){
+ int i, sum, rv;
+ assert(curia);
+ sum = 0;
+ for(i = 0; i < curia->dnum; i++){
+ rv = dpbusenum(curia->depots[i]);
+ if(rv == -1) return -1;
+ sum += rv;
+ }
+ return sum;
+}
+
+
+/* Get the number of the records stored in a database. */
+int crrnum(CURIA *curia){
+ int i, sum, rv;
+ assert(curia);
+ sum = 0;
+ for(i = 0; i < curia->dnum; i++){
+ rv = dprnum(curia->depots[i]);
+ if(rv == -1) return -1;
+ sum += rv;
+ }
+ return sum;
+}
+
+
+/* Check whether a database handle is a writer or not. */
+int crwritable(CURIA *curia){
+ assert(curia);
+ return curia->wmode;
+}
+
+
+/* Check whether a database has a fatal error or not. */
+int crfatalerror(CURIA *curia){
+ int i;
+ assert(curia);
+ if(dpfatalerror(curia->attr)) return TRUE;
+ for(i = 0; i < curia->dnum; i++){
+ if(dpfatalerror(curia->depots[i])) return TRUE;
+ }
+ return FALSE;
+}
+
+
+/* Get the inode number of a database directory. */
+int crinode(CURIA *curia){
+ assert(curia);
+ return curia->inode;
+}
+
+
+/* Get the last modified time of a database. */
+time_t crmtime(CURIA *curia){
+ assert(curia);
+ return dpmtime(curia->attr);
+}
+
+
+/* Remove a database directory. */
+int crremove(const char *name){
+ struct stat sbuf;
+ CURIA *curia;
+ char path[CR_PATHBUFSIZ];
+ assert(name);
+ if(lstat(name, &sbuf) == -1){
+ dpecodeset(DP_ESTAT, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if((curia = cropen(name, CR_OWRITER | CR_OTRUNC, 1, 1)) != NULL) crclose(curia);
+ sprintf(path, "%s%c0001%c%s", name, MYPATHCHR, MYPATHCHR, CR_DPNAME);
+ dpremove(path);
+ sprintf(path, "%s%c0001", name, MYPATHCHR);
+ if(rmdir(path) == -1){
+ dpecodeset(DP_ERMDIR, __FILE__, __LINE__);
+ return FALSE;
+ }
+ sprintf(path, "%s%c%s", name, MYPATHCHR, CR_DPNAME);
+ if(!dpremove(path)) return FALSE;
+ if(rmdir(name) == -1){
+ dpecodeset(DP_ERMDIR, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Repair a broken database directory. */
+int crrepair(const char *name){
+ CURIA *tcuria;
+ DEPOT *tdepot;
+ char path[CR_PATHBUFSIZ], *kbuf, *vbuf;
+ struct stat sbuf;
+ int i, j, err, flags, bnum, dnum, ksiz, vsiz;
+ assert(name);
+ err = FALSE;
+ flags = 0;
+ bnum = 0;
+ dnum = 0;
+ sprintf(path, "%s%c%s", name, MYPATHCHR, CR_DPNAME);
+ if(lstat(path, &sbuf) != -1){
+ if((tdepot = dpopen(path, DP_OREADER, -1)) != NULL){
+ flags = dpgetflags(tdepot);
+ dpclose(tdepot);
+ }
+ }
+ for(i = 1; i <= CR_DPMAX; i++){
+ sprintf(path, "%s%c%04d%c%s", name, MYPATHCHR, i, MYPATHCHR, CR_DPNAME);
+ if(lstat(path, &sbuf) != -1){
+ dnum++;
+ if(!dprepair(path)) err = TRUE;
+ if((tdepot = dpopen(path, DP_OREADER, -1)) != NULL){
+ bnum += dpbnum(tdepot);
+ dpclose(tdepot);
+ }
+ }
+ }
+ if(dnum < CR_DEFDNUM) dnum = CR_DEFDNUM;
+ bnum /= dnum;
+ sprintf(path, "%s%s", name, CR_TMPFSUF);
+ if((tcuria = cropen(path, CR_OWRITER | CR_OCREAT | CR_OTRUNC, bnum, dnum)) != NULL){
+ if(!crsetflags(tcuria, flags)) err = TRUE;
+ for(i = 1; i <= CR_DPMAX; i++){
+ sprintf(path, "%s%c%04d%c%s", name, MYPATHCHR, i, MYPATHCHR, CR_DPNAME);
+ if(lstat(path, &sbuf) != -1){
+ if((tdepot = dpopen(path, DP_OREADER, -1)) != NULL){
+ if(!dpiterinit(tdepot)) err = TRUE;
+ while((kbuf = dpiternext(tdepot, &ksiz)) != NULL){
+ if((vbuf = dpget(tdepot, kbuf, ksiz, 0, -1, &vsiz)) != NULL){
+ if(!crput(tcuria, kbuf, ksiz, vbuf, vsiz, CR_DKEEP)) err = TRUE;
+ free(vbuf);
+ }
+ free(kbuf);
+ }
+ dpclose(tdepot);
+ } else {
+ err = TRUE;
+ }
+ }
+ for(j = 0; j <= CR_DPMAX; j++){
+ sprintf(path, "%s%c%04d%c%s", name, MYPATHCHR, j, MYPATHCHR, CR_LOBDIR);
+ if(lstat(path, &sbuf) != -1){
+ if(!crcplobdir(tcuria, path)) err = TRUE;
+ }
+ }
+ }
+ if(!crclose(tcuria)) err = TRUE;
+ if(!crremove(name)) err = TRUE;
+ sprintf(path, "%s%s", name, CR_TMPFSUF);
+ if(rename(path, name) == -1){
+ if(!err) dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ } else {
+ err = TRUE;
+ }
+ return err ? FALSE : TRUE;
+}
+
+
+/* Dump all records as endian independent data. */
+int crexportdb(CURIA *curia, const char *name){
+ char path[CR_PATHBUFSIZ], *kbuf, *vbuf, *pbuf;
+ int i, err, *fds, ksiz, vsiz, psiz;
+ assert(curia && name);
+ if(!(criterinit(curia))) return FALSE;
+ if(mkdir(name, CR_DIRMODE) == -1 && errno != EEXIST){
+ dpecodeset(DP_EMKDIR, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ fds = malloc(sizeof(int) * curia->dnum);
+ for(i = 0; i < curia->dnum; i++){
+ sprintf(path, "%s%c%04d", name, MYPATHCHR, i + 1);
+ if((fds[i] = open(path, O_RDWR | O_CREAT | O_TRUNC, CR_FILEMODE)) == -1){
+ if(!err) dpecodeset(DP_EOPEN, __FILE__, __LINE__);
+ err = TRUE;
+ break;
+ }
+ }
+ while(!err && (kbuf = criternext(curia, &ksiz)) != NULL){
+ if((vbuf = crget(curia, kbuf, ksiz, 0, -1, &vsiz)) != NULL){
+ if((pbuf = malloc(ksiz + vsiz + CR_NUMBUFSIZ * 2)) != NULL){
+ psiz = 0;
+ psiz += sprintf(pbuf + psiz, "%X\n%X\n", ksiz, vsiz);
+ memcpy(pbuf + psiz, kbuf, ksiz);
+ psiz += ksiz;
+ pbuf[psiz++] = '\n';
+ memcpy(pbuf + psiz, vbuf, vsiz);
+ psiz += vsiz;
+ pbuf[psiz++] = '\n';
+ if(!crwrite(fds[curia->inum], pbuf, psiz)){
+ dpecodeset(DP_EWRITE, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ free(pbuf);
+ } else {
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ free(vbuf);
+ } else {
+ err = TRUE;
+ }
+ free(kbuf);
+ }
+ for(i = 0; i < curia->dnum; i++){
+ if(fds[i] != -1 && close(fds[i]) == -1){
+ if(!err) dpecodeset(DP_ECLOSE, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ }
+ free(fds);
+ return !err && !crfatalerror(curia);
+}
+
+
+/* Load all records from endian independent data. */
+int crimportdb(CURIA *curia, const char *name){
+ DEPOT *depot;
+ char ipath[CR_PATHBUFSIZ], opath[CR_PATHBUFSIZ], *kbuf, *vbuf;
+ int i, err, ksiz, vsiz;
+ struct stat sbuf;
+ assert(curia && name);
+ if(!curia->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(crrnum(curia) > 0){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ for(i = 0; !err && i < CR_DPMAX; i++){
+ sprintf(ipath, "%s%c%04d", name, MYPATHCHR, i + 1);
+ if(lstat(ipath, &sbuf) == -1 || !S_ISREG(sbuf.st_mode)) break;
+ sprintf(opath, "%s%c%04d%s", curia->name, MYPATHCHR, i + 1, CR_TMPFSUF);
+ if((depot = dpopen(opath, DP_OWRITER | DP_OCREAT | DP_OTRUNC, -1)) != NULL){
+ if(dpimportdb(depot, ipath)){
+ dpiterinit(depot);
+ while((kbuf = dpiternext(depot, &ksiz)) != NULL){
+ if((vbuf = dpget(depot, kbuf, ksiz, 0, -1, &vsiz)) != NULL){
+ if(!crput(curia, kbuf, ksiz, vbuf, vsiz, CR_DKEEP)) err = TRUE;
+ free(vbuf);
+ } else {
+ err = TRUE;
+ }
+ free(kbuf);
+ }
+ } else {
+ err = TRUE;
+ }
+ if(!dpclose(depot)) err = TRUE;
+ if(!dpremove(opath)) err = TRUE;
+ } else {
+ err = TRUE;
+ }
+ }
+ return !err && !crfatalerror(curia);
+}
+
+
+/* Retrieve a record directly from a database directory. */
+char *crsnaffle(const char *name, const char *kbuf, int ksiz, int *sp){
+ char path[CR_PATHBUFSIZ], *vbuf;
+ int dnum, vsiz, tnum;
+ assert(name && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ sprintf(path, "%s%c%s", name, MYPATHCHR, CR_DPNAME);
+ if(!(vbuf = dpsnaffle(path, CR_KEYDNUM, -1, &vsiz)) || vsiz != sizeof(int) ||
+ (dnum = *(int *)vbuf) < 1){
+ free(vbuf);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ free(vbuf);
+ tnum = dpouterhash(kbuf, ksiz) % dnum;
+ sprintf(path, "%s%c%04d%c%s", name, MYPATHCHR, tnum + 1, MYPATHCHR, CR_DPNAME);
+ return dpsnaffle(path, kbuf, ksiz, sp);
+}
+
+
+/* Store a large object. */
+int crputlob(CURIA *curia, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode){
+ char *path;
+ int mode, fd, err, be;
+ struct stat sbuf;
+ assert(curia && kbuf && vbuf);
+ if(!curia->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(vsiz < 0) vsiz = strlen(vbuf);
+ if(!(path = crgetlobpath(curia, kbuf, ksiz))) return FALSE;
+ if(!crmklobdir(path)){
+ free(path);
+ return FALSE;
+ }
+ be = lstat(path, &sbuf) != -1 && S_ISREG(sbuf.st_mode);
+ mode = O_RDWR | O_CREAT;
+ if(dmode & CR_DKEEP) mode |= O_EXCL;
+ if(dmode & CR_DCAT){
+ mode |= O_APPEND;
+ } else {
+ mode |= O_TRUNC;
+ }
+ if((fd = open(path, mode, CR_FILEMODE)) == -1){
+ free(path);
+ dpecodeset(DP_EOPEN, __FILE__, __LINE__);
+ if(dmode == CR_DKEEP) dpecodeset(DP_EKEEP, __FILE__, __LINE__);
+ return FALSE;
+ }
+ free(path);
+ err = FALSE;
+ if(crwrite(fd, vbuf, vsiz) == -1){
+ err = TRUE;
+ dpecodeset(DP_EWRITE, __FILE__, __LINE__);
+ }
+ if(close(fd) == -1){
+ err = TRUE;
+ dpecodeset(DP_ECLOSE, __FILE__, __LINE__);
+ }
+ if(!err && !be) (curia->lrnum)++;
+ return err ? FALSE : TRUE;
+}
+
+
+/* Delete a large object. */
+int croutlob(CURIA *curia, const char *kbuf, int ksiz){
+ char *path;
+ int err, be;
+ struct stat sbuf;
+ assert(curia && kbuf);
+ if(!curia->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(!(path = crgetlobpath(curia, kbuf, ksiz))) return FALSE;
+ be = lstat(path, &sbuf) != -1 && S_ISREG(sbuf.st_mode);
+ err = FALSE;
+ if(unlink(path) == -1){
+ err = TRUE;
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ }
+ free(path);
+ if(!err && be) (curia->lrnum)--;
+ return err ? FALSE : TRUE;
+}
+
+
+/* Retrieve a large object. */
+char *crgetlob(CURIA *curia, const char *kbuf, int ksiz, int start, int max, int *sp){
+ char *path, *buf;
+ struct stat sbuf;
+ int fd, size;
+ assert(curia && kbuf && start >= 0);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(!(path = crgetlobpath(curia, kbuf, ksiz))) return NULL;
+ if((fd = open(path, O_RDONLY, CR_FILEMODE)) == -1){
+ free(path);
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return NULL;
+ }
+ free(path);
+ if(fstat(fd, &sbuf) == -1){
+ close(fd);
+ dpecodeset(DP_ESTAT, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(start > sbuf.st_size){
+ close(fd);
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(lseek(fd, start, SEEK_SET) == -1){
+ close(fd);
+ dpecodeset(DP_ESEEK, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(max < 0) max = sbuf.st_size;
+ if(!(buf = malloc(max + 1))){
+ close(fd);
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ return NULL;
+ }
+ size = crread(fd, buf, max);
+ close(fd);
+ if(size == -1){
+ free(buf);
+ dpecodeset(DP_EREAD, __FILE__, __LINE__);
+ return NULL;
+ }
+ buf[size] = '\0';
+ if(sp) *sp = size;
+ return buf;
+}
+
+
+/* Get the file descriptor of a large object. */
+int crgetlobfd(CURIA *curia, const char *kbuf, int ksiz){
+ char *path;
+ int fd;
+ assert(curia && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(!(path = crgetlobpath(curia, kbuf, ksiz))) return -1;
+ if((fd = open(path, curia->wmode ? O_RDWR: O_RDONLY, CR_FILEMODE)) == -1){
+ free(path);
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return -1;
+ }
+ free(path);
+ return fd;
+}
+
+
+/* Get the size of the value of a large object. */
+int crvsizlob(CURIA *curia, const char *kbuf, int ksiz){
+ char *path;
+ struct stat sbuf;
+ assert(curia && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(!(path = crgetlobpath(curia, kbuf, ksiz))) return -1;
+ if(lstat(path, &sbuf) == -1){
+ free(path);
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return -1;
+ }
+ free(path);
+ return sbuf.st_size;
+}
+
+
+/* Get the number of the large objects stored in a database. */
+int crrnumlob(CURIA *curia){
+ assert(curia);
+ return curia->lrnum;
+}
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Synchronize updating contents on memory. */
+int crmemsync(CURIA *curia){
+ int i, err;
+ assert(curia);
+ if(!curia->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ if(!dpput(curia->attr, CR_KEYLRNUM, -1, (char *)&(curia->lrnum), sizeof(int), DP_DOVER) ||
+ !dpmemsync(curia->attr)) err = TRUE;
+ for(i = 0; i < curia->dnum; i++){
+ if(!dpmemsync(curia->depots[i])){
+ err = TRUE;
+ break;
+ }
+ }
+ return err ? FALSE : TRUE;
+}
+
+
+/* Synchronize updating contents on memory, not physically. */
+int crmemflush(CURIA *curia){
+ int i, err;
+ assert(curia);
+ if(!curia->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ if(!dpput(curia->attr, CR_KEYLRNUM, -1, (char *)&(curia->lrnum), sizeof(int), DP_DOVER) ||
+ !dpmemsync(curia->attr)) err = TRUE;
+ for(i = 0; i < curia->dnum; i++){
+ if(!dpmemflush(curia->depots[i])){
+ err = TRUE;
+ break;
+ }
+ }
+ return err ? FALSE : TRUE;
+}
+
+
+/* Get flags of a database. */
+int crgetflags(CURIA *curia){
+ assert(curia);
+ return dpgetflags(curia->attr);
+}
+
+
+/* Set flags of a database. */
+int crsetflags(CURIA *curia, int flags){
+ assert(curia);
+ if(!curia->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return dpsetflags(curia->attr, flags);
+}
+
+
+
+/*************************************************************************************************
+ * private objects
+ *************************************************************************************************/
+
+
+/* Get a copied string.
+ `str' specifies an original string.
+ The return value is a copied string whose region is allocated by `malloc'. */
+static char *crstrdup(const char *str){
+ int len;
+ char *buf;
+ assert(str);
+ len = strlen(str);
+ if(!(buf = malloc(len + 1))) return NULL;
+ memcpy(buf, str, len + 1);
+ return buf;
+}
+
+
+/* Get an integer from a database.
+ `depot' specifies an inner database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the key.
+ The return value is the integer of the corresponding record. */
+static int crdpgetnum(DEPOT *depot, const char *kbuf, int ksiz){
+ char *vbuf;
+ int vsiz, rv;
+ if(!(vbuf = dpget(depot, kbuf, ksiz, 0, -1, &vsiz)) || vsiz != sizeof(int)){
+ free(vbuf);
+ return INT_MIN;
+ }
+ rv = *(int *)vbuf;
+ free(vbuf);
+ return rv;
+}
+
+
+/* Get the path of a large object.
+ `curia' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the key.
+ The return value is a path string whose region is allocated by `malloc'. */
+static char *crgetlobpath(CURIA *curia, const char *kbuf, int ksiz){
+ char prefix[CR_PATHBUFSIZ], *wp, *path;
+ int i, hash;
+ assert(curia && kbuf && ksiz >= 0);
+ wp = prefix;
+ wp += sprintf(wp, "%s%c%04d%c%s%c",
+ curia->name, MYPATHCHR, dpouterhash(kbuf, ksiz) % curia->dnum + 1,
+ MYPATHCHR, CR_LOBDIR, MYPATHCHR);
+ hash = dpinnerhash(kbuf, ksiz);
+ for(i = 0; i < CR_LOBDDEPTH; i++){
+ wp += sprintf(wp, "%02X%c", hash % 0x100, MYPATHCHR);
+ hash /= 0x100;
+ }
+ if(!(path = malloc(strlen(prefix) + ksiz * 2 + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ return NULL;
+ }
+ wp = path;
+ wp += sprintf(path, "%s", prefix);
+ for(i = 0; i < ksiz; i++){
+ wp += sprintf(wp, "%02X", ((unsigned char *)kbuf)[i]);
+ }
+ return path;
+}
+
+
+/* Create directories included in a path.
+ `path' specifies a path.
+ The return value is true if successful, else, it is false. */
+static int crmklobdir(const char *path){
+ char elem[CR_PATHBUFSIZ], *wp;
+ const char *dp;
+ int err, len;
+ wp = elem;
+ err = FALSE;
+ while(*path != '\0' && (dp = strchr(path, MYPATHCHR)) != NULL){
+ len = dp - path;
+ if((wp != elem || dp == path)) wp += sprintf(wp, "%c", MYPATHCHR);
+ memcpy(wp, path, len);
+ wp[len] = '\0';
+ wp += len;
+ if(mkdir(elem, CR_DIRMODE) == -1 && errno != EEXIST) err = TRUE;
+ path = dp + 1;
+ }
+ if(err) dpecodeset(DP_EMKDIR, __FILE__, __LINE__);
+ return err ? FALSE : TRUE;
+}
+
+
+/* Remove file and directories under a directory.
+ `path' specifies a path.
+ The return value is true if successful, else, it is false. */
+static int crrmlobdir(const char *path){
+ char elem[CR_PATHBUFSIZ];
+ DIR *DD;
+ struct dirent *dp;
+ assert(path);
+ if(unlink(path) != -1){
+ return TRUE;
+ } else {
+ if(errno == ENOENT) return TRUE;
+ if(!(DD = opendir(path))){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ while((dp = readdir(DD)) != NULL){
+ if(!strcmp(dp->d_name, MYCDIRSTR) || !strcmp(dp->d_name, MYPDIRSTR)) continue;
+ sprintf(elem, "%s%c%s", path, MYPATHCHR, dp->d_name);
+ if(!crrmlobdir(elem)){
+ closedir(DD);
+ return FALSE;
+ }
+ }
+ }
+ if(closedir(DD) == -1){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(rmdir(path) == -1){
+ dpecodeset(DP_ERMDIR, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Copy file and directories under a directory for repairing.
+ `path' specifies a path.
+ The return value is true if successful, else, it is false. */
+static int crcplobdir(CURIA *curia, const char *path){
+ char elem[CR_PATHBUFSIZ], numbuf[3], *rp, *kbuf, *vbuf;
+ DIR *DD;
+ struct dirent *dp;
+ struct stat sbuf;
+ int i, ksiz, vsiz, fd;
+ assert(curia && path);
+ if(lstat(path, &sbuf) == -1){
+ dpecodeset(DP_ESTAT, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(S_ISREG(sbuf.st_mode)){
+ rp = strrchr(path, MYPATHCHR) + 1;
+ for(i = 0; rp[i] != '\0'; i += 2){
+ numbuf[0] = rp[i];
+ numbuf[1] = rp[i+1];
+ numbuf[2] = '\0';
+ elem[i/2] = (int)strtol(numbuf, NULL, 16);
+ }
+ kbuf = elem;
+ ksiz = i / 2;
+ vsiz = sbuf.st_size;
+ if(!(vbuf = malloc(vsiz + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if((fd = open(path, O_RDONLY, CR_FILEMODE)) == -1){
+ free(vbuf);
+ dpecodeset(DP_EOPEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(crread(fd, vbuf, vsiz) == -1){
+ close(fd);
+ free(vbuf);
+ dpecodeset(DP_EOPEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!crputlob(curia, kbuf, ksiz, vbuf, vsiz, DP_DOVER)){
+ close(fd);
+ free(vbuf);
+ return FALSE;
+ }
+ close(fd);
+ free(vbuf);
+ return TRUE;
+ }
+ if(!(DD = opendir(path))){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ while((dp = readdir(DD)) != NULL){
+ if(!strcmp(dp->d_name, MYCDIRSTR) || !strcmp(dp->d_name, MYPDIRSTR)) continue;
+ sprintf(elem, "%s%c%s", path, MYPATHCHR, dp->d_name);
+ if(!crcplobdir(curia, elem)){
+ closedir(DD);
+ return FALSE;
+ }
+ }
+ if(closedir(DD) == -1){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Write into a file.
+ `fd' specifies a file descriptor.
+ `buf' specifies a buffer to write.
+ `size' specifies the size of the buffer.
+ The return value is the size of the written buffer, or, -1 on failure. */
+static int crwrite(int fd, const void *buf, int size){
+ char *lbuf;
+ int rv, wb;
+ assert(fd >= 0 && buf && size >= 0);
+ lbuf = (char *)buf;
+ rv = 0;
+ do {
+ wb = write(fd, lbuf, size);
+ switch(wb){
+ case -1: if(errno != EINTR) return -1;
+ case 0: break;
+ default:
+ lbuf += wb;
+ size -= wb;
+ rv += wb;
+ break;
+ }
+ } while(size > 0);
+ return rv;
+}
+
+
+/* Read from a file and store the data into a buffer.
+ `fd' specifies a file descriptor.
+ `buffer' specifies a buffer to store into.
+ `size' specifies the size to read with.
+ The return value is the size read with, or, -1 on failure. */
+static int crread(int fd, void *buf, int size){
+ char *lbuf;
+ int i, bs;
+ assert(fd >= 0 && buf && size >= 0);
+ lbuf = buf;
+ for(i = 0; i < size && (bs = read(fd, lbuf + i, size - i)) != 0; i += bs){
+ if(bs == -1 && errno != EINTR) return -1;
+ }
+ return i;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/curia.h b/qdbm/curia.h
new file mode 100644
index 00000000..24ce23f3
--- /dev/null
+++ b/qdbm/curia.h
@@ -0,0 +1,474 @@
+/*************************************************************************************************
+ * The extended API of QDBM
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#ifndef _CURIA_H /* duplication check */
+#define _CURIA_H
+
+#if defined(__cplusplus) /* export for C++ */
+extern "C" {
+#endif
+
+
+#include <depot.h>
+#include <stdlib.h>
+#include <time.h>
+
+
+#if defined(_MSC_VER) && !defined(QDBM_INTERNAL) && !defined(QDBM_STATIC)
+#define MYEXTERN extern __declspec(dllimport)
+#else
+#define MYEXTERN extern
+#endif
+
+
+
+/*************************************************************************************************
+ * API
+ *************************************************************************************************/
+
+
+typedef struct { /* type of structure for the database handle */
+ char *name; /* name of the database directory */
+ int wmode; /* whether to be writable */
+ int inode; /* inode of the database directory */
+ DEPOT *attr; /* database handle for attributes */
+ DEPOT **depots; /* handles of the record database */
+ int dnum; /* number of record database handles */
+ int inum; /* number of the database of the using iterator */
+ int lrnum; /* number of large objects */
+} CURIA;
+
+enum { /* enumeration for open modes */
+ CR_OREADER = 1 << 0, /* open as a reader */
+ CR_OWRITER = 1 << 1, /* open as a writer */
+ CR_OCREAT = 1 << 2, /* a writer creating */
+ CR_OTRUNC = 1 << 3, /* a writer truncating */
+ CR_ONOLCK = 1 << 4, /* open without locking */
+ CR_OLCKNB = 1 << 5, /* lock without blocking */
+ CR_OSPARSE = 1 << 6 /* create as sparse files */
+};
+
+enum { /* enumeration for write modes */
+ CR_DOVER, /* overwrite an existing value */
+ CR_DKEEP, /* keep an existing value */
+ CR_DCAT /* concatenate values */
+};
+
+
+/* Get a database handle.
+ `name' specifies the name of a database directory.
+ `omode' specifies the connection mode: `CR_OWRITER' as a writer, `CR_OREADER' as a reader.
+ If the mode is `CR_OWRITER', the following may be added by bitwise or: `CR_OCREAT', which
+ means it creates a new database if not exist, `CR_OTRUNC', which means it creates a new
+ database regardless if one exists. Both of `CR_OREADER' and `CR_OWRITER' can be added to by
+ bitwise or: `CR_ONOLCK', which means it opens a database directory without file locking, or
+ `CR_OLCKNB', which means locking is performed without blocking. `CR_OCREAT' can be added to
+ by bitwise or: `CR_OSPARSE', which means it creates database files as sparse files.
+ `bnum' specifies the number of elements of each bucket array. If it is not more than 0,
+ the default value is specified. The size of each bucket array is determined on creating,
+ and can not be changed except for by optimization of the database. Suggested size of each
+ bucket array is about from 0.5 to 4 times of the number of all records to store.
+ `dnum' specifies the number of division of the database. If it is not more than 0, the
+ default value is specified. The number of division can not be changed from the initial value.
+ The max number of division is 512.
+ The return value is the database handle or `NULL' if it is not successful.
+ While connecting as a writer, an exclusive lock is invoked to the database directory.
+ While connecting as a reader, a shared lock is invoked to the database directory.
+ The thread blocks until the lock is achieved. If `CR_ONOLCK' is used, the application is
+ responsible for exclusion control. */
+CURIA *cropen(const char *name, int omode, int bnum, int dnum);
+
+
+/* Close a database handle.
+ `curia' specifies a database handle.
+ If successful, the return value is true, else, it is false.
+ Because the region of a closed handle is released, it becomes impossible to use the handle.
+ Updating a database is assured to be written when the handle is closed. If a writer opens
+ a database but does not close it appropriately, the database will be broken. */
+int crclose(CURIA *curia);
+
+
+/* Store a record.
+ `curia' specifies a database handle connected as a writer.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `vbuf' specifies the pointer to the region of a value.
+ `vsiz' specifies the size of the region of the value. If it is negative, the size is
+ assigned with `strlen(vbuf)'.
+ `dmode' specifies behavior when the key overlaps, by the following values: `CR_DOVER',
+ which means the specified value overwrites the existing one, `CR_DKEEP', which means the
+ existing value is kept, `CR_DCAT', which means the specified value is concatenated at the
+ end of the existing value.
+ If successful, the return value is true, else, it is false. */
+int crput(CURIA *curia, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
+
+
+/* Delete a record.
+ `curia' specifies a database handle connected as a writer.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ If successful, the return value is true, else, it is false. False is returned when no
+ record corresponds to the specified key. */
+int crout(CURIA *curia, const char *kbuf, int ksiz);
+
+
+/* Retrieve a record.
+ `curia' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `start' specifies the offset address of the beginning of the region of the value to be read.
+ `max' specifies the max size to be read. If it is negative, the size to read is unlimited.
+ `sp' specifies the pointer to a variable to which the size of the region of the return value
+ is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the value of the
+ corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to
+ the specified key or the size of the value of the corresponding record is less than `start'.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. */
+char *crget(CURIA *curia, const char *kbuf, int ksiz, int start, int max, int *sp);
+
+
+/* Retrieve a record and write the value into a buffer.
+ `curia' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `start' specifies the offset address of the beginning of the region of the value to be read.
+ `max' specifies the max size to be read. It shuld be equal to or less than the size of the
+ writing buffer.
+ `vbuf' specifies the pointer to a buffer into which the value of the corresponding record is
+ written.
+ If successful, the return value is the size of the written data, else, it is -1. -1 is
+ returned when no record corresponds to the specified key or the size of the value of the
+ corresponding record is less than `start'.
+ Note that no additional zero code is appended at the end of the region of the writing buffer. */
+int crgetwb(CURIA *curia, const char *kbuf, int ksiz, int start, int max, char *vbuf);
+
+
+/* Get the size of the value of a record.
+ `curia' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ If successful, the return value is the size of the value of the corresponding record, else,
+ it is -1.
+ Because this function does not read the entity of a record, it is faster than `crget'. */
+int crvsiz(CURIA *curia, const char *kbuf, int ksiz);
+
+
+/* Initialize the iterator of a database handle.
+ `curia' specifies a database handle.
+ If successful, the return value is true, else, it is false.
+ The iterator is used in order to access the key of every record stored in a database. */
+int criterinit(CURIA *curia);
+
+
+/* Get the next key of the iterator.
+ `curia' specifies a database handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the next key, else, it is
+ `NULL'. `NULL' is returned when no record is to be get out of the iterator.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. It is possible to access every record by iteration of calling this
+ function. However, it is not assured if updating the database is occurred while the
+ iteration. Besides, the order of this traversal access method is arbitrary, so it is not
+ assured that the order of storing matches the one of the traversal access. */
+char *criternext(CURIA *curia, int *sp);
+
+
+/* Set alignment of a database handle.
+ `curia' specifies a database handle connected as a writer.
+ `align' specifies the size of alignment.
+ If successful, the return value is true, else, it is false.
+ If alignment is set to a database, the efficiency of overwriting values is improved.
+ The size of alignment is suggested to be average size of the values of the records to be
+ stored. If alignment is positive, padding whose size is multiple number of the alignment
+ is placed. If alignment is negative, as `vsiz' is the size of a value, the size of padding
+ is calculated with `(vsiz / pow(2, abs(align) - 1))'. Because alignment setting is not
+ saved in a database, you should specify alignment every opening a database. */
+int crsetalign(CURIA *curia, int align);
+
+
+/* Set the size of the free block pool of a database handle.
+ `curia' specifies a database handle connected as a writer.
+ `size' specifies the size of the free block pool of a database.
+ If successful, the return value is true, else, it is false.
+ The default size of the free block pool is 16. If the size is greater, the space efficiency
+ of overwriting values is improved with the time efficiency sacrificed. */
+int crsetfbpsiz(CURIA *curia, int size);
+
+
+/* Synchronize updating contents with the files and the devices.
+ `curia' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false.
+ This function is useful when another process uses the connected database directory. */
+int crsync(CURIA *curia);
+
+
+/* Optimize a database.
+ `curia' specifies a database handle connected as a writer.
+ `bnum' specifies the number of the elements of each bucket array. If it is not more than 0,
+ the default value is specified.
+ If successful, the return value is true, else, it is false.
+ In an alternating succession of deleting and storing with overwrite or concatenate,
+ dispensable regions accumulate. This function is useful to do away with them. */
+int croptimize(CURIA *curia, int bnum);
+
+/* Get the name of a database.
+ `curia' specifies a database handle.
+ If successful, the return value is the pointer to the region of the name of the database,
+ else, it is `NULL'.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *crname(CURIA *curia);
+
+
+/* Get the total size of database files.
+ `curia' specifies a database handle.
+ If successful, the return value is the total size of the database files, else, it is -1.
+ If the total size is more than 2GB, the return value overflows. */
+int crfsiz(CURIA *curia);
+
+
+/* Get the total size of database files as double-precision floating-point number.
+ `curia' specifies a database handle.
+ If successful, the return value is the total size of the database files, else, it is -1.0. */
+double crfsizd(CURIA *curia);
+
+
+/* Get the total number of the elements of each bucket array.
+ `curia' specifies a database handle.
+ If successful, the return value is the total number of the elements of each bucket array,
+ else, it is -1. */
+int crbnum(CURIA *curia);
+
+
+/* Get the total number of the used elements of each bucket array.
+ `curia' specifies a database handle.
+ If successful, the return value is the total number of the used elements of each bucket
+ array, else, it is -1.
+ This function is inefficient because it accesses all elements of each bucket array. */
+int crbusenum(CURIA *curia);
+
+
+/* Get the number of the records stored in a database.
+ `curia' specifies a database handle.
+ If successful, the return value is the number of the records stored in the database, else,
+ it is -1. */
+int crrnum(CURIA *curia);
+
+
+/* Check whether a database handle is a writer or not.
+ `curia' specifies a database handle.
+ The return value is true if the handle is a writer, false if not. */
+int crwritable(CURIA *curia);
+
+
+/* Check whether a database has a fatal error or not.
+ `curia' specifies a database handle.
+ The return value is true if the database has a fatal error, false if not. */
+int crfatalerror(CURIA *curia);
+
+
+/* Get the inode number of a database directory.
+ `curia' specifies a database handle.
+ The return value is the inode number of the database directory. */
+int crinode(CURIA *curia);
+
+
+/* Get the last modified time of a database.
+ `curia' specifies a database handle.
+ The return value is the last modified time of the database. */
+time_t crmtime(CURIA *curia);
+
+
+/* Remove a database directory.
+ `name' specifies the name of a database directory.
+ If successful, the return value is true, else, it is false. */
+int crremove(const char *name);
+
+
+/* Repair a broken database directory.
+ `name' specifies the name of a database directory.
+ If successful, the return value is true, else, it is false.
+ There is no guarantee that all records in a repaired database directory correspond to the
+ original or expected state. */
+int crrepair(const char *name);
+
+
+/* Dump all records as endian independent data.
+ `curia' specifies a database handle.
+ `name' specifies the name of an output directory.
+ If successful, the return value is true, else, it is false.
+ Note that large objects are ignored. */
+int crexportdb(CURIA *curia, const char *name);
+
+
+/* Load all records from endian independent data.
+ `curia' specifies a database handle connected as a writer. The database of the handle must
+ be empty.
+ `name' specifies the name of an input directory.
+ If successful, the return value is true, else, it is false.
+ Note that large objects are ignored. */
+int crimportdb(CURIA *curia, const char *name);
+
+
+/* Retrieve a record directly from a database directory.
+ `name' specifies the name of a database directory.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the value of the
+ corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to
+ the specified key.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. Although this function can be used even while the database directory is
+ locked by another process, it is not assured that recent updated is reflected. */
+char *crsnaffle(const char *name, const char *kbuf, int ksiz, int *sp);
+
+
+/* Store a large object.
+ `curia' specifies a database handle connected as a writer.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `vbuf' specifies the pointer to the region of a value.
+ `vsiz' specifies the size of the region of the value. If it is negative, the size is
+ assigned with `strlen(vbuf)'.
+ `dmode' specifies behavior when the key overlaps, by the following values: `CR_DOVER',
+ which means the specified value overwrites the existing one, `CR_DKEEP', which means the
+ existing value is kept, `CR_DCAT', which means the specified value is concatenated at the
+ end of the existing value.
+ If successful, the return value is true, else, it is false. */
+int crputlob(CURIA *curia, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
+
+
+/* Delete a large object.
+ `curia' specifies a database handle connected as a writer.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ If successful, the return value is true, else, it is false. false is returned when no large
+ object corresponds to the specified key. */
+int croutlob(CURIA *curia, const char *kbuf, int ksiz);
+
+
+/* Retrieve a large object.
+ `curia' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `start' specifies the offset address of the beginning of the region of the value to be read.
+ `max' specifies the max size to be read. If it is negative, the size to read is unlimited.
+ `sp' specifies the pointer to a variable to which the size of the region of the return value
+ is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the value of the
+ corresponding large object, else, it is `NULL'. `NULL' is returned when no large object
+ corresponds to the specified key or the size of the value of the corresponding large object
+ is less than `start'.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. */
+char *crgetlob(CURIA *curia, const char *kbuf, int ksiz, int start, int max, int *sp);
+
+
+/* Get the file descriptor of a large object.
+ `curia' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ If successful, the return value is the file descriptor of the corresponding large object,
+ else, it is -1. -1 is returned when no large object corresponds to the specified key. The
+ returned file descriptor is opened with the `open' call. If the database handle was opened
+ as a writer, the descriptor is writable (O_RDWR), else, it is not writable (O_RDONLY). The
+ descriptor should be closed with the `close' call if it is no longer in use. */
+int crgetlobfd(CURIA *curia, const char *kbuf, int ksiz);
+
+
+/* Get the size of the value of a large object.
+ `curia' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ If successful, the return value is the size of the value of the corresponding large object,
+ else, it is -1.
+ Because this function does not read the entity of a large object, it is faster than
+ `crgetlob'. */
+int crvsizlob(CURIA *curia, const char *kbuf, int ksiz);
+
+
+/* Get the number of the large objects stored in a database.
+ `curia' specifies a database handle.
+ If successful, the return value is the number of the large objects stored in the database,
+ else, it is -1. */
+int crrnumlob(CURIA *curia);
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Synchronize updating contents on memory.
+ `curia' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false. */
+int crmemsync(CURIA *curia);
+
+
+/* Synchronize updating contents on memory, not physically.
+ `curia' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false. */
+int crmemflush(CURIA *curia);
+
+
+/* Get flags of a database.
+ `curia' specifies a database handle.
+ The return value is the flags of a database. */
+int crgetflags(CURIA *curia);
+
+
+/* Set flags of a database.
+ `curia' specifies a database handle connected as a writer.
+ `flags' specifies flags to set. Least ten bits are reserved for internal use.
+ If successful, the return value is true, else, it is false. */
+int crsetflags(CURIA *curia, int flags);
+
+
+
+#undef MYEXTERN
+
+#if defined(__cplusplus) /* export for C++ */
+}
+#endif
+
+#endif /* duplication check */
+
+
+/* END OF FILE */
diff --git a/qdbm/depot.c b/qdbm/depot.c
new file mode 100644
index 00000000..717335b8
--- /dev/null
+++ b/qdbm/depot.c
@@ -0,0 +1,2219 @@
+/*************************************************************************************************
+ * Implementation of Depot
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#define QDBM_INTERNAL 1
+
+#include "depot.h"
+#include "myconf.h"
+
+#define DP_FILEMODE 00644 /* permission of a creating file */
+#define DP_MAGICNUMB "[DEPOT]\n\f" /* magic number on environments of big endian */
+#define DP_MAGICNUML "[depot]\n\f" /* magic number on environments of little endian */
+#define DP_HEADSIZ 48 /* size of the reagion of the header */
+#define DP_LIBVEROFF 12 /* offset of the region for the library version */
+#define DP_FLAGSOFF 16 /* offset of the region for flags */
+#define DP_FSIZOFF 24 /* offset of the region for the file size */
+#define DP_BNUMOFF 32 /* offset of the region for the bucket number */
+#define DP_RNUMOFF 40 /* offset of the region for the record number */
+#define DP_DEFBNUM 8191 /* default bucket number */
+#define DP_FBPOOLSIZ 16 /* size of free block pool */
+#define DP_ENTBUFSIZ 128 /* size of the entity buffer */
+#define DP_STKBUFSIZ 256 /* size of the stack key buffer */
+#define DP_WRTBUFSIZ 8192 /* size of the writing buffer */
+#define DP_FSBLKSIZ 4096 /* size of a block of the file system */
+#define DP_TMPFSUF MYEXTSTR "dptmp" /* suffix of a temporary file */
+#define DP_OPTBLOAD 0.25 /* ratio of bucket loading at optimization */
+#define DP_OPTRUNIT 256 /* number of records in a process of optimization */
+#define DP_NUMBUFSIZ 32 /* size of a buffer for a number */
+#define DP_IOBUFSIZ 8192 /* size of an I/O buffer */
+
+/* get the first hash value */
+#define DP_FIRSTHASH(DP_res, DP_kbuf, DP_ksiz) \
+ do { \
+ const unsigned char *_DP_p; \
+ int _DP_ksiz; \
+ _DP_p = (const unsigned char *)(DP_kbuf); \
+ _DP_ksiz = DP_ksiz; \
+ if((_DP_ksiz) == sizeof(int)){ \
+ memcpy(&(DP_res), (DP_kbuf), sizeof(int)); \
+ } else { \
+ (DP_res) = 751; \
+ } \
+ while(_DP_ksiz--){ \
+ (DP_res) = (DP_res) * 31 + *(_DP_p)++; \
+ } \
+ (DP_res) = ((DP_res) * 87767623) & INT_MAX; \
+ } while(FALSE)
+
+/* get the second hash value */
+#define DP_SECONDHASH(DP_res, DP_kbuf, DP_ksiz) \
+ do { \
+ const unsigned char *_DP_p; \
+ int _DP_ksiz; \
+ _DP_p = (const unsigned char *)(DP_kbuf) + DP_ksiz - 1; \
+ _DP_ksiz = DP_ksiz; \
+ for((DP_res) = 19780211; _DP_ksiz--;){ \
+ (DP_res) = (DP_res) * 37 + *(_DP_p)--; \
+ } \
+ (DP_res) = ((DP_res) * 43321879) & INT_MAX; \
+ } while(FALSE)
+
+/* get the third hash value */
+#define DP_THIRDHASH(DP_res, DP_kbuf, DP_ksiz) \
+ do { \
+ int _DP_i; \
+ (DP_res) = 774831917; \
+ for(_DP_i = (DP_ksiz) - 1; _DP_i >= 0; _DP_i--){ \
+ (DP_res) = (DP_res) * 29 + ((const unsigned char *)(DP_kbuf))[_DP_i]; \
+ } \
+ (DP_res) = ((DP_res) * 5157883) & INT_MAX; \
+ } while(FALSE)
+
+enum { /* enumeration for a record header */
+ DP_RHIFLAGS, /* offset of flags */
+ DP_RHIHASH, /* offset of value of the second hash function */
+ DP_RHIKSIZ, /* offset of the size of the key */
+ DP_RHIVSIZ, /* offset of the size of the value */
+ DP_RHIPSIZ, /* offset of the size of the padding bytes */
+ DP_RHILEFT, /* offset of the offset of the left child */
+ DP_RHIRIGHT, /* offset of the offset of the right child */
+ DP_RHNUM /* number of elements of a header */
+};
+
+enum { /* enumeration for the flag of a record */
+ DP_RECFDEL = 1 << 0, /* deleted */
+ DP_RECFREUSE = 1 << 1 /* reusable */
+};
+
+
+/* private function prototypes */
+static int dpbigendian(void);
+static char *dpstrdup(const char *str);
+static int dplock(int fd, int ex, int nb);
+static int dpwrite(int fd, const void *buf, int size);
+static int dpseekwrite(int fd, int off, const void *buf, int size);
+static int dpseekwritenum(int fd, int off, int num);
+static int dpread(int fd, void *buf, int size);
+static int dpseekread(int fd, int off, void *buf, int size);
+static int dpfcopy(int destfd, int destoff, int srcfd, int srcoff);
+static int dpgetprime(int num);
+static int dppadsize(DEPOT *depot, int ksiz, int vsiz);
+static int dprecsize(int *head);
+static int dprechead(DEPOT *depot, int off, int *head, char *ebuf, int *eep);
+static char *dpreckey(DEPOT *depot, int off, int *head);
+static char *dprecval(DEPOT *depot, int off, int *head, int start, int max);
+static int dprecvalwb(DEPOT *depot, int off, int *head, int start, int max, char *vbuf);
+static int dpkeycmp(const char *abuf, int asiz, const char *bbuf, int bsiz);
+static int dprecsearch(DEPOT *depot, const char *kbuf, int ksiz, int hash, int *bip, int *offp,
+ int *entp, int *head, char *ebuf, int *eep, int delhit);
+static int dprecrewrite(DEPOT *depot, int off, int rsiz, const char *kbuf, int ksiz,
+ const char *vbuf, int vsiz, int hash, int left, int right);
+static int dprecappend(DEPOT *depot, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+ int hash, int left, int right);
+static int dprecover(DEPOT *depot, int off, int *head, const char *vbuf, int vsiz, int cat);
+static int dprecdelete(DEPOT *depot, int off, int *head, int reusable);
+static void dpfbpoolcoal(DEPOT *depot);
+static int dpfbpoolcmp(const void *a, const void *b);
+
+
+
+/*************************************************************************************************
+ * public objects
+ *************************************************************************************************/
+
+
+/* String containing the version information. */
+const char *dpversion = _QDBM_VERSION;
+
+
+/* Get a message string corresponding to an error code. */
+const char *dperrmsg(int ecode){
+ switch(ecode){
+ case DP_ENOERR: return "no error";
+ case DP_EFATAL: return "with fatal error";
+ case DP_EMODE: return "invalid mode";
+ case DP_EBROKEN: return "broken database file";
+ case DP_EKEEP: return "existing record";
+ case DP_ENOITEM: return "no item found";
+ case DP_EALLOC: return "memory allocation error";
+ case DP_EMAP: return "memory mapping error";
+ case DP_EOPEN: return "open error";
+ case DP_ECLOSE: return "close error";
+ case DP_ETRUNC: return "trunc error";
+ case DP_ESYNC: return "sync error";
+ case DP_ESTAT: return "stat error";
+ case DP_ESEEK: return "seek error";
+ case DP_EREAD: return "read error";
+ case DP_EWRITE: return "write error";
+ case DP_ELOCK: return "lock error";
+ case DP_EUNLINK: return "unlink error";
+ case DP_EMKDIR: return "mkdir error";
+ case DP_ERMDIR: return "rmdir error";
+ case DP_EMISC: return "miscellaneous error";
+ }
+ return "(invalid ecode)";
+}
+
+
+/* Get a database handle. */
+DEPOT *dpopen(const char *name, int omode, int bnum){
+ char hbuf[DP_HEADSIZ], *map, c, *tname;
+ int i, mode, fd, inode, fsiz, rnum, msiz, *fbpool;
+ struct stat sbuf;
+ time_t mtime;
+ DEPOT *depot;
+ assert(name);
+ mode = O_RDONLY;
+ if(omode & DP_OWRITER){
+ mode = O_RDWR;
+ if(omode & DP_OCREAT) mode |= O_CREAT;
+ }
+ if((fd = open(name, mode, DP_FILEMODE)) == -1){
+ dpecodeset(DP_EOPEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(!(omode & DP_ONOLCK)){
+ if(!dplock(fd, omode & DP_OWRITER, omode & DP_OLCKNB)){
+ close(fd);
+ return NULL;
+ }
+ }
+ if((omode & DP_OWRITER) && (omode & DP_OTRUNC)){
+ if(ftruncate(fd, 0) == -1){
+ close(fd);
+ dpecodeset(DP_ETRUNC, __FILE__, __LINE__);
+ return NULL;
+ }
+ }
+ if(fstat(fd, &sbuf) == -1 || !S_ISREG(sbuf.st_mode) ||
+ (sbuf.st_ino == 0 && lstat(name, &sbuf) == -1)){
+ close(fd);
+ dpecodeset(DP_ESTAT, __FILE__, __LINE__);
+ return NULL;
+ }
+ inode = sbuf.st_ino;
+ mtime = sbuf.st_mtime;
+ fsiz = sbuf.st_size;
+ if((omode & DP_OWRITER) && fsiz == 0){
+ memset(hbuf, 0, DP_HEADSIZ);
+ if(dpbigendian()){
+ memcpy(hbuf, DP_MAGICNUMB, strlen(DP_MAGICNUMB));
+ } else {
+ memcpy(hbuf, DP_MAGICNUML, strlen(DP_MAGICNUML));
+ }
+ sprintf(hbuf + DP_LIBVEROFF, "%d", _QDBM_LIBVER / 100);
+ bnum = bnum < 1 ? DP_DEFBNUM : bnum;
+ bnum = dpgetprime(bnum);
+ memcpy(hbuf + DP_BNUMOFF, &bnum, sizeof(int));
+ rnum = 0;
+ memcpy(hbuf + DP_RNUMOFF, &rnum, sizeof(int));
+ fsiz = DP_HEADSIZ + bnum * sizeof(int);
+ memcpy(hbuf + DP_FSIZOFF, &fsiz, sizeof(int));
+ if(!dpseekwrite(fd, 0, hbuf, DP_HEADSIZ)){
+ close(fd);
+ return NULL;
+ }
+ if(omode & DP_OSPARSE){
+ c = 0;
+ if(!dpseekwrite(fd, fsiz - 1, &c, 1)){
+ close(fd);
+ return NULL;
+ }
+ } else {
+ if(!(map = malloc(bnum * sizeof(int)))){
+ close(fd);
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ return NULL;
+ }
+ memset(map, 0, bnum * sizeof(int));
+ if(!dpseekwrite(fd, DP_HEADSIZ, map, bnum * sizeof(int))){
+ free(map);
+ close(fd);
+ return NULL;
+ }
+ free(map);
+ }
+ }
+ if(!dpseekread(fd, 0, hbuf, DP_HEADSIZ)){
+ close(fd);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(!(omode & DP_ONOLCK) &&
+ ((dpbigendian() ? memcmp(hbuf, DP_MAGICNUMB, strlen(DP_MAGICNUMB)) != 0 :
+ memcmp(hbuf, DP_MAGICNUML, strlen(DP_MAGICNUML)) != 0) ||
+ *((int *)(hbuf + DP_FSIZOFF)) != fsiz)){
+ close(fd);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ bnum = *((int *)(hbuf + DP_BNUMOFF));
+ rnum = *((int *)(hbuf + DP_RNUMOFF));
+ if(bnum < 1 || rnum < 0 || fsiz < DP_HEADSIZ + bnum * sizeof(int)){
+ close(fd);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ msiz = DP_HEADSIZ + bnum * sizeof(int);
+ map = mmap(0, msiz, PROT_READ | ((mode & DP_OWRITER) ? PROT_WRITE : 0), MAP_SHARED, fd, 0);
+ if(map == MAP_FAILED){
+ close(fd);
+ dpecodeset(DP_EMAP, __FILE__, __LINE__);
+ return NULL;
+ }
+ tname = NULL;
+ fbpool = NULL;
+ if(!(depot = malloc(sizeof(DEPOT))) || !(tname = dpstrdup(name)) ||
+ !(fbpool = malloc(DP_FBPOOLSIZ * 2 * sizeof(int)))){
+ free(fbpool);
+ free(tname);
+ free(depot);
+ munmap(map, msiz);
+ close(fd);
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ return NULL;
+ }
+ depot->name = tname;
+ depot->wmode = (mode & DP_OWRITER);
+ depot->inode = inode;
+ depot->mtime = mtime;
+ depot->fd = fd;
+ depot->fsiz = fsiz;
+ depot->map = map;
+ depot->msiz = msiz;
+ depot->buckets = (int *)(map + DP_HEADSIZ);
+ depot->bnum = bnum;
+ depot->rnum = rnum;
+ depot->fatal = FALSE;
+ depot->ioff = 0;
+ depot->fbpool = fbpool;
+ for(i = 0; i < DP_FBPOOLSIZ * 2; i += 2){
+ depot->fbpool[i] = -1;
+ depot->fbpool[i+1] = -1;
+ }
+ depot->fbpsiz = DP_FBPOOLSIZ * 2;
+ depot->fbpinc = 0;
+ depot->align = 0;
+ return depot;
+}
+
+
+/* Close a database handle. */
+int dpclose(DEPOT *depot){
+ int fatal, err;
+ assert(depot);
+ fatal = depot->fatal;
+ err = FALSE;
+ if(depot->wmode){
+ *((int *)(depot->map + DP_FSIZOFF)) = depot->fsiz;
+ *((int *)(depot->map + DP_RNUMOFF)) = depot->rnum;
+ }
+ if(depot->map != MAP_FAILED){
+ if(munmap(depot->map, depot->msiz) == -1){
+ err = TRUE;
+ dpecodeset(DP_EMAP, __FILE__, __LINE__);
+ }
+ }
+ if(close(depot->fd) == -1){
+ err = TRUE;
+ dpecodeset(DP_ECLOSE, __FILE__, __LINE__);
+ }
+ free(depot->fbpool);
+ free(depot->name);
+ free(depot);
+ if(fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return err ? FALSE : TRUE;
+}
+
+
+/* Store a record. */
+int dpput(DEPOT *depot, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode){
+ int head[DP_RHNUM], next[DP_RHNUM];
+ int i, hash, bi, off, entoff, ee, newoff, rsiz, nsiz, fdel, mroff, mrsiz, mi, min;
+ char ebuf[DP_ENTBUFSIZ], *tval, *swap;
+ assert(depot && kbuf && vbuf);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!depot->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(vsiz < 0) vsiz = strlen(vbuf);
+ newoff = -1;
+ DP_SECONDHASH(hash, kbuf, ksiz);
+ switch(dprecsearch(depot, kbuf, ksiz, hash, &bi, &off, &entoff, head, ebuf, &ee, TRUE)){
+ case -1:
+ depot->fatal = TRUE;
+ return FALSE;
+ case 0:
+ fdel = head[DP_RHIFLAGS] & DP_RECFDEL;
+ if(dmode == DP_DKEEP && !fdel){
+ dpecodeset(DP_EKEEP, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(fdel){
+ head[DP_RHIPSIZ] += head[DP_RHIVSIZ];
+ head[DP_RHIVSIZ] = 0;
+ }
+ rsiz = dprecsize(head);
+ nsiz = DP_RHNUM * sizeof(int) + ksiz + vsiz;
+ if(dmode == DP_DCAT) nsiz += head[DP_RHIVSIZ];
+ if(off + rsiz >= depot->fsiz){
+ if(rsiz < nsiz){
+ head[DP_RHIPSIZ] += nsiz - rsiz;
+ rsiz = nsiz;
+ depot->fsiz = off + rsiz;
+ }
+ } else {
+ while(nsiz > rsiz && off + rsiz < depot->fsiz){
+ if(!dprechead(depot, off + rsiz, next, NULL, NULL)) return FALSE;
+ if(!(next[DP_RHIFLAGS] & DP_RECFREUSE)) break;
+ head[DP_RHIPSIZ] += dprecsize(next);
+ rsiz += dprecsize(next);
+ }
+ for(i = 0; i < depot->fbpsiz; i += 2){
+ if(depot->fbpool[i] >= off && depot->fbpool[i] < off + rsiz){
+ depot->fbpool[i] = -1;
+ depot->fbpool[i+1] = -1;
+ }
+ }
+ }
+ if(nsiz <= rsiz){
+ if(!dprecover(depot, off, head, vbuf, vsiz, dmode == DP_DCAT)){
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ } else {
+ tval = NULL;
+ if(dmode == DP_DCAT){
+ if(ee && DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ] + head[DP_RHIVSIZ] <= DP_ENTBUFSIZ){
+ if(!(tval = malloc(head[DP_RHIVSIZ] + vsiz + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ memcpy(tval, ebuf + (DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ]), head[DP_RHIVSIZ]);
+ } else {
+ if(!(tval = dprecval(depot, off, head, 0, -1))){
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ if(!(swap = realloc(tval, head[DP_RHIVSIZ] + vsiz + 1))){
+ free(tval);
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ tval = swap;
+ }
+ memcpy(tval + head[DP_RHIVSIZ], vbuf, vsiz);
+ vsiz += head[DP_RHIVSIZ];
+ vbuf = tval;
+ }
+ mi = -1;
+ min = -1;
+ for(i = 0; i < depot->fbpsiz; i += 2){
+ if(depot->fbpool[i+1] < nsiz) continue;
+ if(mi == -1 || depot->fbpool[i+1] < min){
+ mi = i;
+ min = depot->fbpool[i+1];
+ }
+ }
+ if(mi >= 0){
+ mroff = depot->fbpool[mi];
+ mrsiz = depot->fbpool[mi+1];
+ depot->fbpool[mi] = -1;
+ depot->fbpool[mi+1] = -1;
+ } else {
+ mroff = -1;
+ mrsiz = -1;
+ }
+ if(!dprecdelete(depot, off, head, TRUE)){
+ free(tval);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ if(mroff > 0 && nsiz <= mrsiz){
+ if(!dprecrewrite(depot, mroff, mrsiz, kbuf, ksiz, vbuf, vsiz,
+ hash, head[DP_RHILEFT], head[DP_RHIRIGHT])){
+ free(tval);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ newoff = mroff;
+ } else {
+ if((newoff = dprecappend(depot, kbuf, ksiz, vbuf, vsiz,
+ hash, head[DP_RHILEFT], head[DP_RHIRIGHT])) == -1){
+ free(tval);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ }
+ free(tval);
+ }
+ if(fdel) depot->rnum++;
+ break;
+ default:
+ if((newoff = dprecappend(depot, kbuf, ksiz, vbuf, vsiz, hash, 0, 0)) == -1){
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ depot->rnum++;
+ break;
+ }
+ if(newoff > 0){
+ if(entoff > 0){
+ if(!dpseekwritenum(depot->fd, entoff, newoff)){
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ } else {
+ depot->buckets[bi] = newoff;
+ }
+ }
+ return TRUE;
+}
+
+
+/* Delete a record. */
+int dpout(DEPOT *depot, const char *kbuf, int ksiz){
+ int head[DP_RHNUM], hash, bi, off, entoff, ee;
+ char ebuf[DP_ENTBUFSIZ];
+ assert(depot && kbuf);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!depot->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ DP_SECONDHASH(hash, kbuf, ksiz);
+ switch(dprecsearch(depot, kbuf, ksiz, hash, &bi, &off, &entoff, head, ebuf, &ee, FALSE)){
+ case -1:
+ depot->fatal = TRUE;
+ return FALSE;
+ case 0:
+ break;
+ default:
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!dprecdelete(depot, off, head, FALSE)){
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ depot->rnum--;
+ return TRUE;
+}
+
+
+/* Retrieve a record. */
+char *dpget(DEPOT *depot, const char *kbuf, int ksiz, int start, int max, int *sp){
+ int head[DP_RHNUM], hash, bi, off, entoff, ee, vsiz;
+ char ebuf[DP_ENTBUFSIZ], *vbuf;
+ assert(depot && kbuf && start >= 0);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ DP_SECONDHASH(hash, kbuf, ksiz);
+ switch(dprecsearch(depot, kbuf, ksiz, hash, &bi, &off, &entoff, head, ebuf, &ee, FALSE)){
+ case -1:
+ depot->fatal = TRUE;
+ return NULL;
+ case 0:
+ break;
+ default:
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(start > head[DP_RHIVSIZ]){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(ee && DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ] + head[DP_RHIVSIZ] <= DP_ENTBUFSIZ){
+ head[DP_RHIVSIZ] -= start;
+ if(max < 0){
+ vsiz = head[DP_RHIVSIZ];
+ } else {
+ vsiz = max < head[DP_RHIVSIZ] ? max : head[DP_RHIVSIZ];
+ }
+ if(!(vbuf = malloc(vsiz + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ depot->fatal = TRUE;
+ return NULL;
+ }
+ memcpy(vbuf, ebuf + (DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ] + start), vsiz);
+ vbuf[vsiz] = '\0';
+ } else {
+ if(!(vbuf = dprecval(depot, off, head, start, max))){
+ depot->fatal = TRUE;
+ return NULL;
+ }
+ }
+ if(sp){
+ if(max < 0){
+ *sp = head[DP_RHIVSIZ];
+ } else {
+ *sp = max < head[DP_RHIVSIZ] ? max : head[DP_RHIVSIZ];
+ }
+ }
+ return vbuf;
+}
+
+
+/* Retrieve a record and write the value into a buffer. */
+int dpgetwb(DEPOT *depot, const char *kbuf, int ksiz, int start, int max, char *vbuf){
+ int head[DP_RHNUM], hash, bi, off, entoff, ee, vsiz;
+ char ebuf[DP_ENTBUFSIZ];
+ assert(depot && kbuf && start >= 0 && max >= 0 && vbuf);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return -1;
+ }
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ DP_SECONDHASH(hash, kbuf, ksiz);
+ switch(dprecsearch(depot, kbuf, ksiz, hash, &bi, &off, &entoff, head, ebuf, &ee, FALSE)){
+ case -1:
+ depot->fatal = TRUE;
+ return -1;
+ case 0:
+ break;
+ default:
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return -1;
+ }
+ if(start > head[DP_RHIVSIZ]){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return -1;
+ }
+ if(ee && DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ] + head[DP_RHIVSIZ] <= DP_ENTBUFSIZ){
+ head[DP_RHIVSIZ] -= start;
+ vsiz = max < head[DP_RHIVSIZ] ? max : head[DP_RHIVSIZ];
+ memcpy(vbuf, ebuf + (DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ] + start), vsiz);
+ } else {
+ if((vsiz = dprecvalwb(depot, off, head, start, max, vbuf)) == -1){
+ depot->fatal = TRUE;
+ return -1;
+ }
+ }
+ return vsiz;
+}
+
+
+/* Get the size of the value of a record. */
+int dpvsiz(DEPOT *depot, const char *kbuf, int ksiz){
+ int head[DP_RHNUM], hash, bi, off, entoff, ee;
+ char ebuf[DP_ENTBUFSIZ];
+ assert(depot && kbuf);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return -1;
+ }
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ DP_SECONDHASH(hash, kbuf, ksiz);
+ switch(dprecsearch(depot, kbuf, ksiz, hash, &bi, &off, &entoff, head, ebuf, &ee, FALSE)){
+ case -1:
+ depot->fatal = TRUE;
+ return -1;
+ case 0:
+ break;
+ default:
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return -1;
+ }
+ return head[DP_RHIVSIZ];
+}
+
+
+/* Initialize the iterator of a database handle. */
+int dpiterinit(DEPOT *depot){
+ assert(depot);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ depot->ioff = 0;
+ return TRUE;
+}
+
+
+/* Get the next key of the iterator. */
+char *dpiternext(DEPOT *depot, int *sp){
+ int off, head[DP_RHNUM], ee;
+ char ebuf[DP_ENTBUFSIZ], *kbuf;
+ assert(depot);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return NULL;
+ }
+ off = DP_HEADSIZ + depot->bnum * sizeof(int);
+ off = off > depot->ioff ? off : depot->ioff;
+ while(off < depot->fsiz){
+ if(!dprechead(depot, off, head, ebuf, &ee)){
+ depot->fatal = TRUE;
+ return NULL;
+ }
+ if(head[DP_RHIFLAGS] & DP_RECFDEL){
+ off += dprecsize(head);
+ } else {
+ if(ee && DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ] <= DP_ENTBUFSIZ){
+ if(!(kbuf = malloc(head[DP_RHIKSIZ] + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ depot->fatal = TRUE;
+ return NULL;
+ }
+ memcpy(kbuf, ebuf + (DP_RHNUM * sizeof(int)), head[DP_RHIKSIZ]);
+ kbuf[head[DP_RHIKSIZ]] = '\0';
+ } else {
+ if(!(kbuf = dpreckey(depot, off, head))){
+ depot->fatal = TRUE;
+ return NULL;
+ }
+ }
+ depot->ioff = off + dprecsize(head);
+ if(sp) *sp = head[DP_RHIKSIZ];
+ return kbuf;
+ }
+ }
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return NULL;
+}
+
+
+/* Set alignment of a database handle. */
+int dpsetalign(DEPOT *depot, int align){
+ assert(depot);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!depot->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ depot->align = align;
+ return TRUE;
+}
+
+
+/* Set the size of the free block pool of a database handle. */
+int dpsetfbpsiz(DEPOT *depot, int size){
+ int *fbpool;
+ int i;
+ assert(depot && size >= 0);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!depot->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ size *= 2;
+ if(!(fbpool = realloc(depot->fbpool, size * sizeof(int) + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ for(i = 0; i < size; i += 2){
+ fbpool[i] = -1;
+ fbpool[i+1] = -1;
+ }
+ depot->fbpool = fbpool;
+ depot->fbpsiz = size;
+ return TRUE;
+}
+
+
+
+/* Synchronize contents of updating a database with the file and the device. */
+int dpsync(DEPOT *depot){
+ assert(depot);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!depot->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ *((int *)(depot->map + DP_FSIZOFF)) = depot->fsiz;
+ *((int *)(depot->map + DP_RNUMOFF)) = depot->rnum;
+ if(msync(depot->map, depot->msiz, MS_SYNC) == -1){
+ dpecodeset(DP_EMAP, __FILE__, __LINE__);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ if(fsync(depot->fd) == -1){
+ dpecodeset(DP_ESYNC, __FILE__, __LINE__);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Optimize a database. */
+int dpoptimize(DEPOT *depot, int bnum){
+ DEPOT *tdepot;
+ char *name;
+ int i, err, off, head[DP_RHNUM], ee, ksizs[DP_OPTRUNIT], vsizs[DP_OPTRUNIT], unum;
+ char ebuf[DP_ENTBUFSIZ], *kbufs[DP_OPTRUNIT], *vbufs[DP_OPTRUNIT];
+ assert(depot);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!depot->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(name = malloc(strlen(depot->name) + strlen(DP_TMPFSUF) + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ depot->fatal = FALSE;
+ return FALSE;
+ }
+ sprintf(name, "%s%s", depot->name, DP_TMPFSUF);
+ if(bnum < 0){
+ bnum = (int)(depot->rnum * (1.0 / DP_OPTBLOAD)) + 1;
+ if(bnum < DP_DEFBNUM / 2) bnum = DP_DEFBNUM / 2;
+ }
+ if(!(tdepot = dpopen(name, DP_OWRITER | DP_OCREAT | DP_OTRUNC, bnum))){
+ free(name);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ free(name);
+ if(!dpsetflags(tdepot, dpgetflags(depot))){
+ dpclose(tdepot);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ tdepot->align = depot->align;
+ err = FALSE;
+ off = DP_HEADSIZ + depot->bnum * sizeof(int);
+ unum = 0;
+ while(off < depot->fsiz){
+ if(!dprechead(depot, off, head, ebuf, &ee)){
+ err = TRUE;
+ break;
+ }
+ if(!(head[DP_RHIFLAGS] & DP_RECFDEL)){
+ if(ee && DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ] <= DP_ENTBUFSIZ){
+ if(!(kbufs[unum] = malloc(head[DP_RHIKSIZ] + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ err = TRUE;
+ break;
+ }
+ memcpy(kbufs[unum], ebuf + (DP_RHNUM * sizeof(int)), head[DP_RHIKSIZ]);
+ if(DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ] + head[DP_RHIVSIZ] <= DP_ENTBUFSIZ){
+ if(!(vbufs[unum] = malloc(head[DP_RHIVSIZ] + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ err = TRUE;
+ break;
+ }
+ memcpy(vbufs[unum], ebuf + (DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ]),
+ head[DP_RHIVSIZ]);
+ } else {
+ vbufs[unum] = dprecval(depot, off, head, 0, -1);
+ }
+ } else {
+ kbufs[unum] = dpreckey(depot, off, head);
+ vbufs[unum] = dprecval(depot, off, head, 0, -1);
+ }
+ ksizs[unum] = head[DP_RHIKSIZ];
+ vsizs[unum] = head[DP_RHIVSIZ];
+ unum++;
+ if(unum >= DP_OPTRUNIT){
+ for(i = 0; i < unum; i++){
+ if(kbufs[i] && vbufs[i]){
+ if(!dpput(tdepot, kbufs[i], ksizs[i], vbufs[i], vsizs[i], DP_DKEEP)) err = TRUE;
+ } else {
+ err = TRUE;
+ }
+ free(kbufs[i]);
+ free(vbufs[i]);
+ if(err) break;
+ }
+ unum = 0;
+ }
+ }
+ off += dprecsize(head);
+ if(err) break;
+ }
+ for(i = 0; i < unum; i++){
+ if(kbufs[i] && vbufs[i]){
+ if(!dpput(tdepot, kbufs[i], ksizs[i], vbufs[i], vsizs[i], DP_DKEEP)) err = TRUE;
+ } else {
+ err = TRUE;
+ }
+ free(kbufs[i]);
+ free(vbufs[i]);
+ if(err) break;
+ }
+ if(!dpsync(tdepot)) err = TRUE;
+ if(err){
+ unlink(tdepot->name);
+ dpclose(tdepot);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ if(munmap(depot->map, depot->msiz) == -1){
+ dpclose(tdepot);
+ dpecodeset(DP_EMAP, __FILE__, __LINE__);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ depot->map = MAP_FAILED;
+ if(ftruncate(depot->fd, 0) == -1){
+ dpclose(tdepot);
+ unlink(tdepot->name);
+ dpecodeset(DP_ETRUNC, __FILE__, __LINE__);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ if(dpfcopy(depot->fd, 0, tdepot->fd, 0) == -1){
+ dpclose(tdepot);
+ unlink(tdepot->name);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ depot->fsiz = tdepot->fsiz;
+ depot->bnum = tdepot->bnum;
+ depot->ioff = 0;
+ for(i = 0; i < depot->fbpsiz; i += 2){
+ depot->fbpool[i] = -1;
+ depot->fbpool[i+1] = -1;
+ }
+ depot->msiz = tdepot->msiz;
+ depot->map = mmap(0, depot->msiz, PROT_READ | PROT_WRITE, MAP_SHARED, depot->fd, 0);
+ if(depot->map == MAP_FAILED){
+ dpecodeset(DP_EMAP, __FILE__, __LINE__);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ depot->buckets = (int *)(depot->map + DP_HEADSIZ);
+ if(!(name = dpname(tdepot))){
+ dpclose(tdepot);
+ unlink(tdepot->name);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ if(!dpclose(tdepot)){
+ free(name);
+ unlink(tdepot->name);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ if(unlink(name) == -1){
+ free(name);
+ dpecodeset(DP_EUNLINK, __FILE__, __LINE__);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ free(name);
+ return TRUE;
+}
+
+
+/* Get the name of a database. */
+char *dpname(DEPOT *depot){
+ char *name;
+ assert(depot);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(!(name = dpstrdup(depot->name))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ depot->fatal = TRUE;
+ return NULL;
+ }
+ return name;
+}
+
+
+/* Get the size of a database file. */
+int dpfsiz(DEPOT *depot){
+ assert(depot);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return -1;
+ }
+ return depot->fsiz;
+}
+
+
+/* Get the number of the elements of the bucket array. */
+int dpbnum(DEPOT *depot){
+ assert(depot);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return -1;
+ }
+ return depot->bnum;
+}
+
+
+/* Get the number of the used elements of the bucket array. */
+int dpbusenum(DEPOT *depot){
+ int i, hits;
+ assert(depot);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return -1;
+ }
+ hits = 0;
+ for(i = 0; i < depot->bnum; i++){
+ if(depot->buckets[i]) hits++;
+ }
+ return hits;
+}
+
+
+/* Get the number of the records stored in a database. */
+int dprnum(DEPOT *depot){
+ assert(depot);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return -1;
+ }
+ return depot->rnum;
+}
+
+
+/* Check whether a database handle is a writer or not. */
+int dpwritable(DEPOT *depot){
+ assert(depot);
+ return depot->wmode;
+}
+
+
+/* Check whether a database has a fatal error or not. */
+int dpfatalerror(DEPOT *depot){
+ assert(depot);
+ return depot->fatal;
+}
+
+
+/* Get the inode number of a database file. */
+int dpinode(DEPOT *depot){
+ assert(depot);
+ return depot->inode;
+}
+
+
+/* Get the last modified time of a database. */
+time_t dpmtime(DEPOT *depot){
+ assert(depot);
+ return depot->mtime;
+}
+
+
+/* Get the file descriptor of a database file. */
+int dpfdesc(DEPOT *depot){
+ assert(depot);
+ return depot->fd;
+}
+
+
+/* Remove a database file. */
+int dpremove(const char *name){
+ struct stat sbuf;
+ DEPOT *depot;
+ assert(name);
+ if(lstat(name, &sbuf) == -1){
+ dpecodeset(DP_ESTAT, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if((depot = dpopen(name, DP_OWRITER | DP_OTRUNC, -1)) != NULL) dpclose(depot);
+ if(unlink(name) == -1){
+ dpecodeset(DP_EUNLINK, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Repair a broken database file. */
+int dprepair(const char *name){
+ DEPOT *tdepot;
+ char dbhead[DP_HEADSIZ], *tname, *kbuf, *vbuf;
+ int fd, fsiz, flags, bnum, tbnum, err, head[DP_RHNUM], off, rsiz, ksiz, vsiz;
+ struct stat sbuf;
+ assert(name);
+ if(lstat(name, &sbuf) == -1){
+ dpecodeset(DP_ESTAT, __FILE__, __LINE__);
+ return FALSE;
+ }
+ fsiz = sbuf.st_size;
+ if((fd = open(name, O_RDWR, DP_FILEMODE)) == -1){
+ dpecodeset(DP_EOPEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!dpseekread(fd, 0, dbhead, DP_HEADSIZ)){
+ close(fd);
+ return FALSE;
+ }
+ flags = *(int *)(dbhead + DP_FLAGSOFF);
+ bnum = *(int *)(dbhead + DP_BNUMOFF);
+ tbnum = *(int *)(dbhead + DP_RNUMOFF) * 2;
+ if(tbnum < DP_DEFBNUM) tbnum = DP_DEFBNUM;
+ if(!(tname = malloc(strlen(name) + strlen(DP_TMPFSUF) + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ sprintf(tname, "%s%s", name, DP_TMPFSUF);
+ if(!(tdepot = dpopen(tname, DP_OWRITER | DP_OCREAT | DP_OTRUNC, tbnum))){
+ free(tname);
+ close(fd);
+ return FALSE;
+ }
+ err = FALSE;
+ off = DP_HEADSIZ + bnum * sizeof(int);
+ while(off < fsiz){
+ if(!dpseekread(fd, off, head, DP_RHNUM * sizeof(int))) break;
+ if(head[DP_RHIFLAGS] & DP_RECFDEL){
+ if((rsiz = dprecsize(head)) < 0) break;
+ off += rsiz;
+ continue;
+ }
+ ksiz = head[DP_RHIKSIZ];
+ vsiz = head[DP_RHIVSIZ];
+ if(ksiz >= 0 && vsiz >= 0){
+ kbuf = malloc(ksiz + 1);
+ vbuf = malloc(vsiz + 1);
+ if(kbuf && vbuf){
+ if(dpseekread(fd, off + DP_RHNUM * sizeof(int), kbuf, ksiz) &&
+ dpseekread(fd, off + DP_RHNUM * sizeof(int) + ksiz, vbuf, vsiz)){
+ if(!dpput(tdepot, kbuf, ksiz, vbuf, vsiz, DP_DKEEP)) err = TRUE;
+ } else {
+ err = TRUE;
+ }
+ } else {
+ if(!err) dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ free(vbuf);
+ free(kbuf);
+ } else {
+ if(!err) dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ if((rsiz = dprecsize(head)) < 0) break;
+ off += rsiz;
+ }
+ if(!dpsetflags(tdepot, flags)) err = TRUE;
+ if(!dpsync(tdepot)) err = TRUE;
+ if(ftruncate(fd, 0) == -1){
+ if(!err) dpecodeset(DP_ETRUNC, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ if(dpfcopy(fd, 0, tdepot->fd, 0) == -1) err = TRUE;
+ if(!dpclose(tdepot)) err = TRUE;
+ if(close(fd) == -1){
+ if(!err) dpecodeset(DP_ECLOSE, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ if(unlink(tname) == -1){
+ if(!err) dpecodeset(DP_EUNLINK, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ free(tname);
+ return err ? FALSE : TRUE;
+}
+
+
+/* Dump all records as endian independent data. */
+int dpexportdb(DEPOT *depot, const char *name){
+ char *kbuf, *vbuf, *pbuf;
+ int fd, err, ksiz, vsiz, psiz;
+ assert(depot && name);
+ if(!(dpiterinit(depot))) return FALSE;
+ if((fd = open(name, O_RDWR | O_CREAT | O_TRUNC, DP_FILEMODE)) == -1){
+ dpecodeset(DP_EOPEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ while(!err && (kbuf = dpiternext(depot, &ksiz)) != NULL){
+ if((vbuf = dpget(depot, kbuf, ksiz, 0, -1, &vsiz)) != NULL){
+ if((pbuf = malloc(ksiz + vsiz + DP_NUMBUFSIZ * 2)) != NULL){
+ psiz = 0;
+ psiz += sprintf(pbuf + psiz, "%X\n%X\n", ksiz, vsiz);
+ memcpy(pbuf + psiz, kbuf, ksiz);
+ psiz += ksiz;
+ pbuf[psiz++] = '\n';
+ memcpy(pbuf + psiz, vbuf, vsiz);
+ psiz += vsiz;
+ pbuf[psiz++] = '\n';
+ if(!dpwrite(fd, pbuf, psiz)){
+ dpecodeset(DP_EWRITE, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ free(pbuf);
+ } else {
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ free(vbuf);
+ } else {
+ err = TRUE;
+ }
+ free(kbuf);
+ }
+ if(close(fd) == -1){
+ if(!err) dpecodeset(DP_ECLOSE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return !err && !dpfatalerror(depot);
+}
+
+
+/* Load all records from endian independent data. */
+int dpimportdb(DEPOT *depot, const char *name){
+ char mbuf[DP_IOBUFSIZ], *rbuf;
+ int i, j, fd, err, fsiz, off, msiz, ksiz, vsiz, hlen;
+ struct stat sbuf;
+ assert(depot && name);
+ if(!depot->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(dprnum(depot) > 0){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if((fd = open(name, O_RDONLY, DP_FILEMODE)) == -1){
+ dpecodeset(DP_EOPEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(fstat(fd, &sbuf) == -1 || !S_ISREG(sbuf.st_mode)){
+ dpecodeset(DP_ESTAT, __FILE__, __LINE__);
+ close(fd);
+ return FALSE;
+ }
+ err = FALSE;
+ fsiz = sbuf.st_size;
+ off = 0;
+ while(!err && off < fsiz){
+ msiz = fsiz - off;
+ if(msiz > DP_IOBUFSIZ) msiz = DP_IOBUFSIZ;
+ if(!dpseekread(fd, off, mbuf, msiz)){
+ err = TRUE;
+ break;
+ }
+ hlen = 0;
+ ksiz = -1;
+ vsiz = -1;
+ for(i = 0; i < msiz; i++){
+ if(mbuf[i] == '\n'){
+ mbuf[i] = '\0';
+ ksiz = strtol(mbuf, NULL, 16);
+ for(j = i + 1; j < msiz; j++){
+ if(mbuf[j] == '\n'){
+ mbuf[j] = '\0';
+ vsiz = strtol(mbuf + i + 1, NULL, 16);
+ hlen = j + 1;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ if(ksiz < 0 || vsiz < 0 || hlen < 4){
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ err = TRUE;
+ break;
+ }
+ if(hlen + ksiz + vsiz + 2 < DP_IOBUFSIZ){
+ if(!dpput(depot, mbuf + hlen, ksiz, mbuf + hlen + ksiz + 1, vsiz, DP_DKEEP)) err = TRUE;
+ } else {
+ if((rbuf = malloc(ksiz + vsiz + 2)) != NULL){
+ if(dpseekread(fd, off + hlen, rbuf, ksiz + vsiz + 2)){
+ if(!dpput(depot, rbuf, ksiz, rbuf + ksiz + 1, vsiz, DP_DKEEP)) err = TRUE;
+ } else {
+ err = TRUE;
+ }
+ free(rbuf);
+ } else {
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ }
+ off += hlen + ksiz + vsiz + 2;
+ }
+ if(close(fd) == -1){
+ if(!err) dpecodeset(DP_ECLOSE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return !err && !dpfatalerror(depot);
+}
+
+
+/* Retrieve a record directly from a database file. */
+char *dpsnaffle(const char *name, const char* kbuf, int ksiz, int *sp){
+ char hbuf[DP_HEADSIZ], *map, *vbuf, *tkbuf;
+ int fd, fsiz, bnum, msiz, *buckets, hash, thash, head[DP_RHNUM], err, off, vsiz, tksiz, kcmp;
+ struct stat sbuf;
+ assert(name && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if((fd = open(name, O_RDONLY, DP_FILEMODE)) == -1){
+ dpecodeset(DP_EOPEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(fstat(fd, &sbuf) == -1 || !S_ISREG(sbuf.st_mode)){
+ close(fd);
+ dpecodeset(DP_ESTAT, __FILE__, __LINE__);
+ return NULL;
+ }
+ fsiz = sbuf.st_size;
+ if(!dpseekread(fd, 0, hbuf, DP_HEADSIZ)){
+ close(fd);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(dpbigendian() ? memcmp(hbuf, DP_MAGICNUMB, strlen(DP_MAGICNUMB)) != 0 :
+ memcmp(hbuf, DP_MAGICNUML, strlen(DP_MAGICNUML)) != 0){
+ close(fd);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ bnum = *((int *)(hbuf + DP_BNUMOFF));
+ if(bnum < 1 || fsiz < DP_HEADSIZ + bnum * sizeof(int)){
+ close(fd);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ msiz = DP_HEADSIZ + bnum * sizeof(int);
+ map = mmap(0, msiz, PROT_READ, MAP_SHARED, fd, 0);
+ if(map == MAP_FAILED){
+ close(fd);
+ dpecodeset(DP_EMAP, __FILE__, __LINE__);
+ return NULL;
+ }
+ buckets = (int *)(map + DP_HEADSIZ);
+ err = FALSE;
+ vbuf = NULL;
+ vsiz = 0;
+ DP_SECONDHASH(hash, kbuf, ksiz);
+ DP_FIRSTHASH(thash, kbuf, ksiz);
+ off = buckets[thash%bnum];
+ while(off != 0){
+ if(!dpseekread(fd, off, head, DP_RHNUM * sizeof(int))){
+ err = TRUE;
+ break;
+ }
+ if(head[DP_RHIKSIZ] < 0 || head[DP_RHIVSIZ] < 0 || head[DP_RHIPSIZ] < 0 ||
+ head[DP_RHILEFT] < 0 || head[DP_RHIRIGHT] < 0){
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ err = TRUE;
+ break;
+ }
+ thash = head[DP_RHIHASH];
+ if(hash > thash){
+ off = head[DP_RHILEFT];
+ } else if(hash < thash){
+ off = head[DP_RHIRIGHT];
+ } else {
+ tksiz = head[DP_RHIKSIZ];
+ if(!(tkbuf = malloc(tksiz + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ err = TRUE;
+ break;
+ }
+ if(!dpseekread(fd, off + DP_RHNUM * sizeof(int), tkbuf, tksiz)){
+ free(tkbuf);
+ err = TRUE;
+ break;
+ }
+ tkbuf[tksiz] = '\0';
+ kcmp = dpkeycmp(kbuf, ksiz, tkbuf, tksiz);
+ free(tkbuf);
+ if(kcmp > 0){
+ off = head[DP_RHILEFT];
+ } else if(kcmp < 0){
+ off = head[DP_RHIRIGHT];
+ } else if(head[DP_RHIFLAGS] & DP_RECFDEL){
+ break;
+ } else {
+ vsiz = head[DP_RHIVSIZ];
+ if(!(vbuf = malloc(vsiz + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ err = TRUE;
+ break;
+ }
+ if(!dpseekread(fd, off + DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ], vbuf, vsiz)){
+ free(vbuf);
+ vbuf = NULL;
+ err = TRUE;
+ break;
+ }
+ vbuf[vsiz] = '\0';
+ break;
+ }
+ }
+ }
+ if(vbuf){
+ if(sp) *sp = vsiz;
+ } else if(!err){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ }
+ munmap(map, msiz);
+ close(fd);
+ return vbuf;
+}
+
+
+/* Hash function used inside Depot. */
+int dpinnerhash(const char *kbuf, int ksiz){
+ int res;
+ assert(kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ DP_FIRSTHASH(res, kbuf, ksiz);
+ return res;
+}
+
+
+/* Hash function which is independent from the hash functions used inside Depot. */
+int dpouterhash(const char *kbuf, int ksiz){
+ int res;
+ assert(kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ DP_THIRDHASH(res, kbuf, ksiz);
+ return res;
+}
+
+
+/* Get a natural prime number not less than a number. */
+int dpprimenum(int num){
+ assert(num > 0);
+ return dpgetprime(num);
+}
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Name of the operating system. */
+const char *dpsysname = _QDBM_SYSNAME;
+
+
+/* File descriptor for debugging output. */
+int dpdbgfd = -1;
+
+
+/* Whether this build is reentrant. */
+const int dpisreentrant = _qdbm_ptsafe;
+
+
+/* Set the last happened error code. */
+void dpecodeset(int ecode, const char *file, int line){
+ char iobuf[DP_IOBUFSIZ];
+ assert(ecode >= DP_ENOERR && file && line >= 0);
+ dpecode = ecode;
+ if(dpdbgfd >= 0){
+ fflush(stdout);
+ fflush(stderr);
+ sprintf(iobuf, "* dpecodeset: %s:%d: [%d] %s\n", file, line, ecode, dperrmsg(ecode));
+ dpwrite(dpdbgfd, iobuf, strlen(iobuf));
+ }
+}
+
+
+/* Get the pointer of the variable of the last happened error code. */
+int *dpecodeptr(void){
+ static int defdpecode = DP_ENOERR;
+ void *ptr;
+ if(_qdbm_ptsafe){
+ if(!(ptr = _qdbm_settsd(&defdpecode, sizeof(int), &defdpecode))){
+ defdpecode = DP_EMISC;
+ return &defdpecode;
+ }
+ return (int *)ptr;
+ }
+ return &defdpecode;
+}
+
+
+/* Synchronize updating contents on memory. */
+int dpmemsync(DEPOT *depot){
+ assert(depot);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!depot->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ *((int *)(depot->map + DP_FSIZOFF)) = depot->fsiz;
+ *((int *)(depot->map + DP_RNUMOFF)) = depot->rnum;
+ if(msync(depot->map, depot->msiz, MS_SYNC) == -1){
+ dpecodeset(DP_EMAP, __FILE__, __LINE__);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Synchronize updating contents on memory, not physically. */
+int dpmemflush(DEPOT *depot){
+ assert(depot);
+ if(depot->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!depot->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ *((int *)(depot->map + DP_FSIZOFF)) = depot->fsiz;
+ *((int *)(depot->map + DP_RNUMOFF)) = depot->rnum;
+ if(mflush(depot->map, depot->msiz, MS_SYNC) == -1){
+ dpecodeset(DP_EMAP, __FILE__, __LINE__);
+ depot->fatal = TRUE;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Get flags of a database. */
+int dpgetflags(DEPOT *depot){
+ int flags;
+ assert(depot);
+ memcpy(&flags, depot->map + DP_FLAGSOFF, sizeof(int));
+ return flags;
+}
+
+
+/* Set flags of a database. */
+int dpsetflags(DEPOT *depot, int flags){
+ assert(depot);
+ if(!depot->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ memcpy(depot->map + DP_FLAGSOFF, &flags, sizeof(int));
+ return TRUE;
+}
+
+
+
+/*************************************************************************************************
+ * private objects
+ *************************************************************************************************/
+
+
+/* Check whether the byte order of the platform is big endian or not.
+ The return value is true if bigendian, else, it is false. */
+static int dpbigendian(void){
+ char buf[sizeof(int)];
+ *(int *)buf = 1;
+ return !buf[0];
+}
+
+
+/* Get a copied string.
+ `str' specifies an original string.
+ The return value is a copied string whose region is allocated by `malloc'. */
+static char *dpstrdup(const char *str){
+ int len;
+ char *buf;
+ assert(str);
+ len = strlen(str);
+ if(!(buf = malloc(len + 1))) return NULL;
+ memcpy(buf, str, len + 1);
+ return buf;
+}
+
+
+/* Lock a file descriptor.
+ `fd' specifies a file descriptor.
+ `ex' specifies whether an exclusive lock or a shared lock is performed.
+ `nb' specifies whether to request with non-blocking.
+ The return value is true if successful, else, it is false. */
+static int dplock(int fd, int ex, int nb){
+ struct flock lock;
+ assert(fd >= 0);
+ memset(&lock, 0, sizeof(struct flock));
+ lock.l_type = ex ? F_WRLCK : F_RDLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 0;
+ lock.l_pid = 0;
+ while(fcntl(fd, nb ? F_SETLK : F_SETLKW, &lock) == -1){
+ if(errno != EINTR){
+ dpecodeset(DP_ELOCK, __FILE__, __LINE__);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+
+/* Write into a file.
+ `fd' specifies a file descriptor.
+ `buf' specifies a buffer to write.
+ `size' specifies the size of the buffer.
+ The return value is the size of the written buffer, or, -1 on failure. */
+static int dpwrite(int fd, const void *buf, int size){
+ const char *lbuf;
+ int rv, wb;
+ assert(fd >= 0 && buf && size >= 0);
+ lbuf = buf;
+ rv = 0;
+ do {
+ wb = write(fd, lbuf, size);
+ switch(wb){
+ case -1: if(errno != EINTR) return -1;
+ case 0: break;
+ default:
+ lbuf += wb;
+ size -= wb;
+ rv += wb;
+ break;
+ }
+ } while(size > 0);
+ return rv;
+}
+
+
+/* Write into a file at an offset.
+ `fd' specifies a file descriptor.
+ `off' specifies an offset of the file.
+ `buf' specifies a buffer to write.
+ `size' specifies the size of the buffer.
+ The return value is true if successful, else, it is false. */
+static int dpseekwrite(int fd, int off, const void *buf, int size){
+ assert(fd >= 0 && buf && size >= 0);
+ if(size < 1) return TRUE;
+ if(off < 0){
+ if(lseek(fd, 0, SEEK_END) == -1){
+ dpecodeset(DP_ESEEK, __FILE__, __LINE__);
+ return FALSE;
+ }
+ } else {
+ if(lseek(fd, off, SEEK_SET) != off){
+ dpecodeset(DP_ESEEK, __FILE__, __LINE__);
+ return FALSE;
+ }
+ }
+ if(dpwrite(fd, buf, size) != size){
+ dpecodeset(DP_EWRITE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Write an integer into a file at an offset.
+ `fd' specifies a file descriptor.
+ `off' specifies an offset of the file.
+ `num' specifies an integer.
+ The return value is true if successful, else, it is false. */
+static int dpseekwritenum(int fd, int off, int num){
+ assert(fd >= 0);
+ return dpseekwrite(fd, off, &num, sizeof(int));
+}
+
+
+/* Read from a file and store the data into a buffer.
+ `fd' specifies a file descriptor.
+ `buffer' specifies a buffer to store into.
+ `size' specifies the size to read with.
+ The return value is the size read with, or, -1 on failure. */
+static int dpread(int fd, void *buf, int size){
+ char *lbuf;
+ int i, bs;
+ assert(fd >= 0 && buf && size >= 0);
+ lbuf = buf;
+ for(i = 0; i < size && (bs = read(fd, lbuf + i, size - i)) != 0; i += bs){
+ if(bs == -1 && errno != EINTR) return -1;
+ }
+ return i;
+}
+
+
+/* Read from a file at an offset and store the data into a buffer.
+ `fd' specifies a file descriptor.
+ `off' specifies an offset of the file.
+ `buffer' specifies a buffer to store into.
+ `size' specifies the size to read with.
+ The return value is true if successful, else, it is false. */
+static int dpseekread(int fd, int off, void *buf, int size){
+ char *lbuf;
+ assert(fd >= 0 && off >= 0 && buf && size >= 0);
+ lbuf = (char *)buf;
+ if(lseek(fd, off, SEEK_SET) != off){
+ dpecodeset(DP_ESEEK, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(dpread(fd, lbuf, size) != size){
+ dpecodeset(DP_EREAD, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Copy data between files.
+ `destfd' specifies a file descriptor of a destination file.
+ `destoff' specifies an offset of the destination file.
+ `srcfd' specifies a file descriptor of a source file.
+ `srcoff' specifies an offset of the source file.
+ The return value is the size copied with, or, -1 on failure. */
+static int dpfcopy(int destfd, int destoff, int srcfd, int srcoff){
+ char iobuf[DP_IOBUFSIZ];
+ int sum, iosiz;
+ if(lseek(srcfd, srcoff, SEEK_SET) == -1 || lseek(destfd, destoff, SEEK_SET) == -1){
+ dpecodeset(DP_ESEEK, __FILE__, __LINE__);
+ return -1;
+ }
+ sum = 0;
+ while((iosiz = dpread(srcfd, iobuf, DP_IOBUFSIZ)) > 0){
+ if(dpwrite(destfd, iobuf, iosiz) == -1){
+ dpecodeset(DP_EWRITE, __FILE__, __LINE__);
+ return -1;
+ }
+ sum += iosiz;
+ }
+ if(iosiz < 0){
+ dpecodeset(DP_EREAD, __FILE__, __LINE__);
+ return -1;
+ }
+ return sum;
+}
+
+
+/* Get a natural prime number not less than a number.
+ `num' specified a natural number.
+ The return value is a prime number not less than the specified number. */
+static int dpgetprime(int num){
+ int primes[] = {
+ 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 43, 47, 53, 59, 61, 71, 79, 83,
+ 89, 103, 109, 113, 127, 139, 157, 173, 191, 199, 223, 239, 251, 283, 317, 349,
+ 383, 409, 443, 479, 509, 571, 631, 701, 761, 829, 887, 953, 1021, 1151, 1279,
+ 1399, 1531, 1663, 1789, 1913, 2039, 2297, 2557, 2803, 3067, 3323, 3583, 3833,
+ 4093, 4603, 5119, 5623, 6143, 6653, 7159, 7673, 8191, 9209, 10223, 11261,
+ 12281, 13309, 14327, 15359, 16381, 18427, 20479, 22511, 24571, 26597, 28669,
+ 30713, 32749, 36857, 40949, 45053, 49139, 53239, 57331, 61417, 65521, 73727,
+ 81919, 90107, 98299, 106487, 114679, 122869, 131071, 147451, 163819, 180221,
+ 196597, 212987, 229373, 245759, 262139, 294911, 327673, 360439, 393209, 425977,
+ 458747, 491503, 524287, 589811, 655357, 720887, 786431, 851957, 917503, 982981,
+ 1048573, 1179641, 1310719, 1441771, 1572853, 1703903, 1835003, 1966079,
+ 2097143, 2359267, 2621431, 2883577, 3145721, 3407857, 3670013, 3932153,
+ 4194301, 4718579, 5242877, 5767129, 6291449, 6815741, 7340009, 7864301,
+ 8388593, 9437179, 10485751, 11534329, 12582893, 13631477, 14680063, 15728611,
+ 16777213, 18874367, 20971507, 23068667, 25165813, 27262931, 29360087, 31457269,
+ 33554393, 37748717, 41943023, 46137319, 50331599, 54525917, 58720253, 62914549,
+ 67108859, 75497467, 83886053, 92274671, 100663291, 109051903, 117440509,
+ 125829103, 134217689, 150994939, 167772107, 184549373, 201326557, 218103799,
+ 234881011, 251658227, 268435399, 301989881, 335544301, 369098707, 402653171,
+ 436207613, 469762043, 503316469, 536870909, 603979769, 671088637, 738197503,
+ 805306357, 872415211, 939524087, 1006632947, 1073741789, 1207959503,
+ 1342177237, 1476394991, 1610612711, 1744830457, 1879048183, 2013265907, -1
+ };
+ int i;
+ assert(num > 0);
+ for(i = 0; primes[i] > 0; i++){
+ if(num <= primes[i]) return primes[i];
+ }
+ return primes[i-1];
+}
+
+
+/* Get the padding size of a record.
+ `vsiz' specifies the size of the value of a record.
+ The return value is the padding size of a record. */
+static int dppadsize(DEPOT *depot, int ksiz, int vsiz){
+ int pad;
+ assert(depot && vsiz >= 0);
+ if(depot->align > 0){
+ return depot->align - (depot->fsiz + DP_RHNUM * sizeof(int) + ksiz + vsiz) % depot->align;
+ } else if(depot->align < 0){
+ pad = (int)(vsiz * (2.0 / (1 << -(depot->align))));
+ if(vsiz + pad >= DP_FSBLKSIZ){
+ if(vsiz <= DP_FSBLKSIZ) pad = 0;
+ if(depot->fsiz % DP_FSBLKSIZ == 0){
+ return (pad / DP_FSBLKSIZ) * DP_FSBLKSIZ + DP_FSBLKSIZ -
+ (depot->fsiz + DP_RHNUM * sizeof(int) + ksiz + vsiz) % DP_FSBLKSIZ;
+ } else {
+ return (pad / (DP_FSBLKSIZ / 2)) * (DP_FSBLKSIZ / 2) + (DP_FSBLKSIZ / 2) -
+ (depot->fsiz + DP_RHNUM * sizeof(int) + ksiz + vsiz) % (DP_FSBLKSIZ / 2);
+ }
+ } else {
+ return pad >= DP_RHNUM * sizeof(int) ? pad : DP_RHNUM * sizeof(int);
+ }
+ }
+ return 0;
+}
+
+
+/* Get the size of a record in a database file.
+ `head' specifies the header of a record.
+ The return value is the size of a record in a database file. */
+static int dprecsize(int *head){
+ assert(head);
+ return DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ] + head[DP_RHIVSIZ] + head[DP_RHIPSIZ];
+}
+
+
+/* Read the header of a record.
+ `depot' specifies a database handle.
+ `off' specifies an offset of the database file.
+ `head' specifies a buffer for the header.
+ `ebuf' specifies the pointer to the entity buffer.
+ `eep' specifies the pointer to a variable to which whether ebuf was used is assigned.
+ The return value is true if successful, else, it is false. */
+static int dprechead(DEPOT *depot, int off, int *head, char *ebuf, int *eep){
+ assert(depot && off >= 0 && head);
+ if(off > depot->fsiz){
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(ebuf){
+ *eep = FALSE;
+ if(off < depot->fsiz - DP_ENTBUFSIZ){
+ *eep = TRUE;
+ if(!dpseekread(depot->fd, off, ebuf, DP_ENTBUFSIZ)) return FALSE;
+ memcpy(head, ebuf, DP_RHNUM * sizeof(int));
+ if(head[DP_RHIKSIZ] < 0 || head[DP_RHIVSIZ] < 0 || head[DP_RHIPSIZ] < 0 ||
+ head[DP_RHILEFT] < 0 || head[DP_RHIRIGHT] < 0){
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return TRUE;
+ }
+ }
+ if(!dpseekread(depot->fd, off, head, DP_RHNUM * sizeof(int))) return FALSE;
+ if(head[DP_RHIKSIZ] < 0 || head[DP_RHIVSIZ] < 0 || head[DP_RHIPSIZ] < 0 ||
+ head[DP_RHILEFT] < 0 || head[DP_RHIRIGHT] < 0){
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Read the entitiy of the key of a record.
+ `depot' specifies a database handle.
+ `off' specifies an offset of the database file.
+ `head' specifies the header of a record.
+ The return value is a key data whose region is allocated by `malloc', or NULL on failure. */
+static char *dpreckey(DEPOT *depot, int off, int *head){
+ char *kbuf;
+ int ksiz;
+ assert(depot && off >= 0);
+ ksiz = head[DP_RHIKSIZ];
+ if(!(kbuf = malloc(ksiz + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(!dpseekread(depot->fd, off + DP_RHNUM * sizeof(int), kbuf, ksiz)){
+ free(kbuf);
+ return NULL;
+ }
+ kbuf[ksiz] = '\0';
+ return kbuf;
+}
+
+
+/* Read the entitiy of the value of a record.
+ `depot' specifies a database handle.
+ `off' specifies an offset of the database file.
+ `head' specifies the header of a record.
+ `start' specifies the offset address of the beginning of the region of the value to be read.
+ `max' specifies the max size to be read. If it is negative, the size to read is unlimited.
+ The return value is a value data whose region is allocated by `malloc', or NULL on failure. */
+static char *dprecval(DEPOT *depot, int off, int *head, int start, int max){
+ char *vbuf;
+ int vsiz;
+ assert(depot && off >= 0 && start >= 0);
+ head[DP_RHIVSIZ] -= start;
+ if(max < 0){
+ vsiz = head[DP_RHIVSIZ];
+ } else {
+ vsiz = max < head[DP_RHIVSIZ] ? max : head[DP_RHIVSIZ];
+ }
+ if(!(vbuf = malloc(vsiz + 1))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(!dpseekread(depot->fd, off + DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ] + start, vbuf, vsiz)){
+ free(vbuf);
+ return NULL;
+ }
+ vbuf[vsiz] = '\0';
+ return vbuf;
+}
+
+
+/* Read the entitiy of the value of a record and write it into a given buffer.
+ `depot' specifies a database handle.
+ `off' specifies an offset of the database file.
+ `head' specifies the header of a record.
+ `start' specifies the offset address of the beginning of the region of the value to be read.
+ `max' specifies the max size to be read. It shuld be less than the size of the writing buffer.
+ If successful, the return value is the size of the written data, else, it is -1. */
+static int dprecvalwb(DEPOT *depot, int off, int *head, int start, int max, char *vbuf){
+ int vsiz;
+ assert(depot && off >= 0 && start >= 0 && max >= 0 && vbuf);
+ head[DP_RHIVSIZ] -= start;
+ vsiz = max < head[DP_RHIVSIZ] ? max : head[DP_RHIVSIZ];
+ if(!dpseekread(depot->fd, off + DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ] + start, vbuf, vsiz))
+ return -1;
+ return vsiz;
+}
+
+
+/* Compare two keys.
+ `abuf' specifies the pointer to the region of the former.
+ `asiz' specifies the size of the region.
+ `bbuf' specifies the pointer to the region of the latter.
+ `bsiz' specifies the size of the region.
+ The return value is 0 if two equals, positive if the formar is big, else, negative. */
+static int dpkeycmp(const char *abuf, int asiz, const char *bbuf, int bsiz){
+ assert(abuf && asiz >= 0 && bbuf && bsiz >= 0);
+ if(asiz > bsiz) return 1;
+ if(asiz < bsiz) return -1;
+ return memcmp(abuf, bbuf, asiz);
+}
+
+
+/* Search for a record.
+ `depot' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region.
+ `hash' specifies the second hash value of the key.
+ `bip' specifies the pointer to the region to assign the index of the corresponding record.
+ `offp' specifies the pointer to the region to assign the last visited node in the hash chain,
+ or, -1 if the hash chain is empty.
+ `entp' specifies the offset of the last used joint, or, -1 if the hash chain is empty.
+ `head' specifies the pointer to the region to store the header of the last visited record in.
+ `ebuf' specifies the pointer to the entity buffer.
+ `eep' specifies the pointer to a variable to which whether ebuf was used is assigned.
+ `delhit' specifies whether a deleted record corresponds or not.
+ The return value is 0 if successful, 1 if there is no corresponding record, -1 on error. */
+static int dprecsearch(DEPOT *depot, const char *kbuf, int ksiz, int hash, int *bip, int *offp,
+ int *entp, int *head, char *ebuf, int *eep, int delhit){
+ int off, entoff, thash, kcmp;
+ char stkey[DP_STKBUFSIZ], *tkey;
+ assert(depot && kbuf && ksiz >= 0 && hash >= 0 && bip && offp && entp && head && ebuf && eep);
+ DP_FIRSTHASH(thash, kbuf, ksiz);
+ *bip = thash % depot->bnum;
+ off = depot->buckets[*bip];
+ *offp = -1;
+ *entp = -1;
+ entoff = -1;
+ *eep = FALSE;
+ while(off != 0){
+ if(!dprechead(depot, off, head, ebuf, eep)) return -1;
+ thash = head[DP_RHIHASH];
+ if(hash > thash){
+ entoff = off + DP_RHILEFT * sizeof(int);
+ off = head[DP_RHILEFT];
+ } else if(hash < thash){
+ entoff = off + DP_RHIRIGHT * sizeof(int);
+ off = head[DP_RHIRIGHT];
+ } else {
+ if(*eep && DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ] <= DP_ENTBUFSIZ){
+ kcmp = dpkeycmp(kbuf, ksiz, ebuf + (DP_RHNUM * sizeof(int)), head[DP_RHIKSIZ]);
+ } else if(head[DP_RHIKSIZ] > DP_STKBUFSIZ){
+ if(!(tkey = dpreckey(depot, off, head))) return -1;
+ kcmp = dpkeycmp(kbuf, ksiz, tkey, head[DP_RHIKSIZ]);
+ free(tkey);
+ } else {
+ if(!dpseekread(depot->fd, off + DP_RHNUM * sizeof(int), stkey, head[DP_RHIKSIZ]))
+ return -1;
+ kcmp = dpkeycmp(kbuf, ksiz, stkey, head[DP_RHIKSIZ]);
+ }
+ if(kcmp > 0){
+ entoff = off + DP_RHILEFT * sizeof(int);
+ off = head[DP_RHILEFT];
+ } else if(kcmp < 0){
+ entoff = off + DP_RHIRIGHT * sizeof(int);
+ off = head[DP_RHIRIGHT];
+ } else {
+ if(!delhit && (head[DP_RHIFLAGS] & DP_RECFDEL)){
+ entoff = off + DP_RHILEFT * sizeof(int);
+ off = head[DP_RHILEFT];
+ } else {
+ *offp = off;
+ *entp = entoff;
+ return 0;
+ }
+ }
+ }
+ }
+ *offp = off;
+ *entp = entoff;
+ return 1;
+}
+
+
+/* Overwrite a record.
+ `depot' specifies a database handle.
+ `off' specifies the offset of the database file.
+ `rsiz' specifies the size of the existing record.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region.
+ `vbuf' specifies the pointer to the region of a value.
+ `vsiz' specifies the size of the region.
+ `hash' specifies the second hash value of the key.
+ `left' specifies the offset of the left child.
+ `right' specifies the offset of the right child.
+ The return value is true if successful, or, false on failure. */
+static int dprecrewrite(DEPOT *depot, int off, int rsiz, const char *kbuf, int ksiz,
+ const char *vbuf, int vsiz, int hash, int left, int right){
+ char ebuf[DP_WRTBUFSIZ];
+ int i, head[DP_RHNUM], asiz, hoff, koff, voff, mi, min, size;
+ assert(depot && off >= 1 && rsiz > 0 && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
+ head[DP_RHIFLAGS] = 0;
+ head[DP_RHIHASH] = hash;
+ head[DP_RHIKSIZ] = ksiz;
+ head[DP_RHIVSIZ] = vsiz;
+ head[DP_RHIPSIZ] = rsiz - sizeof(head) - ksiz - vsiz;
+ head[DP_RHILEFT] = left;
+ head[DP_RHIRIGHT] = right;
+ asiz = sizeof(head) + ksiz + vsiz;
+ if(depot->fbpsiz > DP_FBPOOLSIZ * 4 && head[DP_RHIPSIZ] > asiz){
+ rsiz = (head[DP_RHIPSIZ] - asiz) / 2 + asiz;
+ head[DP_RHIPSIZ] -= rsiz;
+ } else {
+ rsiz = 0;
+ }
+ if(asiz <= DP_WRTBUFSIZ){
+ memcpy(ebuf, head, sizeof(head));
+ memcpy(ebuf + sizeof(head), kbuf, ksiz);
+ memcpy(ebuf + sizeof(head) + ksiz, vbuf, vsiz);
+ if(!dpseekwrite(depot->fd, off, ebuf, asiz)) return FALSE;
+ } else {
+ hoff = off;
+ koff = hoff + sizeof(head);
+ voff = koff + ksiz;
+ if(!dpseekwrite(depot->fd, hoff, head, sizeof(head)) ||
+ !dpseekwrite(depot->fd, koff, kbuf, ksiz) || !dpseekwrite(depot->fd, voff, vbuf, vsiz))
+ return FALSE;
+ }
+ if(rsiz > 0){
+ off += sizeof(head) + ksiz + vsiz + head[DP_RHIPSIZ];
+ head[DP_RHIFLAGS] = DP_RECFDEL | DP_RECFREUSE;
+ head[DP_RHIHASH] = hash;
+ head[DP_RHIKSIZ] = ksiz;
+ head[DP_RHIVSIZ] = vsiz;
+ head[DP_RHIPSIZ] = rsiz - sizeof(head) - ksiz - vsiz;
+ head[DP_RHILEFT] = 0;
+ head[DP_RHIRIGHT] = 0;
+ if(!dpseekwrite(depot->fd, off, head, sizeof(head))) return FALSE;
+ size = dprecsize(head);
+ mi = -1;
+ min = -1;
+ for(i = 0; i < depot->fbpsiz; i += 2){
+ if(depot->fbpool[i] == -1){
+ depot->fbpool[i] = off;
+ depot->fbpool[i+1] = size;
+ dpfbpoolcoal(depot);
+ mi = -1;
+ break;
+ }
+ if(mi == -1 || depot->fbpool[i+1] < min){
+ mi = i;
+ min = depot->fbpool[i+1];
+ }
+ }
+ if(mi >= 0 && size > min){
+ depot->fbpool[mi] = off;
+ depot->fbpool[mi+1] = size;
+ dpfbpoolcoal(depot);
+ }
+ }
+ return TRUE;
+}
+
+
+/* Write a record at the end of a database file.
+ `depot' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region.
+ `vbuf' specifies the pointer to the region of a value.
+ `vsiz' specifies the size of the region.
+ `hash' specifies the second hash value of the key.
+ `left' specifies the offset of the left child.
+ `right' specifies the offset of the right child.
+ The return value is the offset of the record, or, -1 on failure. */
+static int dprecappend(DEPOT *depot, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+ int hash, int left, int right){
+ char ebuf[DP_WRTBUFSIZ], *hbuf;
+ int head[DP_RHNUM], asiz, psiz, off;
+ assert(depot && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
+ psiz = dppadsize(depot, ksiz, vsiz);
+ head[DP_RHIFLAGS] = 0;
+ head[DP_RHIHASH] = hash;
+ head[DP_RHIKSIZ] = ksiz;
+ head[DP_RHIVSIZ] = vsiz;
+ head[DP_RHIPSIZ] = psiz;
+ head[DP_RHILEFT] = left;
+ head[DP_RHIRIGHT] = right;
+ asiz = sizeof(head) + ksiz + vsiz + psiz;
+ off = depot->fsiz;
+ if(asiz <= DP_WRTBUFSIZ){
+ memcpy(ebuf, head, sizeof(head));
+ memcpy(ebuf + sizeof(head), kbuf, ksiz);
+ memcpy(ebuf + sizeof(head) + ksiz, vbuf, vsiz);
+ memset(ebuf + sizeof(head) + ksiz + vsiz, 0, psiz);
+ if(!dpseekwrite(depot->fd, off, ebuf, asiz)) return -1;
+ } else {
+ if(!(hbuf = malloc(asiz))){
+ dpecodeset(DP_EALLOC, __FILE__, __LINE__);
+ return -1;
+ }
+ memcpy(hbuf, head, sizeof(head));
+ memcpy(hbuf + sizeof(head), kbuf, ksiz);
+ memcpy(hbuf + sizeof(head) + ksiz, vbuf, vsiz);
+ memset(hbuf + sizeof(head) + ksiz + vsiz, 0, psiz);
+ if(!dpseekwrite(depot->fd, off, hbuf, asiz)){
+ free(hbuf);
+ return -1;
+ }
+ free(hbuf);
+ }
+ depot->fsiz += asiz;
+ return off;
+}
+
+
+/* Overwrite the value of a record.
+ `depot' specifies a database handle.
+ `off' specifies the offset of the database file.
+ `head' specifies the header of the record.
+ `vbuf' specifies the pointer to the region of a value.
+ `vsiz' specifies the size of the region.
+ `cat' specifies whether it is concatenate mode or not.
+ The return value is true if successful, or, false on failure. */
+static int dprecover(DEPOT *depot, int off, int *head, const char *vbuf, int vsiz, int cat){
+ int i, hsiz, hoff, voff;
+ assert(depot && off >= 0 && head && vbuf && vsiz >= 0);
+ for(i = 0; i < depot->fbpsiz; i += 2){
+ if(depot->fbpool[i] == off){
+ depot->fbpool[i] = -1;
+ depot->fbpool[i+1] = -1;
+ break;
+ }
+ }
+ if(cat){
+ head[DP_RHIFLAGS] = 0;
+ head[DP_RHIPSIZ] -= vsiz;
+ head[DP_RHIVSIZ] += vsiz;
+ hsiz = DP_RHNUM * sizeof(int);
+ hoff = off;
+ voff = hoff + DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ] + head[DP_RHIVSIZ] - vsiz;
+ } else {
+ head[DP_RHIFLAGS] = 0;
+ head[DP_RHIPSIZ] += head[DP_RHIVSIZ] - vsiz;
+ head[DP_RHIVSIZ] = vsiz;
+ hsiz = DP_RHNUM * sizeof(int);
+ hoff = off;
+ voff = hoff + DP_RHNUM * sizeof(int) + head[DP_RHIKSIZ];
+ }
+ if(!dpseekwrite(depot->fd, hoff, head, hsiz) ||
+ !dpseekwrite(depot->fd, voff, vbuf, vsiz)) return FALSE;
+ return TRUE;
+}
+
+
+/* Delete a record.
+ `depot' specifies a database handle.
+ `off' specifies the offset of the database file.
+ `head' specifies the header of the record.
+ `reusable' specifies whether the region is reusable or not.
+ The return value is true if successful, or, false on failure. */
+static int dprecdelete(DEPOT *depot, int off, int *head, int reusable){
+ int i, mi, min, size;
+ assert(depot && off >= 0 && head);
+ if(reusable){
+ size = dprecsize(head);
+ mi = -1;
+ min = -1;
+ for(i = 0; i < depot->fbpsiz; i += 2){
+ if(depot->fbpool[i] == -1){
+ depot->fbpool[i] = off;
+ depot->fbpool[i+1] = size;
+ dpfbpoolcoal(depot);
+ mi = -1;
+ break;
+ }
+ if(mi == -1 || depot->fbpool[i+1] < min){
+ mi = i;
+ min = depot->fbpool[i+1];
+ }
+ }
+ if(mi >= 0 && size > min){
+ depot->fbpool[mi] = off;
+ depot->fbpool[mi+1] = size;
+ dpfbpoolcoal(depot);
+ }
+ }
+ return dpseekwritenum(depot->fd, off + DP_RHIFLAGS * sizeof(int),
+ DP_RECFDEL | (reusable ? DP_RECFREUSE : 0));
+}
+
+
+/* Make contiguous records of the free block pool coalesce.
+ `depot' specifies a database handle. */
+static void dpfbpoolcoal(DEPOT *depot){
+ int i;
+ assert(depot);
+ if(depot->fbpinc++ <= depot->fbpsiz / 4) return;
+ depot->fbpinc = 0;
+ qsort(depot->fbpool, depot->fbpsiz / 2, sizeof(int) * 2, dpfbpoolcmp);
+ for(i = 2; i < depot->fbpsiz; i += 2){
+ if(depot->fbpool[i-2] > 0 &&
+ depot->fbpool[i-2] + depot->fbpool[i-1] - depot->fbpool[i] == 0){
+ depot->fbpool[i] = depot->fbpool[i-2];
+ depot->fbpool[i+1] += depot->fbpool[i-1];
+ depot->fbpool[i-2] = -1;
+ depot->fbpool[i-1] = -1;
+ }
+ }
+}
+
+
+/* Compare two records of the free block pool.
+ `a' specifies the pointer to one record.
+ `b' specifies the pointer to the other record.
+ The return value is 0 if two equals, positive if the formar is big, else, negative. */
+static int dpfbpoolcmp(const void *a, const void *b){
+ assert(a && b);
+ return *(int *)a - *(int *)b;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/depot.h b/qdbm/depot.h
new file mode 100644
index 00000000..36c0814d
--- /dev/null
+++ b/qdbm/depot.h
@@ -0,0 +1,492 @@
+/*************************************************************************************************
+ * The basic API of QDBM
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#ifndef _DEPOT_H /* duplication check */
+#define _DEPOT_H
+
+#if defined(__cplusplus) /* export for C++ */
+extern "C" {
+#endif
+
+
+#include <stdlib.h>
+#include <time.h>
+
+
+#if defined(_MSC_VER) && !defined(QDBM_INTERNAL) && !defined(QDBM_STATIC)
+#define MYEXTERN extern __declspec(dllimport)
+#else
+#define MYEXTERN extern
+#endif
+
+
+
+/*************************************************************************************************
+ * API
+ *************************************************************************************************/
+
+
+typedef struct { /* type of structure for a database handle */
+ char *name; /* name of the database file */
+ int wmode; /* whether to be writable */
+ int inode; /* inode of the database file */
+ time_t mtime; /* last modified time of the database */
+ int fd; /* file descriptor of the database file */
+ int fsiz; /* size of the database file */
+ char *map; /* pointer to the mapped memory */
+ int msiz; /* size of the mapped memory */
+ int *buckets; /* pointer to the bucket array */
+ int bnum; /* number of the bucket array */
+ int rnum; /* number of records */
+ int fatal; /* whether a fatal error occured */
+ int ioff; /* offset of the iterator */
+ int *fbpool; /* free block pool */
+ int fbpsiz; /* size of the free block pool */
+ int fbpinc; /* incrementor of update of the free block pool */
+ int align; /* basic size of alignment */
+} DEPOT;
+
+enum { /* enumeration for error codes */
+ DP_ENOERR, /* no error */
+ DP_EFATAL, /* with fatal error */
+ DP_EMODE, /* invalid mode */
+ DP_EBROKEN, /* broken database file */
+ DP_EKEEP, /* existing record */
+ DP_ENOITEM, /* no item found */
+ DP_EALLOC, /* memory allocation error */
+ DP_EMAP, /* memory mapping error */
+ DP_EOPEN, /* open error */
+ DP_ECLOSE, /* close error */
+ DP_ETRUNC, /* trunc error */
+ DP_ESYNC, /* sync error */
+ DP_ESTAT, /* stat error */
+ DP_ESEEK, /* seek error */
+ DP_EREAD, /* read error */
+ DP_EWRITE, /* write error */
+ DP_ELOCK, /* lock error */
+ DP_EUNLINK, /* unlink error */
+ DP_EMKDIR, /* mkdir error */
+ DP_ERMDIR, /* rmdir error */
+ DP_EMISC /* miscellaneous error */
+};
+
+enum { /* enumeration for open modes */
+ DP_OREADER = 1 << 0, /* open as a reader */
+ DP_OWRITER = 1 << 1, /* open as a writer */
+ DP_OCREAT = 1 << 2, /* a writer creating */
+ DP_OTRUNC = 1 << 3, /* a writer truncating */
+ DP_ONOLCK = 1 << 4, /* open without locking */
+ DP_OLCKNB = 1 << 5, /* lock without blocking */
+ DP_OSPARSE = 1 << 6 /* create as a sparse file */
+};
+
+enum { /* enumeration for write modes */
+ DP_DOVER, /* overwrite an existing value */
+ DP_DKEEP, /* keep an existing value */
+ DP_DCAT /* concatenate values */
+};
+
+
+/* String containing the version information. */
+MYEXTERN const char *dpversion;
+
+
+/* Last happened error code. */
+#define dpecode (*dpecodeptr())
+
+
+/* Get a message string corresponding to an error code.
+ `ecode' specifies an error code.
+ The return value is the message string of the error code. The region of the return value
+ is not writable. */
+const char *dperrmsg(int ecode);
+
+
+/* Get a database handle.
+ `name' specifies the name of a database file.
+ `omode' specifies the connection mode: `DP_OWRITER' as a writer, `DP_OREADER' as a reader.
+ If the mode is `DP_OWRITER', the following may be added by bitwise or: `DP_OCREAT', which
+ means it creates a new database if not exist, `DP_OTRUNC', which means it creates a new
+ database regardless if one exists. Both of `DP_OREADER' and `DP_OWRITER' can be added to by
+ bitwise or: `DP_ONOLCK', which means it opens a database file without file locking, or
+ `DP_OLCKNB', which means locking is performed without blocking. `DP_OCREAT' can be added to
+ by bitwise or: `DP_OSPARSE', which means it creates a database file as a sparse file.
+ `bnum' specifies the number of elements of the bucket array. If it is not more than 0,
+ the default value is specified. The size of a bucket array is determined on creating,
+ and can not be changed except for by optimization of the database. Suggested size of a
+ bucket array is about from 0.5 to 4 times of the number of all records to store.
+ The return value is the database handle or `NULL' if it is not successful.
+ While connecting as a writer, an exclusive lock is invoked to the database file.
+ While connecting as a reader, a shared lock is invoked to the database file. The thread
+ blocks until the lock is achieved. If `DP_ONOLCK' is used, the application is responsible
+ for exclusion control. */
+DEPOT *dpopen(const char *name, int omode, int bnum);
+
+
+/* Close a database handle.
+ `depot' specifies a database handle.
+ If successful, the return value is true, else, it is false.
+ Because the region of a closed handle is released, it becomes impossible to use the handle.
+ Updating a database is assured to be written when the handle is closed. If a writer opens
+ a database but does not close it appropriately, the database will be broken. */
+int dpclose(DEPOT *depot);
+
+
+/* Store a record.
+ `depot' specifies a database handle connected as a writer.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `vbuf' specifies the pointer to the region of a value.
+ `vsiz' specifies the size of the region of the value. If it is negative, the size is
+ assigned with `strlen(vbuf)'.
+ `dmode' specifies behavior when the key overlaps, by the following values: `DP_DOVER',
+ which means the specified value overwrites the existing one, `DP_DKEEP', which means the
+ existing value is kept, `DP_DCAT', which means the specified value is concatenated at the
+ end of the existing value.
+ If successful, the return value is true, else, it is false. */
+int dpput(DEPOT *depot, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
+
+
+/* Delete a record.
+ `depot' specifies a database handle connected as a writer.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ If successful, the return value is true, else, it is false. False is returned when no
+ record corresponds to the specified key. */
+int dpout(DEPOT *depot, const char *kbuf, int ksiz);
+
+
+/* Retrieve a record.
+ `depot' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `start' specifies the offset address of the beginning of the region of the value to be read.
+ `max' specifies the max size to be read. If it is negative, the size to read is unlimited.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the value of the
+ corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to
+ the specified key or the size of the value of the corresponding record is less than `start'.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. */
+char *dpget(DEPOT *depot, const char *kbuf, int ksiz, int start, int max, int *sp);
+
+
+/* Retrieve a record and write the value into a buffer.
+ `depot' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `start' specifies the offset address of the beginning of the region of the value to be read.
+ `max' specifies the max size to be read. It shuld be equal to or less than the size of the
+ writing buffer.
+ `vbuf' specifies the pointer to a buffer into which the value of the corresponding record is
+ written.
+ If successful, the return value is the size of the written data, else, it is -1. -1 is
+ returned when no record corresponds to the specified key or the size of the value of the
+ corresponding record is less than `start'.
+ Note that no additional zero code is appended at the end of the region of the writing buffer. */
+int dpgetwb(DEPOT *depot, const char *kbuf, int ksiz, int start, int max, char *vbuf);
+
+
+/* Get the size of the value of a record.
+ `depot' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ If successful, the return value is the size of the value of the corresponding record, else,
+ it is -1.
+ Because this function does not read the entity of a record, it is faster than `dpget'. */
+int dpvsiz(DEPOT *depot, const char *kbuf, int ksiz);
+
+
+/* Initialize the iterator of a database handle.
+ `depot' specifies a database handle.
+ If successful, the return value is true, else, it is false.
+ The iterator is used in order to access the key of every record stored in a database. */
+int dpiterinit(DEPOT *depot);
+
+
+/* Get the next key of the iterator.
+ `depot' specifies a database handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the next key, else, it is
+ `NULL'. `NULL' is returned when no record is to be get out of the iterator.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if
+ it is no longer in use. It is possible to access every record by iteration of calling
+ this function. However, it is not assured if updating the database is occurred while the
+ iteration. Besides, the order of this traversal access method is arbitrary, so it is not
+ assured that the order of storing matches the one of the traversal access. */
+char *dpiternext(DEPOT *depot, int *sp);
+
+
+/* Set alignment of a database handle.
+ `depot' specifies a database handle connected as a writer.
+ `align' specifies the size of alignment.
+ If successful, the return value is true, else, it is false.
+ If alignment is set to a database, the efficiency of overwriting values is improved.
+ The size of alignment is suggested to be average size of the values of the records to be
+ stored. If alignment is positive, padding whose size is multiple number of the alignment
+ is placed. If alignment is negative, as `vsiz' is the size of a value, the size of padding
+ is calculated with `(vsiz / pow(2, abs(align) - 1))'. Because alignment setting is not
+ saved in a database, you should specify alignment every opening a database. */
+int dpsetalign(DEPOT *depot, int align);
+
+
+/* Set the size of the free block pool of a database handle.
+ `depot' specifies a database handle connected as a writer.
+ `size' specifies the size of the free block pool of a database.
+ If successful, the return value is true, else, it is false.
+ The default size of the free block pool is 16. If the size is greater, the space efficiency
+ of overwriting values is improved with the time efficiency sacrificed. */
+int dpsetfbpsiz(DEPOT *depot, int size);
+
+
+/* Synchronize updating contents with the file and the device.
+ `depot' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false.
+ This function is useful when another process uses the connected database file. */
+int dpsync(DEPOT *depot);
+
+
+/* Optimize a database.
+ `depot' specifies a database handle connected as a writer.
+ `bnum' specifies the number of the elements of the bucket array. If it is not more than 0,
+ the default value is specified.
+ If successful, the return value is true, else, it is false.
+ In an alternating succession of deleting and storing with overwrite or concatenate,
+ dispensable regions accumulate. This function is useful to do away with them. */
+int dpoptimize(DEPOT *depot, int bnum);
+
+
+/* Get the name of a database.
+ `depot' specifies a database handle.
+ If successful, the return value is the pointer to the region of the name of the database,
+ else, it is `NULL'.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *dpname(DEPOT *depot);
+
+
+/* Get the size of a database file.
+ `depot' specifies a database handle.
+ If successful, the return value is the size of the database file, else, it is -1. */
+int dpfsiz(DEPOT *depot);
+
+
+/* Get the number of the elements of the bucket array.
+ `depot' specifies a database handle.
+ If successful, the return value is the number of the elements of the bucket array, else, it
+ is -1. */
+int dpbnum(DEPOT *depot);
+
+
+/* Get the number of the used elements of the bucket array.
+ `depot' specifies a database handle.
+ If successful, the return value is the number of the used elements of the bucket array,
+ else, it is -1.
+ This function is inefficient because it accesses all elements of the bucket array. */
+int dpbusenum(DEPOT *depot);
+
+
+/* Get the number of the records stored in a database.
+ `depot' specifies a database handle.
+ If successful, the return value is the number of the records stored in the database, else,
+ it is -1. */
+int dprnum(DEPOT *depot);
+
+
+/* Check whether a database handle is a writer or not.
+ `depot' specifies a database handle.
+ The return value is true if the handle is a writer, false if not. */
+int dpwritable(DEPOT *depot);
+
+
+/* Check whether a database has a fatal error or not.
+ `depot' specifies a database handle.
+ The return value is true if the database has a fatal error, false if not. */
+int dpfatalerror(DEPOT *depot);
+
+
+/* Get the inode number of a database file.
+ `depot' specifies a database handle.
+ The return value is the inode number of the database file. */
+int dpinode(DEPOT *depot);
+
+
+/* Get the last modified time of a database.
+ `depot' specifies a database handle.
+ The return value is the last modified time of the database. */
+time_t dpmtime(DEPOT *depot);
+
+
+/* Get the file descriptor of a database file.
+ `depot' specifies a database handle.
+ The return value is the file descriptor of the database file.
+ Handling the file descriptor of a database file directly is not suggested. */
+int dpfdesc(DEPOT *depot);
+
+
+/* Remove a database file.
+ `name' specifies the name of a database file.
+ If successful, the return value is true, else, it is false. */
+int dpremove(const char *name);
+
+
+/* Repair a broken database file.
+ `name' specifies the name of a database file.
+ If successful, the return value is true, else, it is false.
+ There is no guarantee that all records in a repaired database file correspond to the original
+ or expected state. */
+int dprepair(const char *name);
+
+
+/* Dump all records as endian independent data.
+ `depot' specifies a database handle.
+ `name' specifies the name of an output file.
+ If successful, the return value is true, else, it is false. */
+int dpexportdb(DEPOT *depot, const char *name);
+
+
+/* Load all records from endian independent data.
+ `depot' specifies a database handle connected as a writer. The database of the handle must
+ be empty.
+ `name' specifies the name of an input file.
+ If successful, the return value is true, else, it is false. */
+int dpimportdb(DEPOT *depot, const char *name);
+
+
+/* Retrieve a record directly from a database file.
+ `name' specifies the name of a database file.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the value of the
+ corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to
+ the specified key.
+ Because an additional zero code is appended at the end of the region of the return value,
+ the return value can be treated as a character string. Because the region of the return
+ value is allocated with the `malloc' call, it should be released with the `free' call if it
+ is no longer in use. Although this function can be used even while the database file is
+ locked by another process, it is not assured that recent updated is reflected. */
+char *dpsnaffle(const char *name, const char *kbuf, int ksiz, int *sp);
+
+
+/* Hash function used inside Depot.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ The return value is the hash value of 31 bits length computed from the key.
+ This function is useful when an application calculates the state of the inside bucket array. */
+int dpinnerhash(const char *kbuf, int ksiz);
+
+
+/* Hash function which is independent from the hash functions used inside Depot.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ The return value is the hash value of 31 bits length computed from the key.
+ This function is useful when an application uses its own hash algorithm outside Depot. */
+int dpouterhash(const char *kbuf, int ksiz);
+
+
+/* Get a natural prime number not less than a number.
+ `num' specified a natural number.
+ The return value is a natural prime number not less than the specified number.
+ This function is useful when an application determines the size of a bucket array of its
+ own hash algorithm. */
+int dpprimenum(int num);
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+#define _QDBM_VERSION "1.8.77"
+#define _QDBM_LIBVER 1413
+
+
+/* Name of the operating system. */
+MYEXTERN const char *dpsysname;
+
+
+/* File descriptor for debugging output. */
+MYEXTERN int dpdbgfd;
+
+
+/* Whether this build is reentrant. */
+MYEXTERN const int dpisreentrant;
+
+
+/* Set the last happened error code.
+ `ecode' specifies the error code.
+ `line' specifies the number of the line where the error happened. */
+void dpecodeset(int ecode, const char *file, int line);
+
+
+/* Get the pointer of the variable of the last happened error code.
+ The return value is the pointer of the variable. */
+int *dpecodeptr(void);
+
+
+/* Synchronize updating contents on memory.
+ `depot' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false. */
+int dpmemsync(DEPOT *depot);
+
+
+/* Synchronize updating contents on memory, not physically.
+ `depot' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false. */
+int dpmemflush(DEPOT *depot);
+
+
+/* Get flags of a database.
+ `depot' specifies a database handle.
+ The return value is the flags of a database. */
+int dpgetflags(DEPOT *depot);
+
+
+/* Set flags of a database.
+ `depot' specifies a database handle connected as a writer.
+ `flags' specifies flags to set. Least ten bits are reserved for internal use.
+ If successful, the return value is true, else, it is false. */
+int dpsetflags(DEPOT *depot, int flags);
+
+
+
+#undef MYEXTERN
+
+#if defined(__cplusplus) /* export for C++ */
+}
+#endif
+
+#endif /* duplication check */
+
+
+/* END OF FILE */
diff --git a/qdbm/dpmgr.c b/qdbm/dpmgr.c
new file mode 100644
index 00000000..f02c9e3c
--- /dev/null
+++ b/qdbm/dpmgr.c
@@ -0,0 +1,916 @@
+/*************************************************************************************************
+ * Utility for debugging Depot and its applications
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <cabin.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define ALIGNSIZ 32 /* basic size of alignment */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+char *hextoobj(const char *str, int *sp);
+char *dectoiobj(const char *str, int *sp);
+int runcreate(int argc, char **argv);
+int runput(int argc, char **argv);
+int runout(int argc, char **argv);
+int runget(int argc, char **argv);
+int runlist(int argc, char **argv);
+int runoptimize(int argc, char **argv);
+int runinform(int argc, char **argv);
+int runremove(int argc, char **argv);
+int runrepair(int argc, char **argv);
+int runexportdb(int argc, char **argv);
+int runimportdb(int argc, char **argv);
+int runsnaffle(int argc, char **argv);
+void pdperror(const char *name);
+void printobj(const char *obj, int size);
+void printobjhex(const char *obj, int size);
+int docreate(const char *name, int bnum, int sparse);
+int doput(const char *name, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+ int dmode, int align);
+int doout(const char *name, const char *kbuf, int ksiz);
+int doget(const char *name, int opts, const char *kbuf, int ksiz,
+ int start, int max, int ox, int nb);
+int dolist(const char *name, int opts, int kb, int vb, int ox);
+int dooptimize(const char *name, int bnum, int align);
+int doinform(const char *name, int opts);
+int doremove(const char *name);
+int dorepair(const char *name);
+int doexportdb(const char *name, const char *file);
+int doimportdb(const char *name, const char *file, int bnum);
+int dosnaffle(const char *name, const char *kbuf, int ksiz, int ox, int nb);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ char *env;
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ if((env = getenv("QDBMDBGFD")) != NULL) dpdbgfd = atoi(env);
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "create")){
+ rv = runcreate(argc, argv);
+ } else if(!strcmp(argv[1], "put")){
+ rv = runput(argc, argv);
+ } else if(!strcmp(argv[1], "out")){
+ rv = runout(argc, argv);
+ } else if(!strcmp(argv[1], "get")){
+ rv = runget(argc, argv);
+ } else if(!strcmp(argv[1], "list")){
+ rv = runlist(argc, argv);
+ } else if(!strcmp(argv[1], "optimize")){
+ rv = runoptimize(argc, argv);
+ } else if(!strcmp(argv[1], "inform")){
+ rv = runinform(argc, argv);
+ } else if(!strcmp(argv[1], "remove")){
+ rv = runremove(argc, argv);
+ } else if(!strcmp(argv[1], "repair")){
+ rv = runrepair(argc, argv);
+ } else if(!strcmp(argv[1], "exportdb")){
+ rv = runexportdb(argc, argv);
+ } else if(!strcmp(argv[1], "importdb")){
+ rv = runimportdb(argc, argv);
+ } else if(!strcmp(argv[1], "snaffle")){
+ rv = runsnaffle(argc, argv);
+ } else if(!strcmp(argv[1], "version") || !strcmp(argv[1], "--version")){
+ printf("Powered by QDBM version %s on %s%s\n",
+ dpversion, dpsysname, dpisreentrant ? " (reentrant)" : "");
+ printf("Copyright (c) 2000-2007 Mikio Hirabayashi\n");
+ rv = 0;
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: administration utility for Depot\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s create [-s] [-bnum num] name\n", progname);
+ fprintf(stderr, " %s put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-na] name key val\n",
+ progname);
+ fprintf(stderr, " %s out [-kx|-ki] name key\n", progname);
+ fprintf(stderr, " %s get [-nl] [-kx|-ki] [-start num] [-max num] [-ox] [-n] name key\n",
+ progname);
+ fprintf(stderr, " %s list [-nl] [-k|-v] [-ox] name\n", progname);
+ fprintf(stderr, " %s optimize [-bnum num] [-na] name\n", progname);
+ fprintf(stderr, " %s inform [-nl] name\n", progname);
+ fprintf(stderr, " %s remove name\n", progname);
+ fprintf(stderr, " %s repair name\n", progname);
+ fprintf(stderr, " %s exportdb name file\n", progname);
+ fprintf(stderr, " %s importdb [-bnum num] name file\n", progname);
+ fprintf(stderr, " %s snaffle [-kx|-ki] [-ox] [-n] name key\n", progname);
+ fprintf(stderr, " %s version\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* create a binary object from a hexadecimal string */
+char *hextoobj(const char *str, int *sp){
+ char *buf, mbuf[3];
+ int len, i, j;
+ len = strlen(str);
+ if(!(buf = malloc(len + 1))) return NULL;
+ j = 0;
+ for(i = 0; i < len; i += 2){
+ while(strchr(" \n\r\t\f\v", str[i])){
+ i++;
+ }
+ if((mbuf[0] = str[i]) == '\0') break;
+ if((mbuf[1] = str[i+1]) == '\0') break;
+ mbuf[2] = '\0';
+ buf[j++] = (char)strtol(mbuf, NULL, 16);
+ }
+ buf[j] = '\0';
+ *sp = j;
+ return buf;
+}
+
+
+/* create a integer object from a decimal string */
+char *dectoiobj(const char *str, int *sp){
+ char *buf;
+ int num;
+ num = atoi(str);
+ if(!(buf = malloc(sizeof(int)))) return NULL;
+ *(int *)buf = num;
+ *sp = sizeof(int);
+ return buf;
+}
+
+
+/* parse arguments of create command */
+int runcreate(int argc, char **argv){
+ char *name;
+ int i, sb, bnum, rv;
+ name = NULL;
+ sb = FALSE;
+ bnum = -1;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-s")){
+ sb = TRUE;
+ } else if(!strcmp(argv[i], "-bnum")){
+ if(++i >= argc) usage();
+ bnum = atoi(argv[i]);
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = docreate(name, bnum, sb);
+ return rv;
+}
+
+
+/* parse arguments of put command */
+int runput(int argc, char **argv){
+ char *name, *key, *val, *kbuf, *vbuf;
+ int i, kx, ki, vx, vi, vf, align, ksiz, vsiz, rv;
+ int dmode;
+ name = NULL;
+ kx = FALSE;
+ ki = FALSE;
+ vx = FALSE;
+ vi = FALSE;
+ vf = FALSE;
+ align = ALIGNSIZ;
+ key = NULL;
+ val = NULL;
+ dmode = DP_DOVER;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else if(!strcmp(argv[i], "-vx")){
+ vx = TRUE;
+ } else if(!strcmp(argv[i], "-vi")){
+ vi = TRUE;
+ } else if(!strcmp(argv[i], "-vf")){
+ vf = TRUE;
+ } else if(!strcmp(argv[i], "-keep")){
+ dmode = DP_DKEEP;
+ } else if(!strcmp(argv[i], "-cat")){
+ dmode = DP_DCAT;
+ } else if(!strcmp(argv[i], "-na")){
+ align = 0;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else if(!val){
+ val = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key || !val) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else if(ki){
+ kbuf = dectoiobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = -1;
+ }
+ if(vx){
+ vbuf = hextoobj(val, &vsiz);
+ } else if(vi){
+ vbuf = dectoiobj(val, &vsiz);
+ } else if(vf){
+ vbuf = cbreadfile(val, &vsiz);
+ } else {
+ vbuf = cbmemdup(val, -1);
+ vsiz = -1;
+ }
+ if(kbuf && vbuf){
+ rv = doput(name, kbuf, ksiz, vbuf, vsiz, dmode, align);
+ } else {
+ if(vf){
+ fprintf(stderr, "%s: %s: cannot read\n", progname, val);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ }
+ rv = 1;
+ }
+ free(kbuf);
+ free(vbuf);
+ return rv;
+}
+
+
+/* parse arguments of out command */
+int runout(int argc, char **argv){
+ char *name, *key, *kbuf;
+ int i, kx, ki, ksiz, rv;
+ name = NULL;
+ kx = FALSE;
+ ki = FALSE;
+ key = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else if(ki){
+ kbuf = dectoiobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = -1;
+ }
+ if(kbuf){
+ rv = doout(name, kbuf, ksiz);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ rv = 1;
+ }
+ free(kbuf);
+ return rv;
+}
+
+
+/* parse arguments of get command */
+int runget(int argc, char **argv){
+ char *name, *key, *kbuf;
+ int i, opts, kx, ki, ox, nb, start, max, ksiz, rv;
+ name = NULL;
+ opts = 0;
+ kx = FALSE;
+ ki = FALSE;
+ ox = FALSE;
+ nb = FALSE;
+ start = 0;
+ max = -1;
+ key = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-nl")){
+ opts |= DP_ONOLCK;
+ } else if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else if(!strcmp(argv[i], "-ox")){
+ ox = TRUE;
+ } else if(!strcmp(argv[i], "-n")){
+ nb = TRUE;
+ } else if(!strcmp(argv[i], "-start")){
+ if(++i >= argc) usage();
+ start = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-max")){
+ if(++i >= argc) usage();
+ max = atoi(argv[i]);
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key || start < 0) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else if(ki){
+ kbuf = dectoiobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = -1;
+ }
+ if(kbuf){
+ rv = doget(name, opts, kbuf, ksiz, start, max, ox, nb);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ rv = 1;
+ }
+ free(kbuf);
+ return rv;
+}
+
+
+/* parse arguments of list command */
+int runlist(int argc, char **argv){
+ char *name;
+ int i, opts, kb, vb, ox, rv;
+ name = NULL;
+ opts = 0;
+ kb = FALSE;
+ vb = FALSE;
+ ox = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-nl")){
+ opts |= DP_ONOLCK;
+ } else if(!strcmp(argv[i], "-k")){
+ kb = TRUE;
+ } else if(!strcmp(argv[i], "-v")){
+ vb = TRUE;
+ } else if(!strcmp(argv[i], "-ox")){
+ ox = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = dolist(name, opts, kb, vb, ox);
+ return rv;
+}
+
+
+/* parse arguments of optimize command */
+int runoptimize(int argc, char **argv){
+ char *name;
+ int i, bnum, align, rv;
+ name = NULL;
+ bnum = -1;
+ align = ALIGNSIZ;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-bnum")){
+ if(++i >= argc) usage();
+ bnum = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-na")){
+ align = 0;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = dooptimize(name, bnum, align);
+ return rv;
+}
+
+
+/* parse arguments of inform command */
+int runinform(int argc, char **argv){
+ char *name;
+ int i, opts, rv;
+ name = NULL;
+ opts = 0;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-nl")){
+ opts |= DP_ONOLCK;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doinform(name, opts);
+ return rv;
+}
+
+
+/* parse arguments of remove command */
+int runremove(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doremove(name);
+ return rv;
+}
+
+
+/* parse arguments of repair command */
+int runrepair(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = dorepair(name);
+ return rv;
+}
+
+
+/* parse arguments of exportdb command */
+int runexportdb(int argc, char **argv){
+ char *name, *file;
+ int i, rv;
+ name = NULL;
+ file = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !file) usage();
+ rv = doexportdb(name, file);
+ return rv;
+}
+
+
+/* parse arguments of importdb command */
+int runimportdb(int argc, char **argv){
+ char *name, *file;
+ int i, bnum, rv;
+ name = NULL;
+ file = NULL;
+ bnum = -1;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-bnum")){
+ if(++i >= argc) usage();
+ bnum = atoi(argv[i]);
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !file) usage();
+ rv = doimportdb(name, file, bnum);
+ return rv;
+}
+
+
+/* parse arguments of snaffle command */
+int runsnaffle(int argc, char **argv){
+ char *name, *key, *kbuf;
+ int i, kx, ki, ox, nb, start, max, ksiz, rv;
+ name = NULL;
+ kx = FALSE;
+ ki = FALSE;
+ ox = FALSE;
+ nb = FALSE;
+ start = 0;
+ max = -1;
+ key = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else if(!strcmp(argv[i], "-ox")){
+ ox = TRUE;
+ } else if(!strcmp(argv[i], "-n")){
+ nb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key || start < 0) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else if(ki){
+ kbuf = dectoiobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = -1;
+ }
+ if(kbuf){
+ rv = dosnaffle(name, kbuf, ksiz, ox, nb);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ rv = 1;
+ }
+ free(kbuf);
+ return rv;
+}
+
+
+/* print an error message */
+void pdperror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, dperrmsg(dpecode));
+}
+
+
+/* print an object */
+void printobj(const char *obj, int size){
+ int i;
+ for(i = 0; i < size; i++){
+ putchar(obj[i]);
+ }
+}
+
+
+/* print an object as a hexadecimal string */
+void printobjhex(const char *obj, int size){
+ int i;
+ for(i = 0; i < size; i++){
+ printf("%s%02X", i > 0 ? " " : "", ((const unsigned char *)obj)[i]);
+ }
+}
+
+
+/* perform create command */
+int docreate(const char *name, int bnum, int sparse){
+ DEPOT *depot;
+ int omode;
+ omode = DP_OWRITER | DP_OCREAT | DP_OTRUNC | (sparse ? DP_OSPARSE : 0);
+ if(!(depot = dpopen(name, omode, bnum))){
+ pdperror(name);
+ return 1;
+ }
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform put command */
+int doput(const char *name, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+ int dmode, int align){
+ DEPOT *depot;
+ if(!(depot = dpopen(name, DP_OWRITER, -1))){
+ pdperror(name);
+ return 1;
+ }
+ if(align > 0 && !dpsetalign(depot, align)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ if(!dpput(depot, kbuf, ksiz, vbuf, vsiz, dmode)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform out command */
+int doout(const char *name, const char *kbuf, int ksiz){
+ DEPOT *depot;
+ if(!(depot = dpopen(name, DP_OWRITER, -1))){
+ pdperror(name);
+ return 1;
+ }
+ if(!dpout(depot, kbuf, ksiz)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform get command */
+int doget(const char *name, int opts, const char *kbuf, int ksiz,
+ int start, int max, int ox, int nb){
+ DEPOT *depot;
+ char *vbuf;
+ int vsiz;
+ if(!(depot = dpopen(name, DP_OREADER | opts, -1))){
+ pdperror(name);
+ return 1;
+ }
+ if(!(vbuf = dpget(depot, kbuf, ksiz, start, max, &vsiz))){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ if(ox){
+ printobjhex(vbuf, vsiz);
+ } else {
+ printobj(vbuf, vsiz);
+ }
+ free(vbuf);
+ if(!nb) putchar('\n');
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform list command */
+int dolist(const char *name, int opts, int kb, int vb, int ox){
+ DEPOT *depot;
+ char *kbuf, *vbuf;
+ int ksiz, vsiz;
+ if(!(depot = dpopen(name, DP_OREADER | opts, -1))){
+ pdperror(name);
+ return 1;
+ }
+ dpiterinit(depot);
+ while((kbuf = dpiternext(depot, &ksiz)) != NULL){
+ if(!(vbuf = dpget(depot, kbuf, ksiz, 0, -1, &vsiz))){
+ pdperror(name);
+ free(kbuf);
+ break;
+ }
+ if(ox){
+ if(!vb) printobjhex(kbuf, ksiz);
+ if(!kb && !vb) putchar('\t');
+ if(!kb) printobjhex(vbuf, vsiz);
+ } else {
+ if(!vb) printobj(kbuf, ksiz);
+ if(!kb && !vb) putchar('\t');
+ if(!kb) printobj(vbuf, vsiz);
+ }
+ putchar('\n');
+ free(vbuf);
+ free(kbuf);
+ }
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform optimize command */
+int dooptimize(const char *name, int bnum, int align){
+ DEPOT *depot;
+ if(!(depot = dpopen(name, DP_OWRITER, -1))){
+ pdperror(name);
+ return 1;
+ }
+ if(align > 0 && !dpsetalign(depot, align)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ if(!dpoptimize(depot, bnum)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform inform command */
+int doinform(const char *name, int opts){
+ DEPOT *depot;
+ char *tmp;
+ if(!(depot = dpopen(name, DP_OREADER | opts, -1))){
+ pdperror(name);
+ return 1;
+ }
+ tmp = dpname(depot);
+ printf("name: %s\n", tmp ? tmp : "(null)");
+ free(tmp);
+ printf("file size: %d\n", dpfsiz(depot));
+ printf("all buckets: %d\n", dpbnum(depot));
+ printf("used buckets: %d\n", dpbusenum(depot));
+ printf("records: %d\n", dprnum(depot));
+ printf("inode number: %d\n", dpinode(depot));
+ printf("modified time: %.0f\n", (double)dpmtime(depot));
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform remove command */
+int doremove(const char *name){
+ if(!dpremove(name)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform repair command */
+int dorepair(const char *name){
+ if(!dprepair(name)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform exportdb command */
+int doexportdb(const char *name, const char *file){
+ DEPOT *depot;
+ if(!(depot = dpopen(name, DP_OREADER, -1))){
+ pdperror(name);
+ return 1;
+ }
+ if(!dpexportdb(depot, file)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform importdb command */
+int doimportdb(const char *name, const char *file, int bnum){
+ DEPOT *depot;
+ if(!(depot = dpopen(name, DP_OWRITER | DP_OCREAT | DP_OTRUNC, bnum))){
+ pdperror(name);
+ return 1;
+ }
+ if(!dpimportdb(depot, file)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform snaffle command */
+int dosnaffle(const char *name, const char *kbuf, int ksiz, int ox, int nb){
+ char *vbuf;
+ int vsiz;
+ if(!(vbuf = dpsnaffle(name, kbuf, ksiz, &vsiz))){
+ pdperror(name);
+ return 1;
+ }
+ if(ox){
+ printobjhex(vbuf, vsiz);
+ } else {
+ printobj(vbuf, vsiz);
+ }
+ free(vbuf);
+ if(!nb) putchar('\n');
+ return 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/dptest.c b/qdbm/dptest.c
new file mode 100644
index 00000000..9d584961
--- /dev/null
+++ b/qdbm/dptest.c
@@ -0,0 +1,836 @@
+/*************************************************************************************************
+ * Test cases of Depot
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <cabin.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <time.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define RECBUFSIZ 32 /* buffer for records */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int runrcat(int argc, char **argv);
+int runcombo(int argc, char **argv);
+int runwicked(int argc, char **argv);
+int printfflush(const char *format, ...);
+void pdperror(const char *name);
+int myrand(void);
+int dowrite(const char *name, int rnum, int bnum, int sparse);
+int doread(const char *name, int wb);
+int dorcat(const char *name, int rnum, int bnum, int pnum, int align, int fbpsiz, int cb);
+int docombo(const char *name);
+int dowicked(const char *name, int rnum, int cb);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ char *env;
+ int rv;
+ cbstdiobin();
+ if((env = getenv("QDBMDBGFD")) != NULL) dpdbgfd = atoi(env);
+ progname = argv[0];
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "write")){
+ rv = runwrite(argc, argv);
+ } else if(!strcmp(argv[1], "read")){
+ rv = runread(argc, argv);
+ } else if(!strcmp(argv[1], "rcat")){
+ rv = runrcat(argc, argv);
+ } else if(!strcmp(argv[1], "combo")){
+ rv = runcombo(argc, argv);
+ } else if(!strcmp(argv[1], "wicked")){
+ rv = runwicked(argc, argv);
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: test cases for Depot\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s write [-s] name rnum bnum\n", progname);
+ fprintf(stderr, " %s read [-wb] name\n", progname);
+ fprintf(stderr, " %s rcat [-c] name rnum bnum pnum align fbpsiz\n", progname);
+ fprintf(stderr, " %s combo name\n", progname);
+ fprintf(stderr, " %s wicked [-c] name rnum\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+ char *name, *rstr, *bstr;
+ int i, rnum, bnum, sb, rv;
+ name = NULL;
+ rstr = NULL;
+ bstr = NULL;
+ rnum = 0;
+ bnum = 0;
+ sb = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-s")){
+ sb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!rstr){
+ rstr = argv[i];
+ } else if(!bstr){
+ bstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !rstr || !bstr) usage();
+ rnum = atoi(rstr);
+ bnum = atoi(bstr);
+ if(rnum < 1 || bnum < 1) usage();
+ rv = dowrite(name, rnum, bnum, sb);
+ return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+ char *name;
+ int i, wb, rv;
+ name = NULL;
+ wb = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-wb")){
+ wb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doread(name, wb);
+ return rv;
+}
+
+
+/* parse arguments of rcat command */
+int runrcat(int argc, char **argv){
+ char *name, *rstr, *bstr, *pstr, *astr, *fstr;
+ int i, rnum, bnum, pnum, align, fbpsiz, cb, rv;
+ name = NULL;
+ rstr = NULL;
+ bstr = NULL;
+ pstr = NULL;
+ astr = NULL;
+ fstr = NULL;
+ rnum = 0;
+ bnum = 0;
+ pnum = 0;
+ align = 0;
+ fbpsiz = 0;
+ cb = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-c")){
+ cb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!rstr){
+ rstr = argv[i];
+ } else if(!bstr){
+ bstr = argv[i];
+ } else if(!pstr){
+ pstr = argv[i];
+ } else if(!astr){
+ astr = argv[i];
+ } else if(!fstr){
+ fstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !rstr || !bstr || !pstr || !astr || !fstr) usage();
+ rnum = atoi(rstr);
+ bnum = atoi(bstr);
+ pnum = atoi(pstr);
+ align = atoi(astr);
+ fbpsiz= atoi(fstr);
+ if(rnum < 1 || bnum < 1 || pnum < 1 || fbpsiz < 0) usage();
+ rv = dorcat(name, rnum, bnum, pnum, align, fbpsiz, cb);
+ return rv;
+}
+
+
+/* parse arguments of combo command */
+int runcombo(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = docombo(name);
+ return rv;
+}
+
+
+/* parse arguments of wicked command */
+int runwicked(int argc, char **argv){
+ char *name, *rstr;
+ int i, rnum, cb, rv;
+ name = NULL;
+ rstr = NULL;
+ rnum = 0;
+ cb = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-c")){
+ cb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!rstr){
+ rstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ rv = dowicked(name, rnum, cb);
+ return rv;
+}
+
+
+/* print formatted string and flush the buffer */
+int printfflush(const char *format, ...){
+ va_list ap;
+ int rv;
+ va_start(ap, format);
+ rv = vprintf(format, ap);
+ if(fflush(stdout) == EOF) rv = -1;
+ va_end(ap);
+ return rv;
+}
+
+
+/* print an error message */
+void pdperror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, dperrmsg(dpecode));
+}
+
+
+/* pseudo random number generator */
+int myrand(void){
+ static int cnt = 0;
+ if(cnt == 0) srand(time(NULL));
+ return (rand() * rand() + (rand() >> (sizeof(int) * 4)) + (cnt++)) & INT_MAX;
+}
+
+
+/* perform write command */
+int dowrite(const char *name, int rnum, int bnum, int sparse){
+ DEPOT *depot;
+ int i, omode, err, len;
+ char buf[RECBUFSIZ];
+ printfflush("<Writing Test>\n name=%s rnum=%d bnum=%d s=%d\n\n", name, rnum, bnum, sparse);
+ /* open a database */
+ omode = DP_OWRITER | DP_OCREAT | DP_OTRUNC | (sparse ? DP_OSPARSE : 0);
+ if(!(depot = dpopen(name, omode, bnum))){
+ pdperror(name);
+ return 1;
+ }
+ err = FALSE;
+ /* loop for each record */
+ for(i = 1; i <= rnum; i++){
+ /* store a record */
+ len = sprintf(buf, "%08d", i);
+ if(!dpput(depot, buf, len, buf, len, DP_DOVER)){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ /* print progression */
+ if(rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ /* close the database */
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(const char *name, int wb){
+ DEPOT *depot;
+ int i, rnum, err, len;
+ char buf[RECBUFSIZ], vbuf[RECBUFSIZ], *val;
+ printfflush("<Reading Test>\n name=%s wb=%d\n\n", name, wb);
+ /* open a database */
+ if(!(depot = dpopen(name, DP_OREADER, -1))){
+ pdperror(name);
+ return 1;
+ }
+ /* get the number of records */
+ rnum = dprnum(depot);
+ err = FALSE;
+ /* loop for each record */
+ for(i = 1; i <= rnum; i++){
+ /* retrieve a record */
+ len = sprintf(buf, "%08d", i);
+ if(wb){
+ if(dpgetwb(depot, buf, len, 0, RECBUFSIZ, vbuf) == -1){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ } else {
+ if(!(val = dpget(depot, buf, len, 0, -1, NULL))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ free(val);
+ }
+ /* print progression */
+ if(rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ /* close the database */
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform rcat command */
+int dorcat(const char *name, int rnum, int bnum, int pnum, int align, int fbpsiz, int cb){
+ DEPOT *depot;
+ CBMAP *map;
+ int i, err, len, ksiz, vsiz, rsiz;
+ const char *kbuf, *vbuf;
+ char buf[RECBUFSIZ], *rbuf;
+ printfflush("<Random Writing Test>\n name=%s rnum=%d bnum=%d pnum=%d align=%d"
+ " fbpsiz=%d c=%d\n\n", name, rnum, bnum, pnum, align, fbpsiz, cb);
+ if(!(depot = dpopen(name, DP_OWRITER | DP_OCREAT | DP_OTRUNC, bnum))){
+ pdperror(name);
+ return 1;
+ }
+ if(!dpsetalign(depot, align) || !dpsetfbpsiz(depot, fbpsiz)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ map = NULL;
+ if(cb) map = cbmapopen();
+ err = FALSE;
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", myrand() % pnum + 1);
+ if(!dpput(depot, buf, len, buf, len, DP_DCAT)){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ if(map) cbmapputcat(map, buf, len, buf, len);
+ if(rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d: fsiz=%d rnum=%d)\n", i, dpfsiz(depot), dprnum(depot));
+ }
+ }
+ }
+ if(map){
+ printfflush("Matching records ... ");
+ cbmapiterinit(map);
+ while((kbuf = cbmapiternext(map, &ksiz)) != NULL){
+ vbuf = cbmapget(map, kbuf, ksiz, &vsiz);
+ if(!(rbuf = dpget(depot, kbuf, ksiz, 0, -1, &rsiz))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+ fprintf(stderr, "%s: %s: unmatched record\n", progname, name);
+ free(rbuf);
+ err = TRUE;
+ break;
+ }
+ free(rbuf);
+ }
+ cbmapclose(map);
+ if(!err) printfflush("ok\n");
+ }
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform combo command */
+int docombo(const char *name){
+ DEPOT *depot;
+ char buf[RECBUFSIZ], wbuf[RECBUFSIZ], *vbuf;
+ int i, len, wlen, vsiz;
+ printfflush("<Combination Test>\n name=%s\n\n", name);
+ printfflush("Creating a database with bnum 3 ... ");
+ if(!(depot = dpopen(name, DP_OWRITER | DP_OCREAT | DP_OTRUNC, 3))){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Setting alignment as 16 ... ");
+ if(!dpsetalign(depot, 16)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Adding 20 records ... ");
+ for(i = 1; i <= 20; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!dpput(depot, buf, len, buf, len, DP_DOVER)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Checking records ... ");
+ for(i = 1; i <= 20; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!(vbuf = dpget(depot, buf, len, 0, -1, &vsiz))){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ free(vbuf);
+ if(vsiz != dpvsiz(depot, buf, len)){
+ fprintf(stderr, "%s: %s: invalid vsiz\n", progname, name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Overwriting top 10 records without moving rooms ... ");
+ for(i = 1; i <= 10; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!dpput(depot, buf, len, buf, len, DP_DOVER)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Overwriting top 5 records with moving rooms ... ");
+ for(i = 1; i <= 5; i++){
+ len = sprintf(buf, "%08d", i);
+ wlen = sprintf(wbuf, "%024d", i);
+ if(!dpput(depot, buf, len, wbuf, wlen, DP_DOVER)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Overwriting top 15 records in concatenation with moving rooms ... ");
+ for(i = 1; i <= 15; i++){
+ len = sprintf(buf, "%08d", i);
+ wlen = sprintf(wbuf, "========================");
+ if(!dpput(depot, buf, len, wbuf, wlen, DP_DCAT)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Checking records ... ");
+ for(i = 1; i <= 20; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!(vbuf = dpget(depot, buf, len, 0, -1, &vsiz))){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ free(vbuf);
+ if(vsiz != dpvsiz(depot, buf, len)){
+ fprintf(stderr, "%s: %s: invalid vsiz\n", progname, name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Deleting top 10 records ... ");
+ for(i = 1; i <= 10; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!dpout(depot, buf, len)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Checking deleted records ... ");
+ for(i = 1; i <= 10; i++){
+ len = sprintf(buf, "%08d", i);
+ vbuf = dpget(depot, buf, len, 0, -1, &vsiz);
+ free(vbuf);
+ if(vbuf || dpecode != DP_ENOITEM){
+ fprintf(stderr, "%s: %s: deleting failed\n", progname, name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Overwriting top 15 records in concatenation with moving rooms ... ");
+ for(i = 1; i <= 15; i++){
+ len = sprintf(buf, "%08d", i);
+ wlen = sprintf(wbuf, "========================");
+ if(!dpput(depot, buf, len, wbuf, wlen, DP_DCAT)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Checking records ... ");
+ for(i = 1; i <= 20; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!(vbuf = dpget(depot, buf, len, 0, -1, &vsiz))){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ free(vbuf);
+ if(vsiz != dpvsiz(depot, buf, len)){
+ fprintf(stderr, "%s: %s: invalid vsiz\n", progname, name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Optimizing the database ... ");
+ if(!dpoptimize(depot, -1)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Checking records ... ");
+ for(i = 1; i <= 20; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!(vbuf = dpget(depot, buf, len, 0, -1, &vsiz))){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ free(vbuf);
+ if(vsiz != dpvsiz(depot, buf, len)){
+ fprintf(stderr, "%s: %s: invalid vsiz\n", progname, name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Closing the database ... ");
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Creating a database with bnum 1000000 ... ");
+ if(!(depot = dpopen(name, DP_OWRITER | DP_OCREAT | DP_OTRUNC, 1000000))){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Adding 1000 records ... ");
+ for(i = 1; i <= 1000; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!dpput(depot, buf, len, buf, len, DP_DOVER)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Adding 64 records ... ");
+ for(i = 1; i <= 1000; i++){
+ len = sprintf(buf, "%o", i);
+ if(!dpput(depot, buf, len, buf, len, DP_DOVER)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Syncing the database ... ");
+ if(!dpsync(depot)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Retrieving records directly ... ");
+ for(i = 1; i <= 64; i++){
+ len = sprintf(buf, "%o", i);
+ if(!(vbuf = dpsnaffle(name, buf, len, &vsiz))){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ if(strcmp(vbuf, buf)){
+ fprintf(stderr, "%s: %s: invalid content\n", progname, name);
+ free(vbuf);
+ dpclose(depot);
+ return 1;
+ }
+ free(vbuf);
+ if(vsiz != dpvsiz(depot, buf, len)){
+ fprintf(stderr, "%s: %s: invalid vsiz\n", progname, name);
+ dpclose(depot);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Optimizing the database ... ");
+ if(!dpoptimize(depot, -1)){
+ pdperror(name);
+ dpclose(depot);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Closing the database ... ");
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("all ok\n\n");
+ return 0;
+}
+
+
+/* perform wicked command */
+int dowicked(const char *name, int rnum, int cb){
+ DEPOT *depot;
+ CBMAP *map;
+ int i, len, err, align, mksiz, mvsiz, rsiz;
+ const char *mkbuf, *mvbuf;
+ char buf[RECBUFSIZ], vbuf[RECBUFSIZ], *val;
+ printfflush("<Wicked Writing Test>\n name=%s rnum=%d\n\n", name, rnum);
+ err = FALSE;
+ if(!(depot = dpopen(name, DP_OWRITER | DP_OCREAT | DP_OTRUNC, rnum / 10))){
+ pdperror(name);
+ return 1;
+ }
+ if(!dpsetalign(depot, 16) || !dpsetfbpsiz(depot, 256)){
+ pdperror(name);
+ err = TRUE;
+ }
+ map = NULL;
+ if(cb) map = cbmapopen();
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", myrand() % rnum + 1);
+ switch(myrand() % 16){
+ case 0:
+ putchar('O');
+ if(!dpput(depot, buf, len, buf, len, DP_DOVER)) err = TRUE;
+ if(map) cbmapput(map, buf, len, buf, len, TRUE);
+ break;
+ case 1:
+ putchar('K');
+ if(!dpput(depot, buf, len, buf, len, DP_DKEEP) && dpecode != DP_EKEEP) err = TRUE;
+ if(map) cbmapput(map, buf, len, buf, len, FALSE);
+ break;
+ case 2:
+ putchar('D');
+ if(!dpout(depot, buf, len) && dpecode != DP_ENOITEM) err = TRUE;
+ if(map) cbmapout(map, buf, len);
+ break;
+ case 3:
+ putchar('G');
+ if(dpgetwb(depot, buf, len, 2, RECBUFSIZ, vbuf) == -1 && dpecode != DP_ENOITEM) err = TRUE;
+ break;
+ case 4:
+ putchar('V');
+ if(dpvsiz(depot, buf, len) == -1 && dpecode != DP_ENOITEM) err = TRUE;
+ break;
+ default:
+ putchar('C');
+ if(!dpput(depot, buf, len, buf, len, DP_DCAT)) err = TRUE;
+ if(map) cbmapputcat(map, buf, len, buf, len);
+ break;
+ }
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ if(!err && rnum > 100 && myrand() % (rnum / 100) == 0){
+ if(myrand() % 10 == 0){
+ align = (myrand() % 4 + 1) * -1;
+ } else {
+ align = myrand() % 32;
+ }
+ if(!dpsetalign(depot, align)) err = TRUE;
+ }
+ if(err){
+ pdperror(name);
+ break;
+ }
+ }
+ if(!dpoptimize(depot, -1)){
+ pdperror(name);
+ err = TRUE;
+ }
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!dpput(depot, buf, len, ":", -1, DP_DCAT)){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ if(map) cbmapputcat(map, buf, len, ":", -1);
+ putchar(':');
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ }
+ if(!dpoptimize(depot, -1)){
+ pdperror(name);
+ err = TRUE;
+ }
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!(val = dpget(depot, buf, len, 0, -1, NULL))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ free(val);
+ putchar('=');
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ }
+ if(!dpiterinit(depot)){
+ pdperror(name);
+ err = TRUE;
+ }
+ for(i = 1; i <= rnum; i++){
+ if(!(val = dpiternext(depot, NULL))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ free(val);
+ putchar('@');
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ }
+ if(map){
+ printfflush("Matching records ... ");
+ cbmapiterinit(map);
+ while((mkbuf = cbmapiternext(map, &mksiz)) != NULL){
+ mvbuf = cbmapget(map, mkbuf, mksiz, &mvsiz);
+ if(!(val = dpget(depot, mkbuf, mksiz, 0, -1, &rsiz))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ if(rsiz != mvsiz || memcmp(val, mvbuf, rsiz)){
+ fprintf(stderr, "%s: %s: unmatched record\n", progname, name);
+ free(val);
+ err = TRUE;
+ break;
+ }
+ free(val);
+ }
+ cbmapclose(map);
+ if(!err) printfflush("ok\n");
+ }
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/dptsv.c b/qdbm/dptsv.c
new file mode 100644
index 00000000..7dab42c5
--- /dev/null
+++ b/qdbm/dptsv.c
@@ -0,0 +1,261 @@
+/*************************************************************************************************
+ * Mutual converter between a database of Depot and a TSV text
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <cabin.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runimport(int argc, char **argv);
+int runexport(int argc, char **argv);
+void pdperror(const char *name);
+char *getl(void);
+int doimport(const char *name, int bnum, int bin);
+int doexport(const char *name, int bin);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "import")){
+ rv = runimport(argc, argv);
+ } else if(!strcmp(argv[1], "export")){
+ rv = runexport(argc, argv);
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: mutual converter between TSV and Depot database\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s import [-bnum num] [-bin] name\n", progname);
+ fprintf(stderr, " %s export [-bin] name\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* parse arguments of import command */
+int runimport(int argc, char **argv){
+ char *name;
+ int i, bnum, bin, rv;
+ name = NULL;
+ bnum = -1;
+ bin = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-bnum")){
+ if(++i >= argc) usage();
+ bnum = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-bin")){
+ bin = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doimport(name, bnum, bin);
+ return rv;
+}
+
+
+/* parse arguments of export command */
+int runexport(int argc, char **argv){
+ char *name;
+ int i, bin, rv;
+ name = NULL;
+ bin = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-bin")){
+ bin = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doexport(name, bin);
+ return rv;
+}
+
+
+/* print an error message */
+void pdperror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, dperrmsg(dpecode));
+}
+
+
+/* read a line */
+char *getl(void){
+ char *buf;
+ int c, len, blen;
+ buf = NULL;
+ len = 0;
+ blen = 256;
+ while((c = getchar()) != EOF){
+ if(blen <= len) blen *= 2;
+ buf = cbrealloc(buf, blen + 1);
+ if(c == '\n') c = '\0';
+ buf[len++] = c;
+ if(c == '\0') break;
+ }
+ if(!buf) return NULL;
+ buf[len] = '\0';
+ return buf;
+}
+
+
+/* perform import command */
+int doimport(const char *name, int bnum, int bin){
+ DEPOT *depot;
+ char *buf, *kbuf, *vbuf, *ktmp, *vtmp;
+ int i, err, ktsiz, vtsiz;
+ /* open a database */
+ if(!(depot = dpopen(name, DP_OWRITER | DP_OCREAT, bnum))){
+ pdperror(name);
+ return 1;
+ }
+ /* loop for each line */
+ err = FALSE;
+ for(i = 1; (buf = getl()) != NULL; i++){
+ kbuf = buf;
+ if((vbuf = strchr(buf, '\t')) != NULL){
+ *vbuf = '\0';
+ vbuf++;
+ /* store a record */
+ if(bin){
+ ktmp = cbbasedecode(kbuf, &ktsiz);
+ vtmp = cbbasedecode(vbuf, &vtsiz);
+ if(!dpput(depot, ktmp, ktsiz, vtmp, vtsiz, DP_DOVER)){
+ pdperror(name);
+ err = TRUE;
+ }
+ free(vtmp);
+ free(ktmp);
+ } else {
+ if(!dpput(depot, kbuf, -1, vbuf, -1, DP_DOVER)){
+ pdperror(name);
+ err = TRUE;
+ }
+ }
+ } else {
+ fprintf(stderr, "%s: %s: invalid format in line %d\n", progname, name, i);
+ }
+ free(buf);
+ if(err) break;
+ }
+ /* close the database */
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ return err ? 1 : 0;
+}
+
+
+/* perform export command */
+int doexport(const char *name, int bin){
+ DEPOT *depot;
+ char *kbuf, *vbuf, *tmp;
+ int err, ksiz, vsiz;
+ /* open a database */
+ if(!(depot = dpopen(name, DP_OREADER, -1))){
+ pdperror(name);
+ return 1;
+ }
+ /* initialize the iterator */
+ dpiterinit(depot);
+ /* loop for each key */
+ err = FALSE;
+ while((kbuf = dpiternext(depot, &ksiz)) != NULL){
+ /* retrieve a value with a key */
+ if(!(vbuf = dpget(depot, kbuf, ksiz, 0, -1, &vsiz))){
+ pdperror(name);
+ free(kbuf);
+ err = TRUE;
+ break;
+ }
+ /* output data */
+ if(bin){
+ tmp = cbbaseencode(kbuf, ksiz);
+ printf("%s\t", tmp);
+ free(tmp);
+ tmp = cbbaseencode(vbuf, vsiz);
+ printf("%s\n", tmp);
+ free(tmp);
+ } else {
+ printf("%s\t%s\n", kbuf, vbuf);
+ }
+ /* free resources */
+ free(vbuf);
+ free(kbuf);
+ }
+ /* check whether all records were retrieved */
+ if(dpecode != DP_ENOITEM){
+ pdperror(name);
+ err = TRUE;
+ }
+ /* close the database */
+ if(!dpclose(depot)){
+ pdperror(name);
+ return 1;
+ }
+ return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/hovel.c b/qdbm/hovel.c
new file mode 100644
index 00000000..86964702
--- /dev/null
+++ b/qdbm/hovel.c
@@ -0,0 +1,568 @@
+/*************************************************************************************************
+ * Implementation of Hovel
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#define QDBM_INTERNAL 1
+
+#include "hovel.h"
+#include "myconf.h"
+
+#define HV_INITBNUM 32749 /* initial bucket number */
+#define HV_ALIGNSIZ 16 /* size of alignment */
+
+
+/* private function prototypes */
+static int gdbm_geterrno(int ecode);
+
+
+
+/*************************************************************************************************
+ * public objects
+ *************************************************************************************************/
+
+
+/* String containing the version information. */
+char *gdbm_version = "Powered by QDBM";
+
+
+/* Get a message string corresponding to an error code. */
+char *gdbm_strerror(gdbm_error gdbmerrno){
+ switch(gdbmerrno){
+ case GDBM_NO_ERROR: return "No error";
+ case GDBM_MALLOC_ERROR: return "Malloc error";
+ case GDBM_BLOCK_SIZE_ERROR: return "Block size error";
+ case GDBM_FILE_OPEN_ERROR: return "File open error";
+ case GDBM_FILE_WRITE_ERROR: return "File write error";
+ case GDBM_FILE_SEEK_ERROR: return "File seek error";
+ case GDBM_FILE_READ_ERROR: return "File read error";
+ case GDBM_BAD_MAGIC_NUMBER: return "Bad magic number";
+ case GDBM_EMPTY_DATABASE: return "Empty database";
+ case GDBM_CANT_BE_READER: return "Can't be reader";
+ case GDBM_CANT_BE_WRITER: return "Can't be writer";
+ case GDBM_READER_CANT_DELETE: return "Reader can't delete";
+ case GDBM_READER_CANT_STORE: return "Reader can't store";
+ case GDBM_READER_CANT_REORGANIZE: return "Reader can't reorganize";
+ case GDBM_UNKNOWN_UPDATE: return "Unknown update";
+ case GDBM_ITEM_NOT_FOUND: return "Item not found";
+ case GDBM_REORGANIZE_FAILED: return "Reorganize failed";
+ case GDBM_CANNOT_REPLACE: return "Cannot replace";
+ case GDBM_ILLEGAL_DATA: return "Illegal data";
+ case GDBM_OPT_ALREADY_SET: return "Option already set";
+ case GDBM_OPT_ILLEGAL: return "Illegal option";
+ }
+ return "(invalid ecode)";
+}
+
+
+/* Get a database handle after the fashion of GDBM. */
+GDBM_FILE gdbm_open(char *name, int block_size, int read_write, int mode,
+ void (*fatal_func)(void)){
+ GDBM_FILE dbf;
+ int dpomode;
+ DEPOT *depot;
+ int flags, fd;
+ assert(name);
+ dpomode = DP_OREADER;
+ flags = O_RDONLY;
+ if(read_write & GDBM_READER){
+ dpomode = GDBM_READER;
+ if(read_write & GDBM_NOLOCK) dpomode |= DP_ONOLCK;
+ if(read_write & GDBM_LOCKNB) dpomode |= DP_OLCKNB;
+ flags = O_RDONLY;
+ } else if(read_write & GDBM_WRITER){
+ dpomode = DP_OWRITER;
+ if(read_write & GDBM_NOLOCK) dpomode |= DP_ONOLCK;
+ if(read_write & GDBM_LOCKNB) dpomode |= DP_OLCKNB;
+ flags = O_RDWR;
+ } else if(read_write & GDBM_WRCREAT){
+ dpomode = DP_OWRITER | DP_OCREAT;
+ if(read_write & GDBM_NOLOCK) dpomode |= DP_ONOLCK;
+ if(read_write & GDBM_LOCKNB) dpomode |= DP_OLCKNB;
+ if(read_write & GDBM_SPARSE) dpomode |= DP_OSPARSE;
+ flags = O_RDWR | O_CREAT;
+ } else if(read_write & GDBM_NEWDB){
+ dpomode = DP_OWRITER | DP_OCREAT | DP_OTRUNC;
+ if(read_write & GDBM_NOLOCK) dpomode |= DP_ONOLCK;
+ if(read_write & GDBM_LOCKNB) dpomode |= DP_OLCKNB;
+ if(read_write & GDBM_SPARSE) dpomode |= DP_OSPARSE;
+ flags = O_RDWR | O_CREAT | O_TRUNC;
+ } else {
+ gdbm_errno = GDBM_ILLEGAL_DATA;
+ return NULL;
+ }
+ mode |= 00600;
+ if((fd = open(name, flags, mode)) == -1 || close(fd) == -1){
+ gdbm_errno = GDBM_FILE_OPEN_ERROR;
+ return NULL;
+ }
+ if(!(depot = dpopen(name, dpomode, HV_INITBNUM))){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ if(dpecode == DP_ESTAT) gdbm_errno = GDBM_FILE_OPEN_ERROR;
+ return NULL;
+ }
+ if(dpomode & DP_OWRITER){
+ if(!dpsetalign(depot, HV_ALIGNSIZ)){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ dpclose(depot);
+ }
+ }
+ if((dpomode & DP_OWRITER) && (read_write & GDBM_SYNC)){
+ if(!dpsync(depot)){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ dpclose(depot);
+ }
+ }
+ if(!(dbf = malloc(sizeof(GDBM)))){
+ gdbm_errno = GDBM_MALLOC_ERROR;
+ dpclose(depot);
+ return NULL;
+ }
+ dbf->depot = depot;
+ dbf->curia = NULL;
+ dbf->syncmode = (dpomode & DP_OWRITER) && (read_write & GDBM_SYNC) ? TRUE : FALSE;
+ return dbf;
+}
+
+
+/* Get a database handle after the fashion of QDBM. */
+GDBM_FILE gdbm_open2(char *name, int read_write, int mode, int bnum, int dnum, int align){
+ GDBM_FILE dbf;
+ int dpomode, cromode, flags, fd;
+ struct stat sbuf;
+ DEPOT *depot;
+ CURIA *curia;
+ assert(name);
+ dpomode = DP_OREADER;
+ cromode = CR_OREADER;
+ flags = O_RDONLY;
+ if(read_write & GDBM_READER){
+ dpomode = DP_OREADER;
+ cromode = CR_OREADER;
+ if(read_write & GDBM_NOLOCK){
+ dpomode |= DP_ONOLCK;
+ cromode |= CR_ONOLCK;
+ }
+ if(read_write & GDBM_LOCKNB){
+ dpomode |= DP_OLCKNB;
+ cromode |= CR_OLCKNB;
+ }
+ flags = O_RDONLY;
+ } else if(read_write & GDBM_WRITER){
+ dpomode = DP_OWRITER;
+ cromode = CR_OWRITER;
+ if(read_write & GDBM_NOLOCK){
+ dpomode |= DP_ONOLCK;
+ cromode |= CR_ONOLCK;
+ }
+ if(read_write & GDBM_LOCKNB){
+ dpomode |= DP_OLCKNB;
+ cromode |= CR_OLCKNB;
+ }
+ flags = O_RDWR;
+ } else if(read_write & GDBM_WRCREAT){
+ dpomode = DP_OWRITER | DP_OCREAT;
+ cromode = CR_OWRITER | CR_OCREAT;
+ if(read_write & GDBM_NOLOCK){
+ dpomode |= DP_ONOLCK;
+ cromode |= CR_ONOLCK;
+ }
+ if(read_write & GDBM_LOCKNB){
+ dpomode |= DP_OLCKNB;
+ cromode |= CR_OLCKNB;
+ }
+ if(read_write & GDBM_SPARSE){
+ dpomode |= DP_OSPARSE;
+ cromode |= CR_OSPARSE;
+ }
+ flags = O_RDWR | O_CREAT;
+ } else if(read_write & GDBM_NEWDB){
+ dpomode = DP_OWRITER | DP_OCREAT | DP_OTRUNC;
+ cromode = CR_OWRITER | CR_OCREAT | CR_OTRUNC;
+ if(read_write & GDBM_NOLOCK){
+ dpomode |= DP_ONOLCK;
+ cromode |= CR_ONOLCK;
+ }
+ if(read_write & GDBM_LOCKNB){
+ dpomode |= DP_OLCKNB;
+ cromode |= CR_OLCKNB;
+ }
+ if(read_write & GDBM_SPARSE){
+ dpomode |= DP_OSPARSE;
+ cromode |= CR_OSPARSE;
+ }
+ flags = O_RDWR | O_CREAT | O_TRUNC;
+ } else {
+ gdbm_errno = GDBM_ILLEGAL_DATA;
+ return NULL;
+ }
+ if(lstat(name, &sbuf) != -1){
+ if(S_ISDIR(sbuf.st_mode)){
+ if(dnum < 1) dnum = 1;
+ } else {
+ dnum = 0;
+ }
+ }
+ depot = NULL;
+ curia = NULL;
+ if(dnum > 0){
+ mode |= 00700;
+ if((cromode & CR_OCREAT)){
+ if(mkdir(name, mode) == -1 && errno != EEXIST){
+ gdbm_errno = GDBM_FILE_OPEN_ERROR;
+ return NULL;
+ }
+ }
+ if(!(curia = cropen(name, cromode, bnum, dnum))){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ return NULL;
+ }
+ if(cromode & CR_OWRITER) crsetalign(curia, align);
+ if((cromode & CR_OWRITER) && (read_write & GDBM_SYNC)) crsync(curia);
+ } else {
+ mode |= 00600;
+ if(dpomode & DP_OWRITER){
+ if((fd = open(name, flags, mode)) == -1 || close(fd) == -1){
+ gdbm_errno = GDBM_FILE_OPEN_ERROR;
+ return NULL;
+ }
+ }
+ if(!(depot = dpopen(name, dpomode, bnum))){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ return NULL;
+ }
+ if(dpomode & DP_OWRITER) dpsetalign(depot, align);
+ if((dpomode & DP_OWRITER) && (read_write & GDBM_SYNC)) dpsync(depot);
+ }
+ if(!(dbf = malloc(sizeof(GDBM)))){
+ gdbm_errno = GDBM_MALLOC_ERROR;
+ if(depot) dpclose(depot);
+ if(curia) crclose(curia);
+ return NULL;
+ }
+ dbf->depot = depot;
+ dbf->curia = curia;
+ dbf->syncmode = (dpomode & DP_OWRITER) && (read_write & GDBM_SYNC) ? TRUE : FALSE;
+ return dbf;
+}
+
+
+/* Close a database handle. */
+void gdbm_close(GDBM_FILE dbf){
+ assert(dbf);
+ if(dbf->depot){
+ if(dbf->syncmode) dpsync(dbf->depot);
+ dpclose(dbf->depot);
+ } else {
+ if(dbf->syncmode) crsync(dbf->curia);
+ crclose(dbf->curia);
+ }
+ free(dbf);
+}
+
+
+/* Store a record. */
+int gdbm_store(GDBM_FILE dbf, datum key, datum content, int flag){
+ int dmode;
+ assert(dbf);
+ if(!key.dptr || key.dsize < 0 || !content.dptr || content.dsize < 0){
+ gdbm_errno = GDBM_ILLEGAL_DATA;
+ return -1;
+ }
+ if(dbf->depot){
+ if(!dpwritable(dbf->depot)){
+ gdbm_errno = GDBM_READER_CANT_STORE;
+ return -1;
+ }
+ dmode = (flag == GDBM_INSERT) ? DP_DKEEP : DP_DOVER;
+ if(!dpput(dbf->depot, key.dptr, key.dsize, content.dptr, content.dsize, dmode)){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ if(dpecode == DP_EKEEP) return 1;
+ return -1;
+ }
+ if(dbf->syncmode && !dpsync(dbf->depot)){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ return -1;
+ }
+ } else {
+ if(!crwritable(dbf->curia)){
+ gdbm_errno = GDBM_READER_CANT_STORE;
+ return -1;
+ }
+ dmode = (flag == GDBM_INSERT) ? CR_DKEEP : CR_DOVER;
+ if(!crput(dbf->curia, key.dptr, key.dsize, content.dptr, content.dsize, dmode)){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ if(dpecode == DP_EKEEP) return 1;
+ return -1;
+ }
+ if(dbf->syncmode && !crsync(dbf->curia)){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+/* Delete a record. */
+int gdbm_delete(GDBM_FILE dbf, datum key){
+ assert(dbf);
+ if(!key.dptr || key.dsize < 0){
+ gdbm_errno = GDBM_ILLEGAL_DATA;
+ return -1;
+ }
+ if(dbf->depot){
+ if(!dpwritable(dbf->depot)){
+ gdbm_errno = GDBM_READER_CANT_DELETE;
+ return -1;
+ }
+ if(!dpout(dbf->depot, key.dptr, key.dsize)){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ return -1;
+ }
+ if(dbf->syncmode && !dpsync(dbf->depot)){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ return -1;
+ }
+ } else {
+ if(!crwritable(dbf->curia)){
+ gdbm_errno = GDBM_READER_CANT_DELETE;
+ return -1;
+ }
+ if(!crout(dbf->curia, key.dptr, key.dsize)){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ return -1;
+ }
+ if(dbf->syncmode && !crsync(dbf->curia)){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+/* Retrieve a record. */
+datum gdbm_fetch(GDBM_FILE dbf, datum key){
+ datum content;
+ char *vbuf;
+ int vsiz;
+ assert(dbf);
+ if(!key.dptr || key.dsize < 0){
+ gdbm_errno = GDBM_ILLEGAL_DATA;
+ content.dptr = NULL;
+ content.dsize = 0;
+ return content;
+ }
+ if(dbf->depot){
+ if(!(vbuf = dpget(dbf->depot, key.dptr, key.dsize, 0, -1, &vsiz))){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ content.dptr = NULL;
+ content.dsize = 0;
+ return content;
+ }
+ } else {
+ if(!(vbuf = crget(dbf->curia, key.dptr, key.dsize, 0, -1, &vsiz))){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ content.dptr = NULL;
+ content.dsize = 0;
+ return content;
+ }
+ }
+ content.dptr = vbuf;
+ content.dsize = vsiz;
+ return content;
+}
+
+
+/* Check whether a record exists or not. */
+int gdbm_exists(GDBM_FILE dbf, datum key){
+ assert(dbf);
+ if(!key.dptr || key.dsize < 0){
+ gdbm_errno = GDBM_ILLEGAL_DATA;
+ return FALSE;
+ }
+ if(dbf->depot){
+ if(dpvsiz(dbf->depot, key.dptr, key.dsize) == -1){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ return FALSE;
+ }
+ } else {
+ if(crvsiz(dbf->curia, key.dptr, key.dsize) == -1){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+
+/* Get the first key of a database. */
+datum gdbm_firstkey(GDBM_FILE dbf){
+ datum key;
+ assert(dbf);
+ memset(&key, 0, sizeof(datum));
+ if(dbf->depot){
+ if(dprnum(dbf->depot) < 1){
+ gdbm_errno = GDBM_EMPTY_DATABASE;
+ key.dptr = NULL;
+ key.dsize = 0;
+ return key;
+ }
+ dpiterinit(dbf->depot);
+ return gdbm_nextkey(dbf, key);
+ } else {
+ if(crrnum(dbf->curia) < 1){
+ gdbm_errno = GDBM_EMPTY_DATABASE;
+ key.dptr = NULL;
+ key.dsize = 0;
+ return key;
+ }
+ criterinit(dbf->curia);
+ return gdbm_nextkey(dbf, key);
+ }
+}
+
+
+/* Get the next key of a database. */
+datum gdbm_nextkey(GDBM_FILE dbf, datum key){
+ char *kbuf;
+ int ksiz;
+ assert(dbf);
+ if(dbf->depot){
+ if(!(kbuf = dpiternext(dbf->depot, &ksiz))){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ key.dptr = NULL;
+ key.dsize = 0;
+ return key;
+ }
+ } else {
+ if(!(kbuf = criternext(dbf->curia, &ksiz))){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ key.dptr = NULL;
+ key.dsize = 0;
+ return key;
+ }
+ }
+ key.dptr = kbuf;
+ key.dsize = ksiz;
+ return key;
+}
+
+
+/* Synchronize contents of updating with the file and the device. */
+void gdbm_sync(GDBM_FILE dbf){
+ assert(dbf);
+ if(dbf->depot){
+ if(!dpsync(dbf->depot)) gdbm_errno = gdbm_geterrno(dpecode);
+ } else {
+ if(!crsync(dbf->curia)) gdbm_errno = gdbm_geterrno(dpecode);
+ }
+}
+
+
+/* Reorganize a database. */
+int gdbm_reorganize(GDBM_FILE dbf){
+ assert(dbf);
+ if(dbf->depot){
+ if(!dpwritable(dbf->depot)){
+ gdbm_errno = GDBM_READER_CANT_REORGANIZE;
+ return -1;
+ }
+ if(!dpoptimize(dbf->depot, dprnum(dbf->depot) >= HV_INITBNUM ? -1 : HV_INITBNUM)){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ return -1;
+ }
+ } else {
+ if(!crwritable(dbf->curia)){
+ gdbm_errno = GDBM_READER_CANT_REORGANIZE;
+ return -1;
+ }
+ if(!croptimize(dbf->curia, crrnum(dbf->curia) >= HV_INITBNUM ? -1 : HV_INITBNUM)){
+ gdbm_errno = gdbm_geterrno(dpecode);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+/* Get the file descriptor of a database file. */
+int gdbm_fdesc(GDBM_FILE dbf){
+ assert(dbf);
+ if(dbf->depot){
+ return dpfdesc(dbf->depot);
+ } else {
+ return -1;
+ }
+}
+
+
+/* No effect. */
+int gdbm_setopt(GDBM_FILE dbf, int option, int *value, int size){
+ assert(dbf);
+ return 0;
+}
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Get the pointer of the last happened error code. */
+int *gdbm_errnoptr(void){
+ static int deferrno = GDBM_NO_ERROR;
+ void *ptr;
+ if(_qdbm_ptsafe){
+ if(!(ptr = _qdbm_settsd(&deferrno, sizeof(int), &deferrno))){
+ deferrno = GDBM_ILLEGAL_DATA;
+ return &deferrno;
+ }
+ return (int *)ptr;
+ }
+ return &deferrno;
+}
+
+
+
+/*************************************************************************************************
+ * private objects
+ *************************************************************************************************/
+
+
+/* Get the error code of GDBM corresponding to an error code of Depot.
+ `ecode' specifies an error code of Depot.
+ The return value is the error code of GDBM. */
+static int gdbm_geterrno(int ecode){
+ switch(ecode){
+ case DP_ENOERR: return GDBM_NO_ERROR;
+ case DP_EBROKEN: return GDBM_BAD_MAGIC_NUMBER;
+ case DP_EKEEP: return GDBM_CANNOT_REPLACE;
+ case DP_ENOITEM: return GDBM_ITEM_NOT_FOUND;
+ case DP_EALLOC: return GDBM_MALLOC_ERROR;
+ case DP_EOPEN: return GDBM_FILE_OPEN_ERROR;
+ case DP_ESEEK: return GDBM_FILE_SEEK_ERROR;
+ case DP_EREAD: return GDBM_FILE_READ_ERROR;
+ case DP_EWRITE: return GDBM_FILE_WRITE_ERROR;
+ case DP_EMKDIR: return GDBM_FILE_OPEN_ERROR;
+ default: break;
+ }
+ return GDBM_ILLEGAL_DATA;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/hovel.h b/qdbm/hovel.h
new file mode 100644
index 00000000..f95a3176
--- /dev/null
+++ b/qdbm/hovel.h
@@ -0,0 +1,278 @@
+/*************************************************************************************************
+ * The GDBM-compatible API of QDBM
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#ifndef _HOVEL_H /* duplication check */
+#define _HOVEL_H
+
+#if defined(__cplusplus) /* export for C++ */
+extern "C" {
+#endif
+
+
+#include <depot.h>
+#include <curia.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+
+#if defined(_MSC_VER) && !defined(QDBM_INTERNAL) && !defined(QDBM_STATIC)
+#define MYEXTERN extern __declspec(dllimport)
+#else
+#define MYEXTERN extern
+#endif
+
+
+
+/*************************************************************************************************
+ * API
+ *************************************************************************************************/
+
+
+enum { /* enumeration for error codes */
+ GDBM_NO_ERROR, /* no error */
+ GDBM_MALLOC_ERROR, /* malloc error */
+ GDBM_BLOCK_SIZE_ERROR, /* block size error */
+ GDBM_FILE_OPEN_ERROR, /* file open error */
+ GDBM_FILE_WRITE_ERROR, /* file write error */
+ GDBM_FILE_SEEK_ERROR, /* file seek error */
+ GDBM_FILE_READ_ERROR, /* file read error */
+ GDBM_BAD_MAGIC_NUMBER, /* bad magic number */
+ GDBM_EMPTY_DATABASE, /* empty database */
+ GDBM_CANT_BE_READER, /* can't be reader */
+ GDBM_CANT_BE_WRITER, /* can't be writer */
+ GDBM_READER_CANT_DELETE, /* reader can't delete */
+ GDBM_READER_CANT_STORE, /* reader can't store */
+ GDBM_READER_CANT_REORGANIZE, /* reader can't reorganize */
+ GDBM_UNKNOWN_UPDATE, /* unknown update */
+ GDBM_ITEM_NOT_FOUND, /* item not found */
+ GDBM_REORGANIZE_FAILED, /* reorganize failed */
+ GDBM_CANNOT_REPLACE, /* cannot replace */
+ GDBM_ILLEGAL_DATA, /* illegal data */
+ GDBM_OPT_ALREADY_SET, /* option already set */
+ GDBM_OPT_ILLEGAL /* option illegal */
+};
+
+typedef int gdbm_error; /* type of error codes */
+
+typedef struct { /* type of structure for a database handle */
+ DEPOT *depot; /* internal database handle of Depot */
+ CURIA *curia; /* internal database handle of Curia */
+ int syncmode; /* whether to be besyncronous mode */
+} GDBM;
+
+typedef GDBM *GDBM_FILE; /* type of pointer to a database handle */
+
+typedef struct { /* type of structure for a key or a value */
+ char *dptr; /* pointer to the region */
+ size_t dsize; /* size of the region */
+} datum;
+
+enum { /* enumeration for open modes */
+ GDBM_READER = 1 << 0, /* open as a reader */
+ GDBM_WRITER = 1 << 1, /* open as a writer */
+ GDBM_WRCREAT = 1 << 2, /* a writer creating */
+ GDBM_NEWDB = 1 << 3, /* a writer creating and truncating */
+ GDBM_SYNC = 1 << 4, /* syncronous mode */
+ GDBM_NOLOCK = 1 << 5, /* no lock mode */
+ GDBM_LOCKNB = 1 << 6, /* non-blocking lock mode */
+ GDBM_FAST = 1 << 7, /* fast mode */
+ GDBM_SPARSE = 1 << 8 /* create as sparse file */
+};
+
+enum { /* enumeration for write modes */
+ GDBM_INSERT, /* keep an existing value */
+ GDBM_REPLACE /* overwrite an existing value */
+};
+
+enum { /* enumeration for options */
+ GDBM_CACHESIZE, /* set cache size */
+ GDBM_FASTMODE, /* set fast mode */
+ GDBM_SYNCMODE, /* set syncronous mode */
+ GDBM_CENTFREE, /* set free block pool */
+ GDBM_COALESCEBLKS /* set free block marging */
+};
+
+
+/* String containing the version information. */
+MYEXTERN char *gdbm_version;
+
+
+/* Last happened error code. */
+#define gdbm_errno (*gdbm_errnoptr())
+
+
+/* Get a message string corresponding to an error code.
+ `gdbmerrno' specifies an error code.
+ The return value is the message string of the error code. The region of the return value
+ is not writable. */
+char *gdbm_strerror(gdbm_error gdbmerrno);
+
+
+/* Get a database handle after the fashion of GDBM.
+ `name' specifies a name of a database.
+ `read_write' specifies the connection mode: `GDBM_READER' as a reader, `GDBM_WRITER',
+ `GDBM_WRCREAT' and `GDBM_NEWDB' as a writer. `GDBM_WRCREAT' makes a database file or
+ directory if it does not exist. `GDBM_NEWDB' makes a new database even if it exists.
+ You can add the following to writer modes by bitwise or: `GDBM_SYNC', `GDBM_NOLOCK',
+ `GDBM_LCKNB', `GDBM_FAST', and `GDBM_SPARSE'. `GDBM_SYNC' means a database is synchronized
+ after every updating method. `GDBM_NOLOCK' means a database is opened without file locking.
+ `GDBM_LOCKNB' means file locking is performed without blocking. `GDBM_FAST' is ignored.
+ `GDBM_SPARSE' is an original mode of QDBM and makes database a sparse file.
+ `mode' specifies a mode of a database file or a database directory as the one of `open'
+ or `mkdir' call does.
+ `bnum' specifies the number of elements of each bucket array. If it is not more than 0,
+ the default value is specified.
+ `dnum' specifies the number of division of the database. If it is not more than 0, the
+ returning handle is created as a wrapper of Depot, else, it is as a wrapper of Curia.
+ The return value is the database handle or `NULL' if it is not successful.
+ If the database already exists, whether it is one of Depot or Curia is measured
+ automatically. */
+GDBM_FILE gdbm_open(char *name, int block_size, int read_write, int mode,
+ void (*fatal_func)(void));
+
+
+/* Get a database handle after the fashion of QDBM.
+ `name' specifies a name of a database.
+ `read_write' specifies the connection mode: `GDBM_READER' as a reader, `GDBM_WRITER',
+ `GDBM_WRCREAT' and `GDBM_NEWDB' as a writer. `GDBM_WRCREAT' makes a database file or
+ directory if it does not exist. `GDBM_NEWDB' makes a new database even if it exists.
+ You can add the following to writer modes by bitwise or: `GDBM_SYNC', `GDBM_NOLOCK',
+ `GDBM_LOCKNB', `GDBM_FAST', and `GDBM_SPARSE'. `GDBM_SYNC' means a database is synchronized
+ after every updating method. `GDBM_NOLOCK' means a database is opened without file locking.
+ `GDBM_LOCKNB' means file locking is performed without blocking. `GDBM_FAST' is ignored.
+ `GDBM_SPARSE' is an original mode of QDBM and makes database sparse files.
+ `mode' specifies a mode of a database file as the one of `open' or `mkdir' call does.
+ `bnum' specifies the number of elements of each bucket array. If it is not more than 0,
+ the default value is specified.
+ `dnum' specifies the number of division of the database. If it is not more than 0, the
+ returning handle is created as a wrapper of Depot, else, it is as a wrapper of Curia.
+ `align' specifies the basic size of alignment.
+ The return value is the database handle or `NULL' if it is not successful. */
+GDBM_FILE gdbm_open2(char *name, int read_write, int mode, int bnum, int dnum, int align);
+
+
+/* Close a database handle.
+ `dbf' specifies a database handle.
+ Because the region of the closed handle is released, it becomes impossible to use the
+ handle. */
+void gdbm_close(GDBM_FILE dbf);
+
+
+/* Store a record.
+ `dbf' specifies a database handle connected as a writer.
+ `key' specifies a structure of a key. `content' specifies a structure of a value.
+ `flag' specifies behavior when the key overlaps, by the following values: `GDBM_REPLACE',
+ which means the specified value overwrites the existing one, `GDBM_INSERT', which means
+ the existing value is kept.
+ The return value is 0 if it is successful, 1 if it gives up because of overlaps of the key,
+ -1 if other error occurs. */
+int gdbm_store(GDBM_FILE dbf, datum key, datum content, int flag);
+
+
+/* Delete a record.
+ `dbf' specifies a database handle connected as a writer.
+ `key' specifies a structure of a key.
+ The return value is 0 if it is successful, -1 if some errors occur. */
+int gdbm_delete(GDBM_FILE dbf, datum key);
+
+
+/* Retrieve a record.
+ `dbf' specifies a database handle.
+ `key' specifies a structure of a key.
+ The return value is a structure of the result.
+ If a record corresponds, the member `dptr' of the structure is the pointer to the region
+ of the value. If no record corresponds or some errors occur, `dptr' is `NULL'. Because
+ the region pointed to by `dptr' is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+datum gdbm_fetch(GDBM_FILE dbf, datum key);
+
+
+/* Check whether a record exists or not.
+ `dbf' specifies a database handle.
+ `key' specifies a structure of a key.
+ The return value is true if a record corresponds and no error occurs, or false, else,
+ it is false. */
+int gdbm_exists(GDBM_FILE dbf, datum key);
+
+
+/* Get the first key of a database.
+ `dbf' specifies a database handle.
+ The return value is a structure of the result.
+ If a record corresponds, the member `dptr' of the structure is the pointer to the region
+ of the first key. If no record corresponds or some errors occur, `dptr' is `NULL'.
+ Because the region pointed to by `dptr' is allocated with the `malloc' call, it should
+ be released with the `free' call if it is no longer in use. */
+datum gdbm_firstkey(GDBM_FILE dbf);
+
+
+/* Get the next key of a database.
+ `dbf' specifies a database handle.
+ The return value is a structure of the result.
+ If a record corresponds, the member `dptr' of the structure is the pointer to the region
+ of the next key. If no record corresponds or some errors occur, `dptr' is `NULL'.
+ Because the region pointed to by `dptr' is allocated with the `malloc' call, it should
+ be released with the `free' call if it is no longer in use. */
+datum gdbm_nextkey(GDBM_FILE dbf, datum key);
+
+
+/* Synchronize updating contents with the file and the device.
+ `dbf' specifies a database handle connected as a writer. */
+void gdbm_sync(GDBM_FILE dbf);
+
+
+/* Reorganize a database.
+ `dbf' specifies a database handle connected as a writer.
+ If successful, the return value is 0, else -1. */
+int gdbm_reorganize(GDBM_FILE dbf);
+
+
+/* Get the file descriptor of a database file.
+ `dbf' specifies a database handle connected as a writer.
+ The return value is the file descriptor of the database file.
+ If the database is a directory the return value is -1. */
+int gdbm_fdesc(GDBM_FILE dbf);
+
+
+/* No effect.
+ `dbf' specifies a database handle.
+ `option' is ignored. `size' is ignored.
+ The return value is 0.
+ The function is only for compatibility. */
+int gdbm_setopt(GDBM_FILE dbf, int option, int *value, int size);
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Get the pointer of the last happened error code. */
+int *gdbm_errnoptr(void);
+
+
+
+#undef MYEXTERN
+
+#if defined(__cplusplus) /* export for C++ */
+}
+#endif
+
+#endif /* duplication check */
+
+
+/* END OF FILE */
diff --git a/qdbm/hvmgr.c b/qdbm/hvmgr.c
new file mode 100644
index 00000000..f4141dcc
--- /dev/null
+++ b/qdbm/hvmgr.c
@@ -0,0 +1,582 @@
+/*************************************************************************************************
+ * Utility for debugging Hovel and its applications
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <hovel.h>
+#include <cabin.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define ALIGNSIZ 16 /* basic size of alignment */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+char *hextoobj(const char *str, int *sp);
+int runcreate(int argc, char **argv);
+int runstore(int argc, char **argv);
+int rundelete(int argc, char **argv);
+int runfetch(int argc, char **argv);
+int runlist(int argc, char **argv);
+int runoptimize(int argc, char **argv);
+void pgerror(const char *name);
+void printobj(const char *obj, int size);
+void printobjhex(const char *obj, int size);
+int docreate(char *name, int qdbm, int bnum, int dnum, int sparse);
+int dostore(char *name, int qdbm, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int ins);
+int dodelete(char *name, int qdbm, const char *kbuf, int ksiz);
+int dofetch(char *name, int qdbm, const char *kbuf, int ksiz, int ox, int nb);
+int dolist(char *name, int qdbm, int ox);
+int dooptimize(char *name, int qdbm);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "create")){
+ rv = runcreate(argc, argv);
+ } else if(!strcmp(argv[1], "store")){
+ rv = runstore(argc, argv);
+ } else if(!strcmp(argv[1], "delete")){
+ rv = rundelete(argc, argv);
+ } else if(!strcmp(argv[1], "fetch")){
+ rv = runfetch(argc, argv);
+ } else if(!strcmp(argv[1], "list")){
+ rv = runlist(argc, argv);
+ } else if(!strcmp(argv[1], "optimize")){
+ rv = runoptimize(argc, argv);
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: administration utility for Hovel\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s create [-qdbm bnum dnum] [-s] name\n", progname);
+ fprintf(stderr, " %s store [-qdbm] [-kx] [-vx|-vf] [-insert] name key val\n", progname);
+ fprintf(stderr, " %s delete [-qdbm] [-kx] name key\n", progname);
+ fprintf(stderr, " %s fetch [-qdbm] [-kx] [-ox] [-n] name key\n", progname);
+ fprintf(stderr, " %s list [-qdbm] [-ox] name\n", progname);
+ fprintf(stderr, " %s optimize [-qdbm] name\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* create a binary object from a hexadecimal string */
+char *hextoobj(const char *str, int *sp){
+ char *buf, mbuf[3];
+ int len, i, j;
+ len = strlen(str);
+ if(!(buf = malloc(len + 1))) return NULL;
+ j = 0;
+ for(i = 0; i < len; i += 2){
+ while(strchr(" \n\r\t\f\v", str[i])){
+ i++;
+ }
+ if((mbuf[0] = str[i]) == '\0') break;
+ if((mbuf[1] = str[i+1]) == '\0') break;
+ mbuf[2] = '\0';
+ buf[j++] = (char)strtol(mbuf, NULL, 16);
+ }
+ buf[j] = '\0';
+ *sp = j;
+ return buf;
+}
+
+
+/* parse arguments of create command */
+int runcreate(int argc, char **argv){
+ char *name;
+ int i, sb, qdbm, bnum, dnum, rv;
+ name = NULL;
+ sb = FALSE;
+ qdbm = FALSE;
+ bnum = -1;
+ dnum = -1;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-qdbm")){
+ qdbm = TRUE;
+ if(++i >= argc) usage();
+ bnum = atoi(argv[i]);
+ if(++i >= argc) usage();
+ dnum = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-s")){
+ sb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = docreate(name, qdbm, bnum, dnum, sb);
+ return rv;
+}
+
+
+/* parse arguments of store command */
+int runstore(int argc, char **argv){
+ char *name, *key, *val, *kbuf, *vbuf;
+ int i, qdbm, kx, vx, vf, ins, ksiz, vsiz, rv;
+ name = NULL;
+ qdbm = FALSE;
+ kx = FALSE;
+ vx = FALSE;
+ vf = FALSE;
+ ins = FALSE;
+ key = NULL;
+ val = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-qdbm")){
+ qdbm = TRUE;
+ } else if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-vx")){
+ vx = TRUE;
+ } else if(!strcmp(argv[i], "-vf")){
+ vf = TRUE;
+ } else if(!strcmp(argv[i], "-insert")){
+ ins = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else if(!val){
+ val = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key || !val) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = strlen(kbuf);
+ }
+ if(vx){
+ vbuf = hextoobj(val, &vsiz);
+ } else if(vf){
+ vbuf = cbreadfile(val, &vsiz);
+ } else {
+ vbuf = cbmemdup(val, -1);
+ vsiz = strlen(vbuf);
+ }
+ if(kbuf && vbuf){
+ rv = dostore(name, qdbm, kbuf, ksiz, vbuf, vsiz, ins);
+ } else {
+ if(vf){
+ fprintf(stderr, "%s: %s: cannot read\n", progname, val);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ }
+ rv = 1;
+ }
+ free(kbuf);
+ free(vbuf);
+ return rv;
+}
+
+
+/* parse arguments of delete command */
+int rundelete(int argc, char **argv){
+ char *name, *key, *kbuf;
+ int i, qdbm, kx, ksiz, rv;
+ name = NULL;
+ qdbm = FALSE;
+ kx = FALSE;
+ key = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-qdbm")){
+ qdbm = TRUE;
+ } else if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = strlen(kbuf);
+ }
+ if(kbuf){
+ rv = dodelete(name, qdbm, kbuf, ksiz);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ rv = 1;
+ }
+ free(kbuf);
+ return rv;
+}
+
+
+/* parse arguments of fetch command */
+int runfetch(int argc, char **argv){
+ char *name, *key, *kbuf;
+ int i, qdbm, kx, ox, nb, ksiz, rv;
+ name = NULL;
+ qdbm = FALSE;
+ kx = FALSE;
+ ox = FALSE;
+ nb = FALSE;
+ key = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-qdbm")){
+ qdbm = TRUE;
+ } else if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ox")){
+ ox = TRUE;
+ } else if(!strcmp(argv[i], "-n")){
+ nb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = strlen(kbuf);
+ }
+ if(kbuf){
+ rv = dofetch(name, qdbm, kbuf, ksiz, ox, nb);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ rv = 1;
+ }
+ free(kbuf);
+ return rv;
+}
+
+
+/* parse arguments of list command */
+int runlist(int argc, char **argv){
+ char *name;
+ int i, qdbm, ox, rv;
+ name = NULL;
+ qdbm = FALSE;
+ ox = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-qdbm")){
+ qdbm = TRUE;
+ } else if(!strcmp(argv[i], "-ox")){
+ ox = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = dolist(name, qdbm, ox);
+ return rv;
+}
+
+
+/* parse arguments of optimize command */
+int runoptimize(int argc, char **argv){
+ char *name;
+ int i, qdbm, rv;
+ name = NULL;
+ qdbm = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-qdbm")){
+ qdbm = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = dooptimize(name, qdbm);
+ return rv;
+}
+
+
+/* print an error message */
+void pgerror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, gdbm_strerror(gdbm_errno));
+}
+
+
+/* print an object */
+void printobj(const char *obj, int size){
+ int i;
+ for(i = 0; i < size; i++){
+ putchar(obj[i]);
+ }
+}
+
+
+/* print an object as a hexadecimal string */
+void printobjhex(const char *obj, int size){
+ int i;
+ for(i = 0; i < size; i++){
+ printf("%s%02X", i > 0 ? " " : "", ((const unsigned char *)obj)[i]);
+ }
+}
+
+
+/* perform create command */
+int docreate(char *name, int qdbm, int bnum, int dnum, int sparse){
+ GDBM_FILE dbf;
+ int rwmode;
+ rwmode = GDBM_NEWDB | (sparse ? GDBM_SPARSE : 0);
+ if(qdbm){
+ if(!(dbf = gdbm_open2(name, rwmode, 00644, bnum, dnum, ALIGNSIZ))){
+ pgerror(name);
+ return 1;
+ }
+ } else {
+ if(!(dbf = gdbm_open(name, 0, rwmode, 00644, NULL))){
+ pgerror(name);
+ return 1;
+ }
+ }
+ gdbm_close(dbf);
+ return 0;
+}
+
+
+/* perform store command */
+int dostore(char *name, int qdbm, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int ins){
+ GDBM_FILE dbf;
+ datum key, content;
+ int rv;
+ if(qdbm){
+ if(!(dbf = gdbm_open2(name, GDBM_WRITER, 00644, -1, -1, ALIGNSIZ))){
+ pgerror(name);
+ return 1;
+ }
+ } else {
+ if(!(dbf = gdbm_open(name, 0, GDBM_WRITER, 00644, NULL))){
+ pgerror(name);
+ return 1;
+ }
+ }
+ key.dptr = (char *)kbuf;
+ key.dsize = ksiz;
+ content.dptr = (char *)vbuf;
+ content.dsize = vsiz;
+ rv = 0;
+ if(gdbm_store(dbf, key, content, ins ? GDBM_INSERT : GDBM_REPLACE) != 0){
+ pgerror(name);
+ rv = 1;
+ }
+ gdbm_close(dbf);
+ return rv;
+}
+
+
+/* perform delete command */
+int dodelete(char *name, int qdbm, const char *kbuf, int ksiz){
+ GDBM_FILE dbf;
+ datum key;
+ int rv;
+ if(qdbm){
+ if(!(dbf = gdbm_open2(name, GDBM_WRITER, 00644, -1, -1, ALIGNSIZ))){
+ pgerror(name);
+ return 1;
+ }
+ } else {
+ if(!(dbf = gdbm_open(name, 0, GDBM_WRITER, 00644, NULL))){
+ pgerror(name);
+ return 1;
+ }
+ }
+ key.dptr = (char *)kbuf;
+ key.dsize = ksiz;
+ if(gdbm_delete(dbf, key) == 0){
+ rv = 0;
+ } else {
+ pgerror(name);
+ rv = 1;
+ }
+ gdbm_close(dbf);
+ return rv;
+}
+
+
+/* perform fetch command */
+int dofetch(char *name, int qdbm, const char *kbuf, int ksiz, int ox, int nb){
+ GDBM_FILE dbf;
+ datum key, content;
+ int rv;
+ if(qdbm){
+ if(!(dbf = gdbm_open2(name, GDBM_READER, 00644, -1, -1, -1))){
+ pgerror(name);
+ return 1;
+ }
+ } else {
+ if(!(dbf = gdbm_open(name, 0, GDBM_READER, 00644, NULL))){
+ pgerror(name);
+ return 1;
+ }
+ }
+ key.dptr = (char *)kbuf;
+ key.dsize = ksiz;
+ content = gdbm_fetch(dbf, key);
+ if(content.dptr){
+ if(ox){
+ printobjhex(content.dptr, content.dsize);
+ } else {
+ printobj(content.dptr, content.dsize);
+ }
+ if(!nb) putchar('\n');
+ rv = 0;
+ free(content.dptr);
+ } else {
+ pgerror(name);
+ rv = 1;
+ }
+ gdbm_close(dbf);
+ return rv;
+}
+
+
+/* perform list command */
+int dolist(char *name, int qdbm, int ox){
+ GDBM_FILE dbf;
+ datum key, val;
+ if(qdbm){
+ if(!(dbf = gdbm_open2(name, GDBM_READER, 00644, -1, -1, -1))){
+ pgerror(name);
+ return 1;
+ }
+ } else {
+ if(!(dbf = gdbm_open(name, 0, GDBM_READER, 00644, NULL))){
+ pgerror(name);
+ return 1;
+ }
+ }
+ for(key = gdbm_firstkey(dbf); key.dptr != NULL; key = gdbm_nextkey(dbf, key)){
+ val = gdbm_fetch(dbf, key);
+ if(!val.dptr){
+ free(key.dptr);
+ break;
+ }
+ if(ox){
+ printobjhex(key.dptr, key.dsize);
+ putchar('\t');
+ printobjhex(val.dptr, val.dsize);
+ } else {
+ printobj(key.dptr, key.dsize);
+ putchar('\t');
+ printobj(val.dptr, val.dsize);
+ }
+ putchar('\n');
+ free(val.dptr);
+ free(key.dptr);
+ }
+ gdbm_close(dbf);
+ return 0;
+}
+
+
+/* perform optimize command */
+int dooptimize(char *name, int qdbm){
+ GDBM_FILE dbf;
+ int rv;
+ if(qdbm){
+ if(!(dbf = gdbm_open2(name, GDBM_WRITER, 00644, -1, -1, ALIGNSIZ))){
+ pgerror(name);
+ return 1;
+ }
+ } else {
+ if(!(dbf = gdbm_open(name, 0, GDBM_WRITER, 00644, NULL))){
+ pgerror(name);
+ return 1;
+ }
+ }
+ rv = 0;
+ if(gdbm_reorganize(dbf) != 0){
+ pgerror(name);
+ rv = 1;
+ }
+ gdbm_close(dbf);
+ return rv;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/hvtest.c b/qdbm/hvtest.c
new file mode 100644
index 00000000..5aea24e8
--- /dev/null
+++ b/qdbm/hvtest.c
@@ -0,0 +1,272 @@
+/*************************************************************************************************
+ * Test cases of Hovel
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <hovel.h>
+#include <cabin.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define DIVNUM 5 /* number of division */
+#define ALIGNSIZ 16 /* basic size of alignment */
+#define RECBUFSIZ 32 /* buffer for records */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int printfflush(const char *format, ...);
+void pgerror(const char *name);
+int dowrite(char *name, int rnum, int qdbm, int sparse);
+int doread(char *name, int rnum, int qdbm);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "write")){
+ rv = runwrite(argc, argv);
+ } else if(!strcmp(argv[1], "read")){
+ rv = runread(argc, argv);
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: test cases for Hovel\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s write [-qdbm] [-s] name rnum\n", progname);
+ fprintf(stderr, " %s read [-qdbm] name rnum\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+ char *name, *rstr;
+ int i, sb, qdbm, rnum, rv;
+ name = NULL;
+ rstr = NULL;
+ rnum = 0;
+ sb = FALSE;
+ qdbm = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-qdbm")){
+ qdbm = TRUE;
+ } else if(!strcmp(argv[i], "-s")){
+ sb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!rstr){
+ rstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ rv = dowrite(name, rnum, qdbm, sb);
+ return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+ char *name, *rstr;
+ int i, qdbm, rnum, rv;
+ name = NULL;
+ rstr = NULL;
+ rnum = 0;
+ qdbm = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-qdbm")){
+ qdbm = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!rstr){
+ rstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ rv = doread(name, rnum, qdbm);
+ return rv;
+}
+
+
+/* print formatted string and flush the buffer */
+int printfflush(const char *format, ...){
+ va_list ap;
+ int rv;
+ va_start(ap, format);
+ rv = vprintf(format, ap);
+ if(fflush(stdout) == EOF) rv = -1;
+ va_end(ap);
+ return rv;
+}
+
+
+/* print an error message */
+void pgerror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, gdbm_strerror(gdbm_errno));
+}
+
+
+/* perform write command */
+int dowrite(char *name, int rnum, int qdbm, int sparse){
+ GDBM_FILE dbf;
+ datum key, content;
+ int i, rwmode, err, len;
+ char buf[RECBUFSIZ];
+ printfflush("<Writing Test>\n name=%s rnum=%d qdbm=%d\n\n", name, rnum, qdbm);
+ /* open a database */
+ rwmode = GDBM_NEWDB | (sparse ? GDBM_SPARSE : 0);
+ if(qdbm){
+ if(!(dbf = gdbm_open2(name, rwmode, 00644, rnum / DIVNUM, DIVNUM, ALIGNSIZ))){
+ pgerror(name);
+ return 1;
+ }
+ } else {
+ if(!(dbf = gdbm_open(name, 0, rwmode, 00644, NULL))){
+ pgerror(name);
+ return 1;
+ }
+ }
+ err = FALSE;
+ /* loop for each record */
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", i);
+ key.dptr = buf;
+ key.dsize = len;
+ content.dptr = buf;
+ content.dsize = len;
+ /* store a record */
+ if(gdbm_store(dbf, key, content, GDBM_REPLACE) != 0){
+ pgerror(name);
+ err = TRUE;
+ break;
+ }
+ /* print progression */
+ if(rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ /* close the database */
+ gdbm_close(dbf);
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(char *name, int rnum, int qdbm){
+ GDBM_FILE dbf;
+ datum key, content;
+ int i, err, len;
+ char buf[RECBUFSIZ];
+ printfflush("<Reading Test>\n name=%s rnum=%d qdbm=%d\n\n", name, rnum, qdbm);
+ /* open a database */
+ if(qdbm){
+ if(!(dbf = gdbm_open2(name, GDBM_READER, 00644, -1, -1, -1))){
+ pgerror(name);
+ return 1;
+ }
+ } else {
+ if(!(dbf = gdbm_open(name, 0, GDBM_READER, 00644, NULL))){
+ pgerror(name);
+ return 1;
+ }
+ }
+ err = FALSE;
+ /* loop for each record */
+ for(i = 1; i <= rnum; i++){
+ /* retrieve a record */
+ len = sprintf(buf, "%08d", i);
+ key.dptr = buf;
+ key.dsize = len;
+ content = gdbm_fetch(dbf, key);
+ if(!content.dptr){
+ pgerror(name);
+ err = TRUE;
+ break;
+ }
+ free(content.dptr);
+ /* print progression */
+ if(rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ /* close the database */
+ gdbm_close(dbf);
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/misc/COPYING.txt b/qdbm/misc/COPYING.txt
new file mode 100644
index 00000000..b1e3f5a2
--- /dev/null
+++ b/qdbm/misc/COPYING.txt
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/qdbm/misc/README-win32.txt b/qdbm/misc/README-win32.txt
new file mode 100644
index 00000000..d5a98e2d
--- /dev/null
+++ b/qdbm/misc/README-win32.txt
@@ -0,0 +1,101 @@
+================================================================
+ QDBM: Quick Database Manager
+ Copyright (C) 2000-2007 Mikio Hirabayashi
+================================================================
+
+This is a package of Win32 binaries of QDBM. It contains C/Java
+APIs, their utility commands, and CGI scripts.
+
+See http://qdbm.sourceforge.net/ for more information.
+
+
+The following are documents of specifications.
+
+ spex.html : fundamental specifications
+ spex-ja.html : fundamental specifications in Japanese
+ jspex.html : specifications of Java API
+ jspex-ja.html : specifications of Java API in Japanese
+ japidoc/ : documents of Java API
+
+
+The following are header files of C language.
+Include them at source codes of your applications.
+
+ depot.h
+ curia.h
+ relic.h
+ hovel.h
+ cabin.h
+ villa.h
+ vista.h
+ odeum.h
+
+
+The following are dynamic linking libraries for the API of C.
+Copy them to the system directory or a directory of your project.
+
+ qdbm.dll : QDBM itself
+ libqdbm.dll.a : import library for `qdbm.dll'
+ mgwz.dll : ZLIB
+ libiconv-2.dll : ICONV
+
+
+The following is a dynamic linking library for the API of Java.
+Copy it to the system directory or a directory of your project.
+
+ jqdbm.dll
+
+
+The following is a Java archive of the classes.
+Include it in the CLASSPATH of your environment.
+
+ qdbm.jar
+
+
+The following are utility commands for testing and debugging.
+
+ dpmgr.exe
+ dptest.exe
+ dptsv.exe
+ crmgr.exe
+ crtest.exe
+ crtsv.exe
+ rlmgr.exe
+ rltest.exe
+ hvmgr.exe
+ hvtest.exe
+ cbtest.exe
+ cbcodec.exe
+ vlmgr.exe
+ vltest.exe
+ vltsv.exe
+ odmgr.exe
+ odtest.exe
+ odidx.exe
+ qmttest.exe
+
+
+The sub directory `cgi' contains CGI scripts, their configuration
+files, and their specifications.
+
+If you want an import library or a static library for Visual C++,
+please obtain the source package and use VCmakefile in it.
+
+
+QDBM was released under the terms of the GNU Lesser General Public
+License. See the file `COPYING.txt' for details.
+
+QDBM was written by Mikio Hirabayashi. You can contact the author
+by e-mail to `mikio@users.sourceforge.net'. However, as for
+topics which can be shared among other users, pleae send it to
+the mailing list. To join the mailing list, refer to the following
+URL.
+
+ http://lists.sourceforge.net/lists/listinfo/qdbm-users
+
+
+Thanks.
+
+
+
+== END OF FILE ==
diff --git a/qdbm/misc/VCmakefile-old b/qdbm/misc/VCmakefile-old
new file mode 100644
index 00000000..8cd55467
--- /dev/null
+++ b/qdbm/misc/VCmakefile-old
@@ -0,0 +1,169 @@
+# Makefile to build QDBM using Microsoft Visual C++
+
+
+
+#================================================================
+# Setting Variables
+#================================================================
+
+
+# Targets
+MYLIBS = qdbm.lib
+LIBOBJS = depot.obj curia.obj relic.obj hovel.obj \
+ cabin.obj villa.obj vista.obj odeum.obj myconf.obj
+MYBINS = dpmgr.exe dptest.exe dptsv.exe crmgr.exe crtest.exe crtsv.exe \
+ rlmgr.exe rltest.exe hvmgr.exe hvtest.exe cbtest.exe cbcodec.exe \
+ vlmgr.exe vltest.exe vltsv.exe odmgr.exe odtest.exe odidx.exe
+
+# VC++ directory
+VCPATH = C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7
+
+# Building binaries
+CLFLAGS = /I "$(VCPATH)\Include" /I "$(VCPATH)\PlatformSDK\Include" /I "." /O2 /nologo
+LIBFLAGS = /libpath:"$(VCPATH)\lib" /libpath:"$(VCPATH)\PlatformSDK\Lib" /libpath:"." /nologo
+LINKFLAGS = /libpath:"$(VCPATH)\lib" /libpath:"$(VCPATH)\PlatformSDK\Lib" /libpath:"." /nologo
+
+
+
+#================================================================
+# Suffix rules
+#================================================================
+
+
+.SUFFIXES :
+.SUFFIXES : .c .obj
+
+.c.obj :
+ cl /c $(CLFLAGS) $<
+
+
+
+#================================================================
+# Actions
+#================================================================
+
+
+all : $(MYLIBS) $(MYBINS)
+
+
+clean :
+ del *.obj *.lib *.dll *.exp *.exe
+
+
+
+#================================================================
+# Building binaries
+#================================================================
+
+
+qdbm.lib : $(LIBOBJS)
+ lib $(LIBFLAGS) /OUT:$@ $(LIBOBJS)
+
+
+dpmgr.exe : dpmgr.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ dpmgr.obj qdbm.lib
+
+
+dptest.exe : dptest.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ dptest.obj qdbm.lib
+
+
+dptsv.exe : dptsv.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ dptsv.obj qdbm.lib
+
+
+crmgr.exe : crmgr.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ crmgr.obj qdbm.lib
+
+
+crtest.exe : crtest.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ crtest.obj qdbm.lib
+
+
+crtsv.exe : crtsv.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ crtsv.obj qdbm.lib
+
+
+rlmgr.exe : rlmgr.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ rlmgr.obj qdbm.lib
+
+
+rltest.exe : rltest.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ rltest.obj qdbm.lib
+
+
+hvmgr.exe : hvmgr.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ hvmgr.obj qdbm.lib
+
+
+hvtest.exe : hvtest.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ hvtest.obj qdbm.lib
+
+
+cbtest.exe : cbtest.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ cbtest.obj qdbm.lib
+
+
+cbcodec.exe : cbcodec.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ cbcodec.obj qdbm.lib
+
+
+vlmgr.exe : vlmgr.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ vlmgr.obj qdbm.lib
+
+
+vltest.exe : vltest.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ vltest.obj qdbm.lib
+
+
+vltsv.exe : vltsv.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ vltsv.obj qdbm.lib
+
+
+odmgr.exe : odmgr.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ odmgr.obj qdbm.lib
+
+
+odtest.exe : odtest.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ odtest.obj qdbm.lib
+
+
+odidx.exe : odidx.obj qdbm.lib
+ link $(LINKFLAGS) /OUT:$@ odidx.obj qdbm.lib
+
+
+depot.obj : depot.h myconf.h
+
+curia.obj : depot.h curia.h myconf.h
+
+relic.obj : depot.h relic.h myconf.h
+
+hovel.obj : depot.h curia.h hovel.h myconf.h
+
+cabin.obj : cabin.h myconf.h
+
+villa.obj : depot.h cabin.h villa.h myconf.h
+
+vista.obj : depot.h curia.h cabin.h villa.h vista.h myconf.h
+
+odeum.obj : depot.h curia.h cabin.h villa.h myconf.h
+
+myconf.obj : myconf.h
+
+dpmgr.obj dptest.obj dptsv.obj : depot.h cabin.h
+
+crmgr.obj crtest.obj crtsv.obj : depot.h curia.h cabin.h
+
+rlmgr.obj rltest.obj : depot.h relic.h cabin.h
+
+hvmgr.obj hvtest.obj : depot.h curia.h hovel.h cabin.h
+
+cbtest.obj cbcodec.obj : cabin.h
+
+vlmgr.obj vltest.obj vltsv.obj : depot.h cabin.h villa.h
+
+odmgr.obj odtest.obj odidx.obj : depot.h curia.h cabin.h villa.h odeum.h
+
+
+
+# END OF FILE
diff --git a/qdbm/misc/benchmark.pdf b/qdbm/misc/benchmark.pdf
new file mode 100644
index 00000000..8b9df904
--- /dev/null
+++ b/qdbm/misc/benchmark.pdf
Binary files differ
diff --git a/qdbm/misc/icon16.png b/qdbm/misc/icon16.png
new file mode 100644
index 00000000..6d6ed13d
--- /dev/null
+++ b/qdbm/misc/icon16.png
Binary files differ
diff --git a/qdbm/misc/icon20.png b/qdbm/misc/icon20.png
new file mode 100644
index 00000000..edad7849
--- /dev/null
+++ b/qdbm/misc/icon20.png
Binary files differ
diff --git a/qdbm/misc/index.html b/qdbm/misc/index.html
new file mode 100644
index 00000000..7a5271c3
--- /dev/null
+++ b/qdbm/misc/index.html
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta http-equiv="Content-Style-Type" content="text/css" />
+<meta http-equiv="Content-Script-Type" content="text/javascript" />
+<meta name="author" content="Mikio Hirabayashi" />
+<link rel="contents" href="./" />
+<link rel="icon" href="icon16.png" />
+<link rev="made" href="mailto:mikio@users.sourceforge.net" />
+<title>QDBM: Quick Database Manager</title>
+<style type="text/css">html { margin: 0em 0em; padding: 0em 0em; background: #eeeeee none; }
+body { margin: 2em 2em; padding: 0em 0em;
+ background: #eeeeee none; color: #111111;
+ font-style: normal; font-weight: normal; }
+h1 { margin-top: 1.8em; margin-bottom: 1.3em; font-weight: bold; }
+h2 { margin-top: 1.8em; margin-bottom: 1.1em; font-weight: bold;
+ border-left: solid 0.6em #445555; border-bottom: solid 1pt #bbbbbb;
+ padding: 0.5em 0.5em; width: 60%; }
+h3 { margin-top: 1.8em; margin-bottom: 0.8em; font-weight: bold; }
+hr { margin-top: 2.5em; margin-bottom: 1.5em; height: 1pt;
+ color: #999999; background-color: #999999; border: none; }
+div.note,div.navi { text-align: right; }
+div.logo { text-align: center; margin: 3em 0em; }
+div.logo img { border: inset 2pt #ccccdd; }
+p { margin: 0.8em 0em; line-height: 140%; }
+p,dd { text-indent: 0.8em; }
+div,pre { margin-left: 1.7em; margin-right: 1.7em; }
+pre { background-color: #ddddee; padding: 0.2em; border: 1pt solid #bbbbcc; font-size: smaller; }
+kbd { color: #111111; font-style: normal; font-weight: bold; }
+a { color: #0022aa; text-decoration: none; }
+a:hover,a:focus { color: #0033ee; text-decoration: underline; }
+a.head { color: #111111; text-decoration: none; }
+table { padding: 1pt 2pt 1pt 2pt; border: none; margin-left: 1.7em; border-collapse: collapse; }
+th { padding: 1pt 4pt 1pt 4pt; border-style: none;
+ text-align: left; vertical-align: bottom; }
+td { padding: 1pt 4pt 1pt 4pt; border: 1pt solid #333333;
+ text-align: left; vertical-align: top; }
+ul,ol,dl { line-height: 140%; }
+dt { margin-left: 1.2em; }
+dd { margin-left: 2.0em; }
+ul.lines { list-style-type: none; }
+@media print {
+ html,body { margin: 0em 0em; background-color: #ffffff; color: #000000; }
+ h1 { padding: 8em 0em 0.5em 0em; text-align: center; }
+ h2 { page-break-before: always; }
+ div.note { text-align: center; }
+ div.navi,div.logo { display: none }
+ hr { display: none; }
+ pre { margin: 0.8em 0.8em; background-color: #ffffff;
+ border: 1pt solid #aaaaaa; font-size: smaller; }
+ a,kbd { color: #000000; text-decoration: none; }
+ h1,h2,h3 { font-family: sans-serif; }
+ p,div,li,dt,dd { font-family: serif; }
+ pre,kbd { font-family: monospace; }
+ dd { font-size: smaller; }
+}
+</style>
+<script type="text/javascript">function startup(){
+ var elem = document.getElementById("headline");
+ if(elem){
+ var now = new Date();
+ if((now.getFullYear() + now.getMonth() + now.getDate() + now.getHours()) % 4 == 0){
+ var text;
+ switch((now.getMonth() + now.getDate() + now.getMinutes()) % 10){
+ default: text = "QDBM: Quicker Database Manager"; break;
+ case 1: text = "QDBM: Quantumtheoretically Dangerous Blast Machine"; break;
+ case 3: text = "QDBM: Quite Dramatic Body Metabolism"; break;
+ case 5: text = "QDBM: Quality Documentation of Black Market"; break;
+ case 7: text = "QDBM: Quake Damage Between Mountains"; break;
+ case 9: text = "QDBM: Quiet Down Before Millennium"; break;
+ }
+ elem.firstChild.nodeValue = text;
+ }
+ }
+}
+</script>
+</head>
+
+<body onload="startup();">
+
+<h1 id="headline">QDBM: Quick Database Manager</h1>
+
+<div class="note">Copyright (C) 2000-2007 Mikio Hirabayashi</div>
+<div class="note">Last Update: Thu, 26 Oct 2006 15:00:20 +0900</div>
+<div class="navi">[<span class="void">English</span>/<a href="index.ja.html" hreflang="ja">Japanese</a>]</div>
+
+<div class="logo"><img src="logo.png" alt="QDBM" width="300" height="110" /></div>
+
+<hr />
+
+<h2>Overview</h2>
+
+<p>QDBM is a library of routines for managing a database. The database is a simple data file containing records, each is a pair of a key and a value. Every key and value is serial bytes with variable length. Both binary data and character string can be used as a key and a value. There is neither concept of data tables nor data types. Records are organized in hash table or B+ tree.</p>
+
+<p>As for database of hash table, each key must be unique within a database, so it is impossible to store two or more records with a key overlaps. The following access methods are provided to the database: storing a record with a key and a value, deleting a record by a key, retrieving a record by a key. Moreover, traversal access to every key are provided, although the order is arbitrary. These access methods are similar to ones of DBM (or its followers: NDBM and GDBM) library defined in the UNIX standard. QDBM is an alternative for DBM because of its higher performance.</p>
+
+<p>As for database of B+ tree, records whose keys are duplicated can be stored. Access methods of storing, deleting, and retrieving are provided as with the database of hash table. Records are stored in order by a comparing function assigned by a user. It is possible to access each record with the cursor in ascending or descending order. According to this mechanism, forward matching search for strings and range search for integers are realized. Moreover, transaction is available in database of B+ tree.</p>
+
+<p>QDBM is written in C, and provided as APIs of C, C++, Java, Perl, and Ruby. QDBM is available on platforms which have API conforming to POSIX. QDBM is a free software licensed under the GNU Lesser General Public License.</p>
+
+<hr />
+
+<h2>Documents</h2>
+
+<p>The following are documents of QDBM. They are contained also in the source package.</p>
+
+<ul>
+<li><a href="spex.html">Fundamental Specifications</a></li>
+<li><a href="xspex.html">Specifications of C++ API</a></li>
+<li><a href="jspex.html">Specifications of Java API</a></li>
+<li><a href="plspex.html">Specifications of Perl API</a></li>
+<li><a href="rbspex.html">Specifications of Ruby API</a></li>
+<li><a href="cgispex.html">Specifications of CGI scripts</a></li>
+</ul>
+
+<hr />
+
+<h2>Packages</h2>
+
+<p>The following are packages of QDBM. The Linux binary package contains APIs for C, C++, and Java. It contains CGI scripts also. The Windows binary package contains APIs for C and Java. It contains CGI scripts also.</p>
+
+<ul>
+<li><a href="qdbm-1.8.77.tar.gz">Latest Source Package (version 1.8.77)</a></li>
+</ul>
+
+<ul>
+<li><a href="past/">Past Versions</a></li>
+<li><a href="rpm/">RPM Binary Packages for Linux</a></li>
+<li><a href="win/">Binary Packages for Windows</a></li>
+<li><a href="misc/">Related Packages</a></li>
+</ul>
+
+<ul>
+<li><a href="http://www.freshports.org/databases/qdbm/">Binary Packages for FreeBSD</a></li>
+<li><a href="http://www.blastwave.org/packages.php/qdbm">Binary Packages for Solaris</a></li>
+<li><a href="http://hpux.cs.utah.edu/hppd/cgi-bin/search?package=on&amp;description=on&amp;term=qdbm&amp;Search=Search">Binary Packages for HP-UX</a></li>
+<li><a href="http://www.sbellon.de/sw-ports.html">Binary Packages for RISC OS</a></li>
+</ul>
+
+<hr />
+
+<h2>Applications</h2>
+
+<p>The following are links to applications of QDBM. If you run or find a project using QDBM, please tell it, and it will added to this list.</p>
+
+<ul>
+<li><a href="http://hyperestraier.sourceforge.net/">Hyper Estraier</a> : Full-text Search System for Communities written by Mikio Hirabayashi</li>
+<li><a href="http://estraier.sourceforge.net/">Estraier</a> : Personal Full-text Search System written by Mikio Hirabayashi</li>
+<li><a href="http://diqt.sourceforge.net/">Diqt</a> : Multilingual Dictionary Searcher written by Mikio Hirabayashi</li>
+<li><a href="http://rbbs.sourceforge.jp/">RBBS</a> : WWW-based Bulletin Board System written by Mikio Hirabayashi</li>
+<li><a href="http://harvest.sourceforge.net/">Harvest</a> : Distributed Search System written by Kang-Jin Lee et al</li>
+<li><a href="http://bogofilter.sourceforge.net/">Bogofilter</a> : Bayesian Spam Mail Filter written by Eric S. Raymond et al</li>
+<li><a href="http://www.foo.be/mqs/">MQS</a> : Minimalist Queue Services written by Alexandre Dulaunoy</li>
+<li><a href="http://www.zedshaw.com/projects/ruby_odeum/">Ruby/Odeum</a> : Ruby Binding to the Inverted API written by Zed A. Shaw</li>
+<li><a href="http://www.surfulater.com/">Surfulater</a> : Utility to Save and Organize Web Pages written by Soft As it Gets Pty Ltd</li>
+<li><a href="http://mutt-ng.berlios.de/">Mutt-NG</a> : Text Based Mail Client written by Andreas Krennmair et al</li>
+<li><a href="http://smfs.sourceforge.net/">SMFS</a> : Smart Sendmail Filters written by Eugene Kurmanin</li>
+<li><a href="http://dixit.sourceforge.net/">Dixit</a> : Romanian Dictionary Searcher written by Octavian Procopiuc</li>
+</ul>
+
+<hr />
+
+<h2>Brothers</h2>
+
+<p>There are many followers of UNIX DBM. Select the best suited one for your products. NDBM is ancient and you should not use it. SDBM is maintained by Apache Project, and GDBM is maintained by GNU Project. They are most popular and time-tested. TDB is maintained by Samba Team. It allows multiple simultaneous writers. While CDB does not support updating at a runtime, it is the fastest. Berkeley DB is very multifunctional and ACID compliant. It is used in many commercial products. Finally, QDBM is balanced of performance, functionality, portability, and usability.</p>
+
+<ul>
+<li><a href="bros/ndbm-bsd-5.1.m1.tar.gz">NDBM</a> : New DBM written by BSD</li>
+<li><a href="bros/sdbm-1.0.2.tar.gz">SDBM</a> : Substitute DBM written by Ozan S. Yigit</li>
+<li><a href="bros/gdbm-1.8.3.tar.gz">GDBM</a> : GNU Database Manager written by Philip A. Nelson et al</li>
+<li><a href="bros/tdb-1.0.6.tar.gz">TDB</a> : Trivial Database written by Andrew Tridgell et al</li>
+<li><a href="bros/tinycdb-0.75.tar.gz">TinyCDB</a> : Constant Database written by Michael Tokarev</li>
+<li><a href="bros/db-4.4.20.tar.gz">Berkeley DB</a> : Berkeley DB written by Sleepycat Software</li>
+</ul>
+
+<ul>
+<li><a href="benchmark.pdf">Report of Benchmark Test</a></li>
+<li><a href="http://tokyocabinet.sourceforge.net/">Tokyo Cabinet</a> : the successor of QDBM</li>
+</ul>
+
+<hr />
+
+<h2>Information</h2>
+
+<p>QDBM was written by <a href="http://qdbm.sourceforge.net/mikio/">Mikio Hirabayashi</a>. You can contact the author by e-mail to `mikio@users.sourceforge.net'. However, as for topics which can be shared among other users, pleae send it to the mailing list. To join the mailing list, refer to `<a href="http://lists.sourceforge.net/lists/listinfo/qdbm-users">http://lists.sourceforge.net/lists/listinfo/qdbm-users</a>'.</p>
+
+<p>The project page on SourceForge.net is `<a href="http://sourceforge.net/projects/qdbm/">http://sourceforge.net/projects/qdbm/</a>'.</p>
+
+<p>Update of this project is announced on Freshmeat.net at `<a href="http://freshmeat.net/projects/qdbm/">http://freshmeat.net/projects/qdbm/</a>'.</p>
+
+<hr />
+
+</body>
+
+</html>
+
+<!-- END OF FILE -->
diff --git a/qdbm/misc/index.ja.html b/qdbm/misc/index.ja.html
new file mode 100644
index 00000000..65ea073c
--- /dev/null
+++ b/qdbm/misc/index.ja.html
@@ -0,0 +1,209 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta http-equiv="Content-Style-Type" content="text/css" />
+<meta http-equiv="Content-Script-Type" content="text/javascript" />
+<meta name="author" content="Mikio Hirabayashi" />
+<link rel="contents" href="./" />
+<link rel="icon" href="icon16.png" />
+<link rev="made" href="mailto:mikio@users.sourceforge.net" />
+<title>データベースライブラリ QDBM</title>
+<style type="text/css">html { margin: 0em 0em; padding: 0em 0em; background: #eeeeee none; }
+body { margin: 2em 2em; padding: 0em 0em;
+ background: #eeeeee none; color: #111111;
+ font-style: normal; font-weight: normal; }
+h1 { margin-top: 1.8em; margin-bottom: 1.3em; font-weight: bold; }
+h2 { margin-top: 1.8em; margin-bottom: 1.1em; font-weight: bold;
+ border-left: solid 0.6em #445555; border-bottom: solid 1pt #bbbbbb;
+ padding: 0.5em 0.5em; width: 60%; }
+h3 { margin-top: 1.8em; margin-bottom: 0.8em; font-weight: bold; }
+hr { margin-top: 2.5em; margin-bottom: 1.5em; height: 1pt;
+ color: #999999; background-color: #999999; border: none; }
+div.note,div.navi { text-align: right; }
+div.logo { text-align: center; margin: 3em 0em; }
+div.logo img { border: inset 2pt #ccccdd; }
+p { margin: 0.8em 0em; line-height: 140%; }
+p,dd { text-indent: 0.8em; }
+div,pre { margin-left: 1.7em; margin-right: 1.7em; }
+pre { background-color: #ddddee; padding: 0.2em; border: 1pt solid #bbbbcc; font-size: smaller; }
+kbd { color: #111111; font-style: normal; font-weight: bold; }
+a { color: #0022aa; text-decoration: none; }
+a:hover,a:focus { color: #0033ee; text-decoration: underline; }
+a.head { color: #111111; text-decoration: none; }
+table { padding: 1pt 2pt 1pt 2pt; border: none; margin-left: 1.7em; border-collapse: collapse; }
+th { padding: 1pt 4pt 1pt 4pt; border-style: none;
+ text-align: left; vertical-align: bottom; }
+td { padding: 1pt 4pt 1pt 4pt; border: 1pt solid #333333;
+ text-align: left; vertical-align: top; }
+ul,ol,dl { line-height: 140%; }
+dt { margin-left: 1.2em; }
+dd { margin-left: 2.0em; }
+ul.lines { list-style-type: none; }
+@media print {
+ html,body { margin: 0em 0em; background-color: #ffffff; color: #000000; }
+ h1 { padding: 8em 0em 0.5em 0em; text-align: center; }
+ h2 { page-break-before: always; }
+ div.note { text-align: center; }
+ div.navi,div.logo { display: none }
+ hr { display: none; }
+ pre { margin: 0.8em 0.8em; background-color: #ffffff;
+ border: 1pt solid #aaaaaa; font-size: smaller; }
+ a,kbd { color: #000000; text-decoration: none; }
+ h1,h2,h3 { font-family: sans-serif; }
+ p,div,li,dt,dd { font-family: serif; }
+ pre,kbd { font-family: monospace; }
+ dd { font-size: smaller; }
+}
+</style>
+<script type="text/javascript">function startup(){
+ var elem = document.getElementById("headline");
+ if(elem){
+ var now = new Date();
+ if((now.getFullYear() + now.getMonth() + now.getDate() + now.getHours()) % 4 == 0){
+ var text;
+ switch((now.getMonth() + now.getDate() + now.getMinutes()) % 24){
+ default: text = "QDBM: 高速資料基盤管理器"; break;
+ case 1: text = "QDBM: NDBMとは違うのだよ、NDBMとは!"; break;
+ case 3: text = "QDBM: QDBMは伊達じゃないっ!"; break;
+ case 5: text = "QDBM: 敢えて言おう、高速であると!"; break;
+ case 7: text = "QDBM: しかし、私もDBMのはずだ!"; break;
+ case 9: text = "QDBM: 今の私はQDBMだ。それ以上でもそれ以下でもない。"; break;
+ case 11: text = "QDBM: 私はBerkeleyとは関係ない。私はいつも一人のDBMだった。"; break;
+ case 13: text = "QDBM: 見せてもらおうか!Berkeleyのデータベースの性能とやらを!"; break;
+ case 15: text = "QDBM: 悲しいけどこれ、DBMなのよね。"; break;
+ case 17: text = "QDBM: 新しい時代を創るのはSQLではない!!"; break;
+ case 19: text = "QDBM: えぇぃ、マイナーチェンジのくせにっ!"; break;
+ case 21: text = "QDBM: よくもこんなくたびれたDBMが現役でいられるものだ。"; break;
+ case 22: text = "QDBM: 見える!私にもレコードが見える"; break;
+ case 23: text = "QDBM: そんなRDBみたいな口の利き方、おやめなさい!"; break;
+ }
+ elem.firstChild.nodeValue = text;
+ }
+ }
+}
+</script>
+</head>
+
+<body onload="startup();">
+
+<h1 id="headline">QDBM: Quick Database Manager</h1>
+
+<div class="note">Copyright (C) 2000-2007 Mikio Hirabayashi</div>
+<div class="note">Last Update: Thu, 26 Oct 2006 15:00:20 +0900</div>
+<div class="navi">[<a href="index.html" hreflang="en">English</a>/<span class="void">Japanese</span>]</div>
+
+<div class="logo"><img src="logo.png" alt="QDBM" width="300" height="110" /></div>
+
+<hr />
+
+<h2><a name="overview" id="overview" class="head">概要</a></h2>
+
+<p>QDBMはデータベースを扱うルーチン群のライブラリです。データベースといっても単純なもので、キーと値のペアからなるレコード群を格納したデータファイルです。キーと値は任意の長さを持つ一連のバイト列であり、文字列でもバイナリでも扱うことができます。テーブルやデータ型の概念はありません。レコードはハッシュ表またはB+木で編成されます。</p>
+
+<p>ハッシュ表のデータベースでは、キーはデータベース内で一意であり、キーが重複する複数のレコードを格納することはできません。このデータベースに対しては、キーと値を指定してレコードを格納したり、キーを指定して対応するレコードを削除したり、キーを指定して対応するレコードを検索したりすることができます。また、データベースに格納してある全てのキーを順不同に一つずつ取り出すこともできます。このような操作は、UNIX標準で定義されているDBMライブラリおよびその追従であるNDBMやGDBMに類するものです。QDBMはDBMのより良い代替として利用することができます。</p>
+
+<p>B+木のデータベースでは、キーが重複する複数のレコードを格納することができます。このデータベースに対しては、ハッシュ表のデータベースと同様に、キーを指定してレコードを格納したり取り出したり削除したりすることができます。レコードはユーザが指示した比較関数に基づいて整列されて格納されます。カーソルを用いて各レコードを昇順または降順で参照することができます。この機構によって、文字列の前方一致検索や数値の範囲検索が可能になります。また、B+木のデータベースではトランザクションが利用できます。</p>
+
+<p>QDBMはCで記述され、C、C++、Java、PerlおよびRubyのAPIとして提供されます。QDBMはPOSIX準拠のAPIを備えるプラットフォームで利用できます。QDBMはGNU Lesser General Public Licenseに基づくフリーソフトウェアです。</p>
+
+<hr />
+
+<h2>文書</h2>
+
+<p>以下の文書をお読みください。ソースパッケージにも同じものが含まれています。</p>
+
+<ul>
+<li><a href="spex-ja.html">基本仕様書</a></li>
+<li><a href="xspex-ja.html">C++用API仕様書</a></li>
+<li><a href="jspex-ja.html">Java用API仕様書</a></li>
+<li><a href="plspex-ja.html">Perl用API仕様書</a></li>
+<li><a href="rbspex-ja.html">Ruby用API仕様書</a></li>
+<li><a href="cgispex-ja.html">CGIスクリプト仕様書</a></li>
+<li><a href="tutorial-ja.html">チュートリアル</a></li>
+</ul>
+
+<hr />
+
+<h2>ダウンロード</h2>
+
+<p>以下のパッケージをダウンロードしてください。Linux用バイナリパッケージは、CとC++とJavaのAPIと、CGIスクリプトを納めています。Windows用バイナリパッケージは、CとJavaのAPIと、CGIスクリプトを納めています。</p>
+
+<ul>
+<li><a href="qdbm-1.8.77.tar.gz">最新のソースパッケージ(バージョン1.8.77)</a></li>
+</ul>
+
+<ul>
+<li><a href="past/">過去のバージョン</a></li>
+<li><a href="rpm/">Linux用バイナリパッケージ(RPM)</a></li>
+<li><a href="win/">Windows用バイナリパッケージ</a></li>
+<li><a href="misc/">関連するパッケージ</a></li>
+</ul>
+
+<ul>
+<li><a href="http://www.freshports.org/databases/qdbm/">FreeBSD用パッケージ</a></li>
+<li><a href="http://www.blastwave.org/packages.php/qdbm">Solaris用パッケージ</a></li>
+<li><a href="http://hpux.cs.utah.edu/hppd/cgi-bin/search?package=on&amp;description=on&amp;term=qdbm&amp;Search=Search">HP-UX用パッケージ</a></li>
+<li><a href="http://www.sbellon.de/sw-ports.html">RISC OS用パッケージ</a></li>
+</ul>
+
+<hr />
+
+<h2>アプリケーション</h2>
+
+<p>QDBMのアプリケーションには以下のものがあります。もしあなたのプロジェクトがQDBMを使っているなら、連絡をいただければこのリストに追加いたします。</p>
+
+<ul>
+<li><a href="http://hyperestraier.sourceforge.net/">Hyper Estraier</a> : 平林幹雄による共同体的全文検索システム</li>
+<li><a href="http://estraier.sourceforge.net/">Estraier</a> : 平林幹雄による個人用全文検索システム</li>
+<li><a href="http://diqt.sourceforge.net/">Diqt</a> : 平林幹雄による多言語辞書検索システム</li>
+<li><a href="http://rbbs.sourceforge.jp/">RBBS</a> : 平林幹雄によるWeb掲示板システム</li>
+<li><a href="http://harvest.sourceforge.net/">Harvest</a> : Kang-Jin Leeらによる分散情報検索システム</li>
+<li><a href="http://bogofilter.sourceforge.net/">Bogofilter</a> : Eric S. Raymondらによるベイズ式スパムメールフィルタ</li>
+<li><a href="http://www.foo.be/mqs/">MQS</a> : Alexandre Dulaunoyによるキュー管理サービス</li>
+<li><a href="http://www.zedshaw.com/projects/ruby_odeum/">Ruby/Odeum</a> : Zed A. Shawによる転置APIのRuby用インターフェイス</li>
+<li><a href="http://www.surfulater.com/">Surfulater</a> : Soft As it Gets Pty社によるWebページスクラップブック</li>
+<li><a href="http://mutt-ng.berlios.de/">Mutt-NG</a> : Andreas Krennmairらによるテキスト型メールクライアント</li>
+<li><a href="http://smfs.sourceforge.net/">SMFS</a> : Eugene KurmaninによるSendmail用フィルタシステム</li>
+<li><a href="http://dixit.sourceforge.net/">Dixit</a> : Octavian Procopiucによるルーマニア語辞書検索システム</li>
+</ul>
+
+<hr />
+
+<h2>兄弟</h2>
+
+<p>UNIX標準のDBMに追従したライブラリはたくさんあります。それぞれ特徴がありますので、あなたの製品にとって都合がいいものを選んでください。NDBMは古いので、もはや使う理由はないでしょう。SDBMはApache Projectで管理され、GDBMはGNU Projectで管理されています。それらは最も人気があり、また数多くテストされているものでしょう。TDBはSamba Teamで管理されており、複数同時の書き込みが可能です。TinyCDBは随時の更新はできませんが、最も高速に動作します。Berkeley DBは非常に多機能で、ACID準拠で、多くの商用製品で使われています。最後に、QDBMは性能と機能と移植性と利便性のバランスがとれた製品です。</p>
+
+<ul>
+<li><a href="bros/ndbm-bsd-5.1.m1.tar.gz">NDBM</a> : BSDによるNew DBM</li>
+<li><a href="bros/sdbm-1.0.2.tar.gz">SDBM</a> : Ozan S. YigitによるSubstitute DBM</li>
+<li><a href="bros/gdbm-1.8.3.tar.gz">GDBM</a> : Philip A. NelsonらによるGNU Database Manager</li>
+<li><a href="bros/tdb-1.0.6.tar.gz">TDB</a> : Andrew TridgellらによるTrivial Database</li>
+<li><a href="bros/tinycdb-0.75.tar.gz">TinyCDB</a> : Michael TokarevによるConstant Database</li>
+<li><a href="bros/db-4.4.20.tar.gz">Berkeley DB</a> : Sleepycat Software社によるBerkeley DB</li>
+</ul>
+
+<ul>
+<li><a href="benchmark.pdf">ベンチマークテストのレポート</a></li>
+<li><a href="http://tokyocabinet.sourceforge.net/">Tokyo Cabinet</a> : QDBMの後継製品</li>
+</ul>
+
+<hr />
+
+<h2>その他の情報</h2>
+
+<p>QDBMは<a href="http://qdbm.sourceforge.net/mikio/">平林幹雄</a>が作成しました。作者と連絡をとるには、`mikio@users.sourceforge.net' 宛に電子メールを送ってください。ただし、質問やバグレポートなど、他のユーザと共有できる話題はメーリングリストに送ってください。メーリングリストの参加方法については、`<a href="http://lists.sourceforge.net/lists/listinfo/qdbm-users">http://lists.sourceforge.net/lists/listinfo/qdbm-users</a>' を参照してください。</p>
+
+<p>SourceForge.netにおけるプロジェクトページは `<a href="http://sourceforge.net/projects/qdbm/">http://sourceforge.net/projects/qdbm/</a>' にあります。Freshmeatにおける更新情報は `<a href="http://freshmeat.net/projects/qdbm/">http://freshmeat.net/projects/qdbm/</a>' から参照できます。</p>
+
+<hr />
+
+</body>
+
+</html>
+
+<!-- END OF FILE -->
diff --git a/qdbm/misc/logo.png b/qdbm/misc/logo.png
new file mode 100644
index 00000000..d5cf9771
--- /dev/null
+++ b/qdbm/misc/logo.png
Binary files differ
diff --git a/qdbm/misc/makevcdef b/qdbm/misc/makevcdef
new file mode 100755
index 00000000..7b36e307
--- /dev/null
+++ b/qdbm/misc/makevcdef
@@ -0,0 +1,48 @@
+#! /bin/sh
+
+#================================================================
+# makevcdef
+# Generator of module definition file for Visual C++
+#================================================================
+
+
+# check arguments
+file="$1"
+if [ -f "$file" ]
+then
+ true
+else
+ printf 'usage: makevcdef library\n' 1>&2
+ exit 1
+fi
+
+
+# show headers
+name=`echo $file | sed -e 's/^lib//' -e 's/\..*//'`
+printf 'EXPORTS\r\n'
+
+
+# show variables
+nm -g "$file" | grep ' [BDR] ' | sed 's/.* [BDR] //' | grep -v '^_' | sort | uniq |
+while read name
+do
+ num=$((num + 1))
+ printf ' %s = %s DATA\r\n' "$name" "$name"
+done
+
+
+# show functions
+nm -g "$file" | grep ' T ' | sed 's/.* T //' | grep -v '^_' | sort | uniq |
+while read name
+do
+ num=$((num + 1))
+ printf ' %s = %s\r\n' "$name" "$name"
+done
+
+
+# exit normally
+exit 0
+
+
+
+# END OF FILE
diff --git a/qdbm/misc/mymemo-ja.html b/qdbm/misc/mymemo-ja.html
new file mode 100644
index 00000000..87de7843
--- /dev/null
+++ b/qdbm/misc/mymemo-ja.html
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta name="author" content="Mikio Hirabayashi" />
+<link rel="contents" href="./" />
+<title>My Private Memo for QDBM</title>
+</head>
+
+<body>
+
+<h1>QDBMのための私的メモ</h1>
+
+<hr />
+
+<h2>cygwin+mingwでのビルド環境の設定方法</h2>
+
+<ul>
+<li>Cygwinのセットアップ時に、mingwのgccとmingwのzlibも選択する。</li>
+<li>mingwのlibiconvはmingwのサイトから手に入れる。そして、*.h は /usr/include/mingw の下に、*.dll.a は /lib/mingw の下に、*.dll は /binの下に移動させる。</li>
+<li>QDBMのビルド環境の設定は ./configure --enable-zlib --enable-iconv で行い、パッケージの作成は make win32pkg で行う。</li>
+</ul>
+
+<hr />
+
+</body>
+
+</html>
+
+<!-- END OF FILE -->
diff --git a/qdbm/misc/tutorial-ja.html b/qdbm/misc/tutorial-ja.html
new file mode 100644
index 00000000..66f1c3c3
--- /dev/null
+++ b/qdbm/misc/tutorial-ja.html
@@ -0,0 +1,622 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta http-equiv="Content-Style-Type" content="text/css" />
+<meta name="author" content="Mikio Hirabayashi" />
+<meta name="keywords" content="QDBM, tutrial" />
+<meta name="description" content="tutorial of QDBM" />
+<link rel="contents" href="./" />
+<link rev="made" href="mailto:mikio@users.sourceforge.net" />
+<title>Tutorial of QDBM Version 1 (Japanese)</title>
+<style type="text/css">html { margin: 0em 0em; padding: 0em 0em; background: #eeeeee none; }
+body { margin: 2em 2em; padding: 0em 0em;
+ background: #eeeeee none; color: #111111;
+ font-style: normal; font-weight: normal; }
+h1 { margin-top: 1.8em; margin-bottom: 1.3em; font-weight: bold; }
+h2 { margin-top: 1.8em; margin-bottom: 1.1em; font-weight: bold;
+ border-left: solid 0.6em #445555; border-bottom: solid 1pt #bbbbbb;
+ padding: 0.5em 0.5em; width: 60%; }
+h3 { margin-top: 1.8em; margin-bottom: 0.8em; font-weight: bold; }
+hr { margin-top: 2.5em; margin-bottom: 1.5em; height: 1pt;
+ color: #999999; background-color: #999999; border: none; }
+div.note,div.navi { text-align: right; }
+div.logo { text-align: center; margin: 3em 0em; }
+div.logo img { border: inset 2pt #ccccdd; }
+p { margin: 0.8em 0em; line-height: 140%; }
+p,dd { text-indent: 0.8em; }
+div,pre { margin-left: 1.7em; margin-right: 1.7em; }
+pre { background-color: #ddddee; padding: 0.2em; border: 1pt solid #bbbbcc; font-size: smaller; }
+kbd { color: #111111; font-style: normal; font-weight: bold; }
+a { color: #0022aa; text-decoration: none; }
+a:hover,a:focus { color: #0033ee; text-decoration: underline; }
+a.head { color: #111111; text-decoration: none; }
+table { padding: 1pt 2pt 1pt 2pt; border: none; margin-left: 1.7em; border-collapse: collapse; }
+th { padding: 1pt 4pt 1pt 4pt; border-style: none;
+ text-align: left; vertical-align: bottom; }
+td { padding: 1pt 4pt 1pt 4pt; border: 1pt solid #333333;
+ text-align: left; vertical-align: top; }
+ul,ol,dl { line-height: 140%; }
+dt { margin-left: 1.2em; }
+dd { margin-left: 2.0em; }
+ul.lines { list-style-type: none; }
+@media print {
+ html,body { margin: 0em 0em; background-color: #ffffff; color: #000000; }
+ h1 { padding: 8em 0em 0.5em 0em; text-align: center; }
+ h2 { page-break-before: always; }
+ div.note { text-align: center; }
+ div.navi,div.logo { display: none }
+ hr { display: none; }
+ pre { margin: 0.8em 0.8em; background-color: #ffffff;
+ border: 1pt solid #aaaaaa; font-size: smaller; }
+ a,kbd { color: #000000; text-decoration: none; }
+ h1,h2,h3 { font-family: sans-serif; }
+ p,div,li,dt,dd { font-family: serif; }
+ pre,kbd { font-family: monospace; }
+ dd { font-size: smaller; }
+}
+</style>
+</head>
+
+<body>
+
+<h1>QDBMのチュートリアル</h1>
+
+<div class="note">Copyright (C) 2000-2007 Mikio Hirabayashi</div>
+<div class="note">Last Update: Thu, 26 Oct 2006 15:00:20 +0900</div>
+<div class="navi">[<a href="http://qdbm.sourceforge.net/">Home</a>]</div>
+
+<hr />
+
+<h2>目次</h2>
+
+<ol>
+<li><a href="#introduction">イントロダクション</a></li>
+<li><a href="#depot">Depot: 基本API</a></li>
+<li><a href="#curia">Curia: 拡張API</a></li>
+<li><a href="#relic">Relic: NDBM互換API</a></li>
+<li><a href="#hovel">Hovel: GDBM互換API</a></li>
+<li><a href="#cabin">Cabin: ユーティリティAPI</a></li>
+<li><a href="#villa">Villa: 上級API</a></li>
+<li><a href="#odeum">Odeum: 転置API</a></li>
+</ol>
+
+<hr />
+
+<h2><a name="introduction" id="introduction" class="head">イントロダクション</a></h2>
+
+<p>QDBMは、シンプルながら便利なデータベースライブラリです。データベースというとSQLやリレーショナルデータベースを思い浮かべる人が多いと思いますが、QDBMはそんな高機能なものではありません。「キー」と「値」の組からなるレコードをファイルに保存したり、保存しておいたレコードの中から特定のキーを持つものを取り出す機能を提供するだけです。そのような機能をここでは「ハッシュデータベース」と呼ぶことにします。ハッシュデータベースの特長は、使い方が簡単で、パフォーマンスが高いことです。</p>
+
+<p>QDBMはC言語のライブラリです(他の言語のAPIもありますが)。QDBMにはハッシュデータベースの機能だけでなく、私(QDBMの作者)がプログラミングをする時によく使う機能が詰め込まれています。Cで書いたプログラムは高速に動作するのが利点ですが、C++、Java、Perl、Rubyといった比較的高級な言語では標準的にサポートされるデータ構造やアルゴリズムを自分で実装しなければなりません。そういった作業は面倒ですし、バグを生みやすいものです。そこで、QDBMの登場です。QDBMを再利用すれば、CでのプログラミングがPerlを使っているかのように手軽になります。しかも、UNIXでもWindowsでもMac OS Xでも利用できるので、移植性のあるプログラムが書きやすくなります。</p>
+
+<p>シンプルといいながら、QDBMの機能はなかなか豊富です。データベースとしては、ハッシュ表とB+木が利用できます。メモリ上で扱うユーティリティとしては、リストやマップなどがあります。MIMEやCSVやXMLの解析もできます。しまいには全文検索までできたりするので驚きです。</p>
+
+<p>このチュートリアルではQDBMの使い方を簡単に説明するとともに、基本仕様書の補足を述べます。QDBMの詳細については基本仕様書を御覧ください。なお、ここではハッシュ表やB+木といったデータ構造の説明はしませんので、不慣れな方は適当な本やWebサイトで調べておいてください。</p>
+
+<hr />
+
+<h2><a name="depot" id="depot" class="head">Depot: 基本API</a></h2>
+
+<p>データベースアプリケーションの典型的な例として、「社員番号を入力すると、その内線番号がわかる」というプログラムを考えてみましょう。社員番号をキーとして、それに対応する値である内線番号を検索するということです。</p>
+
+<p>まずはQDBMを使わないでやってみます。社員番号と内線番号の対応表は、CSVテキストでファイルに保存することにします。書式の例を以下に示します。</p>
+
+<pre>00001,8-902-1234
+00002,7-938-834
+00008,4-214-491
+</pre>
+
+<p>レコードを加える関数 `putphonenumber' と、レコードを検索する関数 `getphonenumber' を実装します。</p>
+
+<pre>#include &lt;stdio.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;string.h&gt;
+
+#define PHONEFILE "phone"
+#define LINEBUFSIZ 256
+
+int putphonenumber(const char *id, const char *phone){
+ FILE *OUT;
+ /* ファイルを追記モードで開く */
+ if(!(OUT = fopen(PHONEFILE, "a"))) return -1;
+ /* レコードを書き込む */
+ fprintf(OUT, "%s,%s\n", id, phone);
+ /* ファイルを閉じる */
+ if(fclose(OUT) != 0) return -1;
+ return 0;
+}
+
+char *getphonenumber(const char *id){
+ FILE *IN;
+ char line[LINEBUFSIZ], *pivot, *phone;
+ int len;
+ /* ファイルを読み込みモードで開く */
+ if(!(IN = fopen(PHONEFILE, "r"))) return NULL;
+ /* 各行を読み込む */
+ while(fscanf(IN, "%s", line) == 1){
+ /* 区切り文字を処理する */
+ if(!(pivot = strchr(line, ','))) continue;
+ *pivot = '\0';
+ pivot++;
+ /* キーの一致判定 */
+ if(strcmp(line, id) == 0){
+ /* ファイルを閉じる */
+ if(fclose(IN) != 0) return NULL;
+ /* メモリを確保して戻り値を生成する */
+ len = strlen(pivot);
+ if(!(phone = malloc(len + 1))) return NULL;
+ memcpy(phone, pivot, len + 1);
+ return phone;
+ }
+ }
+ /* ファイルを閉じる */
+ fclose(IN);
+ return NULL;
+}
+</pre>
+
+<p>`fscanf' を使っている時点でかなり貧弱ですが、きちんと書こうとすると非常に長くなるので妥協しました(ちなみに、255文字を越える行があったら暴走します)。とにかく、この程度の処理でやたら長いコードを書かねばならないのでは悲しくなります。さらに重大な欠点は、検索の処理が遅いということです。ファイルの最初から最後まで(平均的には半分まで)読まなければならないからです。既存のレコードを修正する時にもかなり面倒なことをしなければなりません。</p>
+
+<p>QDBMを使えばもっとエレガントなコードが書けます。上記と同じ機能の関数を実装してみます。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;stdlib.h&gt;
+
+#define PHONEFILE "phone"
+
+int putphonenumber(const char *id, const char *phone){
+ DEPOT *depot;
+ /* データベースを追記モードで開く */
+ if(!(depot = dpopen(PHONEFILE, DP_OWRITER | DP_OCREAT, -1))) return -1;
+ /* レコードを書き込む */
+ dpput(depot, id, -1, phone, -1, DP_DOVER);
+ /* データベースを閉じる */
+ if(!dpclose(depot)) return -1;
+ return 0;
+}
+
+char *getphonenumber(const char *id){
+ DEPOT *depot;
+ char *phone;
+ /* データベースを読み込みモードで開く */
+ if(!(depot = dpopen(PHONEFILE, DP_OREADER, -1))) return NULL;
+ /* レコードを探索して戻り値を生成する */
+ phone = dpget(depot, id, -1, 0, -1, NULL);
+ /* データベースを閉じる */
+ dpclose(depot);
+ return phone;
+}
+</pre>
+
+<p>もはやファイル形式はCSVファイルではなく、区切り文字が何であるか気にする必要はありません。プログラマはファイル形式がどうであるかなど考えなくてもよいのです。メモリの確保などもQDBMの内部でやってくれるので、バッファのサイズを気にする必要はありません(解放は必要です)。処理速度を気にする必要もありません。データベースがどんなに大きくても、レコードの追加や削除や検索が一瞬でできます。このように、プログラマをデータ管理の苦悩から解放するのがQDBMの役割です。</p>
+
+<p>上記の例ではQDBMの基本APIであるDepotを利用しています。まず、`DEPOT' という型が登場しています。これは標準ライブラリの `FILE' と同様に、操作対象のファイルの情報を格納している構造体の型です。この型へのポインタをハンドルとして各種の関数に渡すことになります。関数としては、`dpopen'、`dpclose'、`dpput' および `dpget' が登場しています。この四つの使い方を覚えればQDBMの半分は理解したようなものです。</p>
+
+<div><kbd>DEPOT *dpopen(const char *<var>name</var>, int <var>omode</var>, int <var>bnum</var>);</kbd></div>
+
+<p>`dpopen' はその名の通り、Depotのデータベースを開く関数です。その結果として `DEPOT' 型の構造体へのポインタが返されます。第1引数にはデータを格納するファイル名を指定します。これは相対パスで指定しても絶対パスで指定しても構いません。第2引数は接続モードを指定します。読み込みと書き込みの両方をするなら `DP_OWRITER' を指定します。ただし、データベースファイルが存在しない場合に新規作成するならば同時に `DP_OCREAT' をビットORとして加える必要があります。この二つを指定すると、`fopen' の `a+' モードとほぼ同じ意味になります。他に `DP_OTRUNC' というフラグがあるのですが、それはファイルを切り詰めることを指示します。それも加えて三つを指定すると `fopen' の `w+' モードとほぼ同じ意味になります。読み込みだけをする場合、`DP_OREADER' を指定します。これは `fopen' の `r' モードとほぼ同じ意味です。第3引数はハッシュ表のバケット数を指定します。とりあえずデフォルト値を意味する `-1' を指定しておけばいいでしょう。</p>
+
+<p>複数のプロセスが同じファイルを読み書きする場合、「レースコンディション」という問題が起こります。同時にファイルに書き込むと、内容が混ざって変になってしまう可能性があるのです。QDBMではそれに対処するために「ファイルロック」をかけます。あるプロセスがデータベースを書き込みモードで開いている場合は、他のプロセスがデータベースを開こうとしてもブロックされるのです。処理が失敗するわけではなく、既にデータベースを開いているプロセスがデータベースを閉じるまで待ってくれるのです。なお、読み込みモード同士であればレースコンディションは起こらないので同時にアクセスすることができます。</p>
+
+<div><kbd>int dpclose(DEPOT *<var>depot</var>);</kbd></div>
+
+<p>`dpclose' はデータベースを閉じる関数です。第1引数には `dpopen' で開いたハンドルを渡します。開いたデータベースは必ず閉じてください。そうしないとデータベースが壊れます(読み込みモードの場合は壊れませんが、メモリリークになります)。</p>
+
+<p>ところで、QDBMを使わない例では、書き込みの際に呼び出す `fprintf' の戻り値をチェックしていません。`fprintf' が失敗した場合は `fclose' もエラーを返すと規定されているからです。同様に、QDBMでもデータベースに一度でも致命的なエラーが起きた場合は `dpclose' がエラーを返すので、エラーチェックを簡略化することができるのです。</p>
+
+<div><kbd>int dpput(DEPOT *<var>depot</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const char *<var>vbuf</var>, int <var>vsiz</var>, int <var>dmode</var>);</kbd></div>
+
+<p>`dpput' はデータベースにレコードを追加する関数です。第1引数には `dpopen' で開いたハンドルを渡します。第2引数には、書き込むレコードのキーの内容を保持する領域へのポインタを指定します。第3引数にはその領域のサイズを指定します。それが負数の場合は、第2引数を文字列として扱って、`strlen' の値をサイズとして判定します(文字列とバイナリの両方を簡単に扱えるようにするためです)。第4引数と第5引数は、レコードの値に関して同様にポインタとサイズを指定します。第6引数は書き込みのモードです。データベース内には同じキーを持つ複数のレコードを格納することができないので、既存のレコードのキーと同一のキーを持つレコードを格納しようとした際にどうするかを指示する必要があります。`DP_DOVER' とした場合は、既存のレコードを新しいレコードで上書きします。`DP_DKEEP' とした場合は、既存のレコードを優先し、エラーが返されます(`DP_EKEEP' というエラーコードが外部変数 `dpecode' に設定されます)。</p>
+
+<p>`dpput' の書き込みモードには、`DP_DCAT' による「連結モード」もあります。これを利用することはあまりないかもしれませんが、他のDBMにはない特徴なので説明します。連結モードは、レコードの値として配列を入れる際に便利なのです。例えば、[10,11,12] という三つの数値を要素に持つ配列を格納していて、それを [10,11,12,13] にしたい場合を考えてみます。他のDBMでは、まずそのレコードを検索して、[10,11,12] を獲得してから、それを [10,11,12,13] に加工して値を生成し、元のレコードに上書きで書き込むといったことをしなければなりません。QDBMの場合は、連結モードで [13] を既存のレコードに書き込むだけで同じことができます。配列の要素数が大きい場合にはこの違いはパフォーマンスに大きな影響を及ぼします。</p>
+
+<div><kbd>char *dpget(DEPOT *<var>depot</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>start</var>, int <var>max</var>, int *<var>sp</var>);</kbd></div>
+
+<p>`dpget' はデータベースを検索してレコードを取り出す関数です。第1引数は他と一緒ですね。第2引数と第3引数は `dpput' と同様に検索キーのポインタとサイズを指定します。第4引数と第5引数はとりあえず `0' と '-1' にするものだと思っていただいて結構です。一応説明すると、取り出す領域の開始オフセットと最大サイズを指定します。`-1' は無制限という意味です。例えばレコードの値が `abcdef' の場合に開始オフセットを `1'、サイズを `3' にした場合、`bcd' が取り出されます。戻り値は取り出した値の内容を記録した領域へのポインタです。その領域は `malloc' でヒープに確保されているので、アプリケーションの責任で `free' に渡して解放する必要があります。戻り値の領域は終端にゼロコードが付加されていることが保証されているので、文字列として利用できます。ただし、バイナリを扱う場合には明示的にサイズが知りたいでしょうから、その為に第6引数にサイズを受け取る変数へのポインタを指定することができます(このサイズには終端のゼロコードは勘定されません)。該当のレコードがない場合には `NULL' が返されます(`DP_ENOITEM' というエラーコードが外部変数 `dpecode' に設定されます)。</p>
+
+<p>Depotの関数で他によく使うものとして、`dpout'、`dpiterinit'、`dpiternext' があります。これらについても説明しておきます。</p>
+
+<div><kbd>int dpout(DEPOT *<var>depot</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></div>
+
+<p>`dpout' はデータベースからレコードを削除する関数です。三つの引数の扱いは `dpget' のものと同じです。該当のレコードがない場合はエラーを返します(`DP_ENOITEM' というエラーコードが外部変数 `dpecode' に設定されます)。</p>
+
+<p>`dpiterinit' と `dpiternext' はデータベースの中のレコードを一つ一つ見ていく場合に使います。先程の内線番号データベースの全レコードを表示する関数の例を以下に示します。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+#define PHONEFILE "phone"
+
+int printphonenumbers(void){
+ DEPOT *depot;
+ char *kbuf, *vbuf;
+ /* データベースを読み込みモードで開く */
+ if(!(depot = dpopen(PHONEFILE, DP_OREADER, -1))) return -1;
+ /* イテレータを初期化する */
+ dpiterinit(depot);
+ /* 各レコードのキーを取り出す */
+ while((kbuf = dpiternext(depot, NULL)) != NULL){
+ /* 各レコードの値を取り出す */
+ if((vbuf = dpget(depot, kbuf, -1, 0, -1, NULL)) != NULL){
+ printf("%s: %s\n", kbuf, vbuf);
+ /* 値の領域を解放する */
+ free(vbuf);
+ }
+ /* キーの領域を解放する */
+ free(kbuf);
+ }
+ /* データベースを閉じる */
+ return dpclose(depot) ? 0 : -1;
+}
+</pre>
+
+<p>全てのレコードを横断的に見ていくことを「トラバーサルアクセス」と呼ぶことにします。トラバーサルアクセスの際には、「イテレータ」を利用します。イテレータは集合に含まれる個々の要素を処理する際に、その要素を一つずつ取り出す機能です。トラバーサルアクセスをはじめる前にはイテレータを初期化します。そして、イテレータが「打ち止め」の合図を返すまで繰り返して呼び出します。</p>
+
+<div><kbd>int dpiterinit(DEPOT *<var>depot</var>);</kbd></div>
+
+<p>`dpiterinit' はイテレータを初期化する関数です。特に説明は必要ありませんね。</p>
+
+<div><kbd>char *dpiternext(DEPOT *<var>depot</var>, int *<var>sp</var>);</kbd></div>
+
+<p>`dpiternext' はイテレータから次のレコードのキーを取り出す関数です。第1引数は他と同じです。例によって戻り値はゼロコードが付加された領域へのポインタです。領域は `malloc' で確保されているので、アプリケーションの責任で `free' してください。明示的にサイズを知りたい場合は第2引数にそれを受け取る変数へのポインタを指定します。もう取り出すレコードがなくなったら `NULL' が返されます(`DP_ENOITEM' というエラーコードが外部変数 `dpecode' に設定されます)。</p>
+
+<p>トラバーサルアクセスで各レコードを辿る順番については規定されていませんので、レコードの順序に依存したプログラミングをしてはいけません(そのような場合はB+木データベースを使いましょう)。また、イテレータを繰り返している途中でレコードを上書きした場合、既に読んだはずのレコードがまた取り出される可能性があります。途中でレコードを削除することに関しては問題ありません。</p>
+
+<p>ここまででDepotの基本的な使い方は説明し終えました。QDBMのそれ以外のAPIもDepotに似たような使い方をしますので、基本仕様書のサンプルコードを見れば理解してもらえると思います。</p>
+
+<hr />
+
+<h2><a name="curia" id="curia" class="head">Curia: 拡張API</a></h2>
+
+<p>Curiaは、Depotとほとんど同じ機能とインタフェースを備えますが、ファイルでなくディレクトリとしてデータベースを扱うAPIです。やたらと大量のデータを扱わなければならない場合にはCuriaがお薦めです。データをディレクトリの中の複数のファイルに分散して格納するので、Depotよりも大きな(2GB以上の)データベースを扱うことができます。Depotではハンドルに `DEPOT' へのポインタを使いましたが、Curiaでは `CURIA' へのポインタを使います。そして、Depotでは `dp' で始まっていた関数名が、Curiaでは `cr' で始まります。名前が違うだけで、使い方は全く一緒です。ただし、データベースを開く `cropen' という関数は、データベースの分割数を指定する引数が増えています。</p>
+
+<div><kbd>CURIA *cropen(const char *<var>name</var>, int <var>omode</var>, int <var>bnum</var>, int <var>dnum</var>);</kbd></div>
+
+<p>第1、第2、第3引数は `dpopen' のものと全く一緒です。第4引数は、データベースの分割数を指定します。ディレクトリの中に、指定した数だけのファイルが作られます。なお、第3引数で指定した値はその各々のデータベースファイルが持つバケット数になります。戻り値はCuriaのデータベースハンドルとなります。</p>
+
+<p>Curiaには「ラージオブジェクト」を扱う機能もあります。ラージオブジェクトとは、各レコードをファイルとして独立させて保存する仕組みです。ラージオブジェクトにすると通常のレコードより処理速度が落ちますが、データベースのサイズを無限に(ディスクが許す限り)大きくすることができます。なお、ラージオブジェクトのトラバーサルアクセスはサポートされません。</p>
+
+<hr />
+
+<h2><a name="relic" id="relic" class="head">Relic: NDBM互換API</a></h2>
+
+<p>Relicは、NDBMのアプリケーションをすべからくQDBMに乗り換えさせるという野望の下に作られたAPIです。パフォーマンスはオリジナルのNDBMの数倍は出ます。NDBMのアプリケーションはあまり見ないですが、Perl等のスクリプト言語がそのインタフェースを備えています。つまりRelicによって各種のスクリプト言語でQDBMが使えることが保証されるわけです。</p>
+
+<p>あなたのアプリケーションのソースコード中で `ndbm.h' をインクルードしている部分を `relic.h' に書き換え、リンク対象を `ndbm' から `qdbm' に換えて再コンパイルしください。それだけであなたのアプリケーションはQDBMに乗り換えることができます。なお、新たにアプリケーションを書く際には、RelicでなくDepotを利用することをお薦めします。</p>
+
+<hr />
+
+<h2><a name="hovel" id="hovel" class="head">Hovel: GDBM互換API</a></h2>
+
+<p>Hovelは、GDBMのアプリケーションをすべからくQDBMに乗り換えさせるという野望の下に作られたAPIです。パフォーマンスはオリジナルのGDBMの数倍は出ます。GDBMのアプリケーションは市場に多く見られますが、それらをお使いの場合は、ぜひともQDBMに移植してあげてください。パフォーマンスが目に見えてに改善されることうけあいです。</p>
+
+<p>あなたのアプリケーションのソースコード中で `gdbm.h' をインクルードしている部分を `hovel.h' に書き換え、リンク対象を `gdbm' から `qdbm' に換えて再コンパイルしください。それだけであなたのアプリケーションはQDBMに乗り換えることができます。なお、新たにアプリケーションを書く際には、HovelでなくDepotを利用することをお薦めします。</p>
+
+<p>通常、Hovelが生成するデータベースファイルはDepotのものと全く同じものです。しかし、ちょっと細工するとそれをCuriaによるデータベースディレクトリに変更することができます。データベースハンドルを取得する関数 `gdbm_open' を、`gdbm_open2' に書き換えればよいのです。単一のファイルにデータベースを格納している場合は、ファイルサイズが2GBまでという制限にひっかかってしまいますが、`gdbm_open2' を使えばそれを乗り越えることができます。`gdbm_open' を呼び出しているところ以外は全く変更する必要がないというのが嬉しいところです。</p>
+
+<div><kbd>GDBM_FILE gdbm_open2(char *<var>name</var>, int <var>read_write</var>, int <var>mode</var>, int <var>bnum</var>, int <var>dnum</var>, int <var>align</var>);</kbd></div>
+
+<p>第1引数は生成するファイルかディレクトリの名前です。第2引数と第3引数は `gdbm_open' の第3引数と第4引数として渡すものと一緒です。第4引数はバケットの要素数です。第5引数はデータベースファイルの分割数です。第6引数は各レコードのアラインメントです。戻り値は `gdbm_open' と同じくデータベースハンドルです。</p>
+
+<hr />
+
+<h2><a name="cabin" id="cabin" class="head">Cabin: ユーティリティAPI</a></h2>
+
+<p>Cabinは、データの操作を簡単に行うためのユーティリティを集めたAPIです。密かにQDBMのAPIの中で最も充実しています。特にリストとマップに関連する関数が重宝します。他にも、ファイルやディレクトリを読んだり、文字列を分割したり、CSVやXMLを解析したり、各種の符号化と復号もできます。ここではリストとマップとXMLについて詳しく説明します。</p>
+
+<p>リストとは、順序を持った集合のことです。Cabinが扱うリストには任意の文字列やバイナリを要素として加えることができます。リストの先頭に対して要素の追加と削除ができるとともに、リストの末尾に対して要素の追加と削除をすることもできます(つまりデクです)。また、配列を使って実装されているので、任意の順番の要素の値を高速に参照することができます。以下の例では、`first'、`second'、`third' という文字列を順に末尾から追加した上で、先頭から末尾まで要素の内容を表示しています。</p>
+
+<pre>#include &lt;cabin.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+void listtest(void){
+ CBLIST *list;
+ int i;
+ /* リストを開く */
+ list = cblistopen();
+ /* 要素を末尾から追加する */
+ cblistpush(list, "first", -1);
+ cblistpush(list, "second", -1);
+ cblistpush(list, "third", -1);
+ /* 先頭から要素の内容を表示する */
+ for(i = 0; i &lt; cblistnum(list); i++){
+ printf("%s\n", cblistval(list, i, NULL));
+ }
+ /* リストを閉じる */
+ cblistclose(list);
+}
+</pre>
+
+<p>`CBLIST' 型へのポインタをリストのハンドルとして用います。実際のハンドルは `cblistopen' を呼び出して獲得します。ハンドルを閉じてメモリを解放するには、`cblistclose' を呼び出します。`cblistpush' は末尾に要素を追加します。`cblistnum' はリストの要素数を返します。`cblistval' はリスト内の特定の番号(ゼロからはじまる)の要素を返します。リスト操作の関数はその他にもいくつかあります。</p>
+
+<p>マップとは、キーと値からなるレコードの集合です。Cabinが扱うマップには任意の文字列やバイナリをキーや値に持つレコードを格納することができます。キーが完全に一致するレコードを検索して値を取り出すことができます(実装はハッシュ表です)。マップ内のレコードを先頭から一つずつ取り出すこともできます。なお、各要素は格納した順番で並んでいることが保証されています。以下の例では、キー `one' と値 `first'、キー `two' と値 `second'、キー `three' と値 `third' のレコードを順に格納した上で、その各々を検索して表示しています。</p>
+
+<pre>#include &lt;cabin.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+void maptest(void){
+ CBMAP *map;
+ /* マップを開く */
+ map = cbmapopen();
+ /* レコードを追加する */
+ cbmapput(map, "one", -1, "first", -1, 1);
+ cbmapput(map, "two", -1, "second", -1, 1);
+ cbmapput(map, "three", -1, "third", -1, 1);
+ /* レコードを検索して内容を表示する */
+ printf("one: %s\n", cbmapget(map, "one", -1, NULL));
+ printf("two: %s\n", cbmapget(map, "two", -1, NULL));
+ printf("three: %s\n", cbmapget(map, "three", -1, NULL));
+ /* マップを閉じる */
+ cbmapclose(map);
+}
+</pre>
+
+<p>`CBMAP' 型へのポインタをマップのハンドルとして用います。実際のハンドルは `cbmapopen' を呼び出して獲得します。ハンドルを閉じてメモリを解放するには、`cbmapclose' を呼び出します。`cbmapput' でレコードを追加します。`cbmapget' でレコードを検索します。マップ操作の関数はその他にもいくつかあります。</p>
+
+<p>XMLを簡単に処理するために、簡易的なパーザが用意されています。このパーザは妥当性検証をせず、書式の検査も厳密でないのが特徴です。したがって、一般的なHTMLやSGMLの解析にも用いることができます。単純な構造のXML文書を処理する際には、DOMやSAXといったAPIを使うよりも便利です。以下の例では、XML文書の中から `id' 属性の値で要素を指定して、そのテキストを取り出して表示します。</p>
+
+<pre>#include &lt;cabin.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;string.h&gt;
+#include &lt;stdio.h&gt;
+
+void showtextbyid(const char *xml, const char *id){
+ CBLIST *elems;
+ CBMAP *attrs;
+ const char *elem, *attr;
+ char *orig;
+ int i;
+ /* タグとテキストのリストを取得する */
+ elems = cbxmlbreak(xml, 1);
+ /* リストの各要素をたどる */
+ for(i = 0; i &lt; cblistnum(elems); i++){
+ /* 要素を取り出す */
+ elem = cblistval(elems, i, NULL);
+ /* タグでない場合は読み飛ばす */
+ if(elem[0] != '&lt;' || elem[1] == '?' || elem[1] == '!' || elem[1] == '/') continue;
+ /* 属性のマップを取得する */
+ attrs = cbxmlattrs(elem);
+ /* ID要素の値を取り出し、一致を検査する */
+ attr = cbmapget(attrs, "id", -1, NULL);
+ if(attr &amp;&amp; !strcmp(attr, id)){
+ /* 次の要素を取り出す */
+ elem = cblistval(elems, i + 1, NULL);
+ if(elem){
+ /* 実体参照を復元して表示する */
+ orig = cbxmlunescape(elem);
+ printf("%s\n", orig);
+ free(orig);
+ }
+ }
+ /* 属性マップを閉じる */
+ cbmapclose(attrs);
+ }
+ /* 要素リストを閉じる */
+ cblistclose(elems);
+}
+</pre>
+
+<p>処理対象のXML文書のテキストを `cbxmlbreak' で分解します。例えば `&lt;body&gt;&lt;p id="nuts"&gt;NUTS&amp;amp;MILK&lt;/p&gt;&lt;/body&gt;' を分解すると、`&lt;body&gt;'、`&lt;p id="nuts"&gt;'、`NUTS&amp;amp;MILK'、`&lt;/p&gt;'、`&lt;/body&gt;' が得られます。そして、各要素を巡回します。1文字目が '&lt;' であればタグか各種の宣言であり、かつ2文字目が '?'、`!'、`/' のいずれでもなければ開始タグまたは空タグであると判断できます。タグに対して `cbxmlattrs' を呼ぶことで属性のマップが得られます。このマップは属性名をキーにして値を取り出すことができます。属性値やテキストセクションの文字列は文書内に出現したままの形式になっています。実体参照を含んだ文字列を復元するには `cbxmlunescape' を用います。</p>
+
+<p>GTK+に付属するGLibやApacheに付属するAPRなどの便利なライブラリが世の中にはありますので、単体でCabinを利用する価値はあまりありません。正直言って、GLibやAPRの方が高機能で、ユーザ数も多く、参考になる情報も多いです。とはいえ、Cabinの方が手軽に使えるので私は好きです。</p>
+
+<hr />
+
+<h2><a name="villa" id="villa" class="head">Villa: 上級API</a></h2>
+
+<p>Villaは、B+木のデータベースを扱うAPIです。B+木データベースにはユーザが指定した順序でレコードが並べられて格納されます。DepotやCuriaはキーの完全一致による検索しかできませんが、Villaを用いると範囲を指定してレコードを検索することができます。また、同じキーを持つ複数のレコードを格納することもできます。例えば、キーを文字列の辞書順(ABC順とかアイウエオ順と同じほぼ意味です)で並べるように指定した場合は、文字列の前方一致検索ができるのです。</p>
+
+<p>とはいえ、基本的な使い方はDepotと一緒です。Depotの説明で挙げた関数をVillaを使って実装しなおしてみます。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;cabin.h&gt;
+#include &lt;villa.h&gt;
+#include &lt;stdlib.h&gt;
+
+#define PHONEFILE "phone"
+
+int putphonenumber(const char *id, const char *phone){
+ VILLA *villa;
+ /* データベースを追記モードで開く */
+ if(!(villa = vlopen(PHONEFILE, VL_OWRITER | VL_OCREAT, VL_CMPLEX))) return -1;
+ /* レコードを書き込む */
+ vlput(villa, id, -1, phone, -1, VL_DOVER);
+ /* データベースを閉じる */
+ if(!vlclose(villa)) return -1;
+ return 0;
+}
+
+char *getphonenumber(const char *id){
+ VILLA *villa;
+ char *phone;
+ /* データベースを読み込みモードで開く */
+ if(!(villa = vlopen(PHONEFILE, VL_OREADER, VL_CMPLEX))) return NULL;
+ /* レコードを検索して戻り値を生成する */
+ phone = vlget(villa, id, -1, NULL);
+ /* データベースを閉じる */
+ vlclose(villa);
+ return phone;
+}
+</pre>
+
+<p>`VILLA' へのポインタは例によってデータベースハンドルです。`vlopen' でそのハンドルを獲得します。その第3引数の `VL_CMPLEX' は辞書順の比較を行う関数です。開いたハンドルは `vlclose' で閉じます。`vlput' はレコードを格納する関数で、`vlget' はレコードを検索する関数です。</p>
+
+<p>上記の例ではハッシュデータベースと全く同じ使い方をしましたが、順番に基づいてレコードにアクセスする機能がB+木データベースの特徴です。辞書順を例にとって説明します。キーがそれぞれ `one'、`two'、`three'、`four' というレコードを格納したとすれば、それは `four'、`one'、`three'、`two' という順番で並べられて保存されます。検索にはハッシュデータベースと同様に完全一致条件も使えます。さらに、「カーソル」という機能を使って範囲を指定した検索ができます。カーソルはレコードの位置を指し示します。例えば、`one' の場所にカーソルを飛ばすといった指定ができます。`one' がない場合は、`one' の直後の `three' の位置にカーソルが飛ぶことになります(`one' が複数あった場合は、その最初のレコードに飛びます)。カーソルは、現在位置から前に進めたり後ろに戻したりすることもできます。そして、カーソルの位置のレコードの内容を読み出せば、範囲を指定した検索ができるというわけです。以下の例は、`one' から `three' までのレコードのキーと値を表示する関数です。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;cabin.h&gt;
+#include &lt;villa.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+#define WORDFILE "word"
+#define TOPWORD "one"
+#define BOTTOMWORD "three"
+
+void printwords(void){
+ VILLA *villa;
+ char *kbuf, *vbuf;
+ int ksiz;
+ /* データベースを読み込みモードで開く */
+ if(!(villa = vlopen(WORDFILE, VL_OREADER, VL_CMPLEX))) return;
+ /* カーソルを候補の先頭に飛ばす */
+ vlcurjump(villa, TOPWORD, -1, VL_JFORWARD);
+ /* 各候補を処理する */
+ do {
+ /* レコードのキーを取り出す */
+ kbuf = vlcurkey(villa, &amp;ksiz);
+ /* 候補が範囲外であれば抜ける */
+ if(!kbuf || VL_CMPLEX(kbuf, ksiz, BOTTOMWORD, sizeof(BOTTOMWORD) - 1) &gt; 0){
+ free(kbuf);
+ break;
+ }
+ /* レコードの値を取り出して表示する */
+ vbuf = vlcurval(villa, NULL);
+ if(kbuf &amp;&amp; vbuf) printf("%s: %s\n", kbuf, vbuf);
+ /* キーと値の領域を解放する */
+ free(vbuf);
+ free(kbuf);
+ /* カーソルを次に進める */
+ } while(vlcurnext(villa));
+ /* データベースを閉じる */
+ vlclose(villa);
+}
+</pre>
+
+<p>`vlcurjump' でカーソルを候補の先頭に飛ばしています。`VL_JFORWARD' はこれからカーソルを前に進めていく場合に指定します(候補の末尾に飛ばしてから後ろに戻して行く場合は `VL_JBACKWARD' を指定します)。do-whileループの条件部で `vlcurnext' を呼んでいますが、これがカーソルを前に進めています(データベースの最後まで来たら偽を返すのでループから抜けます)。`vlcurkey' と `vlcurval' はそれぞれカーソルのキーと値を取り出します。`VL_CMPLEX' を明示的に呼んでいる場所がありますが、ここで候補の末尾に来たかどうか判定しています。比較関数は、二つのキーのポインタとサイズを渡して、前者が大きい(つまり後ろに位置すべき)なら正の値、前者が小さい(つまり前に位置すべき)なら負の値、両者が同じならゼロを返すと規定されています。したがって、正の値が返された場合、今取り出したキーは候補の末尾よりも後ろだということになります。</p>
+
+<p>辞書順以外の比較関数も使えるところがVillaのミソです。int型の数値を比較する `VL_CMPINT' や、10進数の文字列を比較する `VL_CMPDEC' といった関数が最初から用意されています。さらに、あなたが自分で定義した関数も比較関数として使うことができます。使い方が面倒だという欠点を除けば、B+木データベースはハッシュデータベースよりも多くのシーンで活用できると思います。ファイルがハッシュデータベースよりも小さかったり、トランザクションが使えるといった特徴も、人によっては嬉しいかもしれません。</p>
+
+<p>Villaは、キュー(FIFO)を永続化する目的でも利用できます。`VL_CMPINT' を比較関数にしてデータベースを開きます。各レコードのキーはint型とし、値には任意のオブジェクトを入れることにします。キューに要素を追加するには、末尾のキーの数値(なければゼロ)をインクリメントしてキーを生成して格納します。キューから要素を取り出すには、先頭のレコードを取り出してから削除すればよいのです。そのような機能を持つラッパ関数を、`qopen'、`qclose'、`qappend'、`qconsume' といった名前で作っておくと小粋ですね。</p>
+
+<hr />
+
+<h2><a name="odeum" id="odeum" class="head">Odeum: 転置API</a></h2>
+
+<p>Odeumは、全文検索用の転置インデックスを扱うAPIです。テキストファイル(またはテキストを含むHTMLやMS-Wordの文書など)は単語の集合とみなせますが、ある単語がどのファイルに含まれるかという情報をデータベースにしたものを転置インデックスと呼びます。本の巻末にある索引は、ある単語がどのページに含まれるかという情報を持っていますが、それと似たようなものです。私がQDBMを開発する契機となったのは、とある全文検索システムの開発で使っていたGDBMのパフォーマンスに限界を感じたことです。その経緯から、QDBM(特にCuria)には転置インデックスの実現に都合のよい特徴がいくつか備わっています。</p>
+
+<p>転置インデックスの核となるのは、ある単語をキーとし、その単語を含むファイルのIDの配列を値とするレコードからなるデータベースです。単語は完全一致で検索できればよいので、データ構造にはハッシュ表を採用しています。転置インデックスのイメージを例示します。ファイル `penguin.txt' には「flightless marine birds」というテキストが格納されていて、ファイル `ostrich.txt' には、「flightless birds in africa」というテキストが格納されているとします。各ファイルには、読み込んだ順番でIDをつけることにします。これらを対象として転置インデックスを作成すると、以下のようになります。IDとファイル名の対応づけは別の「文書データベース」に保存しておきます。</p>
+
+<table summary="index example">
+<tr><td>flightless</td><td>1,2</td></tr>
+<tr><td>marine</td><td>1</td></tr>
+<tr><td>birds</td><td>1,2</td></tr>
+<tr><td>in</td><td>2</td></tr>
+<tr><td>africa</td><td>2</td></tr>
+</table>
+
+<p>その次に読み込んだ文書に「birds」という単語が含まれていた場合には、`birds' の値を [1,2,3] に変更することになります。このように、既存のレコードの値の末尾にデータを追加するという操作が頻繁に発生します。この処理を効率的に行うために、DepotやCuriaの書き込み操作には「連結モード」があるのです。</p>
+
+<p>検索時には、検索語をキーにして値の配列を取り出し、個々のIDに対応するファイル名を文書データベースから取り出して提示することになります。ただし該当の文書が多い場合にその全てを提示してもユーザは困惑するので、先頭の何件かに絞って取り出します。DepotやCuriaの読み込み操作ではレコードの値から特定の部分のみを取り出すことができるので、この処理を効率良く行うことができます。実際は、ファイルにおける単語のスコア(重要度)も配列要素の一部として格納しておいて、それを基準にソートしておくことによって、先頭の要素がユーザにとって意味のあるものにしています。</p>
+
+<p>ファイルには名前以外にも、タイトルや作者名や更新日時といった属性をつけたい場合があります。それらの情報は文書データベースに保存します。Odeumで扱うファイル(以後は文書と呼びます)は、ファイル名の代わりにURIをつけるものとしています。URI以外の属性はユーザがラベルをつけて任意のものを格納できます。</p>
+
+<p>Odeumは、文書のテキストや属性を元のデータから抽出する機能については提供しません。それらはドメインに強く依存するので、共通化することが難しいからです。したがって、アプリケーションがそれを実装する必要があります。Odeumのサンプルアプリケーションは、ローカルのファイルシステムにあるプレーンテキストとHTMLからテキストと属性を抽出できます。あなたのアプリケーションでは、PDFやMS-Wordの文書に対応するのもよいでしょう。Webから文書を取得してもよいでしょう。同じ理由から、Odeumはテキストから単語を抽出する機能についても提供しません。英語と日本語では全く異なる手法でテキストの解析をしなければなりませんが、そういったこともアプリケーションに任されます。Odeumのサンプルアプリケーションでは、単に空白で語を区切るという手法をとっています。あなたのアプリケーションでは、日本語の形態素解析を行うのもよいでしょう。英単語のステミング処理を行ってもよいでしょう。</p>
+
+<p>多くの言語では、同じ単語に対して異体や活用が存在します。例えば「使う/使っ(た)」「go/went」「child/children」などです。そこで、Odeumは各単語を「正規形」および「出現形」の組として扱います。テキストから出現形の単語を切り出したり、それらの正規形を生成する処理はアプリケーションに任されます。転置インデックスでのレコードのキーには正規形が使われます。「child」で検索すれば、「children」を含む文書も該当させられるということです。検索語に対しても正規化の処理を行えば、「children」で検索して「child」を該当にできます。なお、出現形もデータベースに記録されますが、それは検索結果として文書の要約を提示するなどの用途で利用されます(不要な場合は出現形を全て空文字列にして記憶領域を節約できます)。</p>
+
+<p>全文検索システムの善し悪しを評価する際には、スコアリング(ランキング)が重要な要素になります。検索結果が多い場合に、その中から絞り込んでユーザに提示する文書をどうやって選択するかということです。いくら検索速度が速くても、満足のいく検索結果が提示されずに何度も検索したり、無駄な文書を閲覧しなければならないのでは、結局は時間がかかることになってしまいます。Odeumでは、文書を登録する際に、そこに含まれる各単語について以下の式でスコアを算出して、文書IDとともに転置インデックスに格納しています。</p>
+
+<div><kbd>スコア = (該当語の出現数 * 10000) / log(文書内の総語数) ^ 3</kbd></div>
+
+<p>ある単語がたくさん出現するということは、その文書でその単語のことについて詳しく説明されている可能性が高いと判断できます。ただし、大きな文書は小さい文書よりも単語の出現数が多くなりますので、その調整をする必要があります。そこで、該当語の出現数を、総語数の自然対数の三乗で割っているのです。なお、テキストの先頭10%以内に出て来る単語は「トピックセンテンス」を構成するとみなして、初出時に限り10000でなく15000を加算しています。</p>
+
+<p>全文検索は通常、複数の検索語を使って行われます。その際には、各検索語のスコアを調整しないと、特定の検索語のスコアの影響が強すぎるという事態が発生します。例えば「the beatles」で検索した場合、「the」のスコアが「beatles」のスコアを圧倒して、「beatles」について知りたいのに、関係ない文書ばかりが提示されることになってしまいます。Odeumは転置インデックスの内容を提示するだけで、それ以後の処理はアプリケーションに任されます。Odeumに付属するサンプルアプリケーションでは以下の式で調整を行っています。</p>
+
+<div><kbd>調整済みスコア = 登録されたスコア / log(その語を含む文書数) ^ 2</kbd></div>
+
+<p>ありふれた単語はスコアを下げ、特徴的な単語はスコアを維持すべきです。そこで、登録されたスコアを、その語を含む文書数の自然対数の二乗で割っているのです(TF-IDF法を強化したものです)。各検索語の調整済みスコアを足したものを文書のスコアとし、その降順で検索結果を提示します。他にもドメインに依存した様々なスコアリング手法があると思いますが、アプリケーションがそれを実装できるのが嬉しいところです。</p>
+
+<p>Odeumのサンプルアプリケーションでは、関連文書検索(類似文書検索と呼んだ方が適切かもしれません)の機能も実装しています。ある文書(種文書と呼びます)に関連した文書の一覧を提示するものです。単語の出現傾向が似通った文書は互いに関連しているという考え方(ベクトル空間モデル)に基づいています。文書をデータベースに登録する際には、各文書に含まれる全単語に対して調整済みスコアを計算し、その上位32語(キーワードと呼びます)の情報を文書と対応づけて登録しておきます。検索時には、種文書のキーワードとスコアを取り出し、それを32次元のベクトルとして表現します。関連度を判定する対象の各文書からもキーワードとスコアを取り出し、種文書のベクトル空間に対応したベクトルを生成します。そうしてできた二つのベクトルのなす角が小さいものは関連度が高いと判定します。実際には、なす角の余弦(0から1の範囲で、完全一致する場合は1になる)が大きいものから提示されることになります。なお、登録された全ての文書を対象として類似度の判定を行うとあまりに時間がかかるので、キーワードでOR検索を行った結果の上位の文書のみを関連度算出の対象としています。</p>
+
+<p>前置きはここまでにして、Odeumの使用方法についての説明に入ります。URIが `http://tako.ika/uni.txt' で、そこから取り出したテキストが「The sun is driven by the Grateful Dead.」で、切り出した単語(正規形/出現形)が「the/The」「sun/sun」「be/is」「drive/driven」「by/by」「the/the」「grateful/Grateful」「die/dead」「(正規形なし)/.」だとしましょう(この処理はアプリケーションが独自に実装してください)(いわゆるストップワードは正規形を空文字列にして表現します)。それを `index' という名前のデータベースに登録する例を示します。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;cabin.h&gt;
+#include &lt;odeum.h&gt;
+#include &lt;stdlib.h&gt;
+
+int docregistertest(void){
+ ODEUM *odeum;
+ ODDOC *doc;
+ /* データベースを開く */
+ if(!(odeum = odopen("index", OD_OWRITER | OD_OCREAT))) return -1;
+ /* 文書を表現する */
+ doc = oddocopen("http://tako.ika/uni.txt");
+ oddocaddword(doc, "the", "the");
+ oddocaddword(doc, "sun", "sun");
+ oddocaddword(doc, "be", "is");
+ oddocaddword(doc, "drive", "driven");
+ oddocaddword(doc, "by", "by");
+ oddocaddword(doc, "the", "the");
+ oddocaddword(doc, "grateful", "Grateful");
+ oddocaddword(doc, "die", "Dead");
+ oddocaddword(doc, "", ".");
+ /* 文書を登録する */
+ odput(odeum, doc, -1, 1);
+ /* 文書の領域を解放する */
+ oddocclose(doc);
+ /* データベースを閉じる */
+ if(!odclose(odeum)) return -1;
+ return 0;
+}
+</pre>
+
+<p>`ODEUM' へのポインタは例によってデータベースハンドルです。`odopen' でそのハンドルを獲得します。開いたハンドルは `odclose' で閉じます。`ODDOC' は文書ハンドルです。各文書の内容は文書ハンドルによって表現されます。`oddocopen' はハンドルを開く関数です。その第1引数で文書のURIを指定します。文書ハンドルは不要になったら `oddocclose' で解放します。テキストの各単語を文書に登録するには、`oddocaddword' を用います。その第2引数は単語の正規形で、第3引数は出現形です。文書を表現したら、それを `odput' でデータベースに登録します。第3引数は、文書データベースに登録する語数を指定します。`-1' にすると全部の語が登録されます。第4引数は、同じURIの既存の文書がある場合に、それを上書きするか否か指定します。</p>
+
+<p>`odput' の第3引数の指定が少し難しいので補足します。ある文書が適切に検索されるために、転置インデックスにおいて、その文書に含まれる全ての単語のレコードの値にその文書のIDが無条件で追加されます。ところで、多くの全文検索システムでは、検索結果の画面で該当の文書の要約を表示します。そのためには、文書データベースの中に、各文書と関連づけて含まれる単語を順番に記録しておく必要があります。検索語の周辺の文を切り出して表示する場合を考えると、検索語が文書中のどこに現れるかは予想できないので、全ての単語を文書データベースに記録しておかなければなりません。あるいは、冒頭の何語かだけ表示する場合には、その語数分の語を登録しておけばよいことになります。そういった決定をアプリケーションに任せるために、文書データベースに登録する語数を指定できるようになっているのです。</p>
+
+<p>以下の例では、`grateful' という単語を含む文書を検索して、そのURIとスコアを表示します。まず転置インデックスを検索して結果の配列を受け取ります。その配列の各要素は文書のIDとスコアの組です。文書の内容を取得するには、文書IDを使って文書データベースに問い合わせます。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;cabin.h&gt;
+#include &lt;odeum.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+void docsearchtest(void){
+ ODEUM *odeum;
+ ODPAIR *pairs;
+ ODDOC *doc;
+ int i, pnum;
+ /* データベースを読み込みモードで開く */
+ if(!(odeum = odopen("index", OD_OREADER))) return;
+ /* 転置インデックスを検索する */
+ pairs = odsearch(odeum, "grateful", -1, &amp;pnum);
+ if(pairs &amp;&amp; pnum &gt; 0){
+ /* 該当の各文書を処理する */
+ for(i = 0; i &lt; pnum; i++){
+ /* 文書データベースから文書を取り出す */
+ if(!(doc = odgetbyid(odeum, pairs[i].id))) continue;
+ /* 文書の内容を表示する */
+ printf("URI: %s\n", oddocuri(doc));
+ printf("SCORE: %d\n", pairs[i].score);
+ /* 文書の領域を解放する */
+ oddocclose(doc);
+ }
+ }
+ /* 検索結果の領域を解放する */
+ free(pairs);
+ /* データベースを閉じる */
+ odclose(odeum);
+}
+</pre>
+
+<p>転置インデックスを検索するのが `odsearch' という関数です。第2引数には正規形の検索語を指定します。第3引数には、検索する文書の最大数を指定しますが、全部取り出す場合は `-1' とします。結果の配列はスコアの降順でソートされていることが規定されています。第4引数には、結果の配列の要素数を格納する変数のポインタを指定します。次に、配列の各要素を処理していきます。`odgetbyid' は文書IDを用いて文書の内容を問い合わせる関数です。転置インデックスの中には既に削除されたり上書きされてIDが変更された文書の情報も入っているので(最適化すれば不要な情報はなくなりますが)、`odgetbyid' は失敗する可能性があります。そういう時は単に無視して次のループに進んでください。文書が取得できたら、あとはそれを表示します。`oddocuri' は文書のURIを返す関数です。他にも文書の情報を取得する関数がいくつか用意されています。</p>
+
+<p>Odeumでは、複数の検索語を用いて、AND条件(検索語の全てを含む)やOR条件(検索語のいずれかを含む)やNOTAND条件(検索語の前者を含むが後者は含まない)といった集合演算を処理するための関数が用意されているほか、全文検索システムの実装に便利なユーティリティ関数が多数提供されます。全文検索システムを実装する際には性能と精度のバランスを考えなければなりませんが、OdeumのAPIはアプリケーションがそれを任意に決められるように設計されています。大規模なインデックスを扱う際には、まず精度を落した検索を行って、その結果がユーザの要求を満たさなければ精度を高めたパラメータで再検索する手法が有効でしょう。</p>
+
+<hr />
+
+</body>
+
+</html>
+
+<!-- END OF FILE -->
diff --git a/qdbm/misc/win32check.bat b/qdbm/misc/win32check.bat
new file mode 100644
index 00000000..63dae180
--- /dev/null
+++ b/qdbm/misc/win32check.bat
@@ -0,0 +1,111 @@
+dptest write casket 50000 5000
+if errorlevel 1 goto error
+dptest read casket
+if errorlevel 1 goto error
+dptest read -wb casket
+if errorlevel 1 goto error
+dptest rcat casket 50000 50 500 32 32
+if errorlevel 1 goto error
+dptest combo casket
+if errorlevel 1 goto error
+dptest wicked casket 5000
+if errorlevel 1 goto error
+del /Q casket
+
+crtest write casket 50000 500 10
+if errorlevel 1 goto error
+crtest read casket
+if errorlevel 1 goto error
+crtest read -wb casket
+if errorlevel 1 goto error
+crtest rcat casket 50000 5 10 500 32 32
+if errorlevel 1 goto error
+crtest combo casket
+if errorlevel 1 goto error
+crtest wicked casket 5000
+if errorlevel 1 goto error
+rd /S /Q casket
+
+crtest write -lob casket 1000 50 10
+if errorlevel 1 goto error
+crtest read -lob casket
+if errorlevel 1 goto error
+rd /S /Q casket
+
+rltest write casket 50000
+if errorlevel 1 goto error
+rltest read casket 50000
+if errorlevel 1 goto error
+del /Q casket*
+
+hvtest write casket 50000
+if errorlevel 1 goto error
+hvtest read casket 50000
+if errorlevel 1 goto error
+del /Q casket
+
+hvtest write -qdbm casket 50000
+if errorlevel 1 goto error
+hvtest read -qdbm casket 50000
+if errorlevel 1 goto error
+rd /S /Q casket
+
+cbtest sort 5000
+if errorlevel 1 goto error
+cbtest strstr 500
+if errorlevel 1 goto error
+cbtest list 50000
+if errorlevel 1 goto error
+cbtest map 50000
+if errorlevel 1 goto error
+cbtest wicked 5000
+if errorlevel 1 goto error
+cbtest misc
+if errorlevel 1 goto error
+
+vltest write -tune 25 64 32 32 casket 50000
+if errorlevel 1 goto error
+vltest read casket
+if errorlevel 1 goto error
+vltest rdup -tune 25 64 256 256 casket 50000 50000
+if errorlevel 1 goto error
+vltest combo casket
+if errorlevel 1 goto error
+vltest wicked casket 5000
+if errorlevel 1 goto error
+del /Q casket
+
+vltest write -int -cz -tune 25 64 32 32 casket 50000
+if errorlevel 1 goto error
+vltest read -int casket
+if errorlevel 1 goto error
+vltest rdup -int -cz -tune 25 64 256 256 casket 50000 50000
+if errorlevel 1 goto error
+vltest combo -cz casket
+if errorlevel 1 goto error
+vltest wicked -cz casket 5000
+if errorlevel 1 goto error
+del /Q casket
+
+odtest write casket 500 50 5000
+if errorlevel 1 goto error
+odtest read casket
+if errorlevel 1 goto error
+odtest combo casket
+if errorlevel 1 goto error
+odtest wicked casket 500
+if errorlevel 1 goto error
+rd /S /Q casket
+
+@echo off
+echo #================================
+echo # SUCCESS
+echo #================================
+goto :EOF
+
+:error
+@echo off
+echo #================================
+echo # ERROR
+echo #================================
+goto :EOF
diff --git a/qdbm/myconf.c b/qdbm/myconf.c
new file mode 100644
index 00000000..1d1f7a7c
--- /dev/null
+++ b/qdbm/myconf.c
@@ -0,0 +1,1113 @@
+/*************************************************************************************************
+ * Emulation of system calls
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include "myconf.h"
+
+
+
+/*************************************************************************************************
+ * for dosish filesystems
+ *************************************************************************************************/
+
+
+#if defined(_SYS_MSVC_) || defined(_SYS_MINGW_) || defined(_SYS_CYGWIN_)
+
+
+#define DOSPATHBUFSIZ 8192
+
+
+int _qdbm_win32_lstat(const char *pathname, struct stat *buf){
+ char pbuf[DOSPATHBUFSIZ], *p;
+ int inode;
+ if(stat(pathname, buf) == -1) return -1;
+ if(GetFullPathName(pathname, DOSPATHBUFSIZ, pbuf, &p) != 0){
+ inode = 11003;
+ for(p = pbuf; *p != '\0'; p++){
+ inode = inode * 31 + *(unsigned char *)p;
+ }
+ buf->st_ino = (inode * 911) & 0x7FFF;
+ }
+ return 0;
+}
+
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for POSIX thread
+ *************************************************************************************************/
+
+
+#if defined(MYPTHREAD)
+
+
+#include <pthread.h>
+
+
+#define PTKEYMAX 8
+
+
+struct { void *ptr; pthread_key_t key; } _qdbm_ptkeys[PTKEYMAX];
+int _qdbm_ptknum = 0;
+
+
+static void *_qdbm_gettsd(void *ptr, int size, const void *initval);
+
+
+void *_qdbm_settsd(void *ptr, int size, const void *initval){
+ static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+ char *val;
+ if((val = _qdbm_gettsd(ptr, size, initval)) != NULL) return val;
+ if(pthread_mutex_lock(&mutex) != 0) return NULL;
+ if((val = _qdbm_gettsd(ptr, size, initval)) != NULL){
+ pthread_mutex_unlock(&mutex);
+ return val;
+ }
+ if(_qdbm_ptknum >= PTKEYMAX){
+ pthread_mutex_unlock(&mutex);
+ return NULL;
+ }
+ _qdbm_ptkeys[_qdbm_ptknum].ptr = ptr;
+ if(pthread_key_create(&(_qdbm_ptkeys[_qdbm_ptknum].key), free) != 0){
+ pthread_mutex_unlock(&mutex);
+ return NULL;
+ }
+ if(!(val = malloc(size))){
+ pthread_key_delete(_qdbm_ptkeys[_qdbm_ptknum].key);
+ pthread_mutex_unlock(&mutex);
+ return NULL;
+ }
+ memcpy(val, initval, size);
+ if(pthread_setspecific(_qdbm_ptkeys[_qdbm_ptknum].key, val) != 0){
+ free(val);
+ pthread_key_delete(_qdbm_ptkeys[_qdbm_ptknum].key);
+ pthread_mutex_unlock(&mutex);
+ return NULL;
+ }
+ _qdbm_ptknum++;
+ pthread_mutex_unlock(&mutex);
+ return val;
+}
+
+
+static void *_qdbm_gettsd(void *ptr, int size, const void *initval){
+ char *val;
+ int i;
+ for(i = 0; i < _qdbm_ptknum; i++){
+ if(_qdbm_ptkeys[i].ptr == ptr){
+ if(!(val = pthread_getspecific(_qdbm_ptkeys[i].key))){
+ if(!(val = malloc(size))) return NULL;
+ memcpy(val, initval, size);
+ if(pthread_setspecific(_qdbm_ptkeys[i].key, val) != 0){
+ free(val);
+ return NULL;
+ }
+ }
+ return val;
+ }
+ }
+ return NULL;
+}
+
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for systems without mmap
+ *************************************************************************************************/
+
+
+#if defined(_SYS_MSVC_) || defined(_SYS_MINGW_)
+
+
+#define MMFDESCMAX 2048
+
+
+struct { void *start; HANDLE handle; } mmhandles[MMFDESCMAX];
+int mmhnum = 0;
+CRITICAL_SECTION mmcsec;
+
+
+static void _qdbm_delete_mmap_env(void);
+
+
+void *_qdbm_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset){
+ static volatile long first = TRUE;
+ static volatile long ready = FALSE;
+ HANDLE handle;
+ int i;
+ if(InterlockedExchange((void *)&first, FALSE)){
+ InitializeCriticalSection(&mmcsec);
+ atexit(_qdbm_delete_mmap_env);
+ InterlockedExchange((void *)&ready, TRUE);
+ }
+ while(!InterlockedCompareExchange((void *)&ready, TRUE, TRUE)){
+ Sleep(1);
+ }
+ if(fd < 0 || flags & MAP_FIXED) return MAP_FAILED;
+ if(!(handle = CreateFileMapping((HANDLE)_get_osfhandle(fd), NULL,
+ (prot & PROT_WRITE) ? PAGE_READWRITE : PAGE_READONLY,
+ 0, length, NULL))) return MAP_FAILED;
+ if(!(start = MapViewOfFile(handle, (prot & PROT_WRITE) ? FILE_MAP_WRITE : FILE_MAP_READ,
+ 0, 0, length))){
+ CloseHandle(handle);
+ return MAP_FAILED;
+ }
+ EnterCriticalSection(&mmcsec);
+ if(mmhnum >= MMFDESCMAX - 1){
+ UnmapViewOfFile(start);
+ CloseHandle(handle);
+ LeaveCriticalSection(&mmcsec);
+ return MAP_FAILED;
+ }
+ for(i = 0; i < MMFDESCMAX; i++){
+ if(!mmhandles[i].start){
+ mmhandles[i].start = start;
+ mmhandles[i].handle = handle;
+ break;
+ }
+ }
+ mmhnum++;
+ LeaveCriticalSection(&mmcsec);
+ return start;
+}
+
+
+int _qdbm_munmap(void *start, size_t length){
+ HANDLE handle;
+ int i;
+ EnterCriticalSection(&mmcsec);
+ handle = NULL;
+ for(i = 0; i < MMFDESCMAX; i++){
+ if(mmhandles[i].start == start){
+ handle = mmhandles[i].handle;
+ mmhandles[i].start = NULL;
+ mmhandles[i].handle = NULL;
+ break;
+ }
+ }
+ if(!handle){
+ LeaveCriticalSection(&mmcsec);
+ return -1;
+ }
+ mmhnum--;
+ LeaveCriticalSection(&mmcsec);
+ if(!UnmapViewOfFile(start)){
+ CloseHandle(handle);
+ return -1;
+ }
+ if(!CloseHandle(handle)) return -1;
+ return 0;
+}
+
+
+int _qdbm_msync(const void *start, size_t length, int flags){
+ if(!FlushViewOfFile(start, length)) return -1;
+ return 0;
+}
+
+
+static void _qdbm_delete_mmap_env(void){
+ DeleteCriticalSection(&mmcsec);
+}
+
+
+#elif defined(_SYS_FREEBSD_) || defined(_SYS_NETBSD_) || defined(_SYS_OPENBSD_) || \
+ defined(_SYS_AIX_) || defined(_SYS_RISCOS_) || defined(MYNOMMAP)
+
+
+void *_qdbm_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset){
+ char *buf, *wp;
+ int rv, rlen;
+ if(flags & MAP_FIXED) return MAP_FAILED;
+ if(lseek(fd, SEEK_SET, offset) == -1) return MAP_FAILED;
+ if(!(buf = malloc(sizeof(int) * 3 + length))) return MAP_FAILED;
+ wp = buf;
+ *(int *)wp = fd;
+ wp += sizeof(int);
+ *(int *)wp = offset;
+ wp += sizeof(int);
+ *(int *)wp = prot;
+ wp += sizeof(int);
+ rlen = 0;
+ while((rv = read(fd, wp + rlen, length - rlen)) > 0){
+ rlen += rv;
+ }
+ if(rv == -1 || rlen != length){
+ free(buf);
+ return MAP_FAILED;
+ }
+ return wp;
+}
+
+
+int _qdbm_munmap(void *start, size_t length){
+ char *buf, *rp;
+ int fd, offset, prot, rv, wlen;
+ buf = (char *)start - sizeof(int) * 3;
+ rp = buf;
+ fd = *(int *)rp;
+ rp += sizeof(int);
+ offset = *(int *)rp;
+ rp += sizeof(int);
+ prot = *(int *)rp;
+ rp += sizeof(int);
+ if(prot & PROT_WRITE){
+ if(lseek(fd, offset, SEEK_SET) == -1){
+ free(buf);
+ return -1;
+ }
+ wlen = 0;
+ while(wlen < (int)length){
+ rv = write(fd, rp + wlen, length - wlen);
+ if(rv == -1){
+ if(errno == EINTR) continue;
+ free(buf);
+ return -1;
+ }
+ wlen += rv;
+ }
+ }
+ free(buf);
+ return 0;
+}
+
+
+int _qdbm_msync(const void *start, size_t length, int flags){
+ char *buf, *rp;
+ int fd, offset, prot, rv, wlen;
+ buf = (char *)start - sizeof(int) * 3;
+ rp = buf;
+ fd = *(int *)rp;
+ rp += sizeof(int);
+ offset = *(int *)rp;
+ rp += sizeof(int);
+ prot = *(int *)rp;
+ rp += sizeof(int);
+ if(prot & PROT_WRITE){
+ if(lseek(fd, offset, SEEK_SET) == -1) return -1;
+ wlen = 0;
+ while(wlen < (int)length){
+ rv = write(fd, rp + wlen, length - wlen);
+ if(rv == -1){
+ if(errno == EINTR) continue;
+ return -1;
+ }
+ wlen += rv;
+ }
+ }
+ return 0;
+}
+
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for reentrant time routines
+ *************************************************************************************************/
+
+
+#if defined(_SYS_LINUX_) || defined(_SYS_FREEBSD_) || defined(_SYS_OPENBSD_) || \
+ defined(_SYS_NETBSD_) || defined(_SYS_SUNOS_) || defined(_SYS_HPUX_) || \
+ defined(_SYS_MACOSX_) || defined(_SYS_CYGWIN_)
+
+
+struct tm *_qdbm_gmtime(const time_t *timep, struct tm *result){
+ return gmtime_r(timep, result);
+}
+
+
+struct tm *_qdbm_localtime(const time_t *timep, struct tm *result){
+ return localtime_r(timep, result);
+}
+
+
+# else
+
+
+struct tm *_qdbm_gmtime(const time_t *timep, struct tm *result){
+ return gmtime(timep);
+}
+
+
+struct tm *_qdbm_localtime(const time_t *timep, struct tm *result){
+ return localtime(timep);
+}
+
+
+# endif
+
+
+
+/*************************************************************************************************
+ * for systems without times
+ *************************************************************************************************/
+
+
+#if defined(_SYS_MSVC_) || defined(_SYS_MINGW_)
+
+
+clock_t _qdbm_times(struct tms *buf){
+ buf->tms_utime = clock();
+ buf->tms_stime = 0;
+ buf->tms_cutime = 0;
+ buf->tms_cstime = 0;
+ return 0;
+}
+
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for Win32
+ *************************************************************************************************/
+
+
+#if defined(_SYS_MSVC_) || defined(_SYS_MINGW_)
+
+
+#define WINLOCKWAIT 100
+
+
+int _qdbm_win32_fcntl(int fd, int cmd, struct flock *lock){
+ HANDLE fh;
+ DWORD opt;
+ OVERLAPPED ol;
+ fh = (HANDLE)_get_osfhandle(fd);
+ opt = (cmd == F_SETLK) ? LOCKFILE_FAIL_IMMEDIATELY : 0;
+ if(lock->l_type == F_WRLCK) opt |= LOCKFILE_EXCLUSIVE_LOCK;
+ memset(&ol, 0, sizeof(OVERLAPPED));
+ ol.Offset = INT_MAX;
+ ol.OffsetHigh = 0;
+ ol.hEvent = 0;
+ if(!LockFileEx(fh, opt, 0, 1, 0, &ol)){
+ if(GetLastError() == ERROR_CALL_NOT_IMPLEMENTED){
+ while(TRUE){
+ if(LockFile(fh, 0, 0, 1, 0)) return 0;
+ Sleep(WINLOCKWAIT);
+ }
+ }
+ return -1;
+ }
+ return 0;
+}
+
+
+#endif
+
+
+#if defined(_SYS_MSVC_)
+
+
+DIR *_qdbm_win32_opendir(const char *name){
+ char expr[8192];
+ int len;
+ DIR *dir;
+ HANDLE fh;
+ WIN32_FIND_DATA data;
+ len = strlen(name);
+ if(len > 0 && name[len-1] == MYPATHCHR){
+ sprintf(expr, "%s*", name);
+ } else {
+ sprintf(expr, "%s%c*", name, MYPATHCHR);
+ }
+ if((fh = FindFirstFile(expr, &data)) == INVALID_HANDLE_VALUE) return NULL;
+ if(!(dir = malloc(sizeof(DIR)))){
+ FindClose(fh);
+ return NULL;
+ }
+ dir->fh = fh;
+ dir->data = data;
+ dir->first = TRUE;
+ return dir;
+}
+
+
+int _qdbm_win32_closedir(DIR *dir){
+ if(!FindClose(dir->fh)){
+ free(dir);
+ return -1;
+ }
+ free(dir);
+ return 0;
+}
+
+
+struct dirent *_qdbm_win32_readdir(DIR *dir){
+ if(dir->first){
+ sprintf(dir->de.d_name, "%s", dir->data.cFileName);
+ dir->first = FALSE;
+ return &(dir->de);
+ }
+ if(!FindNextFile(dir->fh, &(dir->data))) return NULL;
+ sprintf(dir->de.d_name, "%s", dir->data.cFileName);
+ return &(dir->de);
+}
+
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for checking information of the system
+ *************************************************************************************************/
+
+
+#if defined(_SYS_LINUX_)
+
+
+int _qdbm_vmemavail(size_t size){
+ char buf[4096], *rp;
+ int fd, rv, bsiz;
+ double avail;
+ if((fd = open("/proc/meminfo", O_RDONLY, 00644)) == -1) return TRUE;
+ rv = TRUE;
+ if((bsiz = read(fd, buf, sizeof(buf) - 1)) > 0){
+ buf[bsiz] = '\0';
+ avail = -1;
+ if((rp = strstr(buf, "MemFree:")) != NULL){
+ rp = strchr(rp, ':') + 1;
+ avail = strtod(rp, NULL) * 1024.0;
+ if((rp = strstr(buf, "SwapFree:")) != NULL){
+ rp = strchr(rp, ':') + 1;
+ avail += strtod(rp, NULL) * 1024.0;
+ }
+ if(size >= avail) rv = FALSE;
+ }
+ }
+ close(fd);
+ return rv;
+}
+
+
+#elif defined(_SYS_MSVC_) || defined(_SYS_MINGW_) || defined(_SYS_CYGWIN_)
+
+
+int _qdbm_vmemavail(size_t size){
+ MEMORYSTATUS sbuf;
+ sbuf.dwLength = sizeof(MEMORYSTATUS);
+ GlobalMemoryStatus(&sbuf);
+ return size < sbuf.dwAvailVirtual;
+}
+
+
+#else
+
+
+int _qdbm_vmemavail(size_t size){
+ return TRUE;
+}
+
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for ZLIB
+ *************************************************************************************************/
+
+
+#if defined(MYZLIB)
+
+
+#include <zlib.h>
+
+#define ZLIBBUFSIZ 8192
+
+
+static char *_qdbm_deflate_impl(const char *ptr, int size, int *sp, int mode);
+static char *_qdbm_inflate_impl(const char *ptr, int size, int *sp, int mode);
+static unsigned int _qdbm_getcrc_impl(const char *ptr, int size);
+
+
+char *(*_qdbm_deflate)(const char *, int, int *, int) = _qdbm_deflate_impl;
+char *(*_qdbm_inflate)(const char *, int, int *, int) = _qdbm_inflate_impl;
+unsigned int (*_qdbm_getcrc)(const char *, int) = _qdbm_getcrc_impl;
+
+
+static char *_qdbm_deflate_impl(const char *ptr, int size, int *sp, int mode){
+ z_stream zs;
+ char *buf, *swap;
+ unsigned char obuf[ZLIBBUFSIZ];
+ int rv, asiz, bsiz, osiz;
+ if(size < 0) size = strlen(ptr);
+ zs.zalloc = Z_NULL;
+ zs.zfree = Z_NULL;
+ zs.opaque = Z_NULL;
+ switch(mode){
+ case _QDBM_ZMRAW:
+ if(deflateInit2(&zs, 5, Z_DEFLATED, -15, 7, Z_DEFAULT_STRATEGY) != Z_OK)
+ return NULL;
+ break;
+ case _QDBM_ZMGZIP:
+ if(deflateInit2(&zs, 6, Z_DEFLATED, 15 + 16, 9, Z_DEFAULT_STRATEGY) != Z_OK)
+ return NULL;
+ break;
+ default:
+ if(deflateInit2(&zs, 6, Z_DEFLATED, 15, 8, Z_DEFAULT_STRATEGY) != Z_OK)
+ return NULL;
+ break;
+ }
+ asiz = size + 16;
+ if(asiz < ZLIBBUFSIZ) asiz = ZLIBBUFSIZ;
+ if(!(buf = malloc(asiz))){
+ deflateEnd(&zs);
+ return NULL;
+ }
+ bsiz = 0;
+ zs.next_in = (unsigned char *)ptr;
+ zs.avail_in = size;
+ zs.next_out = obuf;
+ zs.avail_out = ZLIBBUFSIZ;
+ while((rv = deflate(&zs, Z_FINISH)) == Z_OK){
+ osiz = ZLIBBUFSIZ - zs.avail_out;
+ if(bsiz + osiz > asiz){
+ asiz = asiz * 2 + osiz;
+ if(!(swap = realloc(buf, asiz))){
+ free(buf);
+ deflateEnd(&zs);
+ return NULL;
+ }
+ buf = swap;
+ }
+ memcpy(buf + bsiz, obuf, osiz);
+ bsiz += osiz;
+ zs.next_out = obuf;
+ zs.avail_out = ZLIBBUFSIZ;
+ }
+ if(rv != Z_STREAM_END){
+ free(buf);
+ deflateEnd(&zs);
+ return NULL;
+ }
+ osiz = ZLIBBUFSIZ - zs.avail_out;
+ if(bsiz + osiz + 1 > asiz){
+ asiz = asiz * 2 + osiz;
+ if(!(swap = realloc(buf, asiz))){
+ free(buf);
+ deflateEnd(&zs);
+ return NULL;
+ }
+ buf = swap;
+ }
+ memcpy(buf + bsiz, obuf, osiz);
+ bsiz += osiz;
+ buf[bsiz] = '\0';
+ if(mode == _QDBM_ZMRAW) bsiz++;
+ *sp = bsiz;
+ deflateEnd(&zs);
+ return buf;
+}
+
+
+static char *_qdbm_inflate_impl(const char *ptr, int size, int *sp, int mode){
+ z_stream zs;
+ char *buf, *swap;
+ unsigned char obuf[ZLIBBUFSIZ];
+ int rv, asiz, bsiz, osiz;
+ zs.zalloc = Z_NULL;
+ zs.zfree = Z_NULL;
+ zs.opaque = Z_NULL;
+ switch(mode){
+ case _QDBM_ZMRAW:
+ if(inflateInit2(&zs, -15) != Z_OK) return NULL;
+ break;
+ case _QDBM_ZMGZIP:
+ if(inflateInit2(&zs, 15 + 16) != Z_OK) return NULL;
+ break;
+ default:
+ if(inflateInit2(&zs, 15) != Z_OK) return NULL;
+ break;
+ }
+ asiz = size * 2 + 16;
+ if(asiz < ZLIBBUFSIZ) asiz = ZLIBBUFSIZ;
+ if(!(buf = malloc(asiz))){
+ inflateEnd(&zs);
+ return NULL;
+ }
+ bsiz = 0;
+ zs.next_in = (unsigned char *)ptr;
+ zs.avail_in = size;
+ zs.next_out = obuf;
+ zs.avail_out = ZLIBBUFSIZ;
+ while((rv = inflate(&zs, Z_NO_FLUSH)) == Z_OK){
+ osiz = ZLIBBUFSIZ - zs.avail_out;
+ if(bsiz + osiz >= asiz){
+ asiz = asiz * 2 + osiz;
+ if(!(swap = realloc(buf, asiz))){
+ free(buf);
+ inflateEnd(&zs);
+ return NULL;
+ }
+ buf = swap;
+ }
+ memcpy(buf + bsiz, obuf, osiz);
+ bsiz += osiz;
+ zs.next_out = obuf;
+ zs.avail_out = ZLIBBUFSIZ;
+ }
+ if(rv != Z_STREAM_END){
+ free(buf);
+ inflateEnd(&zs);
+ return NULL;
+ }
+ osiz = ZLIBBUFSIZ - zs.avail_out;
+ if(bsiz + osiz >= asiz){
+ asiz = asiz * 2 + osiz;
+ if(!(swap = realloc(buf, asiz))){
+ free(buf);
+ inflateEnd(&zs);
+ return NULL;
+ }
+ buf = swap;
+ }
+ memcpy(buf + bsiz, obuf, osiz);
+ bsiz += osiz;
+ buf[bsiz] = '\0';
+ if(sp) *sp = bsiz;
+ inflateEnd(&zs);
+ return buf;
+}
+
+
+static unsigned int _qdbm_getcrc_impl(const char *ptr, int size){
+ int crc;
+ if(size < 0) size = strlen(ptr);
+ crc = crc32(0, Z_NULL, 0);
+ return crc32(crc, (unsigned char *)ptr, size);
+}
+
+
+#else
+
+
+char *(*_qdbm_deflate)(const char *, int, int *, int) = NULL;
+char *(*_qdbm_inflate)(const char *, int, int *, int) = NULL;
+unsigned int (*_qdbm_getcrc)(const char *, int) = NULL;
+
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for LZO
+ *************************************************************************************************/
+
+
+#if defined(MYLZO)
+
+
+#include <lzo/lzo1x.h>
+
+
+static char *_qdbm_lzoencode_impl(const char *ptr, int size, int *sp);
+static char *_qdbm_lzodecode_impl(const char *ptr, int size, int *sp);
+
+
+int _qdbm_lzo_init = FALSE;
+char *(*_qdbm_lzoencode)(const char *, int, int *) = _qdbm_lzoencode_impl;
+char *(*_qdbm_lzodecode)(const char *, int, int *) = _qdbm_lzodecode_impl;
+
+
+static char *_qdbm_lzoencode_impl(const char *ptr, int size, int *sp){
+ char wrkmem[LZO1X_1_MEM_COMPRESS];
+ lzo_bytep buf;
+ lzo_uint bsiz;
+ if(!_qdbm_lzo_init){
+ if(lzo_init() != LZO_E_OK) return NULL;
+ _qdbm_lzo_init = TRUE;
+ }
+ if(size < 0) size = strlen(ptr);
+ if(!(buf = malloc(size + size / 16 + 80))) return NULL;
+ if(lzo1x_1_compress((lzo_bytep)ptr, size, buf, &bsiz, wrkmem) != LZO_E_OK){
+ free(buf);
+ return NULL;
+ }
+ buf[bsiz] = '\0';
+ *sp = bsiz;
+ return (char *)buf;
+}
+
+
+static char *_qdbm_lzodecode_impl(const char *ptr, int size, int *sp){
+ lzo_bytep buf;
+ lzo_uint bsiz;
+ int rat, rv;
+ if(!_qdbm_lzo_init){
+ if(lzo_init() != LZO_E_OK) return NULL;
+ _qdbm_lzo_init = TRUE;
+ }
+ rat = 6;
+ while(TRUE){
+ bsiz = (size + 256) * rat + 3;
+ if(!(buf = malloc(bsiz + 1))) return NULL;
+ rv = lzo1x_decompress_safe((lzo_bytep)(ptr), size, buf, &bsiz, NULL);
+ if(rv == LZO_E_OK){
+ break;
+ } else if(rv == LZO_E_OUTPUT_OVERRUN){
+ free(buf);
+ rat *= 2;
+ } else {
+ free(buf);
+ return NULL;
+ }
+ }
+ buf[bsiz] = '\0';
+ if(sp) *sp = bsiz;
+ return (char *)buf;
+}
+
+
+#else
+
+
+char *(*_qdbm_lzoencode)(const char *, int, int *) = NULL;
+char *(*_qdbm_lzodecode)(const char *, int, int *) = NULL;
+
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for BZIP2
+ *************************************************************************************************/
+
+
+#if defined(MYBZIP)
+
+
+#include <bzlib.h>
+
+#define BZIPBUFSIZ 8192
+
+
+static char *_qdbm_bzencode_impl(const char *ptr, int size, int *sp);
+static char *_qdbm_bzdecode_impl(const char *ptr, int size, int *sp);
+
+
+char *(*_qdbm_bzencode)(const char *, int, int *) = _qdbm_bzencode_impl;
+char *(*_qdbm_bzdecode)(const char *, int, int *) = _qdbm_bzdecode_impl;
+
+
+static char *_qdbm_bzencode_impl(const char *ptr, int size, int *sp){
+ bz_stream zs;
+ char *buf, *swap, obuf[BZIPBUFSIZ];
+ int rv, asiz, bsiz, osiz;
+ if(size < 0) size = strlen(ptr);
+ zs.bzalloc = NULL;
+ zs.bzfree = NULL;
+ zs.opaque = NULL;
+ if(BZ2_bzCompressInit(&zs, 9, 0, 30) != BZ_OK) return NULL;
+ asiz = size + 16;
+ if(asiz < BZIPBUFSIZ) asiz = BZIPBUFSIZ;
+ if(!(buf = malloc(asiz))){
+ BZ2_bzCompressEnd(&zs);
+ return NULL;
+ }
+ bsiz = 0;
+ zs.next_in = (char *)ptr;
+ zs.avail_in = size;
+ zs.next_out = obuf;
+ zs.avail_out = BZIPBUFSIZ;
+ while((rv = BZ2_bzCompress(&zs, BZ_FINISH)) == BZ_FINISH_OK){
+ osiz = BZIPBUFSIZ - zs.avail_out;
+ if(bsiz + osiz > asiz){
+ asiz = asiz * 2 + osiz;
+ if(!(swap = realloc(buf, asiz))){
+ free(buf);
+ BZ2_bzCompressEnd(&zs);
+ return NULL;
+ }
+ buf = swap;
+ }
+ memcpy(buf + bsiz, obuf, osiz);
+ bsiz += osiz;
+ zs.next_out = obuf;
+ zs.avail_out = BZIPBUFSIZ;
+ }
+ if(rv != BZ_STREAM_END){
+ free(buf);
+ BZ2_bzCompressEnd(&zs);
+ return NULL;
+ }
+ osiz = BZIPBUFSIZ - zs.avail_out;
+ if(bsiz + osiz + 1 > asiz){
+ asiz = asiz * 2 + osiz;
+ if(!(swap = realloc(buf, asiz))){
+ free(buf);
+ BZ2_bzCompressEnd(&zs);
+ return NULL;
+ }
+ buf = swap;
+ }
+ memcpy(buf + bsiz, obuf, osiz);
+ bsiz += osiz;
+ buf[bsiz] = '\0';
+ *sp = bsiz;
+ BZ2_bzCompressEnd(&zs);
+ return buf;
+}
+
+
+static char *_qdbm_bzdecode_impl(const char *ptr, int size, int *sp){
+ bz_stream zs;
+ char *buf, *swap, obuf[BZIPBUFSIZ];
+ int rv, asiz, bsiz, osiz;
+ zs.bzalloc = NULL;
+ zs.bzfree = NULL;
+ zs.opaque = NULL;
+ if(BZ2_bzDecompressInit(&zs, 0, 0) != BZ_OK) return NULL;
+ asiz = size * 2 + 16;
+ if(asiz < BZIPBUFSIZ) asiz = BZIPBUFSIZ;
+ if(!(buf = malloc(asiz))){
+ BZ2_bzDecompressEnd(&zs);
+ return NULL;
+ }
+ bsiz = 0;
+ zs.next_in = (char *)ptr;
+ zs.avail_in = size;
+ zs.next_out = obuf;
+ zs.avail_out = BZIPBUFSIZ;
+ while((rv = BZ2_bzDecompress(&zs)) == BZ_OK){
+ osiz = BZIPBUFSIZ - zs.avail_out;
+ if(bsiz + osiz >= asiz){
+ asiz = asiz * 2 + osiz;
+ if(!(swap = realloc(buf, asiz))){
+ free(buf);
+ BZ2_bzDecompressEnd(&zs);
+ return NULL;
+ }
+ buf = swap;
+ }
+ memcpy(buf + bsiz, obuf, osiz);
+ bsiz += osiz;
+ zs.next_out = obuf;
+ zs.avail_out = BZIPBUFSIZ;
+ }
+ if(rv != BZ_STREAM_END){
+ free(buf);
+ BZ2_bzDecompressEnd(&zs);
+ return NULL;
+ }
+ osiz = BZIPBUFSIZ - zs.avail_out;
+ if(bsiz + osiz >= asiz){
+ asiz = asiz * 2 + osiz;
+ if(!(swap = realloc(buf, asiz))){
+ free(buf);
+ BZ2_bzDecompressEnd(&zs);
+ return NULL;
+ }
+ buf = swap;
+ }
+ memcpy(buf + bsiz, obuf, osiz);
+ bsiz += osiz;
+ buf[bsiz] = '\0';
+ if(sp) *sp = bsiz;
+ BZ2_bzDecompressEnd(&zs);
+ return buf;
+}
+
+
+#else
+
+
+char *(*_qdbm_bzencode)(const char *, int, int *) = NULL;
+char *(*_qdbm_bzdecode)(const char *, int, int *) = NULL;
+
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for ICONV
+ *************************************************************************************************/
+
+
+#if defined(MYICONV)
+
+
+#include <iconv.h>
+
+#define ICONVCHECKSIZ 32768
+#define ICONVMISSMAX 256
+#define ICONVALLWRAT 0.001
+
+
+static char *_qdbm_iconv_impl(const char *ptr, int size,
+ const char *icode, const char *ocode, int *sp, int *mp);
+static const char *_qdbm_encname_impl(const char *ptr, int size);
+static int _qdbm_encmiss(const char *ptr, int size, const char *icode, const char *ocode);
+
+
+char *(*_qdbm_iconv)(const char *, int, const char *, const char *,
+ int *, int *) = _qdbm_iconv_impl;
+const char *(*_qdbm_encname)(const char *, int) = _qdbm_encname_impl;
+
+
+static char *_qdbm_iconv_impl(const char *ptr, int size,
+ const char *icode, const char *ocode, int *sp, int *mp){
+ iconv_t ic;
+ char *obuf, *wp, *rp;
+ size_t isiz, osiz;
+ int miss;
+ if(size < 0) size = strlen(ptr);
+ isiz = size;
+ if((ic = iconv_open(ocode, icode)) == (iconv_t)-1) return NULL;
+ osiz = isiz * 5;
+ if(!(obuf = malloc(osiz + 1))){
+ iconv_close(ic);
+ return NULL;
+ }
+ wp = obuf;
+ rp = (char *)ptr;
+ miss = 0;
+ while(isiz > 0){
+ if(iconv(ic, (void *)&rp, &isiz, &wp, &osiz) == -1){
+ if(errno == EILSEQ && (*rp == 0x5c || *rp == 0x7e)){
+ *wp = *rp;
+ wp++;
+ rp++;
+ isiz--;
+ } else if(errno == EILSEQ || errno == EINVAL){
+ rp++;
+ isiz--;
+ miss++;
+ } else {
+ break;
+ }
+ }
+ }
+ *wp = '\0';
+ if(iconv_close(ic) == -1){
+ free(obuf);
+ return NULL;
+ }
+ if(sp) *sp = wp - obuf;
+ if(mp) *mp = miss;
+ return obuf;
+}
+
+
+static const char *_qdbm_encname_impl(const char *ptr, int size){
+ const char *hypo;
+ int i, miss, cr;
+ if(size < 0) size = strlen(ptr);
+ if(size > ICONVCHECKSIZ) size = ICONVCHECKSIZ;
+ if(size >= 2 && (!memcmp(ptr, "\xfe\xff", 2) || !memcmp(ptr, "\xff\xfe", 2))) return "UTF-16";
+ for(i = 0; i < size - 1; i += 2){
+ if(ptr[i] == 0 && ptr[i+1] != 0) return "UTF-16BE";
+ if(ptr[i+1] == 0 && ptr[i] != 0) return "UTF-16LE";
+ }
+ for(i = 0; i < size - 3; i++){
+ if(ptr[i] == 0x1b){
+ i++;
+ if(ptr[i] == '(' && strchr("BJHI", ptr[i+1])) return "ISO-2022-JP";
+ if(ptr[i] == '$' && strchr("@B(", ptr[i+1])) return "ISO-2022-JP";
+ }
+ }
+ if(_qdbm_encmiss(ptr, size, "US-ASCII", "UTF-16BE") < 1) return "US-ASCII";
+ if(_qdbm_encmiss(ptr, size, "UTF-8", "UTF-16BE") < 1) return "UTF-8";
+ hypo = NULL;
+ cr = FALSE;
+ for(i = 0; i < size; i++){
+ if(ptr[i] == 0xd){
+ cr = TRUE;
+ break;
+ }
+ }
+ if(cr){
+ if((miss = _qdbm_encmiss(ptr, size, "Shift_JIS", "EUC-JP")) < 1) return "Shift_JIS";
+ if(!hypo && miss / (double)size <= ICONVALLWRAT) hypo = "Shift_JIS";
+ if((miss = _qdbm_encmiss(ptr, size, "EUC-JP", "UTF-16BE")) < 1) return "EUC-JP";
+ if(!hypo && miss / (double)size <= ICONVALLWRAT) hypo = "EUC-JP";
+ } else {
+ if((miss = _qdbm_encmiss(ptr, size, "EUC-JP", "UTF-16BE")) < 1) return "EUC-JP";
+ if(!hypo && miss / (double)size <= ICONVALLWRAT) hypo = "EUC-JP";
+ if((miss = _qdbm_encmiss(ptr, size, "Shift_JIS", "EUC-JP")) < 1) return "Shift_JIS";
+ if(!hypo && miss / (double)size <= ICONVALLWRAT) hypo = "Shift_JIS";
+ }
+ if((miss = _qdbm_encmiss(ptr, size, "UTF-8", "UTF-16BE")) < 1) return "UTF-8";
+ if(!hypo && miss / (double)size <= ICONVALLWRAT) hypo = "UTF-8";
+ if((miss = _qdbm_encmiss(ptr, size, "CP932", "UTF-16BE")) < 1) return "CP932";
+ if(!hypo && miss / (double)size <= ICONVALLWRAT) hypo = "CP932";
+ return hypo ? hypo : "ISO-8859-1";
+}
+
+
+static int _qdbm_encmiss(const char *ptr, int size, const char *icode, const char *ocode){
+ iconv_t ic;
+ char obuf[ICONVCHECKSIZ], *wp, *rp;
+ size_t isiz, osiz;
+ int miss;
+ isiz = size;
+ if((ic = iconv_open(ocode, icode)) == (iconv_t)-1) return ICONVMISSMAX;
+ miss = 0;
+ rp = (char *)ptr;
+ while(isiz > 0){
+ osiz = ICONVCHECKSIZ;
+ wp = obuf;
+ if(iconv(ic, (void *)&rp, &isiz, &wp, &osiz) == -1){
+ if(errno == EILSEQ || errno == EINVAL){
+ rp++;
+ isiz--;
+ miss++;
+ if(miss >= ICONVMISSMAX) break;
+ } else {
+ break;
+ }
+ }
+ }
+ if(iconv_close(ic) == -1) return ICONVMISSMAX;
+ return miss;
+}
+
+
+#else
+
+
+char *(*_qdbm_iconv)(const char *, int, const char *, const char *, int *, int *) = NULL;
+const char *(*_qdbm_encname)(const char *, int) = NULL;
+
+
+#endif
+
+
+
+/*************************************************************************************************
+ * common settings
+ *************************************************************************************************/
+
+
+int _qdbm_dummyfunc(void){
+ return 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/myconf.h b/qdbm/myconf.h
new file mode 100644
index 00000000..3b33ed37
--- /dev/null
+++ b/qdbm/myconf.h
@@ -0,0 +1,593 @@
+/*************************************************************************************************
+ * System configurations for QDBM
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#ifndef _MYCONF_H /* duplication check */
+#define _MYCONF_H
+
+#if defined(__cplusplus) /* export for C++ */
+extern "C" {
+#endif
+
+
+
+/*************************************************************************************************
+ * system discrimination
+ *************************************************************************************************/
+
+
+#if defined(__linux__)
+
+#define _SYS_LINUX_
+#define _QDBM_SYSNAME "Linux"
+
+#elif defined(__FreeBSD__)
+
+#define _SYS_FREEBSD_
+#define _QDBM_SYSNAME "FreeBSD"
+
+#elif defined(__NetBSD__)
+
+#define _SYS_NETBSD_
+#define _QDBM_SYSNAME "NetBSD"
+
+#elif defined(__OpenBSD__)
+
+#define _SYS_OPENBSD_
+#define _QDBM_SYSNAME "OpenBSD"
+
+#elif defined(__sun__)
+
+#define _SYS_SUNOS_
+#define _QDBM_SYSNAME "SunOS"
+
+#elif defined(__hpux)
+
+#define _SYS_HPUX_
+#define _QDBM_SYSNAME "HP-UX"
+
+#elif defined(__osf)
+
+#define _SYS_TRU64_
+#define _QDBM_SYSNAME "Tru64"
+
+#elif defined(_AIX)
+
+#define _SYS_AIX_
+#define _QDBM_SYSNAME "AIX"
+
+#elif defined(__APPLE__) && defined(__MACH__)
+
+#define _SYS_MACOSX_
+#define _QDBM_SYSNAME "Mac OS X"
+
+#elif defined(_MSC_VER)
+
+#define _SYS_MSVC_
+#define _QDBM_SYSNAME "Windows (VC++)"
+
+#elif defined(_WIN32)
+
+#define _SYS_MINGW_
+#define _QDBM_SYSNAME "Windows (MinGW)"
+
+#elif defined(__CYGWIN__)
+
+#define _SYS_CYGWIN_
+#define _QDBM_SYSNAME "Windows (Cygwin)"
+
+#elif defined(__riscos__) || defined(__riscos)
+
+#define _SYS_RISCOS_
+#define _QDBM_SYSNAME "RISC OS"
+
+#else
+
+#define _SYS_GENERIC_
+#define _QDBM_SYSNAME "Generic"
+
+#endif
+
+
+
+/*************************************************************************************************
+ * general headers
+ *************************************************************************************************/
+
+
+#if defined(_SYS_MSVC_)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <time.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <direct.h>
+#include <windows.h>
+#include <io.h>
+
+#elif defined(_SYS_MINGW_)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <time.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <windows.h>
+#include <io.h>
+
+#elif defined(_SYS_CYGWIN_)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <time.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/times.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <windows.h>
+#include <io.h>
+
+#else
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <time.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/times.h>
+#include <fcntl.h>
+#include <dirent.h>
+
+#endif
+
+
+
+/*************************************************************************************************
+ * notation of filesystems
+ *************************************************************************************************/
+
+
+#if defined(_SYS_MSVC_) || defined(_SYS_MINGW_)
+
+#define MYPATHCHR '\\'
+#define MYPATHSTR "\\"
+#define MYEXTCHR '.'
+#define MYEXTSTR "."
+#define MYCDIRSTR "."
+#define MYPDIRSTR ".."
+
+#elif defined(_SYS_RISCOS_)
+
+#define MYPATHCHR '.'
+#define MYPATHSTR "."
+#define MYEXTCHR '/'
+#define MYEXTSTR "/"
+#define MYCDIRSTR "@"
+#define MYPDIRSTR "^"
+
+#else
+
+#define MYPATHCHR '/'
+#define MYPATHSTR "/"
+#define MYEXTCHR '.'
+#define MYEXTSTR "."
+#define MYCDIRSTR "."
+#define MYPDIRSTR ".."
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for dosish filesystems
+ *************************************************************************************************/
+
+
+#if defined(_SYS_MSVC_) || defined(_SYS_MINGW_) || defined(_SYS_CYGWIN_)
+
+#undef UNICODE
+#undef open
+
+#define \
+ open(pathname, flags, mode) \
+ open(pathname, flags | O_BINARY, mode)
+
+#define \
+ lstat(pathname, buf) \
+ _qdbm_win32_lstat(pathname, buf)
+
+int _qdbm_win32_lstat(const char *pathname, struct stat *buf);
+
+#else
+
+#undef O_BINARY
+#undef O_TEXT
+#undef setmode
+
+#define O_BINARY 0
+#define O_TEXT 1
+
+#define \
+ setmode(fd, mode) \
+ (O_BINARY)
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for POSIX thread
+ *************************************************************************************************/
+
+
+#if defined(MYPTHREAD)
+
+#define _qdbm_ptsafe TRUE
+
+void *_qdbm_settsd(void *ptr, int size, const void *initval);
+
+#else
+
+#define _qdbm_ptsafe FALSE
+
+#define \
+ _qdbm_settsd(ptr, size, initval) \
+ (NULL)
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for systems without file locking
+ *************************************************************************************************/
+
+
+#if defined(_SYS_RISCOS_) || defined(MYNOLOCK)
+
+#undef fcntl
+
+#define \
+ fcntl(fd, cmd, lock) \
+ (0)
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for systems without mmap
+ *************************************************************************************************/
+
+
+#if defined(_SYS_MSVC_) || defined(_SYS_MINGW_) || \
+ defined(_SYS_FREEBSD_) || defined(_SYS_NETBSD_) || defined(_SYS_OPENBSD_) || \
+ defined(_SYS_AIX_) || defined(_SYS_RISCOS_) || defined(MYNOMMAP)
+
+#undef PROT_EXEC
+#undef PROT_READ
+#undef PROT_WRITE
+#undef PROT_NONE
+#undef MAP_FIXED
+#undef MAP_SHARED
+#undef MAP_PRIVATE
+#undef MAP_FAILED
+#undef MS_ASYNC
+#undef MS_SYNC
+#undef MS_INVALIDATE
+#undef mmap
+#undef munmap
+#undef msync
+#undef mflush
+
+#define PROT_EXEC (1 << 0)
+#define PROT_READ (1 << 1)
+#define PROT_WRITE (1 << 2)
+#define PROT_NONE (1 << 3)
+#define MAP_FIXED 1
+#define MAP_SHARED 2
+#define MAP_PRIVATE 3
+#define MAP_FAILED ((void *)-1)
+#define MS_ASYNC (1 << 0)
+#define MS_SYNC (1 << 1)
+#define MS_INVALIDATE (1 << 2)
+
+#define \
+ mmap(start, length, prot, flags, fd, offset) \
+ _qdbm_mmap(start, length, prot, flags, fd, offset)
+
+#define \
+ munmap(start, length) \
+ _qdbm_munmap(start, length)
+
+#define \
+ msync(start, length, flags) \
+ _qdbm_msync(start, length, flags)
+
+#define \
+ mflush(start, length, flags) \
+ _qdbm_msync(start, length, flags)
+
+void *_qdbm_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
+int _qdbm_munmap(void *start, size_t length);
+int _qdbm_msync(const void *start, size_t length, int flags);
+
+#else
+
+#undef mflush
+#define \
+ mflush(start, length, flags) \
+ (0)
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for reentrant time routines
+ *************************************************************************************************/
+
+
+struct tm *_qdbm_gmtime(const time_t *timep, struct tm *result);
+struct tm *_qdbm_localtime(const time_t *timep, struct tm *result);
+
+
+
+/*************************************************************************************************
+ * for systems without times
+ *************************************************************************************************/
+
+
+#if defined(_SYS_MSVC_) || defined(_SYS_MINGW_)
+
+#undef times
+#undef sysconf
+
+struct tms {
+ clock_t tms_utime;
+ clock_t tms_stime;
+ clock_t tms_cutime;
+ clock_t tms_cstime;
+};
+
+#define \
+ times(buf) \
+ _qdbm_times(buf)
+
+#define \
+ sysconf(name) \
+ (CLOCKS_PER_SEC)
+
+clock_t _qdbm_times(struct tms *buf);
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for Win32
+ *************************************************************************************************/
+
+
+#if defined(_SYS_MSVC_) || defined(_SYS_MINGW_)
+
+#undef F_WRLCK
+#undef F_RDLCK
+#undef F_SETLK
+#undef F_SETLKW
+#undef fcntl
+#undef ftruncate
+#undef fsync
+#undef mkdir
+#undef rename
+
+#define F_WRLCK 0
+#define F_RDLCK 1
+#define F_SETLK 0
+#define F_SETLKW 1
+
+struct flock {
+ int l_type;
+ int l_whence;
+ int l_start;
+ int l_len;
+ int l_pid;
+};
+
+#define \
+ fcntl(fd, cmd, lock) \
+ _qdbm_win32_fcntl(fd, cmd, lock)
+
+#define \
+ ftruncate(fd, length) \
+ _chsize(fd, length)
+
+#define \
+ fsync(fd) \
+ (0)
+
+#define \
+ mkdir(pathname, mode) \
+ mkdir(pathname)
+
+#define \
+ rename(oldpath, newpath) \
+ (unlink(newpath), rename(oldpath, newpath))
+
+int _qdbm_win32_fcntl(int fd, int cmd, struct flock *lock);
+
+#endif
+
+
+#if defined(_SYS_MSVC_)
+
+#undef S_ISDIR
+#undef S_ISREG
+#undef opendir
+#undef closedir
+#undef readdir
+
+#define S_ISDIR(x) (x & _S_IFDIR)
+#define S_ISREG(x) (x & _S_IFREG)
+
+struct dirent {
+ char d_name[1024];
+};
+
+typedef struct {
+ HANDLE fh;
+ WIN32_FIND_DATA data;
+ struct dirent de;
+ int first;
+} DIR;
+
+#define \
+ opendir(name) \
+ _qdbm_win32_opendir(name)
+
+#define \
+ closedir(dir) \
+ _qdbm_win32_closedir(dir)
+
+#define \
+ readdir(dir) \
+ _qdbm_win32_readdir(dir)
+
+DIR *_qdbm_win32_opendir(const char *name);
+
+int _qdbm_win32_closedir(DIR *dir);
+
+struct dirent *_qdbm_win32_readdir(DIR *dir);
+
+#endif
+
+
+
+/*************************************************************************************************
+ * for checking information of the system
+ *************************************************************************************************/
+
+
+int _qdbm_vmemavail(size_t size);
+
+
+
+/*************************************************************************************************
+ * for ZLIB
+ *************************************************************************************************/
+
+
+enum {
+ _QDBM_ZMZLIB,
+ _QDBM_ZMRAW,
+ _QDBM_ZMGZIP
+};
+
+
+extern char *(*_qdbm_deflate)(const char *, int, int *, int);
+
+extern char *(*_qdbm_inflate)(const char *, int, int *, int);
+
+extern unsigned int (*_qdbm_getcrc)(const char *, int);
+
+
+
+/*************************************************************************************************
+ * for LZO
+ *************************************************************************************************/
+
+
+extern char *(*_qdbm_lzoencode)(const char *, int, int *);
+
+extern char *(*_qdbm_lzodecode)(const char *, int, int *);
+
+
+
+/*************************************************************************************************
+ * for BZIP2
+ *************************************************************************************************/
+
+
+extern char *(*_qdbm_bzencode)(const char *, int, int *);
+
+extern char *(*_qdbm_bzdecode)(const char *, int, int *);
+
+
+
+/*************************************************************************************************
+ * for ICONV
+ *************************************************************************************************/
+
+
+extern char *(*_qdbm_iconv)(const char *, int, const char *, const char *, int *, int *);
+
+extern const char *(*_qdbm_encname)(const char *, int);
+
+
+
+/*************************************************************************************************
+ * common settings
+ *************************************************************************************************/
+
+
+#undef TRUE
+#define TRUE 1
+#undef FALSE
+#define FALSE 0
+
+#define sizeof(a) ((int)sizeof(a))
+
+int _qdbm_dummyfunc(void);
+
+
+
+#if defined(__cplusplus) /* export for C++ */
+}
+#endif
+
+#endif /* duplication check */
+
+
+/* END OF FILE */
diff --git a/qdbm/odeum.c b/qdbm/odeum.c
new file mode 100644
index 00000000..15395224
--- /dev/null
+++ b/qdbm/odeum.c
@@ -0,0 +1,2090 @@
+/*************************************************************************************************
+ * Implementation of Odeum
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#define QDBM_INTERNAL 1
+
+#include "odeum.h"
+#include "myconf.h"
+
+#define OD_NAMEMAX 256 /* max size of a database name */
+#define OD_DIRMODE 00755 /* permission of a creating directory */
+#define OD_PATHBUFSIZ 1024 /* size of a path buffer */
+#define OD_NUMBUFSIZ 32 /* size of a buffer for a number */
+#define OD_MAPPBNUM 127 /* bucket size of a petit map handle */
+#define OD_DOCSNAME "docs" /* name of the database for documents */
+#define OD_INDEXNAME "index" /* name of the database for inverted index */
+#define OD_RDOCSNAME "rdocs" /* name of the database for reverse dictionary */
+#define OD_DOCSBNUM 2039 /* initial bucket number of document database */
+#define OD_DOCSDNUM 17 /* division number of document database */
+#define OD_DOCSALIGN -4 /* alignment of document database */
+#define OD_DOCSFBP 32 /* size of free block pool of document database */
+#define OD_INDEXBNUM 32749 /* initial bucket number of inverted index */
+#define OD_INDEXDNUM 7 /* division number of inverted index */
+#define OD_INDEXALIGN -2 /* alignment of inverted index */
+#define OD_INDEXFBP 32 /* size of free block pool of inverted index */
+#define OD_RDOCSLRM 81 /* records in a leaf node of reverse dictionary */
+#define OD_RDOCSNIM 192 /* records in a non-leaf node of reverse dictionary */
+#define OD_RDOCSLCN 128 /* number of leaf cache of reverse dictionary */
+#define OD_RDOCSNCN 32 /* number of non-leaf cache of reverse dictionary */
+#define OD_CACHEBNUM 262139 /* number of buckets for dirty buffers */
+#define OD_CACHESIZ 8388608 /* max bytes to use memory for dirty buffers */
+#define OD_CFLIVERAT 0.8 /* ratio of usable cache region */
+#define OD_CFBEGSIZ 2048 /* beginning size of flushing frequent words */
+#define OD_CFENDSIZ 64 /* lower limit of flushing frequent words */
+#define OD_CFRFRAT 0.2 /* ratio of flushing rare words a time */
+#define OD_OTCBBUFSIZ 1024 /* size of a buffer for call back functions */
+#define OD_OTPERWORDS 10000 /* frequency of call back in merging index */
+#define OD_OTPERDOCS 1000 /* frequency of call back in merging docs */
+#define OD_MDBRATIO 2.5 /* ratio of bucket number and document number */
+#define OD_MIBRATIO 1.5 /* ratio of bucket number and word number */
+#define OD_MIARATIO 0.75 /* ratio of alignment to the first words */
+#define OD_MIWUNIT 32 /* writing unit of merging inverted index */
+#define OD_DMAXEXPR "dmax" /* key of max number of the document ID */
+#define OD_DNUMEXPR "dnum" /* key of number of the documents */
+#define OD_URIEXPR "1" /* map key of URI */
+#define OD_ATTRSEXPR "2" /* map key of attributes */
+#define OD_NWORDSEXPR "3" /* map key of normal words */
+#define OD_AWORDSEXPR "4" /* map key of as-is words */
+#define OD_WTOPRATE 0.1 /* ratio of top words */
+#define OD_WTOPBONUS 5000 /* bonus points of top words */
+#define OD_KEYCRATIO 1.75 /* ratio of number to max of keyword candidates */
+#define OD_WOCCRPOINT 10000 /* points per occurence */
+#define OD_SPACECHARS "\t\n\v\f\r " /* space characters */
+#define OD_DELIMCHARS "!\"#$%&'()*/<=>?[\\]^`{|}~" /* delimiter characters */
+#define OD_GLUECHARS "+,-.:;@" /* glueing characters */
+#define OD_MAXWORDLEN 48 /* max length of a word */
+
+typedef struct { /* type of structure for word counting */
+ const char *word; /* pointer to the word */
+ int num; /* frequency of the word */
+} ODWORD;
+
+enum { /* enumeration for events binded to each character */
+ OD_EVWORD, /* word */
+ OD_EVSPACE, /* space */
+ OD_EVDELIM, /* delimiter */
+ OD_EVGLUE /* glue */
+};
+
+
+/* private global variables */
+int odindexbnum = OD_INDEXBNUM;
+int odindexdnum = OD_INDEXDNUM;
+int odcachebnum = OD_CACHEBNUM;
+int odcachesiz = OD_CACHESIZ;
+void (*odotcb)(const char *, ODEUM *, const char *) = NULL;
+
+
+/* private function prototypes */
+static ODEUM *odopendb(const char *name, int omode, int docsbnum, int indexbnum,
+ const char *fname);
+static int odcacheflush(ODEUM *odeum, const char *fname);
+static int odcacheflushfreq(ODEUM *odeum, const char *fname, int min);
+static int odcacheflushrare(ODEUM *odeum, const char *fname, double ratio);
+static int odsortindex(ODEUM *odeum, const char *fname);
+static int odsortcompare(const void *a, const void *b);
+static int odpurgeindex(ODEUM *odeum, const char *fname);
+static CBMAP *odpairsmap(const ODPAIR *pairs, int num);
+static int odwordcompare(const void *a, const void *b);
+static int odmatchoperator(ODEUM *odeum, CBLIST *tokens);
+static ODPAIR *odparsesubexpr(ODEUM *odeum, CBLIST *tokens, CBLIST *nwords, int *np,
+ CBLIST *errors);
+static ODPAIR *odparseexpr(ODEUM *odeum, CBLIST *tokens, CBLIST *nwords, int *np,
+ CBLIST *errors);
+static void odfixtokens(ODEUM *odeum, CBLIST *tokens);
+static void odcleannormalized(ODEUM *odeum, CBLIST *nwords);
+
+
+
+/*************************************************************************************************
+ * public objects
+ *************************************************************************************************/
+
+
+/* Get a database handle. */
+ODEUM *odopen(const char *name, int omode){
+ assert(name);
+ return odopendb(name, omode, OD_DOCSBNUM, odindexbnum, "odopen");
+}
+
+
+/* Close a database handle. */
+int odclose(ODEUM *odeum){
+ char numbuf[OD_NUMBUFSIZ];
+ int err;
+ assert(odeum);
+ err = FALSE;
+ if(odotcb) odotcb("odclose", odeum, "closing the connection");
+ if(odeum->wmode){
+ if(odotcb) odotcb("odclose", odeum, "writing meta information");
+ sprintf(numbuf, "%d", odeum->dmax);
+ if(!vlput(odeum->rdocsdb, OD_DMAXEXPR, sizeof(OD_DMAXEXPR), numbuf, -1, VL_DOVER)) err = TRUE;
+ sprintf(numbuf, "%d", odeum->dnum);
+ if(!vlput(odeum->rdocsdb, OD_DNUMEXPR, sizeof(OD_DNUMEXPR), numbuf, -1, VL_DOVER)) err = TRUE;
+ if(!odcacheflushfreq(odeum, "odclose", OD_CFENDSIZ)) err = TRUE;
+ if(!odcacheflushrare(odeum, "odclose", OD_CFRFRAT)) err = TRUE;
+ if(!odcacheflush(odeum, "odclose")) err = TRUE;
+ if(!odsortindex(odeum, "odclose")) err = TRUE;
+ cbmapclose(odeum->cachemap);
+ cbmapclose(odeum->sortmap);
+ }
+ if(!vlclose(odeum->rdocsdb)) err = TRUE;
+ if(!crclose(odeum->indexdb)) err = TRUE;
+ if(!crclose(odeum->docsdb)) err = TRUE;
+ free(odeum->name);
+ free(odeum);
+ return err ? FALSE : TRUE;
+}
+
+
+/* Store a document. */
+int odput(ODEUM *odeum, ODDOC *doc, int wmax, int over){
+ char *tmp, *zbuf;
+ const char *word, *ctmp;
+ int i, docid, tsiz, wsiz, wnum, tmax, num, zsiz;
+ double ival;
+ ODPAIR pair;
+ CBMAP *map;
+ CBLIST *tlist;
+ assert(odeum);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!odeum->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if((tmp = vlget(odeum->rdocsdb, doc->uri, -1, &tsiz)) != NULL){
+ if(!over){
+ free(tmp);
+ dpecodeset(DP_EKEEP, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(tsiz != sizeof(int) || !odoutbyid(odeum, *(int *)tmp)){
+ free(tmp);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ free(tmp);
+ }
+ odeum->dmax++;
+ odeum->dnum++;
+ docid = odeum->dmax;
+ map = cbmapopen();
+ cbmapput(map, OD_URIEXPR, sizeof(OD_URIEXPR), doc->uri, -1, TRUE);
+ tmp = cbmapdump(doc->attrs, &tsiz);
+ cbmapput(map, OD_ATTRSEXPR, sizeof(OD_ATTRSEXPR), tmp, tsiz, TRUE);
+ free(tmp);
+ if(wmax < 0 || wmax > cblistnum(doc->nwords)) wmax = cblistnum(doc->nwords);
+ tlist = cblistopen();
+ for(i = 0; i < wmax; i++){
+ ctmp = cblistval(doc->nwords, i, &wsiz);
+ cblistpush(tlist, ctmp, wsiz);
+ }
+ tmp = cblistdump(tlist, &tsiz);
+ cbmapput(map, OD_NWORDSEXPR, sizeof(OD_NWORDSEXPR), tmp, tsiz, TRUE);
+ free(tmp);
+ cblistclose(tlist);
+ tlist = cblistopen();
+ for(i = 0; i < wmax; i++){
+ ctmp = cblistval(doc->awords, i, &wsiz);
+ if(strcmp(ctmp, cblistval(doc->nwords, i, NULL))){
+ cblistpush(tlist, ctmp, wsiz);
+ } else {
+ cblistpush(tlist, "\0", 1);
+ }
+ }
+ tmp = cblistdump(tlist, &tsiz);
+ cbmapput(map, OD_AWORDSEXPR, sizeof(OD_AWORDSEXPR), tmp, tsiz, TRUE);
+ free(tmp);
+ cblistclose(tlist);
+ tmp = cbmapdump(map, &tsiz);
+ cbmapclose(map);
+ if(_qdbm_deflate){
+ if(!(zbuf = _qdbm_deflate(tmp, tsiz, &zsiz, _QDBM_ZMRAW))){
+ free(tmp);
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ free(tmp);
+ tmp = zbuf;
+ tsiz = zsiz;
+ }
+ if(!crput(odeum->docsdb, (char *)&docid, sizeof(int), tmp, tsiz, CR_DKEEP)){
+ free(tmp);
+ if(dpecode == DP_EKEEP) dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ free(tmp);
+ if(!vlput(odeum->rdocsdb, doc->uri, -1, (char *)&docid, sizeof(int), VL_DOVER)){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ map = cbmapopen();
+ wnum = cblistnum(doc->nwords);
+ tmax = (int)(wnum * OD_WTOPRATE);
+ for(i = 0; i < wnum; i++){
+ word = cblistval(doc->nwords, i, &wsiz);
+ if(wsiz < 1) continue;
+ if((ctmp = cbmapget(map, word, wsiz, NULL)) != NULL){
+ num = *(int *)ctmp + OD_WOCCRPOINT;
+ } else {
+ num = i <= tmax ? OD_WTOPBONUS + OD_WOCCRPOINT : OD_WOCCRPOINT;
+ }
+ cbmapput(map, word, wsiz, (char *)&num, sizeof(int), TRUE);
+ }
+ ival = odlogarithm(wnum);
+ ival = (ival * ival * ival) / 8.0;
+ if(ival < 8.0) ival = 8.0;
+ cbmapiterinit(map);
+ while((word = cbmapiternext(map, &wsiz)) != NULL){
+ pair.id = docid;
+ pair.score = (int)(*(int *)cbmapget(map, word, wsiz, NULL) / ival);
+ cbmapputcat(odeum->cachemap, word, wsiz, (char *)&pair, sizeof(pair));
+ cbmapmove(odeum->cachemap, word, wsiz, FALSE);
+ odeum->cacheasiz += sizeof(pair);
+ cbmapput(odeum->sortmap, word, wsiz, "", 0, FALSE);
+ }
+ cbmapclose(map);
+ if(odeum->cacheasiz > odcachesiz){
+ for(i = OD_CFBEGSIZ; odeum->cacheasiz > odcachesiz * OD_CFLIVERAT && i >= OD_CFENDSIZ;
+ i /= 2){
+ if(!odcacheflushfreq(odeum, "odput", i)) return FALSE;
+ }
+ while(odeum->cacheasiz > odcachesiz * OD_CFLIVERAT){
+ if(!odcacheflushrare(odeum, "odput", OD_CFRFRAT)) return FALSE;
+ }
+ }
+ doc->id = docid;
+ odeum->ldid = docid;
+ return TRUE;
+}
+
+
+/* Delete a document by a URL. */
+int odout(ODEUM *odeum, const char *uri){
+ char *tmp;
+ int tsiz, docid;
+ assert(odeum && uri);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!odeum->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(tmp = vlget(odeum->rdocsdb, uri, -1, &tsiz))){
+ if(dpecode != DP_ENOITEM) odeum->fatal = TRUE;
+ return FALSE;
+ }
+ if(tsiz != sizeof(int)){
+ free(tmp);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ docid = *(int *)tmp;
+ free(tmp);
+ return odoutbyid(odeum, docid);
+}
+
+
+/* Delete a document specified by an ID number. */
+int odoutbyid(ODEUM *odeum, int id){
+ char *tmp, *zbuf;
+ const char *uritmp;
+ int tsiz, uritsiz, zsiz;
+ CBMAP *map;
+ assert(odeum && id > 0);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!odeum->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(tmp = crget(odeum->docsdb, (char *)&id, sizeof(int), 0, -1, &tsiz))){
+ if(dpecode != DP_ENOITEM) odeum->fatal = TRUE;
+ return FALSE;
+ }
+ if(_qdbm_inflate){
+ if(!(zbuf = _qdbm_inflate(tmp, tsiz, &zsiz, _QDBM_ZMRAW))){
+ free(tmp);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ free(tmp);
+ tmp = zbuf;
+ tsiz = zsiz;
+ }
+ map = cbmapload(tmp, tsiz);
+ free(tmp);
+ uritmp = cbmapget(map, OD_URIEXPR, sizeof(OD_URIEXPR), &uritsiz);
+ if(!uritmp || !vlout(odeum->rdocsdb, uritmp, uritsiz)){
+ cbmapclose(map);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ cbmapclose(map);
+ if(!crout(odeum->docsdb, (char *)&id, sizeof(int))){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ odeum->dnum--;
+ return TRUE;
+}
+
+
+/* Retrieve a document by a URI. */
+ODDOC *odget(ODEUM *odeum, const char *uri){
+ char *tmp;
+ int tsiz, docid;
+ assert(odeum && uri);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(!(tmp = vlget(odeum->rdocsdb, uri, -1, &tsiz))){
+ if(dpecode != DP_ENOITEM) odeum->fatal = TRUE;
+ return NULL;
+ }
+ if(tsiz != sizeof(int)){
+ free(tmp);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ odeum->fatal = TRUE;
+ return NULL;
+ }
+ docid = *(int *)tmp;
+ free(tmp);
+ return odgetbyid(odeum, docid);
+}
+
+
+/* Retrieve a document by an ID number. */
+ODDOC *odgetbyid(ODEUM *odeum, int id){
+ char *tmp, *zbuf;
+ const char *uritmp, *attrstmp, *nwordstmp, *awordstmp, *asis, *normal;
+ int i, tsiz, uritsiz, attrstsiz, nwordstsiz, awordstsiz, zsiz, asiz, nsiz;
+ ODDOC *doc;
+ CBMAP *map;
+ assert(odeum);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(id < 1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(!(tmp = crget(odeum->docsdb, (char *)&id, sizeof(int), 0, -1, &tsiz))){
+ if(dpecode != DP_ENOITEM) odeum->fatal = TRUE;
+ return NULL;
+ }
+ if(_qdbm_inflate){
+ if(!(zbuf = _qdbm_inflate(tmp, tsiz, &zsiz, _QDBM_ZMRAW))){
+ free(tmp);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ odeum->fatal = TRUE;
+ return NULL;
+ }
+ free(tmp);
+ tmp = zbuf;
+ tsiz = zsiz;
+ }
+ map = cbmapload(tmp, tsiz);
+ free(tmp);
+ uritmp = cbmapget(map, OD_URIEXPR, sizeof(OD_URIEXPR), &uritsiz);
+ attrstmp = cbmapget(map, OD_ATTRSEXPR, sizeof(OD_ATTRSEXPR), &attrstsiz);
+ nwordstmp = cbmapget(map, OD_NWORDSEXPR, sizeof(OD_NWORDSEXPR), &nwordstsiz);
+ awordstmp = cbmapget(map, OD_AWORDSEXPR, sizeof(OD_AWORDSEXPR), &awordstsiz);
+ if(!uritmp || !attrstmp || !nwordstmp || !awordstmp){
+ cbmapclose(map);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ odeum->fatal = TRUE;
+ return NULL;
+ }
+ doc = cbmalloc(sizeof(ODDOC));
+ doc->id = id;
+ doc->uri = cbmemdup(uritmp, uritsiz);
+ doc->attrs = cbmapload(attrstmp, attrstsiz);
+ doc->nwords = cblistload(nwordstmp, nwordstsiz);
+ doc->awords = cblistload(awordstmp, awordstsiz);
+ cbmapclose(map);
+ for(i = 0; i < cblistnum(doc->awords); i++){
+ asis = cblistval(doc->awords, i, &asiz);
+ if(asiz == 1 && asis[0] == '\0'){
+ normal = cblistval(doc->nwords, i, &nsiz);
+ cblistover(doc->awords, i, normal, nsiz);
+ }
+ }
+ return doc;
+}
+
+
+/* Retrieve the ID of the document specified by a URI. */
+int odgetidbyuri(ODEUM *odeum, const char *uri){
+ char *tmp;
+ int tsiz, docid;
+ assert(odeum && uri);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return -1;
+ }
+ if(!(tmp = vlget(odeum->rdocsdb, uri, -1, &tsiz))){
+ if(dpecode != DP_ENOITEM) odeum->fatal = TRUE;
+ return -1;
+ }
+ if(tsiz != sizeof(int)){
+ free(tmp);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ odeum->fatal = TRUE;
+ return -1;
+ }
+ docid = *(int *)tmp;
+ free(tmp);
+ return docid;
+}
+
+
+/* Check whether the document specified by an ID number exists. */
+int odcheck(ODEUM *odeum, int id){
+ assert(odeum);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(id < 1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return crvsiz(odeum->docsdb, (char *)&id, sizeof(int)) != -1;
+}
+
+
+/* Search the inverted index for documents including a word. */
+ODPAIR *odsearch(ODEUM *odeum, const char *word, int max, int *np){
+ char *tmp;
+ int tsiz;
+ assert(odeum && word && np);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(odeum->wmode && cbmaprnum(odeum->sortmap) > 0 &&
+ (!odcacheflush(odeum, "odsearch") || !odsortindex(odeum, "odsearch"))){
+ odeum->fatal = TRUE;
+ return NULL;
+ }
+ max = max < 0 ? -1 : max * sizeof(ODPAIR);
+ if(!(tmp = crget(odeum->indexdb, word, -1, 0, max, &tsiz))){
+ if(dpecode != DP_ENOITEM){
+ odeum->fatal = TRUE;
+ return NULL;
+ }
+ *np = 0;
+ return cbmalloc(1);
+ }
+ *np = tsiz / sizeof(ODPAIR);
+ return (ODPAIR *)tmp;
+}
+
+
+/* Get the number of documents including a word. */
+int odsearchdnum(ODEUM *odeum, const char *word){
+ int rv;
+ assert(odeum && word);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return -1;
+ }
+ rv = crvsiz(odeum->indexdb, word, -1);
+ return rv < 0 ? -1 : rv / sizeof(ODPAIR);
+}
+
+
+/* Initialize the iterator of a database handle. */
+int oditerinit(ODEUM *odeum){
+ assert(odeum);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return criterinit(odeum->docsdb);
+}
+
+
+/* Get the next key of the iterator. */
+ODDOC *oditernext(ODEUM *odeum){
+ char *tmp;
+ int tsiz, docsid;
+ ODDOC *doc;
+ assert(odeum);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return NULL;
+ }
+ doc = NULL;
+ while(TRUE){
+ if(!(tmp = criternext(odeum->docsdb, &tsiz))){
+ if(dpecode != DP_ENOITEM) odeum->fatal = TRUE;
+ return NULL;
+ }
+ if(tsiz != sizeof(int)){
+ free(tmp);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ odeum->fatal = TRUE;
+ return NULL;
+ }
+ docsid = *(int *)tmp;
+ free(tmp);
+ if((doc = odgetbyid(odeum, docsid)) != NULL) break;
+ if(dpecode != DP_ENOITEM){
+ odeum->fatal = TRUE;
+ return NULL;
+ }
+ }
+ return doc;
+}
+
+
+/* Synchronize updating contents with the files and the devices. */
+int odsync(ODEUM *odeum){
+ char numbuf[OD_NUMBUFSIZ];
+ assert(odeum);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!odeum->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(odotcb) odotcb("odsync", odeum, "writing meta information");
+ sprintf(numbuf, "%d", odeum->dmax);
+ if(!vlput(odeum->rdocsdb, OD_DMAXEXPR, sizeof(OD_DMAXEXPR), numbuf, -1, VL_DOVER)){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ sprintf(numbuf, "%d", odeum->dnum);
+ if(!vlput(odeum->rdocsdb, OD_DNUMEXPR, sizeof(OD_DNUMEXPR), numbuf, -1, VL_DOVER)){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ if(!odcacheflush(odeum, "odsync")){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ if(!odsortindex(odeum, "odsync")){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ if(odotcb) odotcb("odsync", odeum, "synchronizing the document database");
+ if(!crsync(odeum->docsdb)){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ if(odotcb) odotcb("odsync", odeum, "synchronizing the inverted index");
+ if(!crsync(odeum->indexdb)){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ if(odotcb) odotcb("odsync", odeum, "synchronizing the reverse dictionary");
+ if(!vlsync(odeum->rdocsdb)){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Optimize a database. */
+int odoptimize(ODEUM *odeum){
+ assert(odeum);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!odeum->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!odcacheflush(odeum, "odoptimize")){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ if(odeum->ldid < 1 || odeum->ldid != odeum->dnum){
+ if(!odpurgeindex(odeum, "odoptimize")){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ }
+ if(odeum->ldid > 0){
+ if(!odsortindex(odeum, "odoptimize")){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ }
+ if(odotcb) odotcb("odoptimize", odeum, "optimizing the document database");
+ if(!croptimize(odeum->docsdb, -1)){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ if(odotcb) odotcb("odoptimize", odeum, "optimizing the inverted index");
+ if(!croptimize(odeum->indexdb, -1)){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ if(odotcb) odotcb("odoptimize", odeum, "optimizing the reverse dictionary");
+ if(!vloptimize(odeum->rdocsdb)){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Get the name of a database. */
+char *odname(ODEUM *odeum){
+ assert(odeum);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return NULL;
+ }
+ return cbmemdup(odeum->name, -1);
+}
+
+
+/* Get the total size of database files. */
+double odfsiz(ODEUM *odeum){
+ double fsiz, rv;
+ assert(odeum);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return -1;
+ }
+ fsiz = 0;
+ if((rv = crfsizd(odeum->docsdb)) < 0) return -1.0;
+ fsiz += rv;
+ if((rv = crfsizd(odeum->indexdb)) < 0) return -1.0;
+ fsiz += rv;
+ if((rv = vlfsiz(odeum->rdocsdb)) == -1) return -1.0;
+ fsiz += rv;
+ return fsiz;
+}
+
+
+/* Get the total number of the elements of the bucket arrays for the inverted index. */
+int odbnum(ODEUM *odeum){
+ assert(odeum);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return -1;
+ }
+ return crbnum(odeum->indexdb);
+}
+
+
+/* Get the total number of the used elements of the bucket arrays in the inverted index. */
+int odbusenum(ODEUM *odeum){
+ assert(odeum);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return -1;
+ }
+ return crbusenum(odeum->indexdb);
+}
+
+
+/* Get the number of the documents stored in a database. */
+int oddnum(ODEUM *odeum){
+ assert(odeum);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return -1;
+ }
+ return odeum->dnum;
+}
+
+
+/* Get the number of the words stored in a database. */
+int odwnum(ODEUM *odeum){
+ assert(odeum);
+ if(odeum->fatal){
+ dpecodeset(DP_EFATAL, __FILE__, __LINE__);
+ return -1;
+ }
+ return crrnum(odeum->indexdb);
+}
+
+
+/* Check whether a database handle is a writer or not. */
+int odwritable(ODEUM *odeum){
+ assert(odeum);
+ return odeum->wmode;
+}
+
+
+/* Check whether a database has a fatal error or not. */
+int odfatalerror(ODEUM *odeum){
+ assert(odeum);
+ return odeum->fatal;
+}
+
+
+/* Get the inode number of a database directory. */
+int odinode(ODEUM *odeum){
+ assert(odeum);
+ return odeum->inode;
+}
+
+
+/* Get the last modified time of a database. */
+time_t odmtime(ODEUM *odeum){
+ assert(odeum);
+ return crmtime(odeum->indexdb);
+}
+
+
+/* Merge plural database directories. */
+int odmerge(const char *name, const CBLIST *elemnames){
+ ODEUM *odeum, **elems;
+ CURIA *curia, *ecuria;
+ VILLA *villa, *evilla;
+ ODPAIR *pairs;
+ char *word, *kbuf, *vbuf, *dbuf, otmsg[OD_OTCBBUFSIZ];
+ char *wpunit[OD_MIWUNIT], *vpunit[OD_MIWUNIT];
+ int i, j, k, num, dnum, wnum, dbnum, ibnum, tnum, wsunit[OD_MIWUNIT], vsunit[OD_MIWUNIT];
+ int err, *bases, sum, max, wsiz, ksiz, vsiz, uend, unum, pnum, align, id, nid, dsiz;
+ assert(name && elemnames);
+ num = cblistnum(elemnames);
+ elems = cbmalloc(num * sizeof(ODEUM *) + 1);
+ dnum = 0;
+ wnum = 0;
+ for(i = 0; i < num; i++){
+ if(!(elems[i] = odopen(cblistval(elemnames, i, NULL), OD_OREADER))){
+ for(i -= 1; i >= 0; i--){
+ odclose(elems[i]);
+ }
+ free(elems);
+ return FALSE;
+ }
+ dnum += oddnum(elems[i]);
+ wnum += odwnum(elems[i]);
+ }
+ dbnum = (int)(dnum * OD_MDBRATIO / OD_DOCSDNUM);
+ ibnum = (int)(wnum * OD_MIBRATIO / odindexdnum);
+ if(!(odeum = odopendb(name, OD_OWRITER | OD_OCREAT | OD_OTRUNC, dbnum, ibnum, "odmerge"))){
+ for(i = 0; i < num; i++){
+ odclose(elems[i]);
+ }
+ free(elems);
+ return FALSE;
+ }
+ err = FALSE;
+ if(odotcb) odotcb("odmerge", odeum, "calculating the base ID numbers");
+ bases = cbmalloc(num * sizeof(int) + 1);
+ sum = 0;
+ for(i = 0; i < num; i++){
+ ecuria = elems[i]->docsdb;
+ max = 0;
+ if(!criterinit(ecuria) && dpecode != DP_ENOITEM) err = TRUE;
+ while((kbuf = criternext(ecuria, &ksiz)) != NULL){
+ if(ksiz == sizeof(int)){
+ if(*(int *)kbuf > max) max = *(int *)kbuf;
+ }
+ free(kbuf);
+ }
+ bases[i] = sum;
+ sum += max;
+ }
+ curia = odeum->indexdb;
+ for(i = 0; i < num; i++){
+ if(odotcb){
+ sprintf(otmsg, "merging the inverted index (%d/%d)", i + 1, num);
+ odotcb("odmerge", odeum, otmsg);
+ }
+ ecuria = elems[i]->indexdb;
+ tnum = 0;
+ uend = FALSE;
+ if(!criterinit(ecuria) && dpecode != DP_ENOITEM) err = TRUE;
+ while(!uend){
+ for(unum = 0; unum < OD_MIWUNIT; unum++){
+ if(!(word = criternext(ecuria, &wsiz))){
+ uend = TRUE;
+ break;
+ }
+ if(!(vbuf = crget(ecuria, word, wsiz, 0, -1, &vsiz))){
+ err = TRUE;
+ free(word);
+ break;
+ }
+ wpunit[unum] = word;
+ wsunit[unum] = wsiz;
+ vpunit[unum] = vbuf;
+ vsunit[unum] = vsiz;
+ }
+ for(j = 0; j < unum; j++){
+ word = wpunit[j];
+ wsiz = wsunit[j];
+ vbuf = vpunit[j];
+ vsiz = vsunit[j];
+ pairs = (ODPAIR *)vbuf;
+ pnum = vsiz / sizeof(ODPAIR);
+ for(k = 0; k < pnum; k++){
+ pairs[k].id += bases[i];
+ }
+ align = (int)(i < num - 1 ? vsiz * (num - i) * OD_MIARATIO : OD_INDEXALIGN);
+ if(!crsetalign(curia, align)) err = TRUE;
+ if(!crput(curia, word, wsiz, vbuf, vsiz, CR_DCAT)) err = TRUE;
+ free(vbuf);
+ free(word);
+ if(odotcb && (tnum + 1) % OD_OTPERWORDS == 0){
+ sprintf(otmsg, "... (%d/%d)", tnum + 1, crrnum(ecuria));
+ odotcb("odmerge", odeum, otmsg);
+ }
+ tnum++;
+ }
+ }
+ }
+ if(odotcb) odotcb("odmerge", odeum, "sorting the inverted index");
+ tnum = 0;
+ if(!criterinit(curia) && dpecode != DP_ENOITEM) err = TRUE;
+ while((word = criternext(curia, &wsiz)) != NULL){
+ if((vbuf = crget(curia, word, wsiz, 0, -1, &vsiz)) != NULL){
+ if(vsiz > sizeof(ODPAIR)){
+ pairs = (ODPAIR *)vbuf;
+ pnum = vsiz / sizeof(ODPAIR);
+ qsort(pairs, pnum, sizeof(ODPAIR), odsortcompare);
+ if(!crput(curia, word, wsiz, vbuf, vsiz, CR_DOVER)) err = TRUE;
+ }
+ free(vbuf);
+ }
+ free(word);
+ if(odotcb && (tnum + 1) % OD_OTPERWORDS == 0){
+ sprintf(otmsg, "... (%d/%d)", tnum + 1, crrnum(curia));
+ odotcb("odmerge", odeum, otmsg);
+ }
+ tnum++;
+ }
+ if(odotcb) odotcb("odmerge", odeum, "synchronizing the inverted index");
+ if(!crsync(curia)) err = TRUE;
+ dnum = 0;
+ curia = odeum->docsdb;
+ villa = odeum->rdocsdb;
+ for(i = 0; i < num; i++){
+ if(odotcb){
+ sprintf(otmsg, "merging the document database (%d/%d)", i + 1, num);
+ odotcb("odmerge", odeum, otmsg);
+ }
+ evilla = elems[i]->rdocsdb;
+ ecuria = elems[i]->docsdb;
+ tnum = 0;
+ if(!vlcurfirst(evilla) && dpecode != DP_ENOITEM) err = TRUE;
+ while(TRUE){
+ if(!(kbuf = vlcurkey(evilla, &ksiz))) break;
+ if((ksiz == sizeof(OD_DMAXEXPR) && !memcmp(kbuf, OD_DMAXEXPR, ksiz)) ||
+ (ksiz == sizeof(OD_DNUMEXPR) && !memcmp(kbuf, OD_DNUMEXPR, ksiz))){
+ free(kbuf);
+ if(!vlcurnext(evilla)) break;
+ continue;
+ }
+ if(!(vbuf = vlcurval(evilla, &vsiz))){
+ free(kbuf);
+ if(!vlcurnext(evilla)) break;
+ continue;
+ }
+ if(vsiz != sizeof(int)){
+ free(vbuf);
+ free(kbuf);
+ if(!vlcurnext(evilla)) break;
+ continue;
+ }
+ id = *(int *)vbuf;
+ nid = id + bases[i];
+ if(vlput(villa, kbuf, ksiz, (char *)&nid, sizeof(int), VL_DKEEP)){
+ if((dbuf = crget(ecuria, (char *)&id, sizeof(int), 0, -1, &dsiz)) != NULL){
+ if(crput(curia, (char *)&nid, sizeof(int), dbuf, dsiz, CR_DKEEP)){
+ dnum++;
+ } else {
+ err = TRUE;
+ }
+ free(dbuf);
+ } else {
+ err = TRUE;
+ }
+ } else if(dpecode != DP_EKEEP){
+ err = TRUE;
+ }
+ free(vbuf);
+ free(kbuf);
+ odeum->dnum++;
+ if(odotcb && (tnum + 1) % OD_OTPERDOCS == 0){
+ sprintf(otmsg, "... (%d/%d)", tnum + 1, crrnum(ecuria));
+ odotcb("odmerge", odeum, otmsg);
+ }
+ tnum++;
+ if(!vlcurnext(evilla)) break;
+ }
+ }
+ odeum->dnum = dnum;
+ odeum->dmax = dnum;
+ free(bases);
+ if(odotcb) odotcb("odmerge", odeum, "synchronizing the document index");
+ if(!crsync(curia)) err = TRUE;
+ if(!odclose(odeum)) err = TRUE;
+ for(i = 0; i < num; i++){
+ if(!odclose(elems[i])) err = TRUE;
+ }
+ free(elems);
+ return err ? FALSE : TRUE;
+}
+
+
+/* Remove a database directory. */
+int odremove(const char *name){
+ char docsname[OD_PATHBUFSIZ], indexname[OD_PATHBUFSIZ], rdocsname[OD_PATHBUFSIZ];
+ char path[OD_PATHBUFSIZ];
+ const char *file;
+ struct stat sbuf;
+ CBLIST *list;
+ int i;
+ assert(name);
+ sprintf(docsname, "%s%c%s", name, MYPATHCHR, OD_DOCSNAME);
+ sprintf(indexname, "%s%c%s", name, MYPATHCHR, OD_INDEXNAME);
+ sprintf(rdocsname, "%s%c%s", name, MYPATHCHR, OD_RDOCSNAME);
+ if(lstat(name, &sbuf) == -1){
+ dpecodeset(DP_ESTAT, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(lstat(docsname, &sbuf) != -1 && !crremove(docsname)) return FALSE;
+ if(lstat(indexname, &sbuf) != -1 && !crremove(indexname)) return FALSE;
+ if(lstat(rdocsname, &sbuf) != -1 && !vlremove(rdocsname)) return FALSE;
+ if((list = cbdirlist(name)) != NULL){
+ for(i = 0; i < cblistnum(list); i++){
+ file = cblistval(list, i, NULL);
+ if(!strcmp(file, MYCDIRSTR) || !strcmp(file, MYPDIRSTR)) continue;
+ sprintf(path, "%s%c%s", name, MYPATHCHR, file);
+ if(lstat(path, &sbuf) == -1) continue;
+ if(S_ISDIR(sbuf.st_mode)){
+ if(!crremove(path)) return FALSE;
+ } else {
+ if(!dpremove(path)) return FALSE;
+ }
+ }
+ cblistclose(list);
+ }
+ if(rmdir(name) == -1){
+ dpecodeset(DP_ERMDIR, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Get a document handle. */
+ODDOC *oddocopen(const char *uri){
+ ODDOC *doc;
+ assert(uri);
+ doc = cbmalloc(sizeof(ODDOC));
+ doc->id = -1;
+ doc->uri = cbmemdup(uri, -1);
+ doc->attrs = cbmapopenex(OD_MAPPBNUM);
+ doc->nwords = cblistopen();
+ doc->awords = cblistopen();
+ return doc;
+}
+
+
+/* Close a document handle. */
+void oddocclose(ODDOC *doc){
+ assert(doc);
+ cblistclose(doc->awords);
+ cblistclose(doc->nwords);
+ cbmapclose(doc->attrs);
+ free(doc->uri);
+ free(doc);
+}
+
+
+/* Add an attribute to a document. */
+void oddocaddattr(ODDOC *doc, const char *name, const char *value){
+ assert(doc && name && value);
+ cbmapput(doc->attrs, name, -1, value, -1, TRUE);
+}
+
+
+/* Add a word to a document. */
+void oddocaddword(ODDOC *doc, const char *normal, const char *asis){
+ assert(doc && normal && asis);
+ cblistpush(doc->nwords, normal, -1);
+ cblistpush(doc->awords, asis, -1);
+}
+
+
+/* Get the ID number of a document. */
+int oddocid(const ODDOC *doc){
+ assert(doc);
+ return doc->id;
+}
+
+
+/* Get the URI of a document. */
+const char *oddocuri(const ODDOC *doc){
+ assert(doc);
+ return doc->uri;
+}
+
+
+/* Get the value of an attribute of a document. */
+const char *oddocgetattr(const ODDOC *doc, const char *name){
+ assert(doc && name);
+ return cbmapget(doc->attrs, name, -1, NULL);
+}
+
+
+/* Get the list handle contains words in normalized form of a document. */
+const CBLIST *oddocnwords(const ODDOC *doc){
+ assert(doc);
+ return doc->nwords;
+}
+
+
+/* Get the list handle contains words in appearance form of a document. */
+const CBLIST *oddocawords(const ODDOC *doc){
+ assert(doc);
+ return doc->awords;
+}
+
+
+/* Get the map handle contains keywords in normalized form and their scores. */
+CBMAP *oddocscores(const ODDOC *doc, int max, ODEUM *odeum){
+ const CBLIST *nwords;
+ CBMAP *map, *kwmap;
+ const char *word, *ctmp;
+ char numbuf[OD_NUMBUFSIZ];
+ ODWORD *owords;
+ int i, wsiz, wnum, hnum, mnum, nbsiz;
+ double ival;
+ assert(doc && max >= 0);
+ map = cbmapopen();
+ nwords = oddocnwords(doc);
+ for(i = 0; i < cblistnum(nwords); i++){
+ word = cblistval(nwords, i, &wsiz);
+ if(wsiz < 1) continue;
+ if((ctmp = cbmapget(map, word, wsiz, NULL)) != NULL){
+ wnum = *(int *)ctmp + OD_WOCCRPOINT;
+ } else {
+ wnum = OD_WOCCRPOINT;
+ }
+ cbmapput(map, word, wsiz, (char *)&wnum, sizeof(int), TRUE);
+ }
+ mnum = cbmaprnum(map);
+ owords = cbmalloc(mnum * sizeof(ODWORD) + 1);
+ cbmapiterinit(map);
+ for(i = 0; (word = cbmapiternext(map, &wsiz)) != NULL; i++){
+ owords[i].word = word;
+ owords[i].num = *(int *)cbmapget(map, word, wsiz, NULL);
+ }
+ qsort(owords, mnum, sizeof(ODWORD), odwordcompare);
+ if(odeum){
+ if(mnum > max * OD_KEYCRATIO) mnum = (int)(max * OD_KEYCRATIO);
+ for(i = 0; i < mnum; i++){
+ if((hnum = odsearchdnum(odeum, owords[i].word)) < 0) hnum = 0;
+ ival = odlogarithm(hnum);
+ ival = (ival * ival * ival) / 8.0;
+ if(ival < 8.0) ival = 8.0;
+ owords[i].num = (int)(owords[i].num / ival);
+ }
+ qsort(owords, mnum, sizeof(ODWORD), odwordcompare);
+ }
+ if(mnum > max) mnum = max;
+ kwmap = cbmapopenex(OD_MAPPBNUM);
+ for(i = 0; i < mnum; i++){
+ nbsiz = sprintf(numbuf, "%d", owords[i].num);
+ cbmapput(kwmap, owords[i].word, -1, numbuf, nbsiz, TRUE);
+ }
+ free(owords);
+ cbmapclose(map);
+ return kwmap;
+}
+
+
+/* Break a text into words in appearance form. */
+CBLIST *odbreaktext(const char *text){
+ const char *word;
+ CBLIST *elems, *words;
+ int i, j, dif, wsiz, pv, delim;
+ assert(text);
+ words = cblistopen();
+ elems = cbsplit(text, -1, OD_SPACECHARS);
+ for(i = 0; i < cblistnum(elems); i++){
+ word = cblistval(elems, i, &wsiz);
+ delim = FALSE;
+ j = 0;
+ pv = 0;
+ while(TRUE){
+ dif = j - pv;
+ if(j >= wsiz){
+ if(dif > 0 && dif <= OD_MAXWORDLEN) cblistpush(words, word + pv, j - pv);
+ break;
+ }
+ if(delim){
+ if(!strchr(OD_DELIMCHARS, word[j])){
+ if(dif > 0 && dif <= OD_MAXWORDLEN) cblistpush(words, word + pv, j - pv);
+ pv = j;
+ delim = FALSE;
+ }
+ } else {
+ if(strchr(OD_DELIMCHARS, word[j])){
+ if(dif > 0 && dif <= OD_MAXWORDLEN) cblistpush(words, word + pv, j - pv);
+ pv = j;
+ delim = TRUE;
+ }
+ }
+ j++;
+ }
+ }
+ cblistclose(elems);
+ return words;
+}
+
+
+/* Make the normalized form of a word. */
+char *odnormalizeword(const char *asis){
+ char *nword;
+ int i;
+ assert(asis);
+ for(i = 0; asis[i] != '\0'; i++){
+ if(!strchr(OD_DELIMCHARS, asis[i])) break;
+ }
+ if(asis[i] == '\0') return cbmemdup("", 0);
+ nword = cbmemdup(asis, -1);
+ for(i = 0; nword[i] != '\0'; i++){
+ if(nword[i] >= 'A' && nword[i] <= 'Z') nword[i] += 'a' - 'A';
+ }
+ while(i >= 0){
+ if(strchr(OD_GLUECHARS, nword[i])){
+ nword[i] = '\0';
+ } else {
+ break;
+ }
+ i--;
+ }
+ return nword;
+}
+
+
+/* Get the common elements of two sets of documents. */
+ODPAIR *odpairsand(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np){
+ CBMAP *map;
+ ODPAIR *result;
+ const char *tmp;
+ int i, rnum;
+ assert(apairs && anum >= 0 && bpairs && bnum >= 0);
+ map = odpairsmap(bpairs, bnum);
+ result = cbmalloc(sizeof(ODPAIR) * anum + 1);
+ rnum = 0;
+ for(i = 0; i < anum; i++){
+ if(!(tmp = cbmapget(map, (char *)&(apairs[i].id), sizeof(int), NULL))) continue;
+ result[rnum].id = apairs[i].id;
+ result[rnum].score = apairs[i].score + *(int *)tmp;
+ rnum++;
+ }
+ cbmapclose(map);
+ qsort(result, rnum, sizeof(ODPAIR), odsortcompare);
+ *np = rnum;
+ return result;
+}
+
+
+/* Get the sum of elements of two sets of documents. */
+ODPAIR *odpairsor(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np){
+ CBMAP *map;
+ ODPAIR *result;
+ const char *tmp;
+ int i, score, rnum;
+ assert(apairs && anum >= 0 && bpairs && bnum >= 0);
+ map = odpairsmap(bpairs, bnum);
+ for(i = 0; i < anum; i++){
+ score = 0;
+ if((tmp = cbmapget(map, (char *)&(apairs[i].id), sizeof(int), NULL)) != NULL)
+ score = *(int *)tmp;
+ score += apairs[i].score;
+ cbmapput(map, (char *)&(apairs[i].id), sizeof(int),
+ (char *)&score, sizeof(int), TRUE);
+ }
+ rnum = cbmaprnum(map);
+ result = cbmalloc(rnum * sizeof(ODPAIR) + 1);
+ cbmapiterinit(map);
+ for(i = 0; (tmp = cbmapiternext(map, NULL)) != NULL; i++){
+ result[i].id = *(int *)tmp;
+ result[i].score = *(int *)cbmapget(map, tmp, sizeof(int), NULL);
+ }
+ cbmapclose(map);
+ qsort(result, rnum, sizeof(ODPAIR), odsortcompare);
+ *np = rnum;
+ return result;
+}
+
+
+/* Get the difference set of documents. */
+ODPAIR *odpairsnotand(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np){
+ CBMAP *map;
+ ODPAIR *result;
+ const char *tmp;
+ int i, rnum;
+ assert(apairs && anum >= 0 && bpairs && bnum >= 0);
+ map = odpairsmap(bpairs, bnum);
+ result = cbmalloc(sizeof(ODPAIR) * anum + 1);
+ rnum = 0;
+ for(i = 0; i < anum; i++){
+ if((tmp = cbmapget(map, (char *)&(apairs[i].id), sizeof(int), NULL)) != NULL) continue;
+ result[rnum].id = apairs[i].id;
+ result[rnum].score = apairs[i].score;
+ rnum++;
+ }
+ cbmapclose(map);
+ qsort(result, rnum, sizeof(ODPAIR), odsortcompare);
+ *np = rnum;
+ return result;
+}
+
+
+/* Sort a set of documents in descending order of scores. */
+void odpairssort(ODPAIR *pairs, int pnum){
+ assert(pairs && pnum >= 0);
+ qsort(pairs, pnum, sizeof(ODPAIR), odsortcompare);
+}
+
+
+/* Get the natural logarithm of a number. */
+double odlogarithm(double x){
+ int i;
+ if(x <= 1.0) return 0.0;
+ x = x * x * x * x * x * x * x * x * x * x;
+ for(i = 0; x > 1.0; i++){
+ x /= 2.718281828459;
+ }
+ return (double)i / 10.0;
+}
+
+
+/* Get the cosine of the angle of two vectors. */
+double odvectorcosine(const int *avec, const int *bvec, int vnum){
+ double rv;
+ assert(avec && bvec && vnum >= 0);
+ rv = odvecinnerproduct(avec, bvec, vnum) /
+ ((odvecabsolute(avec, vnum) * odvecabsolute(bvec, vnum)));
+ return rv > 0.0 ? rv : 0.0;
+}
+
+
+/* Set the global tuning parameters. */
+void odsettuning(int ibnum, int idnum, int cbnum, int csiz){
+ if(ibnum > 0) odindexbnum = ibnum;
+ if(idnum > 0) odindexdnum = idnum;
+ if(cbnum > 0) odcachebnum = dpprimenum(cbnum);
+ if(csiz > 0) odcachesiz = csiz;
+}
+
+
+/* Break a text into words and store appearance forms and normalized form into lists. */
+void odanalyzetext(ODEUM *odeum, const char *text, CBLIST *awords, CBLIST *nwords){
+ char aword[OD_MAXWORDLEN+1], *wp;
+ int lev, wsiz;
+ assert(odeum && text && awords);
+ lev = OD_EVSPACE;
+ wsiz = 0;
+ for(; *text != '\0'; text++){
+ switch(odeum->statechars[*(unsigned char *)text]){
+ case OD_EVWORD:
+ if(wsiz > 0 && lev == OD_EVDELIM){
+ cblistpush(awords, aword, wsiz);
+ if(nwords) cblistpush(nwords, "", 0);
+ wsiz = 0;
+ }
+ if(wsiz <= OD_MAXWORDLEN){
+ aword[wsiz++] = *text;
+ }
+ lev = OD_EVWORD;
+ break;
+ case OD_EVGLUE:
+ if(wsiz > 0 && lev == OD_EVDELIM){
+ cblistpush(awords, aword, wsiz);
+ if(nwords) cblistpush(nwords, "", 0);
+ wsiz = 0;
+ }
+ if(wsiz <= OD_MAXWORDLEN){
+ aword[wsiz++] = *text;
+ }
+ lev = OD_EVGLUE;
+ break;
+ case OD_EVDELIM:
+ if(wsiz > 0 && lev != OD_EVDELIM){
+ cblistpush(awords, aword, wsiz);
+ if(nwords){
+ wp = aword;
+ aword[wsiz] = '\0';
+ while(*wp != '\0'){
+ if(*wp >= 'A' && *wp <= 'Z') *wp += 'a' - 'A';
+ wp++;
+ }
+ wp--;
+ while(wp >= aword && odeum->statechars[*(unsigned char *)wp] == OD_EVGLUE){
+ wsiz--;
+ wp--;
+ }
+ cblistpush(nwords, aword, wsiz);
+ }
+ wsiz = 0;
+ }
+ if(wsiz <= OD_MAXWORDLEN){
+ aword[wsiz++] = *text;
+ }
+ lev = OD_EVDELIM;
+ break;
+ default:
+ if(wsiz > 0){
+ cblistpush(awords, aword, wsiz);
+ if(nwords){
+ if(lev == OD_EVDELIM){
+ cblistpush(nwords, "", 0);
+ } else {
+ wp = aword;
+ aword[wsiz] = '\0';
+ while(*wp != '\0'){
+ if(*wp >= 'A' && *wp <= 'Z') *wp += 'a' - 'A';
+ wp++;
+ }
+ wp--;
+ while(wp >= aword && odeum->statechars[*(unsigned char *)wp] == OD_EVGLUE){
+ wsiz--;
+ wp--;
+ }
+ cblistpush(nwords, aword, wsiz);
+ }
+ }
+ wsiz = 0;
+ }
+ lev = OD_EVSPACE;
+ break;
+ }
+ }
+ if(wsiz > 0){
+ cblistpush(awords, aword, wsiz);
+ if(nwords){
+ if(lev == OD_EVDELIM){
+ cblistpush(nwords, "", 0);
+ } else {
+ wp = aword;
+ aword[wsiz] = '\0';
+ while(*wp != '\0'){
+ if(*wp >= 'A' && *wp <= 'Z') *wp += 'a' - 'A';
+ wp++;
+ }
+ wp--;
+ while(wp >= aword && odeum->statechars[*(unsigned char *)wp] == OD_EVGLUE){
+ wsiz--;
+ wp--;
+ }
+ cblistpush(nwords, aword, wsiz);
+ }
+ }
+ wsiz = 0;
+ }
+}
+
+
+/* Set the classes of characters used by `odanalyzetext'. */
+void odsetcharclass(ODEUM *odeum, const char *spacechars, const char *delimchars,
+ const char *gluechars){
+ assert(odeum && spacechars && delimchars && gluechars);
+ memset(odeum->statechars, OD_EVWORD, sizeof(odeum->statechars));
+ for(; *spacechars != '\0'; spacechars++){
+ odeum->statechars[*(unsigned char *)spacechars] = OD_EVSPACE;
+ }
+ for(; *delimchars != '\0'; delimchars++){
+ odeum->statechars[*(unsigned char *)delimchars] = OD_EVDELIM;
+ }
+ for(; *gluechars != '\0'; gluechars++){
+ odeum->statechars[*(unsigned char *)gluechars] = OD_EVGLUE;
+ }
+}
+
+
+/* Query a database using a small boolean query language. */
+ODPAIR *odquery(ODEUM *odeum, const char *query, int *np, CBLIST *errors){
+ CBLIST *tokens = cblistopen();
+ CBLIST *nwords = cblistopen();
+ ODPAIR *results = NULL;
+ assert(odeum && query && np);
+ odanalyzetext(odeum, query, tokens, nwords);
+ odcleannormalized(odeum, nwords);
+ odfixtokens(odeum, tokens);
+ results = odparseexpr(odeum, tokens, nwords, np, errors);
+ cblistclose(tokens);
+ cblistclose(nwords);
+ return results;
+}
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Get the internal database handle for documents. */
+CURIA *odidbdocs(ODEUM *odeum){
+ assert(odeum);
+ return odeum->docsdb;
+}
+
+
+/* Get the internal database handle for the inverted index. */
+CURIA *odidbindex(ODEUM *odeum){
+ assert(odeum);
+ return odeum->indexdb;
+}
+
+
+/* Get the internal database handle for the reverse dictionary. */
+VILLA *odidbrdocs(ODEUM *odeum){
+ assert(odeum);
+ return odeum->rdocsdb;
+}
+
+
+/* Set the call back function called in merging. */
+void odsetotcb(void (*otcb)(const char *, ODEUM *, const char *)){
+ odotcb = otcb;
+}
+
+
+/* Get the positive one of square roots of a number. */
+double odsquareroot(double x){
+ double c, rv;
+ if(x <= 0.0) return 0.0;
+ c = x > 1.0 ? x : 1;
+ do {
+ rv = c;
+ c = (x / c + c) * 0.5;
+ } while(c < rv);
+ return rv;
+}
+
+
+/* Get the absolute of a vector. */
+double odvecabsolute(const int *vec, int vnum){
+ double rv;
+ int i;
+ assert(vec && vnum >= 0);
+ rv = 0;
+ for(i = 0; i < vnum; i++){
+ rv += (double)vec[i] * (double)vec[i];
+ }
+ return odsquareroot(rv);
+}
+
+
+/* Get the inner product of two vectors. */
+double odvecinnerproduct(const int *avec, const int *bvec, int vnum){
+ double rv;
+ int i;
+ assert(avec && bvec && vnum >= 0);
+ rv = 0;
+ for(i = 0; i < vnum; i++){
+ rv += (double)avec[i] * (double)bvec[i];
+ }
+ return rv;
+}
+
+
+
+/*************************************************************************************************
+ * private objects
+ *************************************************************************************************/
+
+
+/* Get a database handle.
+ `name' specifies the name of a database directory.
+ `omode' specifies the connection mode.
+ `docsbnum` specifies the number of buckets of the document database.
+ `indexbnum` specifies the number of buckets of the index database.
+ `fname' specifies the name of caller function.
+ The return value is the database handle or `NULL' if it is not successful. */
+static ODEUM *odopendb(const char *name, int omode, int docsbnum, int indexbnum,
+ const char *fname){
+ int cromode, vlomode, inode, dmax, dnum;
+ char docsname[OD_PATHBUFSIZ], indexname[OD_PATHBUFSIZ], rdocsname[OD_PATHBUFSIZ], *tmp;
+ struct stat sbuf;
+ CURIA *docsdb, *indexdb;
+ VILLA *rdocsdb;
+ CBMAP *cachemap;
+ CBMAP *sortmap;
+ ODEUM *odeum;
+ assert(name);
+ if(strlen(name) > OD_NAMEMAX){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return NULL;
+ }
+ cromode = CR_OREADER;
+ vlomode = VL_OREADER;
+ if(omode & OD_OWRITER){
+ cromode = CR_OWRITER;
+ vlomode = VL_OWRITER | VL_OZCOMP | VL_OYCOMP;
+ if(omode & OD_OCREAT){
+ cromode |= CR_OCREAT;
+ vlomode |= VL_OCREAT;
+ }
+ if(omode & OD_OTRUNC){
+ cromode |= CR_OTRUNC;
+ vlomode |= VL_OTRUNC;
+ }
+ }
+ if(omode & OD_ONOLCK){
+ cromode |= CR_ONOLCK;
+ vlomode |= VL_ONOLCK;
+ }
+ if(omode & OD_OLCKNB){
+ cromode |= CR_OLCKNB;
+ vlomode |= VL_OLCKNB;
+ }
+ sprintf(docsname, "%s%c%s", name, MYPATHCHR, OD_DOCSNAME);
+ sprintf(indexname, "%s%c%s", name, MYPATHCHR, OD_INDEXNAME);
+ sprintf(rdocsname, "%s%c%s", name, MYPATHCHR, OD_RDOCSNAME);
+ docsdb = NULL;
+ indexdb = NULL;
+ rdocsdb = NULL;
+ if((omode & OD_OWRITER) && (omode & OD_OCREAT)){
+ if(mkdir(name, OD_DIRMODE) == -1 && errno != EEXIST){
+ dpecodeset(DP_EMKDIR, __FILE__, __LINE__);
+ return NULL;
+ }
+ }
+ if(lstat(name, &sbuf) == -1){
+ dpecodeset(DP_ESTAT, __FILE__, __LINE__);
+ return NULL;
+ }
+ inode = sbuf.st_ino;
+ if(!(docsdb = cropen(docsname, cromode, docsbnum, OD_DOCSDNUM))) return NULL;
+ if(!(indexdb = cropen(indexname, cromode, indexbnum, odindexdnum))){
+ crclose(docsdb);
+ return NULL;
+ }
+ if(omode & OD_OWRITER){
+ if(!crsetalign(docsdb, OD_DOCSALIGN) || !crsetfbpsiz(docsdb, OD_DOCSFBP) ||
+ !crsetalign(indexdb, OD_INDEXALIGN) || !crsetfbpsiz(indexdb, OD_INDEXFBP)){
+ crclose(indexdb);
+ crclose(docsdb);
+ return NULL;
+ }
+ }
+ if(!(rdocsdb = vlopen(rdocsname, vlomode, VL_CMPLEX))){
+ crclose(indexdb);
+ crclose(docsdb);
+ return NULL;
+ }
+ vlsettuning(rdocsdb, OD_RDOCSLRM, OD_RDOCSNIM, OD_RDOCSLCN, OD_RDOCSNCN);
+ if(omode & OD_OWRITER){
+ cachemap = cbmapopenex(odcachebnum);
+ sortmap = cbmapopenex(odcachebnum);
+ } else {
+ cachemap = NULL;
+ sortmap = NULL;
+ }
+ if(vlrnum(rdocsdb) > 0){
+ dmax = -1;
+ dnum = -1;
+ if((tmp = vlget(rdocsdb, OD_DMAXEXPR, sizeof(OD_DMAXEXPR), NULL)) != NULL){
+ dmax = atoi(tmp);
+ free(tmp);
+ }
+ if((tmp = vlget(rdocsdb, OD_DNUMEXPR, sizeof(OD_DNUMEXPR), NULL)) != NULL){
+ dnum = atoi(tmp);
+ free(tmp);
+ }
+ if(dmax < 0 || dnum < 0){
+ if(sortmap) cbmapclose(sortmap);
+ if(cachemap) cbmapclose(cachemap);
+ vlclose(rdocsdb);
+ crclose(indexdb);
+ crclose(docsdb);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ } else {
+ dmax = 0;
+ dnum = 0;
+ }
+ odeum = cbmalloc(sizeof(ODEUM));
+ odeum->name = cbmemdup(name, -1);
+ odeum->wmode = omode & OD_OWRITER;
+ odeum->fatal = FALSE;
+ odeum->inode = inode;
+ odeum->docsdb = docsdb;
+ odeum->indexdb = indexdb;
+ odeum->rdocsdb = rdocsdb;
+ odeum->cachemap = cachemap;
+ odeum->cacheasiz = 0;
+ odeum->sortmap = sortmap;
+ odeum->dmax = dmax;
+ odeum->dnum = dnum;
+ odeum->ldid = -1;
+ odsetcharclass(odeum, OD_SPACECHARS, OD_DELIMCHARS, OD_GLUECHARS);
+ if(odotcb) odotcb(fname, odeum, "the connection was established");
+ return odeum;
+}
+
+
+/* Flush the cache for dirty buffer of words.
+ `odeum' specifies a database handle.
+ `fname' specifies the name of caller function.
+ If successful, the return value is true, else, it is false. */
+static int odcacheflush(ODEUM *odeum, const char *fname){
+ const char *kbuf, *vbuf;
+ char otmsg[OD_OTCBBUFSIZ];
+ int i, rnum, ksiz, vsiz;
+ assert(odeum);
+ if((rnum = cbmaprnum(odeum->cachemap)) < 1) return TRUE;
+ if(odotcb) odotcb(fname, odeum, "flushing caches");
+ cbmapiterinit(odeum->cachemap);
+ for(i = 0; (kbuf = cbmapiternext(odeum->cachemap, &ksiz)) != NULL; i++){
+ vbuf = cbmapget(odeum->cachemap, kbuf, ksiz, &vsiz);
+ if(!crput(odeum->indexdb, kbuf, ksiz, vbuf, vsiz, CR_DCAT)){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ if(odotcb && (i + 1) % OD_OTPERWORDS == 0){
+ sprintf(otmsg, "... (%d/%d)", i + 1, rnum);
+ odotcb(fname, odeum, otmsg);
+ }
+ }
+ cbmapclose(odeum->cachemap);
+ odeum->cachemap = cbmapopenex(odcachebnum);
+ odeum->cacheasiz = 0;
+ return TRUE;
+}
+
+
+/* Flush all frequent words in the cache for dirty buffer of words.
+ `odeum' specifies a database handle.
+ `fname' specifies the name of caller function.
+ `min' specifies the minimum size of frequent words.
+ If successful, the return value is true, else, it is false. */
+static int odcacheflushfreq(ODEUM *odeum, const char *fname, int min){
+ const char *kbuf, *vbuf;
+ char otmsg[OD_OTCBBUFSIZ];
+ int rnum, ksiz, vsiz;
+ assert(odeum);
+ if((rnum = cbmaprnum(odeum->cachemap)) < 1) return TRUE;
+ if(odotcb){
+ sprintf(otmsg, "flushing frequent words: min=%d asiz=%d rnum=%d)",
+ min, odeum->cacheasiz, rnum);
+ odotcb(fname, odeum, otmsg);
+ }
+ cbmapiterinit(odeum->cachemap);
+ while((kbuf = cbmapiternext(odeum->cachemap, &ksiz)) != NULL){
+ vbuf = cbmapget(odeum->cachemap, kbuf, ksiz, &vsiz);
+ if(vsiz >= sizeof(ODPAIR) * min){
+ if(!crput(odeum->indexdb, kbuf, ksiz, vbuf, vsiz, CR_DCAT)){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ cbmapout(odeum->cachemap, kbuf, ksiz);
+ odeum->cacheasiz -= vsiz;
+ }
+ }
+ if(odotcb){
+ sprintf(otmsg, "... (done): min=%d asiz=%d rnum=%d)",
+ min, odeum->cacheasiz, cbmaprnum(odeum->cachemap));
+ odotcb(fname, odeum, otmsg);
+ }
+ return TRUE;
+}
+
+
+/* Flush the half of rare words in the cache for dirty buffer of words.
+ `odeum' specifies a database handle.
+ `fname' specifies the name of caller function.
+ `ratio' specifies the ratio of rare words.
+ If successful, the return value is true, else, it is false. */
+static int odcacheflushrare(ODEUM *odeum, const char *fname, double ratio){
+ const char *kbuf, *vbuf;
+ char otmsg[OD_OTCBBUFSIZ];
+ int i, rnum, limit, ksiz, vsiz;
+ assert(odeum);
+ if((rnum = cbmaprnum(odeum->cachemap)) < 1) return TRUE;
+ if(odotcb){
+ sprintf(otmsg, "flushing rare words: ratio=%.2f asiz=%d rnum=%d)",
+ ratio, odeum->cacheasiz, rnum);
+ odotcb(fname, odeum, otmsg);
+ }
+ cbmapiterinit(odeum->cachemap);
+ limit = (int)(rnum * ratio);
+ for(i = 0; i < limit && (kbuf = cbmapiternext(odeum->cachemap, &ksiz)) != NULL; i++){
+ vbuf = cbmapget(odeum->cachemap, kbuf, ksiz, &vsiz);
+ if(!crput(odeum->indexdb, kbuf, ksiz, vbuf, vsiz, CR_DCAT)){
+ odeum->fatal = TRUE;
+ return FALSE;
+ }
+ cbmapout(odeum->cachemap, kbuf, ksiz);
+ odeum->cacheasiz -= vsiz;
+ }
+ if(odotcb){
+ sprintf(otmsg, "... (done): ratio=%.2f asiz=%d rnum=%d)",
+ ratio, odeum->cacheasiz, cbmaprnum(odeum->cachemap));
+ odotcb(fname, odeum, otmsg);
+ }
+ return TRUE;
+}
+
+
+/* Sort the records of inverted index.
+ `odeum' specifies a database handle.
+ `fname' specifies the name of caller function.
+ If successful, the return value is true, else, it is false. */
+static int odsortindex(ODEUM *odeum, const char *fname){
+ const char *word;
+ char *tmp, otmsg[OD_OTCBBUFSIZ];
+ int i, rnum, wsiz, tsiz;
+ ODPAIR *pairs;
+ assert(odeum);
+ if((rnum = cbmaprnum(odeum->sortmap)) < 1) return TRUE;
+ if(odotcb) odotcb(fname, odeum, "sorting the inverted index");
+ cbmapiterinit(odeum->sortmap);
+ for(i = 0; (word = cbmapiternext(odeum->sortmap, &wsiz)) != NULL; i++){
+ if((tmp = crget(odeum->indexdb, word, wsiz, 0, -1, &tsiz)) != NULL){
+ if(tsiz > sizeof(ODPAIR)){
+ pairs = (ODPAIR *)tmp;
+ qsort(pairs, tsiz / sizeof(ODPAIR), sizeof(ODPAIR), odsortcompare);
+ if(!crput(odeum->indexdb, word, wsiz, tmp, tsiz, CR_DOVER)){
+ free(tmp);
+ return FALSE;
+ }
+ }
+ free(tmp);
+ } else if(dpecode != DP_ENOITEM){
+ return FALSE;
+ }
+ if(odotcb && (i + 1) % OD_OTPERWORDS == 0){
+ sprintf(otmsg, "... (%d/%d)", i + 1, rnum);
+ odotcb(fname, odeum, otmsg);
+ }
+ }
+ cbmapclose(odeum->sortmap);
+ odeum->sortmap = cbmapopenex(odcachebnum);
+ return TRUE;
+}
+
+
+/* Compare two pairs of structures of a search result.
+ `a' specifies the pointer to the region of one pair.
+ `b' specifies the pointer to the region of the other pair.
+ The return value is positive if the former is big, negative if the latter is big, 0 if both
+ are equivalent. */
+static int odsortcompare(const void *a, const void *b){
+ ODPAIR *ap, *bp;
+ int rv;
+ assert(a && b);
+ ap = (ODPAIR *)a;
+ bp = (ODPAIR *)b;
+ rv = bp->score - ap->score;
+ if(rv != 0) return rv;
+ return ap->id - bp->id;
+}
+
+
+/* Purge the elements of the deleted documents from the inverted index.
+ `odeum' specifies a database handle.
+ `fname' specifies the name of caller function.
+ If successful, the return value is true, else, it is false. */
+static int odpurgeindex(ODEUM *odeum, const char *fname){
+ ODPAIR *pairs;
+ char *kbuf, *vbuf, otmsg[OD_OTCBBUFSIZ];
+ int i, rnum, tnum, ksiz, vsiz, pnum, wi;
+ assert(odeum);
+ if((rnum = crrnum(odeum->indexdb)) < 1) return TRUE;
+ if(odotcb) odotcb(fname, odeum, "purging dispensable regions");
+ if(!criterinit(odeum->indexdb)) return FALSE;
+ tnum = 0;
+ while(TRUE){
+ if(!(kbuf = criternext(odeum->indexdb, &ksiz))){
+ if(dpecode != DP_ENOITEM) return FALSE;
+ break;
+ }
+ if(!(vbuf = crget(odeum->indexdb, kbuf, ksiz, 0, -1, &vsiz))){
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ free(kbuf);
+ return FALSE;
+ }
+ pairs = (ODPAIR *)vbuf;
+ pnum = vsiz / sizeof(ODPAIR);
+ wi = 0;
+ for(i = 0; i < pnum; i++){
+ if(crvsiz(odeum->docsdb, (char *)&(pairs[i].id), sizeof(int)) != -1){
+ pairs[wi++] = pairs[i];
+ }
+ }
+ if(wi > 0){
+ if(!crput(odeum->indexdb, kbuf, ksiz, vbuf, wi * sizeof(ODPAIR), CR_DOVER)){
+ free(vbuf);
+ free(kbuf);
+ return FALSE;
+ }
+ } else {
+ if(!crout(odeum->indexdb, kbuf, ksiz)){
+ free(vbuf);
+ free(kbuf);
+ return FALSE;
+ }
+ }
+ free(vbuf);
+ free(kbuf);
+ if(odotcb && (tnum + 1) % OD_OTPERWORDS == 0){
+ sprintf(otmsg, "... (%d/%d)", tnum + 1, rnum);
+ odotcb(fname, odeum, otmsg);
+ }
+ tnum++;
+ }
+ return TRUE;
+}
+
+
+/* Create a map of a document array.
+ `pairs' specifies the pointer to a document array.
+ `num' specifies the number of elements of the array.
+ The return value is a map of the document array. */
+static CBMAP *odpairsmap(const ODPAIR *pairs, int num){
+ CBMAP *map;
+ int i;
+ assert(pairs && num >= 0);
+ map = cbmapopen();
+ for(i = 0; i < num; i++){
+ cbmapput(map, (char *)&(pairs[i].id), sizeof(int),
+ (char *)&(pairs[i].score), sizeof(int), TRUE);
+ }
+ return map;
+}
+
+
+/* compare two pairs of structures of words in a document.
+ `a' specifies the pointer to the region of one word.
+ `b' specifies the pointer to the region of the other word.
+ The return value is positive if the former is big, negative if the latter is big, 0 if both
+ are equivalent. */
+static int odwordcompare(const void *a, const void *b){
+ ODWORD *ap, *bp;
+ int rv;
+ assert(a && b);
+ ap = (ODWORD *)a;
+ bp = (ODWORD *)b;
+ if((rv = bp->num - ap->num) != 0) return rv;
+ if((rv = strlen(bp->word) - strlen(ap->word)) != 0) return rv;
+ return strcmp(ap->word, bp->word);
+}
+
+
+/* Match an operator without taking it off the token list.
+ `odeum' specifies a database handle.
+ `tokens' specifies a list handle of tokens.
+ The return value is whether the next token is an operator. */
+static int odmatchoperator(ODEUM *odeum, CBLIST *tokens){
+ const char *tk = NULL;
+ int tk_len = 0;
+ tk = cblistval(tokens, 0, &tk_len);
+ if(tk && (tk[0] == '&' || tk[0] == '|' || tk[0] == '!')) return 1;
+ return 0;
+}
+
+
+/* Implements the subexpr part of the grammar.
+ `odeum' specifies a database handle.
+ `tokens' specifies a list handle of tokens.
+ `nwords' specifies a list handle of normalized words.
+ `np' specifies the pointer to a variable to which the number of the elements of the return
+ value is assigned.
+ `errors' specifies a list handle into which error messages are stored.
+ The return value is the pointer to an array of document IDs. */
+static ODPAIR *odparsesubexpr(ODEUM *odeum, CBLIST *tokens, CBLIST *nwords, int *np,
+ CBLIST *errors){
+ char *tk = NULL;
+ int tk_len = 0;
+ char *nword = NULL; /* used to do the actual search, should match with tokens */
+ ODPAIR *result = NULL;
+ int result_num = 0;
+ int i;
+ double ival;
+ if((tk = cblistshift(tokens, &tk_len)) != NULL){
+ assert(tk != NULL);
+ if(tk[0] == '('){
+ free(tk);
+ /* recurse into expr */
+ result = odparseexpr(odeum, tokens, nwords, &result_num, errors);
+ /* match right token RPAREN */
+ tk = cblistshift(tokens, &tk_len);
+ /* print an error if either we didn't get anything or we didn't get a ) */
+ if(tk == NULL){
+ if(errors) cblistpush(errors, "Expression ended without closing ')'", -1);
+ } else if(tk[0] != ')'){
+ if(errors) cblistpush(errors, "Un-balanced parenthesis.", -1);
+ }
+ } else if(odeum->statechars[*(unsigned char *)tk] == 0){
+ /* Perform odsearch with the next norm word that isn't an operator. */
+ nword = cblistshift(nwords, NULL);
+ assert(nword != NULL);
+ if((result = odsearch(odeum, nword, -1, &result_num)) != NULL){
+ /* TF-IDF tuning */
+ ival = odlogarithm(result_num);
+ ival = (ival * ival) / 4.0;
+ if(ival < 4.0) ival = 4.0;
+ for(i = 0; i < result_num; i++){
+ result[i].score = (int)(result[i].score / ival);
+ }
+ }
+ free(nword);
+ } else {
+ if(errors) cblistpush(errors, "Invalid sub-expression. Expected '(' or WORD.", -1);
+ result = cbmalloc(1);
+ result_num = 0;
+ }
+ /* done with the token */
+ free(tk);
+ }
+ *np = result_num;
+ return result;
+}
+
+
+/* Implements the actual recursive decent parser for the mini query language.
+ `odeum' specifies a database handle.
+ `tokens' specifies a list handle of tokens.
+ `nwords' specifies a list handle of normalized words.
+ `np' specifies the pointer to a variable to which the number of the elements of the return
+ value is assigned.
+ `errors' specifies a list handle into which error messages are stored.
+ The return value is the pointer to an array of document IDs.
+ It simply parses an initial subexpr, and then loops over as many (operator subexpr)
+ sequences as it can find. The odmatchoperator function handles injecting a default &
+ between consecutive words. */
+static ODPAIR *odparseexpr(ODEUM *odeum, CBLIST *tokens, CBLIST *nwords, int *np,
+ CBLIST *errors){
+ ODPAIR *left = NULL;
+ ODPAIR *right = NULL;
+ ODPAIR *temp = NULL;
+ int left_num = 0;
+ int right_num = 0;
+ int temp_num = 0;
+ char *op = NULL;
+ int op_len = 0;
+ if(!(left = odparsesubexpr(odeum, tokens, nwords, &left_num, errors))) return NULL;
+ /* expr ::= subexpr ( op subexpr )* */
+ while(odmatchoperator(odeum, tokens)){
+ op = cblistshift(tokens, &op_len);
+ if(!(right = odparsesubexpr(odeum, tokens, nwords, &right_num, errors))){
+ free(op);
+ free(left);
+ return NULL;
+ }
+ switch(op[0]){
+ case '&':
+ temp = odpairsand(left, left_num, right, right_num, &temp_num);
+ break;
+ case '|':
+ temp = odpairsor(left, left_num, right, right_num, &temp_num);
+ break;
+ case '!':
+ temp = odpairsnotand(left, left_num, right, right_num, &temp_num);
+ break;
+ default:
+ if(errors) cblistpush(errors, "Invalid operator. Expected '&', '|', or '!'.", -1);
+ break;
+ }
+ if(temp){
+ /* an operator was done so we must swap it with the left */
+ free(left); left = NULL;
+ left = temp;
+ left_num = temp_num;
+ }
+ free(op);
+ if(right) free(right);
+ }
+ *np = left_num;
+ return left;
+}
+
+
+/* Processes the tokens in order to break them up further.
+ `odeum' specifies a database handle.
+ `tokens' specifies a list handle of tokens. */
+static void odfixtokens(ODEUM *odeum, CBLIST *tokens){
+ const char *tk = NULL;
+ int tk_len = 0;
+ int i = 0;
+ int lastword = 0;
+ for(i = 0; i < cblistnum(tokens); i++){
+ tk = cblistval(tokens, i, &tk_len);
+ assert(tk);
+ if(tk[0] == '&' || tk[0] == '|' || tk[0] == '!' || tk[0] == '(' || tk[0] == ')'){
+ lastword = 0;
+ if(tk_len > 1){
+ /* need to break it up for the next loop around */
+ tk = cblistremove(tokens, i, &tk_len);
+ cblistinsert(tokens, i, tk, 1);
+ cblistinsert(tokens, i+1, tk+1, tk_len-1);
+ free((char *)tk);
+ }
+ } else if(odeum->statechars[*(unsigned char *)tk] == 0){
+ /* if the last one was a word and this is a word then we need a default & between them */
+ if(lastword){
+ cblistinsert(tokens, i, "&", 1);
+ i++;
+ }
+ lastword = 1;
+ }
+ }
+}
+
+
+/* Cleans out the parts of the normalized word list that are not considered words.
+ `odeum' specifies a database handle.
+ `tokens' specifies a list handle of tokens. */
+static void odcleannormalized(ODEUM *odeum, CBLIST *nwords){
+ char *tk = NULL;
+ int tk_len = 0;
+ int i = 0;
+ for(i = 0; i < cblistnum(nwords); i++){
+ tk = (char *)cblistval(nwords, i, &tk_len);
+ if(tk_len == 0 || (!odeum->statechars[*(unsigned char *)tk] == 0)){
+ /* not a word so delete it */
+ tk = cblistremove(nwords, i, &tk_len);
+ free(tk);
+ i--;
+ }
+ }
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/odeum.h b/qdbm/odeum.h
new file mode 100644
index 00000000..62def9ee
--- /dev/null
+++ b/qdbm/odeum.h
@@ -0,0 +1,590 @@
+/*************************************************************************************************
+ * The inverted API of QDBM
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#ifndef _ODEUM_H /* duplication check */
+#define _ODEUM_H
+
+#if defined(__cplusplus) /* export for C++ */
+extern "C" {
+#endif
+
+
+#include <depot.h>
+#include <curia.h>
+#include <cabin.h>
+#include <villa.h>
+#include <stdlib.h>
+#include <time.h>
+
+
+#if defined(_MSC_VER) && !defined(QDBM_INTERNAL) && !defined(QDBM_STATIC)
+#define MYEXTERN extern __declspec(dllimport)
+#else
+#define MYEXTERN extern
+#endif
+
+
+
+/*************************************************************************************************
+ * API
+ *************************************************************************************************/
+
+
+typedef struct { /* type of structure for a database handle */
+ char *name; /* name of the database directory */
+ int wmode; /* whether to be writable */
+ int fatal; /* whether a fatal error occured */
+ int inode; /* inode of the database directory */
+ CURIA *docsdb; /* database handle for documents */
+ CURIA *indexdb; /* database handle for the inverted index */
+ VILLA *rdocsdb; /* database handle for the reverse dictionary */
+ CBMAP *cachemap; /* cache for dirty buffers of words */
+ int cacheasiz; /* total allocated size of dirty buffers */
+ CBMAP *sortmap; /* map handle for candidates of sorting */
+ int dmax; /* max number of the document ID */
+ int dnum; /* number of the documents */
+ int ldid; /* ID number of the last registered document */
+ char statechars[256]; /* state of single byte characters */
+} ODEUM;
+
+typedef struct { /* type of structure for a document handle */
+ int id; /* ID number */
+ char *uri; /* uniform resource identifier */
+ CBMAP *attrs; /* map handle for attrubutes */
+ CBLIST *nwords; /* list handle for words in normalized form */
+ CBLIST *awords; /* list handle for words in appearance form */
+} ODDOC;
+
+typedef struct { /* type of structure for an element of search result */
+ int id; /* ID number of the document */
+ int score; /* score of the document */
+} ODPAIR;
+
+enum { /* enumeration for open modes */
+ OD_OREADER = 1 << 0, /* open as a reader */
+ OD_OWRITER = 1 << 1, /* open as a writer */
+ OD_OCREAT = 1 << 2, /* a writer creating */
+ OD_OTRUNC = 1 << 3, /* a writer truncating */
+ OD_ONOLCK = 1 << 4, /* open without locking */
+ OD_OLCKNB = 1 << 5 /* lock without blocking */
+};
+
+
+/* Get a database handle.
+ `name' specifies the name of a database directory.
+ `omode' specifies the connection mode: `OD_OWRITER' as a writer, `OD_OREADER' as a reader.
+ If the mode is `OD_OWRITER', the following may be added by bitwise or: `OD_OCREAT', which
+ means it creates a new database if not exist, `OD_OTRUNC', which means it creates a new
+ database regardless if one exists. Both of `OD_OREADER' and `OD_OWRITER' can be added to by
+ bitwise or: `OD_ONOLCK', which means it opens a database directory without file locking, or
+ `OD_OLCKNB', which means locking is performed without blocking.
+ The return value is the database handle or `NULL' if it is not successful.
+ While connecting as a writer, an exclusive lock is invoked to the database directory.
+ While connecting as a reader, a shared lock is invoked to the database directory.
+ The thread blocks until the lock is achieved. If `OD_ONOLCK' is used, the application is
+ responsible for exclusion control. */
+ODEUM *odopen(const char *name, int omode);
+
+
+/* Close a database handle.
+ `odeum' specifies a database handle.
+ If successful, the return value is true, else, it is false.
+ Because the region of a closed handle is released, it becomes impossible to use the handle.
+ Updating a database is assured to be written when the handle is closed. If a writer opens
+ a database but does not close it appropriately, the database will be broken. */
+int odclose(ODEUM *odeum);
+
+
+/* Store a document.
+ `odeum' specifies a database handle connected as a writer.
+ `doc' specifies a document handle.
+ `wmax' specifies the max number of words to be stored in the document database. If it is
+ negative, the number is unlimited.
+ `over' specifies whether the data of the duplicated document is overwritten or not. If it
+ is false and the URI of the document is duplicated, the function returns as an error.
+ If successful, the return value is true, else, it is false. */
+int odput(ODEUM *odeum, ODDOC *doc, int wmax, int over);
+
+
+/* Delete a document specified by a URI.
+ `odeum' specifies a database handle connected as a writer.
+ `uri' specifies the string of the URI of a document.
+ If successful, the return value is true, else, it is false. False is returned when no
+ document corresponds to the specified URI. */
+int odout(ODEUM *odeum, const char *uri);
+
+
+/* Delete a document specified by an ID number.
+ `odeum' specifies a database handle connected as a writer.
+ `id' specifies the ID number of a document.
+ If successful, the return value is true, else, it is false. False is returned when no
+ document corresponds to the specified ID number. */
+int odoutbyid(ODEUM *odeum, int id);
+
+
+/* Retrieve a document specified by a URI.
+ `odeum' specifies a database handle.
+ `uri' specifies the string the URI of a document.
+ If successful, the return value is the handle of the corresponding document, else, it is
+ `NULL'. `NULL' is returned when no document corresponds to the specified URI.
+ Because the handle of the return value is opened with the function `oddocopen', it should
+ be closed with the function `oddocclose'. */
+ODDOC *odget(ODEUM *odeum, const char *uri);
+
+
+/* Retrieve a document by an ID number.
+ `odeum' specifies a database handle.
+ `id' specifies the ID number of a document.
+ If successful, the return value is the handle of the corresponding document, else, it is
+ `NULL'. `NULL' is returned when no document corresponds to the specified ID number.
+ Because the handle of the return value is opened with the function `oddocopen', it should
+ be closed with the function `oddocclose'. */
+ODDOC *odgetbyid(ODEUM *odeum, int id);
+
+
+/* Retrieve the ID of the document specified by a URI.
+ `odeum' specifies a database handle.
+ `uri' specifies the string the URI of a document.
+ If successful, the return value is the ID number of the document, else, it is -1. -1 is
+ returned when no document corresponds to the specified URI. */
+int odgetidbyuri(ODEUM *odeum, const char *uri);
+
+
+/* Check whether the document specified by an ID number exists.
+ `odeum' specifies a database handle.
+ `id' specifies the ID number of a document.
+ The return value is true if the document exists, else, it is false. */
+int odcheck(ODEUM *odeum, int id);
+
+
+/* Search the inverted index for documents including a particular word.
+ `odeum' specifies a database handle.
+ `word' specifies a searching word.
+ `max' specifies the max number of documents to be retrieve.
+ `np' specifies the pointer to a variable to which the number of the elements of the return
+ value is assigned.
+ If successful, the return value is the pointer to an array, else, it is `NULL'. Each
+ element of the array is a pair of the ID number and the score of a document, and sorted in
+ descending order of their scores. Even if no document corresponds to the specified word,
+ it is not error but returns an dummy array.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. Note that each element of the array
+ of the return value can be data of a deleted document. */
+ODPAIR *odsearch(ODEUM *odeum, const char *word, int max, int *np);
+
+
+/* Get the number of documents including a word.
+ `odeum' specifies a database handle.
+ `word' specifies a searching word.
+ If successful, the return value is the number of documents including the word, else, it is -1.
+ Because this function does not read the entity of the inverted index, it is faster than
+ `odsearch'. */
+int odsearchdnum(ODEUM *odeum, const char *word);
+
+
+/* Initialize the iterator of a database handle.
+ `odeum' specifies a database handle.
+ If successful, the return value is true, else, it is false.
+ The iterator is used in order to access every document stored in a database. */
+int oditerinit(ODEUM *odeum);
+
+
+/* Get the next key of the iterator.
+ `odeum' specifies a database handle.
+ If successful, the return value is the handle of the next document, else, it is `NULL'.
+ `NULL' is returned when no document is to be get out of the iterator.
+ It is possible to access every document by iteration of calling this function. However,
+ it is not assured if updating the database is occurred while the iteration. Besides, the
+ order of this traversal access method is arbitrary, so it is not assured that the order of
+ string matches the one of the traversal access. Because the handle of the return value is
+ opened with the function `oddocopen', it should be closed with the function `oddocclose'. */
+ODDOC *oditernext(ODEUM *odeum);
+
+
+/* Synchronize updating contents with the files and the devices.
+ `odeum' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false.
+ This function is useful when another process uses the connected database directory. */
+int odsync(ODEUM *odeum);
+
+
+/* Optimize a database.
+ `odeum' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false.
+ Elements of the deleted documents in the inverted index are purged. */
+int odoptimize(ODEUM *odeum);
+
+
+/* Get the name of a database.
+ `odeum' specifies a database handle.
+ If successful, the return value is the pointer to the region of the name of the database,
+ else, it is `NULL'.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *odname(ODEUM *odeum);
+
+
+/* Get the total size of database files.
+ `odeum' specifies a database handle.
+ If successful, the return value is the total size of the database files, else, it is -1.0. */
+double odfsiz(ODEUM *odeum);
+
+
+/* Get the total number of the elements of the bucket arrays in the inverted index.
+ `odeum' specifies a database handle.
+ If successful, the return value is the total number of the elements of the bucket arrays,
+ else, it is -1. */
+int odbnum(ODEUM *odeum);
+
+
+/* Get the total number of the used elements of the bucket arrays in the inverted index.
+ `odeum' specifies a database handle.
+ If successful, the return value is the total number of the used elements of the bucket
+ arrays, else, it is -1. */
+int odbusenum(ODEUM *odeum);
+
+
+/* Get the number of the documents stored in a database.
+ `odeum' specifies a database handle.
+ If successful, the return value is the number of the documents stored in the database, else,
+ it is -1. */
+int oddnum(ODEUM *odeum);
+
+
+/* Get the number of the words stored in a database.
+ `odeum' specifies a database handle.
+ If successful, the return value is the number of the words stored in the database, else,
+ it is -1.
+ Because of the I/O buffer, the return value may be less than the hard number. */
+int odwnum(ODEUM *odeum);
+
+
+/* Check whether a database handle is a writer or not.
+ `odeum' specifies a database handle.
+ The return value is true if the handle is a writer, false if not. */
+int odwritable(ODEUM *odeum);
+
+
+/* Check whether a database has a fatal error or not.
+ `odeum' specifies a database handle.
+ The return value is true if the database has a fatal error, false if not. */
+int odfatalerror(ODEUM *odeum);
+
+
+/* Get the inode number of a database directory.
+ `odeum' specifies a database handle.
+ The return value is the inode number of the database directory. */
+int odinode(ODEUM *odeum);
+
+
+/* Get the last modified time of a database.
+ `odeum' specifies a database handle.
+ The return value is the last modified time of the database. */
+time_t odmtime(ODEUM *odeum);
+
+
+/* Merge plural database directories.
+ `name' specifies the name of a database directory to create.
+ `elemnames' specifies a list of names of element databases.
+ If successful, the return value is true, else, it is false.
+ If two or more documents which have the same URL come in, the first one is adopted and the
+ others are ignored. */
+int odmerge(const char *name, const CBLIST *elemnames);
+
+
+/* Remove a database directory.
+ `name' specifies the name of a database directory.
+ If successful, the return value is true, else, it is false.
+ A database directory can contain databases of other APIs of QDBM, they are also removed by
+ this function. */
+int odremove(const char *name);
+
+
+/* Get a document handle.
+ `uri' specifies the URI of a document.
+ The return value is a document handle.
+ The ID number of a new document is not defined. It is defined when the document is stored
+ in a database. */
+ODDOC *oddocopen(const char *uri);
+
+
+/* Close a document handle.
+ `doc' specifies a document handle.
+ Because the region of a closed handle is released, it becomes impossible to use the handle. */
+void oddocclose(ODDOC *doc);
+
+
+/* Add an attribute to a document.
+ `doc' specifies a document handle.
+ `name' specifies the string of the name of an attribute.
+ `value' specifies the string of the value of the attribute. */
+void oddocaddattr(ODDOC *doc, const char *name, const char *value);
+
+
+/* Add a word to a document.
+ `doc' specifies a document handle.
+ `normal' specifies the string of the normalized form of a word. Normalized forms are
+ treated as keys of the inverted index. If the normalized form of a word is an empty
+ string, the word is not reflected in the inverted index.
+ `asis' specifies the string of the appearance form of the word. Appearance forms are used
+ after the document is retrieved by an application. */
+void oddocaddword(ODDOC *doc, const char *normal, const char *asis);
+
+
+/* Get the ID number of a document.
+ `doc' specifies a document handle.
+ The return value is the ID number of a document. */
+int oddocid(const ODDOC *doc);
+
+
+/* Get the URI of a document.
+ `doc' specifies a document handle.
+ The return value is the string of the URI of a document. */
+const char *oddocuri(const ODDOC *doc);
+
+
+/* Get the value of an attribute of a document.
+ `doc' specifies a document handle.
+ `name' specifies the string of the name of an attribute.
+ The return value is the string of the value of the attribute, or `NULL' if no attribute
+ corresponds. */
+const char *oddocgetattr(const ODDOC *doc, const char *name);
+
+
+/* Get the list handle contains words in normalized form of a document.
+ `doc' specifies a document handle.
+ The return value is the list handle contains words in normalized form. */
+const CBLIST *oddocnwords(const ODDOC *doc);
+
+
+/* Get the list handle contains words in appearance form of a document.
+ `doc' specifies a document handle.
+ The return value is the list handle contains words in appearance form. */
+const CBLIST *oddocawords(const ODDOC *doc);
+
+
+/* Get the map handle contains keywords in normalized form and their scores.
+ `doc' specifies a document handle.
+ `max' specifies the max number of keywords to get.
+ `odeum' specifies a database handle with which the IDF for weighting is calculate.
+ If it is `NULL', it is not used.
+ The return value is the map handle contains keywords and their scores. Scores are expressed
+ as decimal strings.
+ Because the handle of the return value is opened with the function `cbmapopen', it should
+ be closed with the function `cbmapclose' if it is no longer in use. */
+CBMAP *oddocscores(const ODDOC *doc, int max, ODEUM *odeum);
+
+
+/* Break a text into words in appearance form.
+ `text' specifies the string of a text.
+ The return value is the list handle contains words in appearance form.
+ Words are separated with space characters and such delimiters as period, comma and so on.
+ Because the handle of the return value is opened with the function `cblistopen', it should
+ be closed with the function `cblistclose' if it is no longer in use. */
+CBLIST *odbreaktext(const char *text);
+
+
+/* Make the normalized form of a word.
+ `asis' specifies the string of the appearance form of a word.
+ The return value is is the string of the normalized form of the word.
+ Alphabets of the ASCII code are unified into lower cases. Words composed of only delimiters
+ are treated as empty strings. Because the region of the return value is allocated with the
+ `malloc' call, it should be released with the `free' call if it is no longer in use. */
+char *odnormalizeword(const char *asis);
+
+
+/* Get the common elements of two sets of documents.
+ `apairs' specifies the pointer to the former document array.
+ `anum' specifies the number of the elements of the former document array.
+ `bpairs' specifies the pointer to the latter document array.
+ `bnum' specifies the number of the elements of the latter document array.
+ `np' specifies the pointer to a variable to which the number of the elements of the return
+ value is assigned.
+ The return value is the pointer to a new document array whose elements commonly belong to
+ the specified two sets.
+ Elements of the array are sorted in descending order of their scores. Because the region of
+ the return value is allocated with the `malloc' call, it should be released with the `free'
+ call if it is no longer in use. */
+ODPAIR *odpairsand(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np);
+
+
+/* Get the sum of elements of two sets of documents.
+ `apairs' specifies the pointer to the former document array.
+ `anum' specifies the number of the elements of the former document array.
+ `bpairs' specifies the pointer to the latter document array.
+ `bnum' specifies the number of the elements of the latter document array.
+ `np' specifies the pointer to a variable to which the number of the elements of the return
+ value is assigned.
+ The return value is the pointer to a new document array whose elements belong to both or
+ either of the specified two sets.
+ Elements of the array are sorted in descending order of their scores. Because the region of
+ the return value is allocated with the `malloc' call, it should be released with the `free'
+ call if it is no longer in use. */
+ODPAIR *odpairsor(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np);
+
+
+/* Get the difference set of documents.
+ `apairs' specifies the pointer to the former document array.
+ `anum' specifies the number of the elements of the former document array.
+ `bpairs' specifies the pointer to the latter document array of the sum of elements.
+ `bnum' specifies the number of the elements of the latter document array.
+ `np' specifies the pointer to a variable to which the number of the elements of the return
+ value is assigned.
+ The return value is the pointer to a new document array whose elements belong to the former
+ set but not to the latter set.
+ Elements of the array are sorted in descending order of their scores. Because the region of
+ the return value is allocated with the `malloc' call, it should be released with the `free'
+ call if it is no longer in use. */
+ODPAIR *odpairsnotand(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np);
+
+
+/* Sort a set of documents in descending order of scores.
+ `pairs' specifies the pointer to a document array.
+ `pnum' specifies the number of the elements of the document array. */
+void odpairssort(ODPAIR *pairs, int pnum);
+
+
+/* Get the natural logarithm of a number.
+ `x' specifies a number.
+ The return value is the natural logarithm of the number. If the number is equal to or less
+ than 1.0, the return value is 0.0.
+ This function is useful when an application calculates the IDF of search results. */
+double odlogarithm(double x);
+
+
+/* Get the cosine of the angle of two vectors.
+ `avec' specifies the pointer to one array of numbers.
+ `bvec' specifies the pointer to the other array of numbers.
+ `vnum' specifies the number of elements of each array.
+ The return value is the cosine of the angle of two vectors.
+ This function is useful when an application calculates similarity of documents. */
+double odvectorcosine(const int *avec, const int *bvec, int vnum);
+
+
+/* Set the global tuning parameters.
+ `ibnum' specifies the number of buckets for inverted indexes.
+ `idnum' specifies the division number of inverted index.
+ `cbnum' specifies the number of buckets for dirty buffers.
+ `csiz' specifies the maximum bytes to use memory for dirty buffers.
+ The default setting is equivalent to `odsettuning(32749, 7, 262139, 8388608)'. This function
+ should be called before opening a handle. */
+void odsettuning(int ibnum, int idnum, int cbnum, int csiz);
+
+
+/* Break a text into words and store appearance forms and normalized form into lists.
+ `odeum' specifies a database handle.
+ `text' specifies the string of a text.
+ `awords' specifies a list handle into which appearance form is store.
+ `nwords' specifies a list handle into which normalized form is store. If it is `NULL', it is
+ ignored.
+ Words are separated with space characters and such delimiters as period, comma and so on. */
+void odanalyzetext(ODEUM *odeum, const char *text, CBLIST *awords, CBLIST *nwords);
+
+
+/* Set the classes of characters used by `odanalyzetext'.
+ `odeum' specifies a database handle.
+ `spacechars' spacifies a string contains space characters.
+ `delimchars' spacifies a string contains delimiter characters.
+ `gluechars' spacifies a string contains glue characters. */
+void odsetcharclass(ODEUM *odeum, const char *spacechars, const char *delimchars,
+ const char *gluechars);
+
+
+/* Query a database using a small boolean query language.
+ `odeum' specifies a database handle.
+ 'query' specifies the text of the query.
+ `np' specifies the pointer to a variable to which the number of the elements of the return
+ value is assigned.
+ `errors' specifies a list handle into which error messages are stored. If it is `NULL', it
+ is ignored.
+ If successful, the return value is the pointer to an array, else, it is `NULL'. Each
+ element of the array is a pair of the ID number and the score of a document, and sorted in
+ descending order of their scores. Even if no document corresponds to the specified condition,
+ it is not error but returns an dummy array.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. Note that each element of the array
+ of the return value can be data of a deleted document. */
+ODPAIR *odquery(ODEUM *odeum, const char *query, int *np, CBLIST *errors);
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Get the internal database handle for documents.
+ `odeum' specifies a database handle.
+ The return value is the internal database handle for documents.
+ Note that the the returned handle should not be updated. */
+CURIA *odidbdocs(ODEUM *odeum);
+
+
+/* Get the internal database handle for the inverted index.
+ `odeum' specifies a database handle.
+ The return value is the internal database handle for the inverted index.
+ Note that the the returned handle should not be updated. */
+CURIA *odidbindex(ODEUM *odeum);
+
+
+/* Get the internal database handle for the reverse dictionary.
+ `odeum' specifies a database handle.
+ The return value is the internal database handle for the reverse dictionary.
+ Note that the the returned handle should not be updated. */
+VILLA *odidbrdocs(ODEUM *odeum);
+
+
+/* Set the call back function called in merging.
+ `otcb' specifires the pointer to a function to report outturn. Its first argument is the name
+ of processing function. Its second argument is the handle of the database being processed.
+ Its third argument is ths string of a log message. If it is `NULL', the call back function is
+ cleared. */
+void odsetotcb(void (*otcb)(const char *, ODEUM *, const char *));
+
+
+/* Get the positive one of square roots of a number.
+ `x' specifies a number.
+ The return value is the positive one of square roots of a number. If the number is equal to
+ or less than 0.0, the return value is 0.0. */
+double odsquareroot(double x);
+
+
+/* Get the absolute of a vector.
+ `vec' specifies the pointer to an array of numbers.
+ `vnum' specifies the number of elements of the array.
+ The return value is the absolute of a vector. */
+double odvecabsolute(const int *vec, int vnum);
+
+
+/* Get the inner product of two vectors.
+ `avec' specifies the pointer to one array of numbers.
+ `bvec' specifies the pointer to the other array of numbers.
+ `vnum' specifies the number of elements of each array.
+ The return value is the inner product of two vectors. */
+double odvecinnerproduct(const int *avec, const int *bvec, int vnum);
+
+
+
+#undef MYEXTERN
+
+#if defined(__cplusplus) /* export for C++ */
+}
+#endif
+
+#endif /* duplication check */
+
+
+/* END OF FILE */
diff --git a/qdbm/odidx.c b/qdbm/odidx.c
new file mode 100644
index 00000000..a4b5b406
--- /dev/null
+++ b/qdbm/odidx.c
@@ -0,0 +1,890 @@
+/*************************************************************************************************
+ * Utility for indexing document files into a database of Odeum
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <cabin.h>
+#include <odeum.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+#include <signal.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define PATHCHR '/' /* delimiter character of path */
+#define EXTCHR '.' /* delimiter character of extension */
+#define CDIRSTR "." /* string of current directory */
+#define PDIRSTR ".." /* string of parent directory */
+#define MTDBNAME "_mtime" /* name of the database for last modified times */
+#define MTDBLRM 81 /* records in a leaf node of time database */
+#define MTDBNIM 192 /* records in a non-leaf node of time database */
+#define MTDBLCN 64 /* number of leaf cache of time database */
+#define MTDBNCN 32 /* number of non-leaf cache of time database */
+#define SCDBNAME "_score" /* name of the database for scores */
+#define SCDBBNUM 32749 /* bucket number of the score database */
+#define SCDBALIGN -3 /* alignment of the score database */
+#define PATHBUFSIZ 2048 /* size of a path buffer */
+#define MAXLOAD 0.85 /* max ratio of bucket loading */
+#define KEYNUM 32 /* number of keywords to store */
+
+
+/* for Win32 and RISC OS */
+#if defined(_WIN32)
+#undef PATHCHR
+#define PATHCHR '\\'
+#undef EXTCHR
+#define EXTCHR '.'
+#undef CDIRSTR
+#define CDIRSTR "."
+#undef PDIRSTR
+#define PDIRSTR ".."
+#elif defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#undef PATHCHR
+#define PATHCHR '.'
+#undef EXTCHR
+#define EXTCHR '/'
+#undef CDIRSTR
+#define CDIRSTR "@"
+#undef PDIRSTR
+#define PDIRSTR "^"
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+int sigterm; /* flag for termination signal */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void setsignals(void);
+void sigtermhandler(int num);
+void usage(void);
+int runregister(int argc, char **argv);
+int runrelate(int argc, char **argv);
+int runpurge(int argc, char **argv);
+int bwimatchlist(const char *str, const CBLIST *keys);
+char *fgetl(FILE *ifp);
+void otcb(const char *fname, ODEUM *odeum, const char *msg);
+void pdperror(const char *name);
+void printferror(const char *format, ...);
+void printfinfo(const char *format, ...);
+const char *datestr(time_t t);
+int proclist(const char *name, const char *lfile, int wmax,
+ const CBLIST *tsuflist, const CBLIST *hsuflist);
+int procdir(const char *name, const char *dir, int wmax,
+ const CBLIST *tsuflist, const CBLIST *hsuflist);
+int indexdir(ODEUM *odeum, VILLA *mtdb, const char *name, const char *dir, int wmax,
+ const CBLIST *tsuflist, const CBLIST *hsuflist);
+int indexfile(ODEUM *odeum, VILLA *mtdb, const char *name, const char *file, int wmax,
+ const CBLIST *tsuflist, const CBLIST *hsuflist);
+char *filetouri(const char *file);
+ODDOC *makedocplain(const char *uri, const char *text, const char *date);
+ODDOC *makedochtml(const char *uri, const char *html, const char *date);
+CBMAP *htmlescpairs(void);
+int procrelate(const char *name);
+int procpurge(const char *name);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ sigterm = FALSE;
+ setsignals();
+ if(argc < 2) usage();
+ odsetotcb(otcb);
+ rv = 0;
+ if(!strcmp(argv[1], "register")){
+ rv = runregister(argc, argv);
+ } else if(!strcmp(argv[1], "relate")){
+ rv = runrelate(argc, argv);
+ } else if(!strcmp(argv[1], "purge")){
+ rv = runpurge(argc, argv);
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* set signal handlers */
+void setsignals(void){
+ signal(1, sigtermhandler);
+ signal(2, sigtermhandler);
+ signal(3, sigtermhandler);
+ signal(13, sigtermhandler);
+ signal(15, sigtermhandler);
+}
+
+
+/* handler of termination signal */
+void sigtermhandler(int num){
+ signal(num, SIG_DFL);
+ sigterm = TRUE;
+ printfinfo("the termination signal %d catched", num);
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: indexer of document files\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s register [-l file] [-wmax num] [-tsuf sufs] [-hsuf sufs] name [dir]\n",
+ progname);
+ fprintf(stderr, " %s relate name\n", progname);
+ fprintf(stderr, " %s purge name\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* parse arguments of register command */
+int runregister(int argc, char **argv){
+ char *name, *dir, *lfile, *tsuf, *hsuf, path[PATHBUFSIZ];
+ int i, wmax, plen, rv;
+ CBLIST *tsuflist, *hsuflist;
+ name = NULL;
+ dir = NULL;
+ lfile = NULL;
+ tsuf = NULL;
+ hsuf = NULL;
+ wmax = -1;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-l")){
+ if(++i >= argc) usage();
+ lfile = argv[i];
+ } else if(!strcmp(argv[i], "-wmax")){
+ if(++i >= argc) usage();
+ wmax = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-tsuf")){
+ if(++i >= argc) usage();
+ tsuf = argv[i];
+ } else if(!strcmp(argv[i], "-hsuf")){
+ if(++i >= argc) usage();
+ hsuf = argv[i];
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!dir){
+ dir = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ if(!dir) dir = CDIRSTR;
+ plen = sprintf(path, "%s", dir);
+ if(plen > 1 && path[plen-1] == PATHCHR) path[plen-1] = '\0';
+ tsuflist = cbsplit(tsuf ? tsuf : ".txt,.text", -1, ",");
+ hsuflist = cbsplit(hsuf ? hsuf : ".html,.htm", -1, ",");
+ if(lfile){
+ rv = proclist(name, lfile, wmax, tsuflist, hsuflist);
+ } else {
+ rv = procdir(name, path, wmax, tsuflist, hsuflist);
+ }
+ cblistclose(hsuflist);
+ cblistclose(tsuflist);
+ return rv;
+}
+
+
+/* parse arguments of relate command */
+int runrelate(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = procrelate(name);
+ return rv;
+}
+
+
+/* parse arguments of purge command */
+int runpurge(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = procpurge(name);
+ return rv;
+}
+
+
+/* case insensitive backward matching with a list */
+int bwimatchlist(const char *str, const CBLIST *keys){
+ int i;
+ for(i = 0; i < cblistnum(keys); i++){
+ if(cbstrbwimatch(str, cblistval(keys, i, NULL))) return TRUE;
+ }
+ return FALSE;
+}
+
+
+/* read a line */
+char *fgetl(FILE *ifp){
+ char *buf;
+ int c, len, blen;
+ buf = NULL;
+ len = 0;
+ blen = 256;
+ while((c = fgetc(ifp)) != EOF){
+ if(blen <= len) blen *= 2;
+ buf = cbrealloc(buf, blen + 1);
+ if(c == '\n') c = '\0';
+ buf[len++] = c;
+ if(c == '\0') break;
+ }
+ if(!buf) return NULL;
+ buf[len] = '\0';
+ return buf;
+}
+
+
+/* report the outturn */
+void otcb(const char *fname, ODEUM *odeum, const char *msg){
+ char *name;
+ name = odname(odeum);
+ printf("%s: %s: %s: %s\n", progname, fname, name, msg);
+ free(name);
+}
+
+
+/* print an error message */
+void pdperror(const char *name){
+ printf("%s: ERROR: %s: %s\n", progname, name, dperrmsg(dpecode));
+ fflush(stdout);
+}
+
+
+/* print formatted error string and flush the buffer */
+void printferror(const char *format, ...){
+ va_list ap;
+ va_start(ap, format);
+ printf("%s: ERROR: ", progname);
+ vprintf(format, ap);
+ putchar('\n');
+ fflush(stdout);
+ va_end(ap);
+}
+
+
+/* print formatted information string and flush the buffer */
+void printfinfo(const char *format, ...){
+ va_list ap;
+ va_start(ap, format);
+ printf("%s: INFO: ", progname);
+ vprintf(format, ap);
+ putchar('\n');
+ fflush(stdout);
+ va_end(ap);
+}
+
+
+/* get static string of the date */
+const char *datestr(time_t t){
+ static char buf[32];
+ struct tm *stp;
+ if(!(stp = localtime(&t))) return "0000/00/00 00:00:00";
+ sprintf(buf, "%04d/%02d/%02d %02d:%02d:%02d",
+ stp->tm_year + 1900, stp->tm_mon + 1, stp->tm_mday,
+ stp->tm_hour, stp->tm_min, stp->tm_sec);
+ return buf;
+}
+
+
+/* processing with finding files in a list file */
+int proclist(const char *name, const char *lfile, int wmax,
+ const CBLIST *tsuflist, const CBLIST *hsuflist){
+ ODEUM *odeum;
+ VILLA *mtdb;
+ FILE *ifp;
+ char *line, path[PATHBUFSIZ];
+ int err, fatal;
+ if(!strcmp(lfile, "-")){
+ ifp = stdin;
+ } else {
+ if(!(ifp = fopen(lfile, "rb"))){
+ printferror("%s: file cannot be opened", lfile);
+ return 1;
+ }
+ }
+ printfinfo("%s: registration started", name);
+ if(!(odeum = odopen(name, OD_OWRITER | OD_OCREAT))){
+ pdperror(name);
+ if(ifp != stdin) fclose(ifp);
+ return 1;
+ }
+ sprintf(path, "%s%c%s", name, PATHCHR, MTDBNAME);
+ if(!(mtdb = vlopen(path, VL_OWRITER | VL_OCREAT, VL_CMPLEX))){
+ pdperror(name);
+ odclose(odeum);
+ if(ifp != stdin) fclose(ifp);
+ return 1;
+ }
+ vlsettuning(mtdb, MTDBLRM, MTDBNIM, MTDBLCN, MTDBNCN);
+ printfinfo("%s: database opened: fsiz=%.0f dnum=%d wnum=%d bnum=%d",
+ name, odfsiz(odeum), oddnum(odeum), odwnum(odeum), odbnum(odeum));
+ err = FALSE;
+ while((line = fgetl(ifp)) != NULL){
+ if(sigterm){
+ printferror("aborting due to a termination signal");
+ free(line);
+ err = TRUE;
+ break;
+ }
+ if(!indexfile(odeum, mtdb, name, line, wmax, tsuflist, hsuflist)) err = TRUE;
+ free(line);
+ }
+ fatal = odfatalerror(odeum);
+ printfinfo("%s: database closing: fsiz=%.0f dnum=%d wnum=%d bnum=%d",
+ name, odfsiz(odeum), oddnum(odeum), odwnum(odeum), odbnum(odeum));
+ if(!vlclose(mtdb)){
+ pdperror(name);
+ err = TRUE;
+ }
+ if(!odclose(odeum)){
+ pdperror(name);
+ err = TRUE;
+ }
+ if(ifp != stdin) fclose(ifp);
+ if(err){
+ printfinfo("%s: registration was over%s", name, fatal ? " with fatal error" : "");
+ } else {
+ printfinfo("%s: registration completed successfully", name);
+ }
+ return err ? 1 : 0;
+}
+
+
+/* processing with finding files in a directory */
+int procdir(const char *name, const char *dir, int wmax,
+ const CBLIST *tsuflist, const CBLIST *hsuflist){
+ ODEUM *odeum;
+ VILLA *mtdb;
+ char path[PATHBUFSIZ];
+ int err, fatal;
+ printfinfo("%s: registration started", name);
+ if(!(odeum = odopen(name, OD_OWRITER | OD_OCREAT))){
+ pdperror(name);
+ return 1;
+ }
+ sprintf(path, "%s%c%s", name, PATHCHR, MTDBNAME);
+ if(!(mtdb = vlopen(path, VL_OWRITER | VL_OCREAT, VL_CMPLEX))){
+ pdperror(name);
+ odclose(odeum);
+ return 1;
+ }
+ vlsettuning(mtdb, MTDBLRM, MTDBNIM, MTDBLCN, MTDBNCN);
+ printfinfo("%s: database opened: fsiz=%.0f dnum=%d wnum=%d bnum=%d",
+ name, odfsiz(odeum), oddnum(odeum), odwnum(odeum), odbnum(odeum));
+ err = FALSE;
+ if(!indexdir(odeum, mtdb, name, dir, wmax, tsuflist, hsuflist)) err = TRUE;
+ fatal = odfatalerror(odeum);
+ printfinfo("%s: database closing: fsiz=%.0f dnum=%d wnum=%d bnum=%d",
+ name, odfsiz(odeum), oddnum(odeum), odwnum(odeum), odbnum(odeum));
+ if(!vlclose(mtdb)){
+ pdperror(name);
+ err = TRUE;
+ }
+ if(!odclose(odeum)){
+ pdperror(name);
+ err = TRUE;
+ }
+ if(err){
+ printfinfo("%s: registration was over%s", name, fatal ? " with fatal error" : "");
+ } else {
+ printfinfo("%s: registration completed successfully", name);
+ }
+ return err ? 1 : 0;
+}
+
+
+/* find and index files in a directory */
+int indexdir(ODEUM *odeum, VILLA *mtdb, const char *name, const char *dir, int wmax,
+ const CBLIST *tsuflist, const CBLIST *hsuflist){
+ CBLIST *files;
+ const char *file;
+ char path[PATHBUFSIZ];
+ int i, isroot, isdir, err;
+ if(!(files = cbdirlist(dir))){
+ printferror("%s: directory cannot be opened", dir);
+ return FALSE;
+ }
+ isroot = dir[0] == PATHCHR && dir[1] == '\0';
+ err = FALSE;
+ for(i = 0; i < cblistnum(files); i++){
+ if(sigterm){
+ printferror("aborting due to a termination signal");
+ cblistclose(files);
+ return FALSE;
+ }
+ file = cblistval(files, i, NULL);
+ if(!strcmp(file, CDIRSTR) || !strcmp(file, PDIRSTR)) continue;
+ if(isroot){
+ sprintf(path, "%s%s", dir, file);
+ } else {
+ sprintf(path, "%s%c%s", dir, PATHCHR, file);
+ }
+ if(!cbfilestat(path, &isdir, NULL, NULL)){
+ printferror("%s: file does not exist", file);
+ err = TRUE;
+ continue;
+ }
+ if(isdir){
+ if(!indexdir(odeum, mtdb, name, path, wmax, tsuflist, hsuflist)) err = TRUE;
+ } else {
+ if(!indexfile(odeum, mtdb, name, path, wmax, tsuflist, hsuflist)) err = TRUE;
+ }
+ }
+ cblistclose(files);
+ return err ? FALSE : TRUE;
+}
+
+
+/* index a file into the database */
+int indexfile(ODEUM *odeum, VILLA *mtdb, const char *name, const char *file, int wmax,
+ const CBLIST *tsuflist, const CBLIST *hsuflist){
+ static int cnt = 0;
+ char *vbuf, *buf, *uri;
+ const char *title;
+ int size, hot, vsiz, wnum, bnum;
+ time_t mtime;
+ ODDOC *doc;
+ if(!cbfilestat(file, NULL, &size, &mtime)){
+ printferror("%s: file does not exist", file);
+ return FALSE;
+ }
+ hot = TRUE;
+ if((vbuf = vlget(mtdb, file, -1, &vsiz)) != NULL){
+ if(vsiz == sizeof(int) && mtime <= *(int *)vbuf) hot = FALSE;
+ free(vbuf);
+ }
+ if(!hot){
+ printfinfo("%s: passed", file);
+ return TRUE;
+ }
+ doc = NULL;
+ uri = filetouri(file);
+ if(bwimatchlist(file, tsuflist)){
+ if(!(buf = cbreadfile(file, NULL))){
+ printferror("%s: file cannot be opened", file);
+ return FALSE;
+ }
+ doc = makedocplain(uri, buf, datestr(mtime));
+ free(buf);
+ } else if(bwimatchlist(file, hsuflist)){
+ if(!(buf = cbreadfile(file, NULL))){
+ printferror("%s: file cannot be opened", file);
+ return FALSE;
+ }
+ doc = makedochtml(uri, buf, datestr(mtime));
+ free(buf);
+ }
+ free(uri);
+ if(doc){
+ if(!(title = oddocgetattr(doc, "title")) || strlen(title) < 1){
+ if((title = strrchr(file, PATHCHR)) != NULL){
+ title++;
+ } else {
+ title = file;
+ }
+ oddocaddattr(doc, "title", title);
+ }
+ if(odput(odeum, doc, wmax, TRUE) &&
+ vlput(mtdb, file, -1, (char *)&mtime, sizeof(int), VL_DOVER)){
+ printfinfo("%s: registered: id=%d wnum=%d",
+ file, oddocid(doc), cblistnum(oddocnwords(doc)));
+ cnt++;
+ } else {
+ pdperror(file);
+ }
+ oddocclose(doc);
+ }
+ wnum = odwnum(odeum);
+ bnum = odbnum(odeum);
+ if(wnum != -1 && bnum != -1 && (double)wnum / (double)bnum > MAXLOAD){
+ printfinfo("%s: optimizing started: fsiz=%.0f dnum=%d wnum=%d bnum=%d",
+ name, odfsiz(odeum), oddnum(odeum), odwnum(odeum), odbnum(odeum));
+ if(!odoptimize(odeum)){
+ pdperror(file);
+ return FALSE;
+ }
+ printfinfo("%s: optimizing completed: fsiz=%.0f dnum=%d wnum=%d bnum=%d",
+ name, odfsiz(odeum), oddnum(odeum), odwnum(odeum), odbnum(odeum));
+ }
+ if(cnt >= 256){
+ printfinfo("%s: database status: fsiz=%.0f dnum=%d wnum=%d bnum=%d",
+ name, odfsiz(odeum), oddnum(odeum), odwnum(odeum), odbnum(odeum));
+ cnt = 0;
+ }
+ return TRUE;
+}
+
+
+/* make the url from file path */
+char *filetouri(const char *file){
+ CBLIST *list;
+ char str[PATHBUFSIZ], *wp, *enc;
+ const char *name;
+ int i, nsiz;
+ sprintf(str, "%c", PATHCHR);
+ list = cbsplit(file, -1, str);
+ wp = str;
+ for(i = 0; i < cblistnum(list); i++){
+ if(i > 0) *(wp++) = '/';
+ name = cblistval(list, i, &nsiz);
+ enc = cburlencode(name, nsiz);
+ wp += sprintf(wp, "%s", enc);
+ free(enc);
+ }
+ cblistclose(list);
+ *wp = '\0';
+ return cbmemdup(str, -1);
+}
+
+
+/* make a document of plain text */
+ODDOC *makedocplain(const char *uri, const char *text, const char *date){
+ ODDOC *doc;
+ CBLIST *awords;
+ const char *asis;
+ char *normal;
+ int i;
+ doc = oddocopen(uri);
+ if(date) oddocaddattr(doc, "date", date);
+ awords = odbreaktext(text);
+ for(i = 0; i < cblistnum(awords); i++){
+ asis = cblistval(awords, i, NULL);
+ normal = odnormalizeword(asis);
+ oddocaddword(doc, normal, asis);
+ free(normal);
+ }
+ cblistclose(awords);
+ return doc;
+}
+
+
+/* make a document of HTML */
+ODDOC *makedochtml(const char *uri, const char *html, const char *date){
+ ODDOC *doc;
+ CBMAP *pairs;
+ CBLIST *elems, *awords;
+ const char *text, *asis;
+ char *rtext, *normal;
+ int i, j, body;
+ pairs = htmlescpairs();
+ doc = oddocopen(uri);
+ if(date) oddocaddattr(doc, "date", date);
+ elems = cbxmlbreak(html, TRUE);
+ body = FALSE;
+ for(i = 0; i < cblistnum(elems); i++){
+ text = cblistval(elems, i, NULL);
+ if(cbstrfwimatch(text, "<title")){
+ i++;
+ if(i < cblistnum(elems)){
+ text = cblistval(elems, i, NULL);
+ if(text[0] == '<') text = "";
+ rtext = cbreplace(text, pairs);
+ for(j = 0; rtext[j] != '\0'; j++){
+ if(strchr("\t\n\v\f\r", rtext[j])) rtext[j] = ' ';
+ }
+ while(--j >= 0){
+ if(rtext[j] != ' ') break;
+ rtext[j] = '\0';
+ }
+ for(j = 0; rtext[j] != '\0'; j++){
+ if(rtext[j] != ' ') break;
+ }
+ oddocaddattr(doc, "title", rtext + j);
+ awords = odbreaktext(rtext);
+ for(j = 0; j < cblistnum(awords); j++){
+ asis = cblistval(awords, j, NULL);
+ normal = odnormalizeword(asis);
+ oddocaddword(doc, normal, "");
+ free(normal);
+ }
+ cblistclose(awords);
+ free(rtext);
+ }
+ } else if(cbstrfwimatch(text, "<body")){
+ body = TRUE;
+ } else if(body && text[0] != '<'){
+ rtext = cbreplace(text, pairs);
+ awords = odbreaktext(rtext);
+ for(j = 0; j < cblistnum(awords); j++){
+ asis = cblistval(awords, j, NULL);
+ normal = odnormalizeword(asis);
+ oddocaddword(doc, normal, asis);
+ free(normal);
+ }
+ cblistclose(awords);
+ free(rtext);
+ }
+ }
+ if(!body){
+ for(i = 0; i < cblistnum(elems); i++){
+ text = cblistval(elems, i, NULL);
+ if(cbstrfwimatch(text, "<title")){
+ i++;
+ } else if(text[0] != '<'){
+ rtext = cbreplace(text, pairs);
+ awords = odbreaktext(rtext);
+ for(j = 0; j < cblistnum(awords); j++){
+ asis = cblistval(awords, j, NULL);
+ normal = odnormalizeword(asis);
+ oddocaddword(doc, normal, asis);
+ free(normal);
+ }
+ cblistclose(awords);
+ free(rtext);
+ }
+ }
+ }
+ cblistclose(elems);
+ return doc;
+}
+
+
+/* get pairs of escaping characters */
+CBMAP *htmlescpairs(void){
+ char *latinext[] = {
+ " ", "!", "(cent)", "(pound)", "(currency)", "(yen)", "|", "(section)", "\"", "(C)",
+ "", "<<", "(not)", "-", "(R)", "~", "(degree)", "+-", "^2", "^3",
+ "'", "(u)", "(P)", "*", ",", "^1", "", ">>", "(1/4)", "(1/2)",
+ "(3/4)", "?", "A", "A", "A", "A", "A", "A", "AE", "C",
+ "E", "E", "E", "E", "I", "I", "I", "I", "D", "N",
+ "O", "O", "O", "O", "O", "*", "O", "U", "U", "U",
+ "U", "Y", "P", "s", "a", "a", "a", "a", "a", "a",
+ "ae", "c", "e", "e", "e", "e", "i", "i", "i", "i",
+ "o", "n", "o", "o", "o", "o", "o", "/", "o", "u",
+ "u", "u", "u", "y", "p", "y", NULL
+ };
+ static CBMAP *pairs = NULL;
+ char kbuf[8], vbuf[8];
+ int i, ksiz, vsiz;
+ if(pairs) return pairs;
+ pairs = cbmapopen();
+ cbglobalgc(pairs, (void (*)(void *))cbmapclose);
+ cbmapput(pairs, "&amp;", -1, "&", -1, TRUE);
+ cbmapput(pairs, "&lt;", -1, "<", -1, TRUE);
+ cbmapput(pairs, "&gt;", -1, ">", -1, TRUE);
+ cbmapput(pairs, "&quot;", -1, "\"", -1, TRUE);
+ cbmapput(pairs, "&apos;", -1, "'", -1, TRUE);
+ cbmapput(pairs, "&nbsp;", -1, " ", -1, TRUE);
+ cbmapput(pairs, "&copy;", -1, "(C)", -1, TRUE);
+ cbmapput(pairs, "&reg;", -1, "(R)", -1, TRUE);
+ cbmapput(pairs, "&trade;", -1, "(TM)", -1, TRUE);
+ for(i = 1; i <= 127; i++){
+ ksiz = sprintf(kbuf, "&#%d;", i);
+ vsiz = sprintf(vbuf, "%c", i);
+ cbmapput(pairs, kbuf, ksiz, vbuf, vsiz, TRUE);
+ }
+ cbmapput(pairs, "&#130;", -1, ",", -1, TRUE);
+ cbmapput(pairs, "&#132;", -1, ",,", -1, TRUE);
+ cbmapput(pairs, "&#133;", -1, "...", -1, TRUE);
+ cbmapput(pairs, "&#139;", -1, "<", -1, TRUE);
+ cbmapput(pairs, "&#145;", -1, "'", -1, TRUE);
+ cbmapput(pairs, "&#146;", -1, "'", -1, TRUE);
+ cbmapput(pairs, "&#147;", -1, "\"", -1, TRUE);
+ cbmapput(pairs, "&#148;", -1, "\"", -1, TRUE);
+ cbmapput(pairs, "&#150;", -1, "-", -1, TRUE);
+ cbmapput(pairs, "&#151;", -1, "-", -1, TRUE);
+ cbmapput(pairs, "&#152;", -1, "~", -1, TRUE);
+ cbmapput(pairs, "&#153;", -1, "(TM)", -1, TRUE);
+ cbmapput(pairs, "&#155;", -1, ">", -1, TRUE);
+ for(i = 0; latinext[i]; i++){
+ ksiz = sprintf(kbuf, "&#%d;", i + 160);
+ cbmapput(pairs, kbuf, ksiz, latinext[i], -1, TRUE);
+ }
+ return pairs;
+}
+
+
+/* register scores of documents */
+int procrelate(const char *name){
+ ODEUM *odeum;
+ DEPOT *scdb;
+ ODDOC *doc;
+ CBMAP *scores;
+ const char *file;
+ char path[PATHBUFSIZ], *mbuf;
+ int err, fatal, id, msiz;
+ printfinfo("%s: relating started", name);
+ if(!(odeum = odopen(name, OD_OWRITER))){
+ pdperror(name);
+ return 1;
+ }
+ sprintf(path, "%s%c%s", name, PATHCHR, SCDBNAME);
+ if(!(scdb = dpopen(path, OD_OWRITER | OD_OCREAT, SCDBBNUM))){
+ pdperror(name);
+ odclose(odeum);
+ return 1;
+ }
+ if(!dpsetalign(scdb, SCDBALIGN)){
+ pdperror(name);
+ dpclose(scdb);
+ odclose(odeum);
+ return 1;
+ }
+ printfinfo("%s: database opened: fsiz=%.0f dnum=%d wnum=%d bnum=%d",
+ name, odfsiz(odeum), oddnum(odeum), odwnum(odeum), odbnum(odeum));
+ err = FALSE;
+ if(!oditerinit(odeum)){
+ pdperror(name);
+ err = TRUE;
+ } else {
+ while(TRUE){
+ if(sigterm){
+ printferror("aborting due to a termination signal");
+ err = TRUE;
+ break;
+ }
+ if(!(doc = oditernext(odeum))){
+ if(dpecode != DP_ENOITEM){
+ pdperror(name);
+ err = TRUE;
+ }
+ break;
+ }
+ file = oddocuri(doc);
+ id = oddocid(doc);
+ scores = oddocscores(doc, KEYNUM, odeum);
+ mbuf = cbmapdump(scores, &msiz);
+ if(!dpput(scdb, (char *)&id, sizeof(int), mbuf, msiz, DP_DOVER)){
+ pdperror(name);
+ err = TRUE;
+ } else {
+ printfinfo("%s: related", file);
+ }
+ free(mbuf);
+ cbmapclose(scores);
+ oddocclose(doc);
+ if(err) break;
+ }
+ }
+ fatal = odfatalerror(odeum);
+ printfinfo("%s: database closing: fsiz=%.0f dnum=%d wnum=%d bnum=%d",
+ name, odfsiz(odeum), oddnum(odeum), odwnum(odeum), odbnum(odeum));
+ if(!dpclose(scdb)){
+ pdperror(name);
+ err = TRUE;
+ }
+ if(!odclose(odeum)){
+ pdperror(name);
+ err = TRUE;
+ }
+ if(err){
+ printfinfo("%s: relating was over%s", name, fatal ? " with fatal error" : "");
+ } else {
+ printfinfo("%s: relating completed successfully", name);
+ }
+ return err ? 1 : 0;
+}
+
+
+/* purge documents which is not existing. */
+int procpurge(const char *name){
+ ODEUM *odeum;
+ ODDOC *doc;
+ const char *file;
+ int err, fatal;
+ printfinfo("%s: purging started", name);
+ if(!(odeum = odopen(name, OD_OWRITER))){
+ pdperror(name);
+ return 1;
+ }
+ printfinfo("%s: database opened: fsiz=%.0f dnum=%d wnum=%d bnum=%d",
+ name, odfsiz(odeum), oddnum(odeum), odwnum(odeum), odbnum(odeum));
+ err = FALSE;
+ if(!oditerinit(odeum)){
+ pdperror(name);
+ err = TRUE;
+ } else {
+ while(TRUE){
+ if(sigterm){
+ printferror("aborting due to a termination signal");
+ err = TRUE;
+ break;
+ }
+ if(!(doc = oditernext(odeum))){
+ if(dpecode != DP_ENOITEM){
+ pdperror(name);
+ err = TRUE;
+ }
+ break;
+ }
+ file = oddocuri(doc);
+ if(cbfilestat(file, NULL, NULL, NULL)){
+ printfinfo("%s: passed", file);
+ } else {
+ if(!odout(odeum, file)){
+ pdperror(file);
+ err = TRUE;
+ }
+ printfinfo("%s: purged", file);
+ }
+ oddocclose(doc);
+ }
+ }
+ fatal = odfatalerror(odeum);
+ printfinfo("%s: database closing: fsiz=%.0f dnum=%d wnum=%d bnum=%d",
+ name, odfsiz(odeum), oddnum(odeum), odwnum(odeum), odbnum(odeum));
+ if(!odclose(odeum)){
+ pdperror(name);
+ err = TRUE;
+ }
+ if(err){
+ printfinfo("%s: purging was over%s", name, fatal ? " with fatal error" : "");
+ } else {
+ printfinfo("%s: purging completed successfully", name);
+ }
+ return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/odmgr.c b/qdbm/odmgr.c
new file mode 100644
index 00000000..7b688280
--- /dev/null
+++ b/qdbm/odmgr.c
@@ -0,0 +1,1085 @@
+/*************************************************************************************************
+ * Utility for debugging Odeum and its applications
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <cabin.h>
+#include <odeum.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define MAXSRCHWORDS 256 /* max number of search words */
+#define WOCCRPOINT 10000 /* points per occurence */
+#define MAXKEYWORDS 8 /* max number of keywords */
+#define SUMMARYWIDTH 16 /* width of each phrase in a summary */
+#define MAXSUMMARY 128 /* max number of words in a summary */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+char *readstdin(int *sp);
+void otcb(const char *fname, ODEUM *odeum, const char *msg);
+int runcreate(int argc, char **argv);
+int runput(int argc, char **argv);
+int runout(int argc, char **argv);
+int runget(int argc, char **argv);
+int runsearch(int argc, char **argv);
+int runlist(int argc, char **argv);
+int runoptimize(int argc, char **argv);
+int runinform(int argc, char **argv);
+int runmerge(int argc, char **argv);
+int runremove(int argc, char **argv);
+int runbreak(int argc, char **argv);
+void pdperror(const char *name);
+void printdoc(const ODDOC *doc, int tb, int hb, int score, ODEUM *odeum, const CBLIST *skeys);
+char *docsummary(const ODDOC *doc, const CBLIST *kwords, int num, int hilight);
+CBMAP *listtomap(const CBLIST *list);
+int docreate(const char *name);
+int doput(const char *name, const char *text, const char *uri, const char *title,
+ const char *author, const char *date, int wmax, int keep);
+int doout(const char *name, const char *uri, int id);
+int doget(const char *name, const char *uri, int id, int tb, int hb);
+int dosearch(const char *name, const char *text, int max, int or, int idf, int ql,
+ int tb, int hb, int nb);
+int dolist(const char *name, int tb, int hb);
+int dooptimize(const char *name);
+int doinform(const char *name);
+int domerge(const char *name, const CBLIST *elems);
+int doremove(const char *name);
+int dobreak(const char *text, int hb, int kb, int sb);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ char *env;
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ if((env = getenv("QDBMDBGFD")) != NULL) dpdbgfd = atoi(env);
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "create")){
+ odsetotcb(otcb);
+ rv = runcreate(argc, argv);
+ } else if(!strcmp(argv[1], "put")){
+ odsetotcb(otcb);
+ rv = runput(argc, argv);
+ } else if(!strcmp(argv[1], "out")){
+ odsetotcb(otcb);
+ rv = runout(argc, argv);
+ } else if(!strcmp(argv[1], "get")){
+ rv = runget(argc, argv);
+ } else if(!strcmp(argv[1], "search")){
+ rv = runsearch(argc, argv);
+ } else if(!strcmp(argv[1], "list")){
+ rv = runlist(argc, argv);
+ } else if(!strcmp(argv[1], "optimize")){
+ odsetotcb(otcb);
+ rv = runoptimize(argc, argv);
+ } else if(!strcmp(argv[1], "inform")){
+ rv = runinform(argc, argv);
+ } else if(!strcmp(argv[1], "merge")){
+ odsetotcb(otcb);
+ rv = runmerge(argc, argv);
+ } else if(!strcmp(argv[1], "remove")){
+ rv = runremove(argc, argv);
+ } else if(!strcmp(argv[1], "break")){
+ rv = runbreak(argc, argv);
+ } else if(!strcmp(argv[1], "version") || !strcmp(argv[1], "--version")){
+ printf("Powered by QDBM version %s\n", dpversion);
+ printf("Copyright (c) 2000-2007 Mikio Hirabayashi\n");
+ rv = 0;
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: administration utility for Odeum\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s create name\n", progname);
+ fprintf(stderr, " %s put [-uri str] [-title str] [-author str] [-date str]"
+ " [-wmax num] [-keep] name [file]\n", progname);
+ fprintf(stderr, " %s out [-id] name expr\n", progname);
+ fprintf(stderr, " %s get [-id] [-t|-h] name expr\n", progname);
+ fprintf(stderr, " %s search [-max num] [-or] [-idf] [-t|-h|-n] name words...\n", progname);
+ fprintf(stderr, " %s list [-t|-h] name\n", progname);
+ fprintf(stderr, " %s optimize name\n", progname);
+ fprintf(stderr, " %s inform name\n", progname);
+ fprintf(stderr, " %s merge name elems...\n", progname);
+ fprintf(stderr, " %s remove name\n", progname);
+ fprintf(stderr, " %s break [-h|-k|-s] [file]\n", progname);
+ fprintf(stderr, " %s version\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* read the standard input */
+char *readstdin(int *sp){
+ char *buf;
+ int i, blen, c;
+ blen = 256;
+ buf = cbmalloc(blen);
+ for(i = 0; (c = getchar()) != EOF; i++){
+ if(i >= blen - 1) buf = cbrealloc(buf, blen *= 2);
+ buf[i] = c;
+ }
+ buf[i] = '\0';
+ *sp = i;
+ return buf;
+}
+
+
+/* report the outturn */
+void otcb(const char *fname, ODEUM *odeum, const char *msg){
+ char *name;
+ name = odname(odeum);
+ printf("%s: %s: %s: %s\n", progname, fname, name, msg);
+ free(name);
+}
+
+
+/* parse arguments of create command */
+int runcreate(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = docreate(name);
+ return rv;
+}
+
+
+/* parse arguments of put command */
+int runput(int argc, char **argv){
+ char *name, *file, *uri, *title, *author, *date, *text;
+ int i, wmax, keep, size, rv;
+ name = NULL;
+ file = NULL;
+ uri = NULL;
+ title = NULL;
+ author = NULL;
+ date = NULL;
+ wmax = -1;
+ keep = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-uri")){
+ if(++i >= argc) usage();
+ uri = argv[i];
+ } else if(!strcmp(argv[i], "-uri")){
+ if(++i >= argc) usage();
+ uri = argv[i];
+ } else if(!strcmp(argv[i], "-title")){
+ if(++i >= argc) usage();
+ title = argv[i];
+ } else if(!strcmp(argv[i], "-author")){
+ if(++i >= argc) usage();
+ author = argv[i];
+ } else if(!strcmp(argv[i], "-date")){
+ if(++i >= argc) usage();
+ date = argv[i];
+ } else if(!strcmp(argv[i], "-wmax")){
+ if(++i >= argc) usage();
+ wmax = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-keep")){
+ keep = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ if(!uri) uri = file;
+ if(!uri) usage();
+ if(file){
+ if(!(text = cbreadfile(file, &size))){
+ fprintf(stderr, "%s: %s: cannot open\n", progname, file);
+ return 1;
+ }
+ } else {
+ text = readstdin(&size);
+ }
+ rv = doput(name, text, uri, title, author, date, wmax, keep);
+ free(text);
+ return rv;
+}
+
+
+/* parse arguments of out command */
+int runout(int argc, char **argv){
+ char *name, *expr;
+ int i, ib, id, rv;
+ name = NULL;
+ expr = NULL;
+ ib = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-id")){
+ ib = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!expr){
+ expr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !expr) usage();
+ id = -1;
+ if(ib){
+ id = atoi(expr);
+ if(id < 1) usage();
+ }
+ rv = doout(name, expr, id);
+ return rv;
+}
+
+
+/* parse arguments of get command */
+int runget(int argc, char **argv){
+ char *name, *expr;
+ int i, ib, tb, hb, id, rv;
+ name = NULL;
+ expr = NULL;
+ ib = FALSE;
+ tb = FALSE;
+ hb = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-id")){
+ ib = TRUE;
+ } else if(!strcmp(argv[i], "-t")){
+ tb = TRUE;
+ } else if(!strcmp(argv[i], "-h")){
+ hb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!expr){
+ expr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !expr) usage();
+ id = -1;
+ if(ib){
+ id = atoi(expr);
+ if(id < 1) usage();
+ }
+ rv = doget(name, expr, id, tb, hb);
+ return rv;
+}
+
+
+/* parse arguments of search command */
+int runsearch(int argc, char **argv){
+ char *name, *srchwords[MAXSRCHWORDS];
+ int i, wnum, max, or, idf, ql, tb, hb, nb, rv;
+ CBDATUM *text;
+ name = NULL;
+ wnum = 0;
+ max = -1;
+ or = FALSE;
+ idf = FALSE;
+ ql = FALSE;
+ tb = FALSE;
+ hb = FALSE;
+ nb = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-max")){
+ if(++i >= argc) usage();
+ max = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-or")){
+ or = TRUE;
+ } else if(!strcmp(argv[i], "-idf")){
+ idf = TRUE;
+ } else if(!strcmp(argv[i], "-ql")){
+ ql = TRUE;
+ } else if(!strcmp(argv[i], "-t")){
+ tb = TRUE;
+ } else if(!strcmp(argv[i], "-h")){
+ hb = TRUE;
+ } else if(!strcmp(argv[i], "-n")){
+ nb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(wnum < MAXSRCHWORDS){
+ srchwords[wnum++] = argv[i];
+ }
+ }
+ if(!name) usage();
+ text = cbdatumopen(NULL, -1);
+ for(i = 0; i < wnum; i++){
+ if(i > 0) cbdatumcat(text, " ", 1);
+ cbdatumcat(text, srchwords[i], -1);
+ }
+ rv = dosearch(name, cbdatumptr(text), max, or, idf, ql, tb, hb, nb);
+ cbdatumclose(text);
+ return rv;
+}
+
+
+/* parse arguments of list command */
+int runlist(int argc, char **argv){
+ char *name;
+ int i, tb, hb, rv;
+ name = NULL;
+ tb = FALSE;
+ hb = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-t")){
+ tb = TRUE;
+ } else if(!strcmp(argv[i], "-h")){
+ hb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = dolist(name, tb, hb);
+ return rv;
+}
+
+
+/* parse arguments of optimize command */
+int runoptimize(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = dooptimize(name);
+ return rv;
+}
+
+
+/* parse arguments of inform command */
+int runinform(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doinform(name);
+ return rv;
+}
+
+
+/* parse arguments of merge command */
+int runmerge(int argc, char **argv){
+ char *name;
+ CBLIST *elems;
+ int i, rv;
+ name = NULL;
+ elems = cblistopen();
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ cblistpush(elems, argv[i], -1);
+ }
+ }
+ if(!name) usage();
+ if(cblistnum(elems) < 1){
+ cblistclose(elems);
+ usage();
+ }
+ rv = domerge(name, elems);
+ cblistclose(elems);
+ return rv;
+}
+
+
+/* parse arguments of remove command */
+int runremove(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doremove(name);
+ return rv;
+}
+
+
+/* parse arguments of break command */
+int runbreak(int argc, char **argv){
+ char *file, *text;
+ int i, hb, kb, sb, size, rv;
+ file = NULL;
+ hb = FALSE;
+ kb = FALSE;
+ sb = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!file && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-h")){
+ hb = TRUE;
+ } else if(!strcmp(argv[i], "-k")){
+ kb = TRUE;
+ } else if(!strcmp(argv[i], "-s")){
+ sb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(file){
+ if(!(text = cbreadfile(file, &size))){
+ fprintf(stderr, "%s: %s: cannot open\n", progname, file);
+ return 1;
+ }
+ } else {
+ text = readstdin(&size);
+ }
+ rv = dobreak(text, hb, kb, sb);
+ free(text);
+ return rv;
+}
+
+
+/* print an error message */
+void pdperror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, dperrmsg(dpecode));
+}
+
+
+/* print the contents of a document */
+void printdoc(const ODDOC *doc, int tb, int hb, int score, ODEUM *odeum, const CBLIST *skeys){
+ const CBLIST *words;
+ CBMAP *scores;
+ CBLIST *kwords;
+ const char *title, *author, *word, *date;
+ char *summary;
+ int i, wsiz;
+ title = oddocgetattr(doc, "title");
+ author = oddocgetattr(doc, "author");
+ date = oddocgetattr(doc, "date");
+ if(hb){
+ printf("ID: %d\n", oddocid(doc));
+ printf("URI: %s\n", oddocuri(doc));
+ if(title) printf("TITLE: %s\n", title);
+ if(author) printf("AUTHOR: %s\n", author);
+ if(date) printf("DATE: %s\n", date);
+ if(score >= 0) printf("SCORE: %d\n", score);
+ scores = oddocscores(doc, MAXKEYWORDS, odeum);
+ kwords = cblistopen();
+ printf("KEYWORDS: ");
+ cbmapiterinit(scores);
+ while((word = cbmapiternext(scores, &wsiz)) != NULL){
+ if(cblistnum(kwords) > 0) printf(", ");
+ printf("%s (%s)", word, cbmapget(scores, word, wsiz, NULL));
+ cblistpush(kwords, word, wsiz);
+ }
+ putchar('\n');
+ summary = docsummary(doc, skeys ? skeys : kwords, MAXSUMMARY, skeys != NULL);
+ printf("SUMMARY: %s\n", summary);
+ free(summary);
+ cblistclose(kwords);
+ cbmapclose(scores);
+ printf("\n\n");
+ } else if(tb){
+ printf("%d\t%s\t%s\t%s\t%s\t%d\n", oddocid(doc), oddocuri(doc),
+ title ? title : "", author ? author : "", date ? date : "", score);
+ words = oddocnwords(doc);
+ for(i = 0; i < cblistnum(words); i++){
+ word = cblistval(words, i, &wsiz);
+ if(i > 0) putchar('\t');
+ printf("%s", word);
+ }
+ putchar('\n');
+ words = oddocawords(doc);
+ for(i = 0; i < cblistnum(words); i++){
+ word = cblistval(words, i, &wsiz);
+ if(i > 0) putchar('\t');
+ printf("%s", word);
+ }
+ putchar('\n');
+ } else {
+ printf("%d\t%s\t%d\n", oddocid(doc), oddocuri(doc), score);
+ }
+}
+
+
+/* get a list handle contains summary of a document */
+char *docsummary(const ODDOC *doc, const CBLIST *kwords, int num, int hilight){
+ const CBLIST *nwords, *awords;
+ CBMAP *kmap, *map;
+ const char *normal, *asis;
+ char *sbuf;
+ int i, j, bsiz, ssiz, lnum, nwsiz, awsiz, pv, bi, first;
+ bsiz = 256;
+ sbuf = cbmalloc(bsiz);
+ ssiz = 0;
+ nwords = oddocnwords(doc);
+ awords = oddocawords(doc);
+ kmap = listtomap(kwords);
+ map = listtomap(kwords);
+ lnum = cblistnum(nwords);
+ first = TRUE;
+ for(i = 0; i < lnum && i < SUMMARYWIDTH; i++){
+ normal = cblistval(nwords, i, &nwsiz);
+ asis = cblistval(awords, i, &awsiz);
+ if(awsiz < 1) continue;
+ cbmapout(map, normal, nwsiz);
+ if(ssiz + awsiz + 16 >= bsiz){
+ bsiz = bsiz * 2 + awsiz;
+ sbuf = cbrealloc(sbuf, bsiz);
+ }
+ if(!first) ssiz += sprintf(sbuf + ssiz, " ");
+ if(hilight && normal[0] != '\0' && cbmapget(kmap, normal, nwsiz, NULL)){
+ ssiz += sprintf(sbuf + ssiz, "<<%s>>", asis);
+ } else {
+ ssiz += sprintf(sbuf + ssiz, "%s", asis);
+ }
+ first = FALSE;
+ num--;
+ }
+ ssiz += sprintf(sbuf + ssiz, " ...");
+ pv = i;
+ while(i < lnum){
+ if(cbmaprnum(map) < 1){
+ cbmapclose(map);
+ map = listtomap(kwords);
+ }
+ normal = cblistval(nwords, i, &nwsiz);
+ if(cbmapget(map, normal, nwsiz, NULL)){
+ bi = i - SUMMARYWIDTH / 2;
+ bi = bi > pv ? bi : pv;
+ for(j = bi; j < lnum && j <= bi + SUMMARYWIDTH; j++){
+ normal = cblistval(nwords, j, &nwsiz);
+ asis = cblistval(awords, j, &awsiz);
+ if(awsiz < 1) continue;
+ cbmapout(map, normal, nwsiz);
+ if(ssiz + awsiz + 16 >= bsiz){
+ bsiz = bsiz * 2 + awsiz;
+ sbuf = cbrealloc(sbuf, bsiz);
+ }
+ ssiz += sprintf(sbuf + ssiz, " ");
+ if(hilight && normal[0] != '\0' && cbmapget(kmap, normal, nwsiz, NULL)){
+ ssiz += sprintf(sbuf + ssiz, "<<%s>>", asis);
+ } else {
+ ssiz += sprintf(sbuf + ssiz, "%s", asis);
+ }
+ num--;
+ }
+ ssiz += sprintf(sbuf + ssiz, " ...");
+ i = j;
+ pv = i;
+ } else {
+ i++;
+ }
+ if(num <= 0) break;
+ }
+ cbmapclose(map);
+ cbmapclose(kmap);
+ return sbuf;
+}
+
+
+/* get a map made from a list */
+CBMAP *listtomap(const CBLIST *list){
+ CBMAP *map;
+ const char *tmp;
+ int i, tsiz;
+ map = cbmapopen();
+ for(i = 0; i < cblistnum(list); i++){
+ tmp = cblistval(list, i, &tsiz);
+ cbmapput(map, tmp, tsiz, "", 0, FALSE);
+ }
+ return map;
+}
+
+
+/* perform create command */
+int docreate(const char *name){
+ ODEUM *odeum;
+ if(!(odeum = odopen(name, OD_OWRITER | OD_OCREAT | OD_OTRUNC))){
+ pdperror(name);
+ return 1;
+ }
+ if(!odclose(odeum)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform put command */
+int doput(const char *name, const char *text, const char *uri, const char *title,
+ const char *author, const char *date, int wmax, int keep){
+ ODEUM *odeum;
+ ODDOC *doc;
+ CBLIST *awords;
+ const char *asis;
+ char *normal;
+ int i;
+ if(!(odeum = odopen(name, OD_OWRITER))){
+ pdperror(name);
+ return 1;
+ }
+ doc = oddocopen(uri);
+ if(title) oddocaddattr(doc, "title", title);
+ if(author) oddocaddattr(doc, "author", author);
+ if(date) oddocaddattr(doc, "date", date);
+ awords = odbreaktext(text);
+ for(i = 0; i < cblistnum(awords); i++){
+ asis = cblistval(awords, i, NULL);
+ normal = odnormalizeword(asis);
+ oddocaddword(doc, normal, asis);
+ free(normal);
+ }
+ cblistclose(awords);
+ if(!odput(odeum, doc, wmax, keep ? FALSE : TRUE)){
+ pdperror(name);
+ oddocclose(doc);
+ odclose(odeum);
+ return 1;
+ }
+ oddocclose(doc);
+ if(!odclose(odeum)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform out command */
+int doout(const char *name, const char *uri, int id){
+ ODEUM *odeum;
+ if(!(odeum = odopen(name, OD_OWRITER))){
+ pdperror(name);
+ return 1;
+ }
+ if(id > 0){
+ if(!odoutbyid(odeum, id)){
+ pdperror(name);
+ odclose(odeum);
+ return 1;
+ }
+ } else {
+ if(!odout(odeum, uri)){
+ pdperror(name);
+ odclose(odeum);
+ return 1;
+ }
+ }
+ if(!odclose(odeum)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform get command */
+int doget(const char *name, const char *uri, int id, int tb, int hb){
+ ODEUM *odeum;
+ ODDOC *doc;
+ if(!(odeum = odopen(name, OD_OREADER))){
+ pdperror(name);
+ return 1;
+ }
+ if(id > 0){
+ if(!(doc = odgetbyid(odeum, id))){
+ pdperror(name);
+ odclose(odeum);
+ return 1;
+ }
+ } else {
+ if(!(doc = odget(odeum, uri))){
+ pdperror(name);
+ odclose(odeum);
+ return 1;
+ }
+ }
+ printdoc(doc, tb, hb, -1, odeum, NULL);
+ oddocclose(doc);
+ if(!odclose(odeum)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform search command */
+int dosearch(const char *name, const char *text, int max, int or, int idf, int ql,
+ int tb, int hb, int nb){
+ ODEUM *odeum;
+ CBLIST *awords, *nwords, *uris, *hits;
+ ODPAIR *pairs, *last, *tmp;
+ ODDOC *doc;
+ const char *asis;
+ char *normal, numbuf[32];
+ int i, j, pnum, lnum, hnum, tnum, shows;
+ double ival;
+ if(!(odeum = odopen(name, OD_OREADER))){
+ pdperror(name);
+ return 1;
+ }
+ awords = odbreaktext(text);
+ nwords = cblistopen();
+ uris = cblistopen();
+ hits = cblistopen();
+ last = NULL;
+ lnum = 0;
+ if(ql){
+ last= odquery(odeum, text, &lnum, NULL);
+ } else {
+ for(i = 0; i < cblistnum(awords); i++){
+ asis = cblistval(awords, i, NULL);
+ normal = odnormalizeword(asis);
+ cblistpush(nwords, normal, -1);
+ if(strlen(normal) < 1){
+ free(normal);
+ continue;
+ }
+ if(!(pairs = odsearch(odeum, normal, or ? max : -1, &pnum))){
+ pdperror(name);
+ free(normal);
+ continue;
+ }
+ if((hnum = odsearchdnum(odeum, normal)) < 0) hnum = 0;
+ if(idf){
+ ival = odlogarithm(hnum);
+ ival = (ival * ival) / 4.0;
+ if(ival < 4.0) ival = 4.0;
+ for(j = 0; j < pnum; j++){
+ pairs[j].score = (int)(pairs[j].score / ival);
+ }
+ }
+ cblistpush(uris, normal, -1);
+ sprintf(numbuf, "%d", hnum);
+ cblistpush(hits, numbuf, -1);
+ if(last){
+ if(or){
+ tmp = odpairsor(last, lnum, pairs, pnum, &tnum);
+ } else {
+ tmp = odpairsand(last, lnum, pairs, pnum, &tnum);
+ }
+ free(last);
+ free(pairs);
+ last = tmp;
+ lnum = tnum;
+ } else {
+ last = pairs;
+ lnum = pnum;
+ }
+ free(normal);
+ }
+ }
+ if(hb){
+ printf("TOTAL: %d\n", lnum);
+ printf("EACHWORD: ");
+ } else {
+ printf("%d", lnum);
+ }
+ for(i = 0; i < cblistnum(uris); i++){
+ if(hb){
+ if(i > 0) printf(", ");
+ printf("%s(%s)", cblistval(uris, i, NULL), cblistval(hits, i, NULL));
+ } else {
+ printf("\t%s\t%s", cblistval(uris, i, NULL), cblistval(hits, i, NULL));
+ }
+ }
+ putchar('\n');
+ if(hb) putchar('\n');
+ if(last){
+ if(max < 0) max = lnum;
+ shows = 0;
+ for(i = 0; i < lnum && shows < max; i++){
+ if(nb){
+ printf("%d\t%d\n", last[i].id, last[i].score);
+ shows++;
+ } else {
+ if(!(doc = odgetbyid(odeum, last[i].id))) continue;
+ printdoc(doc, tb, hb, last[i].score, odeum, nwords);
+ oddocclose(doc);
+ shows++;
+ }
+ }
+ free(last);
+ }
+ cblistclose(uris);
+ cblistclose(hits);
+ cblistclose(nwords);
+ cblistclose(awords);
+ if(!odclose(odeum)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform list command */
+int dolist(const char *name, int tb, int hb){
+ ODEUM *odeum;
+ ODDOC *doc;
+ if(!(odeum = odopen(name, OD_OREADER))){
+ pdperror(name);
+ return 1;
+ }
+ if(!oditerinit(odeum)){
+ odclose(odeum);
+ pdperror(name);
+ return 1;
+ }
+ while(TRUE){
+ if(!(doc = oditernext(odeum))){
+ if(dpecode == DP_ENOITEM) break;
+ odclose(odeum);
+ pdperror(name);
+ return 1;
+ }
+ printdoc(doc, tb, hb, -1, odeum, NULL);
+ oddocclose(doc);
+ }
+ if(!odclose(odeum)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform optimize command */
+int dooptimize(const char *name){
+ ODEUM *odeum;
+ if(!(odeum = odopen(name, OD_OWRITER))){
+ pdperror(name);
+ return 1;
+ }
+ if(!odoptimize(odeum)){
+ pdperror(name);
+ odclose(odeum);
+ return 1;
+ }
+ if(!odclose(odeum)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform inform command */
+int doinform(const char *name){
+ ODEUM *odeum;
+ char *tmp;
+ if(!(odeum = odopen(name, OD_OREADER))){
+ pdperror(name);
+ return 1;
+ }
+ tmp = odname(odeum);
+ printf("name: %s\n", tmp ? tmp : "(null)");
+ free(tmp);
+ printf("file size: %.0f\n", odfsiz(odeum));
+ printf("index buckets: %d\n", odbnum(odeum));
+ printf("used buckets: %d\n", odbusenum(odeum));
+ printf("all documents: %d\n", oddnum(odeum));
+ printf("all words: %d\n", odwnum(odeum));
+ printf("inode number: %d\n", odinode(odeum));
+ printf("modified time: %.0f\n", (double)odmtime(odeum));
+ if(!odclose(odeum)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform merge command */
+int domerge(const char *name, const CBLIST *elems){
+ if(!odmerge(name, elems)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform remove command */
+int doremove(const char *name){
+ if(!odremove(name)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform break command */
+int dobreak(const char *text, int hb, int kb, int sb){
+ CBLIST *awords, *kwords;
+ CBMAP *scores;
+ ODDOC *doc;
+ const char *asis;
+ char *normal, *summary;
+ int i, first;
+ awords = odbreaktext(text);
+ if(kb || sb){
+ doc = oddocopen("");
+ for(i = 0; i < cblistnum(awords); i++){
+ asis = cblistval(awords, i, NULL);
+ normal = odnormalizeword(asis);
+ oddocaddword(doc, normal, asis);
+ free(normal);
+ }
+ scores = oddocscores(doc, MAXKEYWORDS, NULL);
+ cbmapiterinit(scores);
+ kwords = cbmapkeys(scores);
+ if(kb){
+ for(i = 0; i < cblistnum(kwords); i++){
+ if(i > 0) putchar('\t');
+ printf("%s", cblistval(kwords, i, NULL));
+ }
+ putchar('\n');
+ } else {
+ summary = docsummary(doc, kwords, MAXSUMMARY, FALSE);
+ printf("%s\n", summary);
+ free(summary);
+ }
+ cblistclose(kwords);
+ cbmapclose(scores);
+ oddocclose(doc);
+ } else if(hb){
+ printf("NWORDS: ");
+ first = TRUE;
+ for(i = 0; i < cblistnum(awords); i++){
+ asis = cblistval(awords, i, NULL);
+ normal = odnormalizeword(asis);
+ if(normal[0] == '\0'){
+ free(normal);
+ continue;
+ }
+ if(!first) putchar(' ');
+ first = FALSE;
+ printf("%s", normal);
+ free(normal);
+ }
+ putchar('\n');
+ printf("AWORDS: ");
+ first = TRUE;
+ for(i = 0; i < cblistnum(awords); i++){
+ asis = cblistval(awords, i, NULL);
+ if(asis[0] == '\0') continue;
+ if(!first) putchar(' ');
+ first = FALSE;
+ printf("%s", asis);
+ }
+ putchar('\n');
+ } else {
+ for(i = 0; i < cblistnum(awords); i++){
+ asis = cblistval(awords, i, NULL);
+ normal = odnormalizeword(asis);
+ printf("%s\t%s\n", normal, asis);
+ free(normal);
+ }
+ }
+ cblistclose(awords);
+ return 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/odtest.c b/qdbm/odtest.c
new file mode 100644
index 00000000..41f9ac12
--- /dev/null
+++ b/qdbm/odtest.c
@@ -0,0 +1,694 @@
+/*************************************************************************************************
+ * Test cases of Odeum
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <cabin.h>
+#include <odeum.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <time.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define DOCBUFSIZ 256 /* buffer for documents */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int runcombo(int argc, char **argv);
+int runwicked(int argc, char **argv);
+int printfflush(const char *format, ...);
+void pdperror(const char *name);
+int myrand(void);
+ODDOC *makedoc(int id, int wnum, int pnum);
+int dowrite(const char *name, int dnum, int wnum, int pnum,
+ int ibnum, int idnum, int cbnum, int csiz);
+int doread(const char *name);
+int docombo(const char *name);
+int dowicked(const char *name, int dnum);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ char *env;
+ int rv;
+ cbstdiobin();
+ if((env = getenv("QDBMDBGFD")) != NULL) dpdbgfd = atoi(env);
+ progname = argv[0];
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "write")){
+ rv = runwrite(argc, argv);
+ } else if(!strcmp(argv[1], "read")){
+ rv = runread(argc, argv);
+ } else if(!strcmp(argv[1], "combo")){
+ rv = runcombo(argc, argv);
+ } else if(!strcmp(argv[1], "wicked")){
+ rv = runwicked(argc, argv);
+ } else {
+ usage();
+ }
+ return 0;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: test cases for Odeum\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s write [-tune ibnum idnum cbnum csiz] name dnum wnum pnum\n", progname);
+ fprintf(stderr, " %s read name\n", progname);
+ fprintf(stderr, " %s combo name\n", progname);
+ fprintf(stderr, " %s wicked name dnum\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+ char *name, *dstr, *wstr, *pstr;
+ int i, dnum, wnum, pnum, ibnum, idnum, cbnum, csiz, rv;
+ name = NULL;
+ dstr = NULL;
+ wstr = NULL;
+ pstr = NULL;
+ dnum = 0;
+ wnum = 0;
+ pnum = 0;
+ ibnum = -1;
+ idnum = -1;
+ cbnum = -1;
+ csiz = -1;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-tune")){
+ if(++i >= argc) usage();
+ ibnum = atoi(argv[i]);
+ if(++i >= argc) usage();
+ idnum = atoi(argv[i]);
+ if(++i >= argc) usage();
+ cbnum = atoi(argv[i]);
+ if(++i >= argc) usage();
+ csiz = atoi(argv[i]);
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!dstr){
+ dstr = argv[i];
+ } else if(!wstr){
+ wstr = argv[i];
+ } else if(!pstr){
+ pstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !dstr || !wstr || !pstr) usage();
+ dnum = atoi(dstr);
+ wnum = atoi(wstr);
+ pnum = atoi(pstr);
+ if(dnum < 1 || wnum < 1 || pnum < 1) usage();
+ rv = dowrite(name, dnum, wnum, pnum, ibnum, idnum, cbnum, csiz);
+ return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doread(name);
+ return rv;
+}
+
+
+/* parse arguments of combo command */
+int runcombo(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = docombo(name);
+ return rv;
+}
+
+
+/* parse arguments of wicked command */
+int runwicked(int argc, char **argv){
+ char *name, *dstr;
+ int i, dnum, rv;
+ name = NULL;
+ dstr = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else if(!dstr){
+ dstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !dstr) usage();
+ dnum = atoi(dstr);
+ if(dnum < 1) usage();
+ rv = dowicked(name, dnum);
+ return rv;
+}
+
+
+/* print formatted string and flush the buffer */
+int printfflush(const char *format, ...){
+ va_list ap;
+ int rv;
+ va_start(ap, format);
+ rv = vprintf(format, ap);
+ if(fflush(stdout) == EOF) rv = -1;
+ va_end(ap);
+ return rv;
+}
+
+
+/* print an error message */
+void pdperror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, dperrmsg(dpecode));
+}
+
+
+/* pseudo random number generator */
+int myrand(void){
+ static int cnt = 0;
+ if(cnt == 0) srand(time(NULL));
+ return (rand() * rand() + (rand() >> (sizeof(int) * 4)) + (cnt++)) & INT_MAX;
+}
+
+
+/* create a document */
+ODDOC *makedoc(int id, int wnum, int pnum){
+ ODDOC *doc;
+ char buf[DOCBUFSIZ];
+ int i;
+ sprintf(buf, "%08d", id);
+ doc = oddocopen(buf);
+ oddocaddattr(doc, "title", buf);
+ oddocaddattr(doc, "author", buf);
+ oddocaddattr(doc, "date", buf);
+ for(i = 0; i < wnum; i++){
+ sprintf(buf, "%08d", myrand() % pnum);
+ oddocaddword(doc, buf, buf);
+ }
+ return doc;
+}
+
+
+/* perform write command */
+int dowrite(const char *name, int dnum, int wnum, int pnum,
+ int ibnum, int idnum, int cbnum, int csiz){
+ ODEUM *odeum;
+ ODDOC *doc;
+ int i, err;
+ printfflush("<Writing Test>\n name=%s dnum=%d wnum=%d pnum=%d"
+ " ibnum=%d idnum=%d cbnum=%d csiz=%d\n\n",
+ name, dnum, wnum, pnum, ibnum, idnum, cbnum, csiz);
+ /* open a database */
+ if(ibnum > 0) odsettuning(ibnum, idnum, cbnum, csiz);
+ if(!(odeum = odopen(name, OD_OWRITER | OD_OCREAT | OD_OTRUNC))){
+ pdperror(name);
+ return 1;
+ }
+ err = FALSE;
+ /* loop for each document */
+ for(i = 1; i <= dnum; i++){
+ /* store a document */
+ doc = makedoc(i, wnum, pnum);
+ if(!odput(odeum, doc, -1, FALSE)){
+ pdperror(name);
+ oddocclose(doc);
+ err = TRUE;
+ break;
+ }
+ oddocclose(doc);
+ /* print progression */
+ if(dnum > 250 && i % (dnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == dnum || i % (dnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ /* close the database */
+ if(!odclose(odeum)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(const char *name){
+ ODEUM *odeum;
+ ODDOC *doc;
+ char buf[DOCBUFSIZ];
+ int i, dnum, err;
+ printfflush("<Reading Test>\n name=%s\n\n", name);
+ /* open a database */
+ if(!(odeum = odopen(name, OD_OREADER))){
+ pdperror(name);
+ return 1;
+ }
+ /* get the number of documents */
+ dnum = oddnum(odeum);
+ err = FALSE;
+ /* loop for each document */
+ for(i = 1; i <= dnum; i++){
+ /* retrieve a document */
+ sprintf(buf, "%08d", i);
+ if(!(doc = odget(odeum, buf))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ oddocclose(doc);
+ /* print progression */
+ if(dnum > 250 && i % (dnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == dnum || i % (dnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ /* close the database */
+ if(!odclose(odeum)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform combo command */
+int docombo(const char *name){
+ ODEUM *odeum;
+ ODDOC *doc;
+ const CBLIST *nwords, *awords;
+ CBLIST *tawords, *tnwords, *oawords;
+ ODPAIR *pairs;
+ const char *asis;
+ char buf[DOCBUFSIZ], *normal;
+ int i, j, pnum;
+ printfflush("<Combination Test>\n name=%s\n\n", name);
+ printfflush("Creating a database with ... ");
+ if(!(odeum = odopen(name, OD_OWRITER | OD_OCREAT | OD_OTRUNC))){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Adding 20 documents including about 200 words ... ");
+ for(i = 1; i <= 20; i++){
+ sprintf(buf, "%08d", i);
+ doc = makedoc(i, 120 + myrand() % 160, myrand() % 500 + 500);
+ if(!odput(odeum, doc, 180 + myrand() % 40, FALSE)){
+ pdperror(name);
+ oddocclose(doc);
+ odclose(odeum);
+ return 1;
+ }
+ oddocclose(doc);
+ }
+ printfflush("ok\n");
+ printfflush("Checking documents ... ");
+ for(i = 1; i <= 20; i++){
+ sprintf(buf, "%08d", i);
+ if(!(doc = odget(odeum, buf))){
+ pdperror(name);
+ return 1;
+ }
+ nwords = oddocnwords(doc);
+ awords = oddocawords(doc);
+ if(!oddocuri(doc) || !oddocgetattr(doc, "title") || cblistnum(nwords) != cblistnum(awords)){
+ fprintf(stderr, "%s: %s: invalid document\n", progname, name);
+ oddocclose(doc);
+ odclose(odeum);
+ return 1;
+ }
+ for(j = 0; j < cblistnum(nwords); j++){
+ if(strcmp(cblistval(nwords, j, NULL), cblistval(nwords, j, NULL))){
+ fprintf(stderr, "%s: %s: invalid words\n", progname, name);
+ oddocclose(doc);
+ odclose(odeum);
+ return 1;
+ }
+ }
+ oddocclose(doc);
+ }
+ printfflush("ok\n");
+ printfflush("Syncing the database ... ");
+ if(!odsync(odeum)){
+ pdperror(name);
+ odclose(odeum);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Overwriting 1 - 10 documents ... ");
+ for(i = 1; i <= 10; i++){
+ sprintf(buf, "%08d", i);
+ doc = makedoc(i, 120 + myrand() % 160, myrand() % 500 + 500);
+ if(!odput(odeum, doc, 180 + myrand() % 40, TRUE)){
+ pdperror(name);
+ oddocclose(doc);
+ odclose(odeum);
+ return 1;
+ }
+ oddocclose(doc);
+ }
+ printfflush("ok\n");
+ printfflush("Deleting 11 - 20 documents ... ");
+ for(i = 11; i <= 20; i++){
+ sprintf(buf, "%08d", i);
+ if(!odout(odeum, buf)){
+ pdperror(name);
+ odclose(odeum);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Checking documents ... ");
+ for(i = 1; i <= 10; i++){
+ sprintf(buf, "%08d", i);
+ if(!(doc = odget(odeum, buf))){
+ pdperror(name);
+ return 1;
+ }
+ nwords = oddocnwords(doc);
+ awords = oddocawords(doc);
+ if(!oddocuri(doc) || !oddocgetattr(doc, "title") || cblistnum(nwords) != cblistnum(awords)){
+ fprintf(stderr, "%s: %s: invalid document\n", progname, name);
+ oddocclose(doc);
+ odclose(odeum);
+ return 1;
+ }
+ for(j = 0; j < cblistnum(nwords); j++){
+ if(strcmp(cblistval(nwords, j, NULL), cblistval(nwords, j, NULL))){
+ fprintf(stderr, "%s: %s: invalid words\n", progname, name);
+ oddocclose(doc);
+ odclose(odeum);
+ return 1;
+ }
+ }
+ oddocclose(doc);
+ }
+ if(oddnum(odeum) != 10){
+ fprintf(stderr, "%s: %s: invalid document number\n", progname, name);
+ odclose(odeum);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Optimizing the database ... ");
+ if(!odoptimize(odeum)){
+ pdperror(name);
+ odclose(odeum);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Adding 10 documents including about 200 words ... ");
+ for(i = 11; i <= 20; i++){
+ sprintf(buf, "%08d", i);
+ doc = makedoc(i, 120 + myrand() % 160, myrand() % 500 + 500);
+ if(!odput(odeum, doc, 180 + myrand() % 40, FALSE)){
+ pdperror(name);
+ oddocclose(doc);
+ odclose(odeum);
+ return 1;
+ }
+ oddocclose(doc);
+ }
+ printfflush("ok\n");
+ printfflush("Deleting 6 - 15 documents ... ");
+ for(i = 6; i <= 15; i++){
+ sprintf(buf, "%08d", i);
+ if(!odout(odeum, buf)){
+ pdperror(name);
+ odclose(odeum);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Retrieving documents 100 times ... ");
+ for(i = 1; i <= 100; i++){
+ sprintf(buf, "%08d", myrand() % 1000 + 1);
+ if((pairs = odsearch(odeum, buf, -1, &pnum)) != NULL){
+ for(j = 0; j < pnum; j++){
+ if((doc = odgetbyid(odeum, pairs[j].id)) != NULL){
+ oddocclose(doc);
+ } else if(dpecode != DP_ENOITEM){
+ pdperror(name);
+ odclose(odeum);
+ return 1;
+ }
+ }
+ free(pairs);
+ } else if(dpecode != DP_ENOITEM){
+ pdperror(name);
+ odclose(odeum);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Analyzing text ... ");
+ tawords = cblistopen();
+ tnwords = cblistopen();
+ odanalyzetext(odeum, "I'd like to ++see++ Mr. X-men tomorrow.", tawords, tnwords);
+ odanalyzetext(odeum, "=== :-) SMILE . @ . SAD :-< ===", tawords, tnwords);
+ for(i = 0; i < DOCBUFSIZ - 1; i++){
+ buf[i] = myrand() % 255 + 1;
+ }
+ buf[DOCBUFSIZ-1] = '\0';
+ cblistclose(tnwords);
+ cblistclose(tawords);
+ for(i = 0; i < 1000; i++){
+ for(j = 0; j < DOCBUFSIZ - 1; j++){
+ if((j + 1) % 32 == 0){
+ buf[j] = ' ';
+ } else {
+ buf[j] = myrand() % 255 + 1;
+ }
+ }
+ buf[DOCBUFSIZ-1] = '\0';
+ tawords = cblistopen();
+ tnwords = cblistopen();
+ odanalyzetext(odeum, buf, tawords, tnwords);
+ oawords = odbreaktext(buf);
+ if(cblistnum(tawords) != cblistnum(oawords) || cblistnum(tnwords) != cblistnum(oawords)){
+ fprintf(stderr, "%s: %s: invalid analyzing\n", progname, name);
+ cblistclose(oawords);
+ cblistclose(tnwords);
+ cblistclose(tawords);
+ odclose(odeum);
+ return 1;
+ }
+ for(j = 0; j < cblistnum(oawords); j++){
+ asis = cblistval(oawords, j, NULL);
+ normal = odnormalizeword(asis);
+ if(strcmp(asis, cblistval(oawords, j, NULL)) || strcmp(normal, cblistval(tnwords, j, NULL))){
+ fprintf(stderr, "%s: %s: invalid analyzing\n", progname, name);
+ free(normal);
+ cblistclose(oawords);
+ cblistclose(tnwords);
+ cblistclose(tawords);
+ odclose(odeum);
+ return 1;
+ }
+ free(normal);
+ }
+ cblistclose(oawords);
+ cblistclose(tnwords);
+ cblistclose(tawords);
+ }
+ printfflush("ok\n");
+ printfflush("Closing the database ... ");
+ if(!odclose(odeum)){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("all ok\n\n");
+ return 0;
+}
+
+
+/* perform wicked command */
+int dowicked(const char *name, int dnum){
+ ODEUM *odeum;
+ ODDOC *doc;
+ ODPAIR *pairs;
+ char buf[DOCBUFSIZ];
+ int i, j, pnum, err;
+ printfflush("<Wicked Writing Test>\n name=%s dnum=%d\n\n", name, dnum);
+ err = FALSE;
+ if(!(odeum = odopen(name, OD_OWRITER | OD_OCREAT | OD_OTRUNC))){
+ pdperror(name);
+ return 1;
+ }
+ for(i = 1; i <= dnum; i++){
+ switch(myrand() % 8){
+ case 1:
+ putchar('K');
+ doc = makedoc(myrand() % dnum + 1, myrand() % 10 + 10, myrand() % dnum + 500);
+ if(!odput(odeum, doc, 5, FALSE) && dpecode != DP_EKEEP) err = TRUE;
+ oddocclose(doc);
+ break;
+ case 3:
+ putchar('D');
+ if(!odoutbyid(odeum, myrand() % dnum + 1) && dpecode != DP_ENOITEM) err = TRUE;
+ break;
+ case 4:
+ putchar('R');
+ sprintf(buf, "%08d", myrand() % (dnum + 500) + 1);
+ if((pairs = odsearch(odeum, buf, 5, &pnum)) != NULL){
+ if(myrand() % 5 == 0){
+ for(j = 0; j < pnum; j++){
+ if((doc = odgetbyid(odeum, pairs[j].id)) != NULL){
+ oddocclose(doc);
+ } else if(dpecode != DP_ENOITEM){
+ err = TRUE;
+ break;
+ }
+ }
+ }
+ free(pairs);
+ } else if(dpecode != DP_ENOITEM){
+ err = TRUE;
+ }
+ break;
+ default:
+ putchar('O');
+ doc = makedoc(myrand() % dnum + 1, myrand() % 10 + 10, myrand() % dnum + 500);
+ if(!odput(odeum, doc, 5, TRUE)) err = TRUE;
+ oddocclose(doc);
+ break;
+ }
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ if(err){
+ pdperror(name);
+ break;
+ }
+ }
+ if(!odoptimize(odeum)){
+ pdperror(name);
+ err = TRUE;
+ }
+ for(i = 1; i <= dnum; i++){
+ doc = makedoc(i, 5, 5);
+ if(!odput(odeum, doc, 5, FALSE) && dpecode != DP_EKEEP){
+ pdperror(name);
+ oddocclose(doc);
+ err = TRUE;
+ break;
+ }
+ oddocclose(doc);
+ putchar(':');
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ }
+ if(!odoptimize(odeum)){
+ pdperror(name);
+ err = TRUE;
+ }
+ for(i = 1; i <= dnum; i++){
+ sprintf(buf, "%08d", i);
+ if(!(doc = odget(odeum, buf))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ oddocclose(doc);
+ putchar('=');
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ }
+ if(!oditerinit(odeum)){
+ pdperror(name);
+ err = TRUE;
+ }
+ for(i = 1; i <= dnum; i++){
+ if(!(doc = oditernext(odeum))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ oddocclose(doc);
+ putchar('@');
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ }
+ if(!odclose(odeum)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/qdbm.def b/qdbm/qdbm.def
new file mode 100644
index 00000000..4c10e49e
--- /dev/null
+++ b/qdbm/qdbm.def
@@ -0,0 +1,424 @@
+EXPORTS
+ VL_CMPDEC = VL_CMPDEC DATA
+ VL_CMPINT = VL_CMPINT DATA
+ VL_CMPLEX = VL_CMPLEX DATA
+ VL_CMPNUM = VL_CMPNUM DATA
+ VST_CMPDEC = VST_CMPDEC DATA
+ VST_CMPINT = VST_CMPINT DATA
+ VST_CMPLEX = VST_CMPLEX DATA
+ VST_CMPNUM = VST_CMPNUM DATA
+ cbfatalfunc = cbfatalfunc DATA
+ dpdbgfd = dpdbgfd DATA
+ dpisreentrant = dpisreentrant DATA
+ dpsysname = dpsysname DATA
+ dpversion = dpversion DATA
+ gdbm_version = gdbm_version DATA
+ odcachebnum = odcachebnum DATA
+ odcachesiz = odcachesiz DATA
+ odindexbnum = odindexbnum DATA
+ odindexdnum = odindexdnum DATA
+ odotcb = odotcb DATA
+ cbbasedecode = cbbasedecode
+ cbbaseencode = cbbaseencode
+ cbbzdecode = cbbzdecode
+ cbbzencode = cbbzencode
+ cbcalendar = cbcalendar
+ cbcsvcells = cbcsvcells
+ cbcsvescape = cbcsvescape
+ cbcsvrows = cbcsvrows
+ cbcsvunescape = cbcsvunescape
+ cbdatestrhttp = cbdatestrhttp
+ cbdatestrwww = cbdatestrwww
+ cbdatumcat = cbdatumcat
+ cbdatumclose = cbdatumclose
+ cbdatumdup = cbdatumdup
+ cbdatumopen = cbdatumopen
+ cbdatumopenbuf = cbdatumopenbuf
+ cbdatumprintf = cbdatumprintf
+ cbdatumptr = cbdatumptr
+ cbdatumsetbuf = cbdatumsetbuf
+ cbdatumsetsize = cbdatumsetsize
+ cbdatumsize = cbdatumsize
+ cbdatumtomalloc = cbdatumtomalloc
+ cbdayofweek = cbdayofweek
+ cbdeflate = cbdeflate
+ cbdirlist = cbdirlist
+ cbencname = cbencname
+ cbfilestat = cbfilestat
+ cbfree = cbfree
+ cbgetcrc = cbgetcrc
+ cbggcsweep = cbggcsweep
+ cbglobalgc = cbglobalgc
+ cbgzdecode = cbgzdecode
+ cbgzencode = cbgzencode
+ cbheapclose = cbheapclose
+ cbheapdup = cbheapdup
+ cbheapinsert = cbheapinsert
+ cbheapnum = cbheapnum
+ cbheapopen = cbheapopen
+ cbheaptomalloc = cbheaptomalloc
+ cbheapval = cbheapval
+ cbhsort = cbhsort
+ cbiconv = cbiconv
+ cbinflate = cbinflate
+ cbisort = cbisort
+ cbjetlag = cbjetlag
+ cblistbsearch = cblistbsearch
+ cblistclose = cblistclose
+ cblistdump = cblistdump
+ cblistdup = cblistdup
+ cblistinsert = cblistinsert
+ cblistload = cblistload
+ cblistlsearch = cblistlsearch
+ cblistnum = cblistnum
+ cblistopen = cblistopen
+ cblistover = cblistover
+ cblistpop = cblistpop
+ cblistpush = cblistpush
+ cblistpushbuf = cblistpushbuf
+ cblistremove = cblistremove
+ cblistshift = cblistshift
+ cblistsort = cblistsort
+ cblistunshift = cblistunshift
+ cblistval = cblistval
+ cblzodecode = cblzodecode
+ cblzoencode = cblzoencode
+ cbmalloc = cbmalloc
+ cbmapclose = cbmapclose
+ cbmapdump = cbmapdump
+ cbmapdup = cbmapdup
+ cbmapget = cbmapget
+ cbmapiterinit = cbmapiterinit
+ cbmapiternext = cbmapiternext
+ cbmapiterval = cbmapiterval
+ cbmapkeys = cbmapkeys
+ cbmapload = cbmapload
+ cbmaploadone = cbmaploadone
+ cbmapmove = cbmapmove
+ cbmapopen = cbmapopen
+ cbmapopenex = cbmapopenex
+ cbmapout = cbmapout
+ cbmapput = cbmapput
+ cbmapputcat = cbmapputcat
+ cbmaprnum = cbmaprnum
+ cbmapvals = cbmapvals
+ cbmemdup = cbmemdup
+ cbmimebreak = cbmimebreak
+ cbmimedecode = cbmimedecode
+ cbmimeencode = cbmimeencode
+ cbmimeparts = cbmimeparts
+ cbmyfatal = cbmyfatal
+ cbproctime = cbproctime
+ cbqsort = cbqsort
+ cbquotedecode = cbquotedecode
+ cbquoteencode = cbquoteencode
+ cbreadfile = cbreadfile
+ cbreadlines = cbreadlines
+ cbrealloc = cbrealloc
+ cbremove = cbremove
+ cbreplace = cbreplace
+ cbsplit = cbsplit
+ cbsprintf = cbsprintf
+ cbssort = cbssort
+ cbstdiobin = cbstdiobin
+ cbstrbwimatch = cbstrbwimatch
+ cbstrbwmatch = cbstrbwmatch
+ cbstrcountutf = cbstrcountutf
+ cbstrcututf = cbstrcututf
+ cbstrfwimatch = cbstrfwimatch
+ cbstrfwmatch = cbstrfwmatch
+ cbstricmp = cbstricmp
+ cbstrmktime = cbstrmktime
+ cbstrsqzspc = cbstrsqzspc
+ cbstrstrbm = cbstrstrbm
+ cbstrstrkmp = cbstrstrkmp
+ cbstrtolower = cbstrtolower
+ cbstrtoupper = cbstrtoupper
+ cbstrtrim = cbstrtrim
+ cburlbreak = cburlbreak
+ cburldecode = cburldecode
+ cburlencode = cburlencode
+ cburlresolve = cburlresolve
+ cbvmemavail = cbvmemavail
+ cbwritefile = cbwritefile
+ cbxmlattrs = cbxmlattrs
+ cbxmlbreak = cbxmlbreak
+ cbxmlescape = cbxmlescape
+ cbxmlunescape = cbxmlunescape
+ crbnum = crbnum
+ crbusenum = crbusenum
+ crclose = crclose
+ crexportdb = crexportdb
+ crfatalerror = crfatalerror
+ crfsiz = crfsiz
+ crfsizd = crfsizd
+ crget = crget
+ crgetflags = crgetflags
+ crgetlob = crgetlob
+ crgetlobfd = crgetlobfd
+ crgetwb = crgetwb
+ crimportdb = crimportdb
+ crinode = crinode
+ criterinit = criterinit
+ criternext = criternext
+ crmemflush = crmemflush
+ crmemsync = crmemsync
+ crmtime = crmtime
+ crname = crname
+ cropen = cropen
+ croptimize = croptimize
+ crout = crout
+ croutlob = croutlob
+ crput = crput
+ crputlob = crputlob
+ crremove = crremove
+ crrepair = crrepair
+ crrnum = crrnum
+ crrnumlob = crrnumlob
+ crsetalign = crsetalign
+ crsetfbpsiz = crsetfbpsiz
+ crsetflags = crsetflags
+ crsnaffle = crsnaffle
+ crsync = crsync
+ crvsiz = crvsiz
+ crvsizlob = crvsizlob
+ crwritable = crwritable
+ dbm_clearerr = dbm_clearerr
+ dbm_close = dbm_close
+ dbm_delete = dbm_delete
+ dbm_dirfno = dbm_dirfno
+ dbm_error = dbm_error
+ dbm_fetch = dbm_fetch
+ dbm_firstkey = dbm_firstkey
+ dbm_nextkey = dbm_nextkey
+ dbm_open = dbm_open
+ dbm_pagfno = dbm_pagfno
+ dbm_rdonly = dbm_rdonly
+ dbm_store = dbm_store
+ dpbnum = dpbnum
+ dpbusenum = dpbusenum
+ dpclose = dpclose
+ dpecodeptr = dpecodeptr
+ dpecodeset = dpecodeset
+ dperrmsg = dperrmsg
+ dpexportdb = dpexportdb
+ dpfatalerror = dpfatalerror
+ dpfdesc = dpfdesc
+ dpfsiz = dpfsiz
+ dpget = dpget
+ dpgetflags = dpgetflags
+ dpgetwb = dpgetwb
+ dpimportdb = dpimportdb
+ dpinnerhash = dpinnerhash
+ dpinode = dpinode
+ dpiterinit = dpiterinit
+ dpiternext = dpiternext
+ dpmemflush = dpmemflush
+ dpmemsync = dpmemsync
+ dpmtime = dpmtime
+ dpname = dpname
+ dpopen = dpopen
+ dpoptimize = dpoptimize
+ dpout = dpout
+ dpouterhash = dpouterhash
+ dpprimenum = dpprimenum
+ dpput = dpput
+ dpremove = dpremove
+ dprepair = dprepair
+ dprnum = dprnum
+ dpsetalign = dpsetalign
+ dpsetfbpsiz = dpsetfbpsiz
+ dpsetflags = dpsetflags
+ dpsnaffle = dpsnaffle
+ dpsync = dpsync
+ dpvsiz = dpvsiz
+ dpwritable = dpwritable
+ gdbm_close = gdbm_close
+ gdbm_delete = gdbm_delete
+ gdbm_errnoptr = gdbm_errnoptr
+ gdbm_exists = gdbm_exists
+ gdbm_fdesc = gdbm_fdesc
+ gdbm_fetch = gdbm_fetch
+ gdbm_firstkey = gdbm_firstkey
+ gdbm_nextkey = gdbm_nextkey
+ gdbm_open = gdbm_open
+ gdbm_open2 = gdbm_open2
+ gdbm_reorganize = gdbm_reorganize
+ gdbm_setopt = gdbm_setopt
+ gdbm_store = gdbm_store
+ gdbm_strerror = gdbm_strerror
+ gdbm_sync = gdbm_sync
+ odanalyzetext = odanalyzetext
+ odbnum = odbnum
+ odbreaktext = odbreaktext
+ odbusenum = odbusenum
+ odcheck = odcheck
+ odclose = odclose
+ oddnum = oddnum
+ oddocaddattr = oddocaddattr
+ oddocaddword = oddocaddword
+ oddocawords = oddocawords
+ oddocclose = oddocclose
+ oddocgetattr = oddocgetattr
+ oddocid = oddocid
+ oddocnwords = oddocnwords
+ oddocopen = oddocopen
+ oddocscores = oddocscores
+ oddocuri = oddocuri
+ odfatalerror = odfatalerror
+ odfsiz = odfsiz
+ odget = odget
+ odgetbyid = odgetbyid
+ odgetidbyuri = odgetidbyuri
+ odidbdocs = odidbdocs
+ odidbindex = odidbindex
+ odidbrdocs = odidbrdocs
+ odinode = odinode
+ oditerinit = oditerinit
+ oditernext = oditernext
+ odlogarithm = odlogarithm
+ odmerge = odmerge
+ odmtime = odmtime
+ odname = odname
+ odnormalizeword = odnormalizeword
+ odopen = odopen
+ odoptimize = odoptimize
+ odout = odout
+ odoutbyid = odoutbyid
+ odpairsand = odpairsand
+ odpairsnotand = odpairsnotand
+ odpairsor = odpairsor
+ odpairssort = odpairssort
+ odput = odput
+ odquery = odquery
+ odremove = odremove
+ odsearch = odsearch
+ odsearchdnum = odsearchdnum
+ odsetcharclass = odsetcharclass
+ odsetotcb = odsetotcb
+ odsettuning = odsettuning
+ odsquareroot = odsquareroot
+ odsync = odsync
+ odvecabsolute = odvecabsolute
+ odvecinnerproduct = odvecinnerproduct
+ odvectorcosine = odvectorcosine
+ odwnum = odwnum
+ odwritable = odwritable
+ vlclose = vlclose
+ vlcrdnumptr = vlcrdnumptr
+ vlcurfirst = vlcurfirst
+ vlcurjump = vlcurjump
+ vlcurkey = vlcurkey
+ vlcurkeycache = vlcurkeycache
+ vlcurlast = vlcurlast
+ vlcurnext = vlcurnext
+ vlcurout = vlcurout
+ vlcurprev = vlcurprev
+ vlcurput = vlcurput
+ vlcurval = vlcurval
+ vlcurvalcache = vlcurvalcache
+ vlexportdb = vlexportdb
+ vlfatalerror = vlfatalerror
+ vlfsiz = vlfsiz
+ vlget = vlget
+ vlgetcache = vlgetcache
+ vlgetcat = vlgetcat
+ vlgetflags = vlgetflags
+ vlgetlist = vlgetlist
+ vlimportdb = vlimportdb
+ vlinode = vlinode
+ vllnum = vllnum
+ vlmemflush = vlmemflush
+ vlmemsync = vlmemsync
+ vlmtime = vlmtime
+ vlmulcurclose = vlmulcurclose
+ vlmulcurfirst = vlmulcurfirst
+ vlmulcurjump = vlmulcurjump
+ vlmulcurkey = vlmulcurkey
+ vlmulcurkeycache = vlmulcurkeycache
+ vlmulcurlast = vlmulcurlast
+ vlmulcurnext = vlmulcurnext
+ vlmulcuropen = vlmulcuropen
+ vlmulcurprev = vlmulcurprev
+ vlmulcurval = vlmulcurval
+ vlmulcurvalcache = vlmulcurvalcache
+ vlname = vlname
+ vlnnum = vlnnum
+ vlopen = vlopen
+ vloptimize = vloptimize
+ vlout = vlout
+ vloutlist = vloutlist
+ vlput = vlput
+ vlputlist = vlputlist
+ vlremove = vlremove
+ vlrepair = vlrepair
+ vlrnum = vlrnum
+ vlsetfbpsiz = vlsetfbpsiz
+ vlsetflags = vlsetflags
+ vlsettuning = vlsettuning
+ vlsync = vlsync
+ vltranabort = vltranabort
+ vltranbegin = vltranbegin
+ vltrancommit = vltrancommit
+ vlvnum = vlvnum
+ vlvsiz = vlvsiz
+ vlwritable = vlwritable
+ vstclose = vstclose
+ vstcrdnumptr = vstcrdnumptr
+ vstcurfirst = vstcurfirst
+ vstcurjump = vstcurjump
+ vstcurkey = vstcurkey
+ vstcurkeycache = vstcurkeycache
+ vstcurlast = vstcurlast
+ vstcurnext = vstcurnext
+ vstcurout = vstcurout
+ vstcurprev = vstcurprev
+ vstcurput = vstcurput
+ vstcurval = vstcurval
+ vstcurvalcache = vstcurvalcache
+ vstexportdb = vstexportdb
+ vstfatalerror = vstfatalerror
+ vstfsiz = vstfsiz
+ vstget = vstget
+ vstgetcache = vstgetcache
+ vstgetcat = vstgetcat
+ vstgetflags = vstgetflags
+ vstgetlist = vstgetlist
+ vstimportdb = vstimportdb
+ vstinode = vstinode
+ vstlnum = vstlnum
+ vstmemflush = vstmemflush
+ vstmemsync = vstmemsync
+ vstmtime = vstmtime
+ vstmulcurclose = vstmulcurclose
+ vstmulcurfirst = vstmulcurfirst
+ vstmulcurjump = vstmulcurjump
+ vstmulcurkey = vstmulcurkey
+ vstmulcurkeycache = vstmulcurkeycache
+ vstmulcurlast = vstmulcurlast
+ vstmulcurnext = vstmulcurnext
+ vstmulcuropen = vstmulcuropen
+ vstmulcurprev = vstmulcurprev
+ vstmulcurval = vstmulcurval
+ vstmulcurvalcache = vstmulcurvalcache
+ vstname = vstname
+ vstnnum = vstnnum
+ vstopen = vstopen
+ vstoptimize = vstoptimize
+ vstout = vstout
+ vstoutlist = vstoutlist
+ vstput = vstput
+ vstputlist = vstputlist
+ vstremove = vstremove
+ vstrepair = vstrepair
+ vstrnum = vstrnum
+ vstsetfbpsiz = vstsetfbpsiz
+ vstsetflags = vstsetflags
+ vstsettuning = vstsettuning
+ vstsync = vstsync
+ vsttranabort = vsttranabort
+ vsttranbegin = vsttranbegin
+ vsttrancommit = vsttrancommit
+ vstvnum = vstvnum
+ vstvsiz = vstvsiz
+ vstwritable = vstwritable
diff --git a/qdbm/qdbm.pc.in b/qdbm/qdbm.pc.in
new file mode 100644
index 00000000..fae04061
--- /dev/null
+++ b/qdbm/qdbm.pc.in
@@ -0,0 +1,14 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+bindir=@bindir@
+libdir=@libdir@
+libexecdir=@libexecdir@
+includedir=@includedir@
+datadir=@datadir@
+
+Name: QDBM
+Description: a high performance embedded database library
+Version: @PACKAGE_VERSION@
+Requires:
+Libs: -L${libdir} -lqdbm @LIBS@
+Cflags: -I${includedir}
diff --git a/qdbm/qdbm.spec.in b/qdbm/qdbm.spec.in
new file mode 100644
index 00000000..9fecddbb
--- /dev/null
+++ b/qdbm/qdbm.spec.in
@@ -0,0 +1,218 @@
+#================================================================
+# RPM Specification for QDBM
+#================================================================
+
+
+
+%define name @PACKAGE_NAME@
+%define version @PACKAGE_VERSION@
+%define release 1
+%define libver @LIBVER@
+%define librev @LIBREV@
+%define disturl http://qdbm.sourceforge.net/
+%define homeurl http://qdbm.sourceforge.net/
+
+Summary: Quick Database Manager
+Name: %{name}
+Version: %{version}
+Release: %{release}
+Source: %{disturl}%{name}-%{version}.tar.gz
+Copyright: LGPL
+Group: Development/Libraries
+Packager: Mikio Hirabayashi <mikio@users.sourceforge.net>
+Distribution: Private
+Vendor: Private
+Url: %{homeurl}
+Requires: zlib
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
+
+%description
+QDBM is an embeded database library compatible with GDBM and NDBM.
+It features hash database and B+ tree database and is developed referring
+to GDBM for the purpose of the following three points: higher processing
+speed, smaller size of a database file, and simpler API.
+This package includes APIs for C, C++, and Java. CGI scripts are also
+contained. APIs for Perl and Ruby should be installed with a source package.
+
+%package devel
+Summary: Headers, libraries, utilities and documentation for QDBM
+Group: Development/Libraries
+Requires: %{name} = %{version}
+
+%description devel
+This package contains header files and libraries needed to develop programs
+using the QDBM library. Some utility commands are also provided.
+
+%package plus
+Summary: C++ libraries for QDBM
+Group: Development/Libraries
+Requires: %{name} = %{version}
+
+%description plus
+This package contains libraries needed to develop and run programs using the
+QDBM C++ bindings.
+
+%package java
+Summary: Java libraries for QDBM
+Group: Development/Libraries
+Requires: %{name} = %{version}
+
+%description java
+This package contains libraries needed to develop and run programs using the
+QDBM Java bindings.
+
+%package cgi
+Summary: CGI scripts with QDBM
+Group: Development/Libraries
+Requires: %{name} = %{version}
+
+%description cgi
+This package contains CGI scripts with QDBM, for administration of databases,
+file uploading, and full-text search.
+
+%prep
+rm -rf $RPM_BUILD_ROOT
+
+%setup -q
+
+%build
+( cd . ; %{configure} --prefix=%{_usr} --mandir=%{_mandir} \
+ --enable-stable --enable-zlib --enable-iconv --enable-pthread ; make )
+( cd plus ; %{configure} --prefix=%{_usr} --mandir=%{_mandir} ; make )
+( cd java ; %{configure} --prefix=%{_usr} --mandir=%{_mandir} ; make )
+( cd cgi ; %{configure} --prefix=%{_usr} --mandir=%{_mandir} ; make )
+
+%install
+rm -rf $RPM_BUILD_ROOT
+( cd . ; make DESTDIR=$RPM_BUILD_ROOT install-strip )
+( cd plus ; make DESTDIR=$RPM_BUILD_ROOT install-strip )
+( cd java ; make DESTDIR=$RPM_BUILD_ROOT install )
+( cd cgi ; make DESTDIR=$RPM_BUILD_ROOT install-strip )
+( mkdir -p $RPM_BUILD_ROOT%{_datadir}/doc/qdbm && \
+ cd $RPM_BUILD_ROOT%{_usr}/share/qdbm && \
+ cp -Rf spex-ja.html spex.html COPYING ChangeLog NEWS THANKS \
+ plus/xspex.html plus/xspex-ja.html plus/xapidoc \
+ java/jspex.html java/jspex-ja.html java/japidoc \
+ cgi/cgispex-ja.html cgi/cgispex.html $RPM_BUILD_ROOT%{_datadir}/doc/qdbm )
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root)
+%{_libdir}/libqdbm.so.%{libver}.%{librev}.0
+%{_libdir}/libqdbm.so.%{libver}
+%{_libdir}/libqdbm.so
+
+%files devel
+%defattr(-,root,root)
+%{_includedir}/depot.h
+%{_includedir}/curia.h
+%{_includedir}/relic.h
+%{_includedir}/hovel.h
+%{_includedir}/cabin.h
+%{_includedir}/villa.h
+%{_includedir}/vista.h
+%{_includedir}/odeum.h
+%{_libdir}/libqdbm.a
+%{_bindir}/dpmgr
+%{_bindir}/dptest
+%{_bindir}/dptsv
+%{_bindir}/crmgr
+%{_bindir}/crtest
+%{_bindir}/crtsv
+%{_bindir}/rlmgr
+%{_bindir}/rltest
+%{_bindir}/hvmgr
+%{_bindir}/hvtest
+%{_bindir}/cbtest
+%{_bindir}/cbcodec
+%{_bindir}/vlmgr
+%{_bindir}/vltest
+%{_bindir}/vltsv
+%{_bindir}/odmgr
+%{_bindir}/odtest
+%{_bindir}/odidx
+%{_bindir}/qmttest
+%{_mandir}/man1/dpmgr.1.gz
+%{_mandir}/man1/dptest.1.gz
+%{_mandir}/man1/dptsv.1.gz
+%{_mandir}/man1/crmgr.1.gz
+%{_mandir}/man1/crtest.1.gz
+%{_mandir}/man1/crtsv.1.gz
+%{_mandir}/man1/rlmgr.1.gz
+%{_mandir}/man1/rltest.1.gz
+%{_mandir}/man1/hvmgr.1.gz
+%{_mandir}/man1/hvtest.1.gz
+%{_mandir}/man1/cbtest.1.gz
+%{_mandir}/man1/cbcodec.1.gz
+%{_mandir}/man1/vlmgr.1.gz
+%{_mandir}/man1/vltest.1.gz
+%{_mandir}/man1/vltsv.1.gz
+%{_mandir}/man1/odmgr.1.gz
+%{_mandir}/man1/odtest.1.gz
+%{_mandir}/man1/odidx.1.gz
+%{_mandir}/man1/qmttest.1.gz
+%{_mandir}/man3/qdbm.3.gz
+%{_mandir}/man3/depot.3.gz
+%{_mandir}/man3/dpopen.3.gz
+%{_mandir}/man3/curia.3.gz
+%{_mandir}/man3/cropen.3.gz
+%{_mandir}/man3/relic.3.gz
+%{_mandir}/man3/hovel.3.gz
+%{_mandir}/man3/cabin.3.gz
+%{_mandir}/man3/villa.3.gz
+%{_mandir}/man3/vlopen.3.gz
+%{_mandir}/man3/vista.3.gz
+%{_mandir}/man3/odeum.3.gz
+%{_mandir}/man3/odopen.3.gz
+%{_datadir}/doc/qdbm/spex.html
+%{_datadir}/doc/qdbm/spex-ja.html
+%{_datadir}/doc/qdbm/COPYING
+%{_datadir}/doc/qdbm/ChangeLog
+%{_datadir}/doc/qdbm/NEWS
+%{_datadir}/doc/qdbm/THANKS
+%{_libdir}/pkgconfig/qdbm.pc
+
+%files plus
+%defattr(-,root,root)
+%{_includedir}/xqdbm.h
+%{_includedir}/xadbm.h
+%{_includedir}/xdepot.h
+%{_includedir}/xcuria.h
+%{_includedir}/xvilla.h
+%{_libdir}/libxqdbm.a
+%{_libdir}/libxqdbm.so.3.0.0
+%{_libdir}/libxqdbm.so.3
+%{_libdir}/libxqdbm.so
+%{_bindir}/xdptest
+%{_bindir}/xcrtest
+%{_bindir}/xvltest
+%{_datadir}/doc/qdbm/xspex.html
+%{_datadir}/doc/qdbm/xspex-ja.html
+%{_datadir}/doc/qdbm/xapidoc/
+
+%files java
+%defattr(-,root,root)
+%{_libdir}/qdbm.jar
+%{_libdir}/libjqdbm.so.1.0.0
+%{_libdir}/libjqdbm.so.1
+%{_libdir}/libjqdbm.so
+%{_datadir}/doc/qdbm/jspex.html
+%{_datadir}/doc/qdbm/jspex-ja.html
+%{_datadir}/doc/qdbm/japidoc/
+
+%files cgi
+%defattr(-,root,root)
+%{_libexecdir}/qadm.cgi
+%{_libexecdir}/qupl.cgi
+%{_libexecdir}/qfts.cgi
+%{_datadir}/qdbm/cgi/qadm.conf
+%{_datadir}/qdbm/cgi/qupl.conf
+%{_datadir}/qdbm/cgi/qfts.conf
+%{_datadir}/doc/qdbm/cgispex.html
+%{_datadir}/doc/qdbm/cgispex-ja.html
+
+
+
+# END OF FILE
diff --git a/qdbm/qmttest.c b/qdbm/qmttest.c
new file mode 100644
index 00000000..afc8b361
--- /dev/null
+++ b/qdbm/qmttest.c
@@ -0,0 +1,300 @@
+/*************************************************************************************************
+ * Test cases for thread-safety
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <curia.h>
+#include <cabin.h>
+#include <villa.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <time.h>
+
+#if defined(MYPTHREAD)
+#include <sys/types.h>
+#include <pthread.h>
+#endif
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define PATHBUFSIZ 1024 /* buffer for paths */
+#define RECBUFSIZ 32 /* buffer for records */
+
+typedef struct { /* type of structure of thread arguments */
+ int id; /* ID of the thread */
+ const char *name; /* prefix of the database */
+ int rnum; /* number of records */
+ int alive; /* alive or not */
+} MYARGS;
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+void pdperror(const char *name);
+void *procthread(void *args);
+int dotest(const char *name, int rnum, int tnum);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ char *env, *name;
+ int rv, rnum, tnum;
+ cbstdiobin();
+ if((env = getenv("QDBMDBGFD")) != NULL) dpdbgfd = atoi(env);
+ progname = argv[0];
+ srand(time(NULL));
+ if(argc < 4) usage();
+ name = argv[1];
+ if((rnum = atoi(argv[2])) < 1) usage();
+ if((tnum = atoi(argv[3])) < 1) usage();
+ rv = dotest(name, rnum, tnum);
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: test cases for thread-safety\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s name rnum tnum\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* print formatted string and flush the buffer */
+int printfflush(const char *format, ...){
+#if defined(MYPTHREAD)
+ static pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
+ va_list ap;
+ int rv;
+ if(pthread_mutex_lock(&mymutex) != 0) return -1;
+ va_start(ap, format);
+ rv = vprintf(format, ap);
+ if(fflush(stdout) == EOF) rv = -1;
+ va_end(ap);
+ pthread_mutex_unlock(&mymutex);
+ return rv;
+#else
+ va_list ap;
+ int rv;
+ va_start(ap, format);
+ rv = vprintf(format, ap);
+ if(fflush(stdout) == EOF) rv = -1;
+ va_end(ap);
+ return rv;
+#endif
+}
+
+
+/* print an error message */
+void pdperror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, dperrmsg(dpecode));
+}
+
+
+/* pseudo random number generator */
+int myrand(void){
+ static int cnt = 0;
+ return (rand() * rand() + (rand() >> (sizeof(int) * 4)) + (cnt++)) & INT_MAX;
+}
+
+
+/* process the test */
+void *procthread(void *args){
+ MYARGS *myargs;
+ DEPOT *depot;
+ CURIA *curia;
+ VILLA *villa;
+ CBLIST *list;
+ CBMAP *map;
+ char name[PATHBUFSIZ], buf[RECBUFSIZ];
+ int i, err, len;
+ myargs = (MYARGS *)args;
+ err = FALSE;
+ sprintf(name, "%s-%04d", myargs->name, myargs->id);
+ dpremove(name);
+ crremove(name);
+ vlremove(name);
+ switch(myrand() % 4){
+ case 0:
+ printfflush("\n[Depot Test] name=%s rnum=%d\n", name, myargs->rnum);
+ if(!(depot = dpopen(name, DP_OWRITER | DP_OCREAT | DP_OTRUNC, -1))){
+ pdperror(name);
+ return "error";
+ }
+ for(i = 1; i <= myargs->rnum; i++){
+ len = sprintf(buf, "%d", myrand() % i + 1);
+ if(!dpput(depot, buf, len, buf, len, i % 2 == 0 ? DP_DOVER : DP_DCAT)){
+ pdperror(name);
+ err = TRUE;
+ }
+ if(myargs->rnum > 250 && i % (myargs->rnum / 250) == 0){
+ printfflush(".");
+ if(i == myargs->rnum || i % (myargs->rnum / 10) == 0){
+ printfflush("\n%s: (%d)\n", name, i);
+ }
+ }
+ }
+ if(!dpclose(depot)){
+ pdperror(name);
+ err = TRUE;
+ }
+ printfflush("\n%s: finished\n", name);
+ break;
+ case 1:
+ printfflush("\n[Curia Test] name=%s rnum=%d\n", name, myargs->rnum);
+ if(!(curia = cropen(name, CR_OWRITER | CR_OCREAT | CR_OTRUNC, -1, -1))){
+ pdperror(name);
+ return "error";
+ }
+ for(i = 1; i <= myargs->rnum; i++){
+ len = sprintf(buf, "%d", myrand() % i + 1);
+ if(!crput(curia, buf, len, buf, len, i % 2 == 0 ? CR_DOVER : CR_DCAT)){
+ pdperror(name);
+ err = TRUE;
+ }
+ if(myargs->rnum > 250 && i % (myargs->rnum / 250) == 0){
+ printfflush(".");
+ if(i == myargs->rnum || i % (myargs->rnum / 10) == 0){
+ printfflush("\n%s: (%d)\n", name, i);
+ }
+ }
+ }
+ if(!crclose(curia)){
+ pdperror(name);
+ err = TRUE;
+ }
+ printfflush("\n%s: finished\n", name);
+ break;
+ case 2:
+ printfflush("\n[Villa Test] name=%s rnum=%d\n", name, myargs->rnum);
+ if(!(villa = vlopen(name, VL_OWRITER | VL_OCREAT | VL_OTRUNC, VL_CMPLEX))){
+ pdperror(name);
+ return "error";
+ }
+ for(i = 1; i <= myargs->rnum; i++){
+ len = sprintf(buf, "%d", myrand() % i + 1);
+ if(!vlput(villa, buf, len, buf, len, i % 2 == 0 ? VL_DOVER : VL_DDUP)){
+ pdperror(name);
+ err = TRUE;
+ }
+ if(myargs->rnum > 250 && i % (myargs->rnum / 250) == 0){
+ printfflush(".");
+ if(i == myargs->rnum || i % (myargs->rnum / 10) == 0){
+ printfflush("\n%s: (%d)\n", name, i);
+ }
+ }
+ }
+ if(!vlclose(villa)){
+ pdperror(name);
+ err = TRUE;
+ }
+ printfflush("\n%s: finished\n", name);
+ break;
+ case 3:
+ printfflush("\n[Cabin Test] name=%s rnum=%d\n", name, myargs->rnum);
+ list = cblistopen();
+ map = cbmapopen();
+ for(i = 1; i <= myargs->rnum; i++){
+ len = sprintf(buf, "%d", myrand() % i + 1);
+ cblistpush(list, buf, len);
+ cbmapput(map, buf, len, buf, len, i % 2 == 0 ? TRUE : FALSE);
+ if(myargs->rnum > 250 && i % (myargs->rnum / 250) == 0){
+ printfflush(".");
+ if(i == myargs->rnum || i % (myargs->rnum / 10) == 0){
+ printfflush("\n%s: (%d)\n", name, i);
+ }
+ }
+ }
+ cbmapclose(map);
+ cblistclose(list);
+ printfflush("\n%s: finished\n", name);
+ break;
+ }
+ return err ? "error" : NULL;
+}
+
+
+/* drive the test */
+int dotest(const char *name, int rnum, int tnum){
+#if defined(MYPTHREAD)
+ pthread_t *thary;
+ MYARGS *argsary;
+ char *rv;
+ int i, err;
+ printfflush("<Thread-Safety Test>\n name=%s rnum=%d tnum=%d\n", name, rnum, tnum);
+ err = FALSE;
+ thary = cbmalloc(tnum * sizeof(pthread_t));
+ argsary = cbmalloc(tnum * sizeof(MYARGS));
+ for(i = 0; i < tnum; i++){
+ argsary[i].id = i + 1;
+ argsary[i].name = name;
+ argsary[i].rnum = rnum;
+ argsary[i].alive = TRUE;
+ if(pthread_create(thary + i, NULL, procthread, argsary + i) != 0){
+ argsary[i].alive = FALSE;
+ err = TRUE;
+ }
+ }
+ for(i = 0; i < tnum; i++){
+ if(!argsary[i].alive) continue;
+ if(pthread_join(thary[i], (void *)&rv) != 0 || rv) err = TRUE;
+ }
+ free(argsary);
+ free(thary);
+ if(!err) printfflush("\nall ok\n");
+ return err ? 1 : 0;
+#else
+ MYARGS *argsary;
+ int i, err;
+ printfflush("<Thread-Safety Test>\n name=%s rnum=%d tnum=%d\n", name, rnum, tnum);
+ err = FALSE;
+ argsary = cbmalloc(tnum * sizeof(MYARGS));
+ for(i = 0; i < tnum; i++){
+ argsary[i].id = i + 1;
+ argsary[i].name = name;
+ argsary[i].rnum = rnum;
+ argsary[i].alive = TRUE;
+ if(procthread(argsary + i)) err = TRUE;
+ }
+ free(argsary);
+ if(!err) printfflush("\nall ok\n");
+ return err ? 1 : 0;
+#endif
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/relic.c b/qdbm/relic.c
new file mode 100644
index 00000000..31bbbde2
--- /dev/null
+++ b/qdbm/relic.c
@@ -0,0 +1,266 @@
+/*************************************************************************************************
+ * Implementation of Relic
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#define QDBM_INTERNAL 1
+
+#include "relic.h"
+#include "myconf.h"
+
+#define RL_NAMEMAX 512 /* max size of a database name */
+#define RL_DIRFSUF MYEXTSTR "dir" /* suffix of a directory file */
+#define RL_DATAFSUF MYEXTSTR "pag" /* suffix of a page file */
+#define RL_PATHBUFSIZ 1024 /* size of a path buffer */
+#define RL_INITBNUM 1913 /* initial bucket number */
+#define RL_ALIGNSIZ 16 /* size of alignment */
+#define RL_MAXLOAD 1.25 /* max ratio of bucket loading */
+#define RL_DIRMAGIC "[depot]\0\v" /* magic number of a directory file */
+
+
+/* private function prototypes */
+static void dbm_writedummy(int fd);
+static int dbm_writestr(int fd, const char *str);
+
+
+
+/*************************************************************************************************
+ * public objects
+ *************************************************************************************************/
+
+
+/* Get a database handle. */
+DBM *dbm_open(char *name, int flags, int mode){
+ DBM *db;
+ DEPOT *depot;
+ int dpomode;
+ char path[RL_PATHBUFSIZ];
+ int dfd, fd;
+ assert(name);
+ if(strlen(name) > RL_NAMEMAX) return NULL;
+ dpomode = DP_OREADER;
+ if((flags & O_WRONLY) || (flags & O_RDWR)){
+ dpomode = DP_OWRITER;
+ if(flags & O_CREAT) dpomode |= DP_OCREAT;
+ if(flags & O_TRUNC) dpomode |= DP_OTRUNC;
+ }
+ mode |= 00600;
+ sprintf(path, "%s%s", name, RL_DIRFSUF);
+ if((dfd = open(path, flags, mode)) == -1) return NULL;
+ dbm_writedummy(dfd);
+ sprintf(path, "%s%s", name, RL_DATAFSUF);
+ if((fd = open(path, flags, mode)) == -1 || close(fd) == -1){
+ close(dfd);
+ return NULL;
+ }
+ if(!(depot = dpopen(path, dpomode, RL_INITBNUM))){
+ close(dfd);
+ return NULL;
+ }
+ if(dpomode & DP_OWRITER){
+ if(!dpsetalign(depot, RL_ALIGNSIZ)){
+ close(dfd);
+ dpclose(depot);
+ return NULL;
+ }
+ }
+ if(!(db = malloc(sizeof(DBM)))){
+ close(dfd);
+ dpclose(depot);
+ return NULL;
+ }
+ db->depot = depot;
+ db->dfd = dfd;
+ db->dbm_fetch_vbuf = NULL;
+ db->dbm_nextkey_kbuf = NULL;
+ return db;
+}
+
+
+/* Close a database handle. */
+void dbm_close(DBM *db){
+ assert(db);
+ free(db->dbm_fetch_vbuf);
+ free(db->dbm_nextkey_kbuf);
+ close(db->dfd);
+ dpclose(db->depot);
+ free(db);
+}
+
+
+/* Store a record. */
+int dbm_store(DBM *db, datum key, datum content, int flags){
+ int dmode;
+ int bnum, rnum;
+ assert(db);
+ if(!key.dptr || key.dsize < 0 || !content.dptr || content.dsize < 0) return -1;
+ dmode = (flags == DBM_INSERT) ? DP_DKEEP : DP_DOVER;
+ if(!dpput(db->depot, key.dptr, key.dsize, content.dptr, content.dsize, dmode)){
+ if(dpecode == DP_EKEEP) return 1;
+ return -1;
+ }
+ bnum = dpbnum(db->depot);
+ rnum = dprnum(db->depot);
+ if(bnum > 0 && rnum > 0 && ((double)rnum / (double)bnum > RL_MAXLOAD)){
+ if(!dpoptimize(db->depot, -1)) return -1;
+ }
+ return 0;
+}
+
+
+/* Delete a record. */
+int dbm_delete(DBM *db, datum key){
+ assert(db);
+ if(!key.dptr || key.dsize < 0) return -1;
+ if(!dpout(db->depot, key.dptr, key.dsize)) return -1;
+ return 0;
+}
+
+
+/* Retrieve a record. */
+datum dbm_fetch(DBM *db, datum key){
+ datum content;
+ char *vbuf;
+ int vsiz;
+ assert(db);
+ if(!key.dptr || key.dsize < 0 ||
+ !(vbuf = dpget(db->depot, key.dptr, key.dsize, 0, -1, &vsiz))){
+ content.dptr = NULL;
+ content.dsize = 0;
+ return content;
+ }
+ free(db->dbm_fetch_vbuf);
+ db->dbm_fetch_vbuf = vbuf;
+ content.dptr = vbuf;
+ content.dsize = vsiz;
+ return content;
+}
+
+
+/* Get the first key of a database. */
+datum dbm_firstkey(DBM *db){
+ assert(db);
+ dpiterinit(db->depot);
+ return dbm_nextkey(db);
+}
+
+
+/* Get the next key of a database. */
+datum dbm_nextkey(DBM *db){
+ datum key;
+ char *kbuf;
+ int ksiz;
+ if(!(kbuf = dpiternext(db->depot, &ksiz))){
+ key.dptr = NULL;
+ key.dsize = 0;
+ return key;
+ }
+ free(db->dbm_nextkey_kbuf);
+ db->dbm_nextkey_kbuf = kbuf;
+ key.dptr = kbuf;
+ key.dsize = ksiz;
+ return key;
+}
+
+
+/* Check whether a database has a fatal error or not. */
+int dbm_error(DBM *db){
+ assert(db);
+ return dpfatalerror(db->depot) ? TRUE : FALSE;
+}
+
+
+/* No effect. */
+int dbm_clearerr(DBM *db){
+ assert(db);
+ return 0;
+}
+
+
+/* Check whether a handle is read-only or not. */
+int dbm_rdonly(DBM *db){
+ assert(db);
+ return dpwritable(db->depot) ? FALSE : TRUE;
+}
+
+
+/* Get the file descriptor of a directory file. */
+int dbm_dirfno(DBM *db){
+ assert(db);
+ return db->dfd;
+}
+
+
+/* Get the file descriptor of a data file. */
+int dbm_pagfno(DBM *db){
+ assert(db);
+ return dpfdesc(db->depot);
+}
+
+
+
+/*************************************************************************************************
+ * private objects
+ *************************************************************************************************/
+
+
+/* Write dummy data into a dummy file.
+ `fd' specifies a file descriptor. */
+static void dbm_writedummy(int fd){
+ struct stat sbuf;
+ if(fstat(fd, &sbuf) == -1 || sbuf.st_size > 0) return;
+ write(fd, RL_DIRMAGIC, sizeof(RL_DIRMAGIC) - 1);
+ dbm_writestr(fd, "\n\n");
+ dbm_writestr(fd, "\x20\x20\xa2\xca\xa1\xb2\xa2\xca\x20\x20\x20\x20\x20\xa1\xbf\xa1");
+ dbm_writestr(fd, "\xb1\xa1\xb1\xa1\xb1\xa1\xb1\xa1\xb1\xa1\xb1\xa1\xb1\xa1\xb1\xa1");
+ dbm_writestr(fd, "\xb1\x0a\xa1\xca\x20\xa1\xad\xa2\xcf\xa1\xae\xa1\xcb\xa1\xe3\x20");
+ dbm_writestr(fd, "\x20\x4e\x44\x42\x4d\x20\x43\x6f\x6d\x70\x61\x74\x69\x62\x69\x6c");
+ dbm_writestr(fd, "\x69\x74\x79\x0a\xa1\xca\x20\x20\x20\x20\x20\x20\x20\xa1\xcb\x20");
+ dbm_writestr(fd, "\x20\xa1\xc0\xa1\xb2\xa1\xb2\xa1\xb2\xa1\xb2\xa1\xb2\xa1\xb2\xa1");
+ dbm_writestr(fd, "\xb2\xa1\xb2\xa1\xb2\x0a\x20\xa1\xc3\x20\x20\xa1\xc3\x20\xa1\xc3");
+ dbm_writestr(fd, "\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20");
+ dbm_writestr(fd, "\x20\x20\x20\x20\x20\x20\x20\x0a\xa1\xca\x5f\x5f\xa1\xb2\xa1\xcb");
+ dbm_writestr(fd, "\x5f\xa1\xcb\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20");
+ dbm_writestr(fd, "\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a");
+}
+
+
+/* Write a string into a file.
+ `fd' specifies a file descriptor.
+ `str' specifies a string. */
+static int dbm_writestr(int fd, const char *str){
+ const char *lbuf;
+ int size, rv, wb;
+ assert(fd >= 0 && str);
+ lbuf = str;
+ size = strlen(str);
+ rv = 0;
+ do {
+ wb = write(fd, lbuf, size);
+ switch(wb){
+ case -1: if(errno != EINTR) return -1;
+ case 0: break;
+ default:
+ lbuf += wb;
+ size -= wb;
+ rv += wb;
+ break;
+ }
+ } while(size > 0);
+ return rv;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/relic.h b/qdbm/relic.h
new file mode 100644
index 00000000..c5b7c51f
--- /dev/null
+++ b/qdbm/relic.h
@@ -0,0 +1,170 @@
+/*************************************************************************************************
+ * The NDBM-compatible API of QDBM
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#ifndef _RELIC_H /* duplication check */
+#define _RELIC_H
+
+#if defined(__cplusplus) /* export for C++ */
+extern "C" {
+#endif
+
+
+#include <depot.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+
+#if defined(_MSC_VER) && !defined(QDBM_INTERNAL) && !defined(QDBM_STATIC)
+#define MYEXTERN extern __declspec(dllimport)
+#else
+#define MYEXTERN extern
+#endif
+
+
+
+/*************************************************************************************************
+ * API
+ *************************************************************************************************/
+
+
+typedef struct { /* type of structure for a database handle */
+ DEPOT *depot; /* internal database handle */
+ int dfd; /* file descriptor of a dummy file */
+ char *dbm_fetch_vbuf; /* buffer for dbm_fetch */
+ char *dbm_nextkey_kbuf; /* buffer for dbm_nextkey */
+} DBM;
+
+typedef struct { /* type of structure for a key or a value */
+ void *dptr; /* pointer to the region */
+ size_t dsize; /* size of the region */
+} datum;
+
+enum { /* enumeration for write modes */
+ DBM_INSERT, /* keep an existing value */
+ DBM_REPLACE /* overwrite an existing value */
+};
+
+
+/* Get a database handle.
+ `name' specifies the name of a database. The file names are concatenated with suffixes.
+ `flags' is the same as the one of `open' call, although `O_WRONLY' is treated as `O_RDWR'
+ and additional flags except for `O_CREAT' and `O_TRUNC' have no effect.
+ `mode' specifies the mode of the database file as the one of `open' call does.
+ The return value is the database handle or `NULL' if it is not successful. */
+DBM *dbm_open(char *name, int flags, int mode);
+
+
+/* Close a database handle.
+ `db' specifies a database handle.
+ Because the region of the closed handle is released, it becomes impossible to use the
+ handle. */
+void dbm_close(DBM *db);
+
+
+/* Store a record.
+ `db' specifies a database handle.
+ `key' specifies a structure of a key.
+ `content' specifies a structure of a value.
+ `flags' specifies behavior when the key overlaps, by the following values: `DBM_REPLACE',
+ which means the specified value overwrites the existing one, `DBM_INSERT', which means the
+ existing value is kept.
+ The return value is 0 if it is successful, 1 if it gives up because of overlaps of the key,
+ -1 if other error occurs. */
+int dbm_store(DBM *db, datum key, datum content, int flags);
+
+
+/* Delete a record.
+ `db' specifies a database handle.
+ `key' specifies a structure of a key.
+ The return value is 0 if it is successful, -1 if some errors occur. */
+int dbm_delete(DBM *db, datum key);
+
+
+/* Retrieve a record.
+ `db' specifies a database handle.
+ `key' specifies a structure of a key.
+ The return value is a structure of the result.
+ If a record corresponds, the member `dptr' of the structure is the pointer to the region of
+ the value. If no record corresponds or some errors occur, `dptr' is `NULL'. `dptr' points
+ to the region related with the handle. The region is available until the next time of
+ calling this function with the same handle. */
+datum dbm_fetch(DBM *db, datum key);
+
+
+/* Get the first key of a database.
+ `db' specifies a database handle.
+ The return value is a structure of the result.
+ If a record corresponds, the member `dptr' of the structure is the pointer to the region of
+ the first key. If no record corresponds or some errors occur, `dptr' is `NULL'. `dptr'
+ points to the region related with the handle. The region is available until the next time
+ of calling this function or the function `dbm_nextkey' with the same handle. */
+datum dbm_firstkey(DBM *db);
+
+
+/* Get the next key of a database.
+ `db' specifies a database handle.
+ The return value is a structure of the result.
+ If a record corresponds, the member `dptr' of the structure is the pointer to the region of
+ the next key. If no record corresponds or some errors occur, `dptr' is `NULL'. `dptr'
+ points to the region related with the handle. The region is available until the next time
+ of calling this function or the function `dbm_firstkey' with the same handle. */
+datum dbm_nextkey(DBM *db);
+
+
+/* Check whether a database has a fatal error or not.
+ `db' specifies a database handle.
+ The return value is true if the database has a fatal error, false if not. */
+int dbm_error(DBM *db);
+
+
+/* No effect.
+ `db' specifies a database handle.
+ The return value is 0.
+ The function is only for compatibility. */
+int dbm_clearerr(DBM *db);
+
+
+/* Check whether a handle is read-only or not.
+ `db' specifies a database handle.
+ The return value is true if the handle is read-only, or false if not read-only. */
+int dbm_rdonly(DBM *db);
+
+
+/* Get the file descriptor of a directory file.
+ `db' specifies a database handle.
+ The return value is the file descriptor of the directory file. */
+int dbm_dirfno(DBM *db);
+
+
+/* Get the file descriptor of a data file.
+ `db' specifies a database handle.
+ The return value is the file descriptor of the data file. */
+int dbm_pagfno(DBM *db);
+
+
+
+#undef MYEXTERN
+
+#if defined(__cplusplus) /* export for C++ */
+}
+#endif
+
+#endif /* duplication check */
+
+
+/* END OF FILE */
diff --git a/qdbm/rlmgr.c b/qdbm/rlmgr.c
new file mode 100644
index 00000000..a44bc70a
--- /dev/null
+++ b/qdbm/rlmgr.c
@@ -0,0 +1,465 @@
+/*************************************************************************************************
+ * Utility for debugging Relic and its applications
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <relic.h>
+#include <cabin.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+char *hextoobj(const char *str, int *sp);
+int runcreate(int argc, char **argv);
+int runstore(int argc, char **argv);
+int rundelete(int argc, char **argv);
+int runfetch(int argc, char **argv);
+int runlist(int argc, char **argv);
+void pmyerror(const char *name, const char *msg);
+void printobj(const char *obj, int size);
+void printobjhex(const char *obj, int size);
+int docreate(char *name);
+int dostore(char *name, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int ins);
+int dodelete(char *name, const char *kbuf, int ksiz);
+int dofetch(char *name, const char *kbuf, int ksiz, int ox, int nb);
+int dolist(char *name, int ox);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "create")){
+ rv = runcreate(argc, argv);
+ } else if(!strcmp(argv[1], "store")){
+ rv = runstore(argc, argv);
+ } else if(!strcmp(argv[1], "delete")){
+ rv = rundelete(argc, argv);
+ } else if(!strcmp(argv[1], "fetch")){
+ rv = runfetch(argc, argv);
+ } else if(!strcmp(argv[1], "list")){
+ rv = runlist(argc, argv);
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: administration utility for Relic\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s create name\n", progname);
+ fprintf(stderr, " %s store [-kx] [-vx|-vf] [-insert] name key val\n", progname);
+ fprintf(stderr, " %s delete [-kx] name key\n", progname);
+ fprintf(stderr, " %s fetch [-kx] [-ox] [-n] name key\n", progname);
+ fprintf(stderr, " %s list [-ox] name\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* create a binary object from a hexadecimal string */
+char *hextoobj(const char *str, int *sp){
+ char *buf, mbuf[3];
+ int len, i, j;
+ len = strlen(str);
+ if(!(buf = malloc(len + 1))) return NULL;
+ j = 0;
+ for(i = 0; i < len; i += 2){
+ while(strchr(" \n\r\t\f\v", str[i])){
+ i++;
+ }
+ if((mbuf[0] = str[i]) == '\0') break;
+ if((mbuf[1] = str[i+1]) == '\0') break;
+ mbuf[2] = '\0';
+ buf[j++] = (char)strtol(mbuf, NULL, 16);
+ }
+ buf[j] = '\0';
+ *sp = j;
+ return buf;
+}
+
+
+/* parse arguments of create command */
+int runcreate(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = docreate(name);
+ return rv;
+}
+
+
+/* parse arguments of store command */
+int runstore(int argc, char **argv){
+ char *name, *key, *val, *kbuf, *vbuf;
+ int i, kx, vx, vf, ins, ksiz, vsiz, rv;
+ name = NULL;
+ kx = FALSE;
+ vx = FALSE;
+ vf = FALSE;
+ ins = FALSE;
+ key = NULL;
+ val = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-vx")){
+ vx = TRUE;
+ } else if(!strcmp(argv[i], "-vf")){
+ vf = TRUE;
+ } else if(!strcmp(argv[i], "-insert")){
+ ins = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else if(!val){
+ val = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key || !val) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = strlen(kbuf);
+ }
+ if(vx){
+ vbuf = hextoobj(val, &vsiz);
+ } else if(vf){
+ vbuf = cbreadfile(val, &vsiz);
+ } else {
+ vbuf = cbmemdup(val, -1);
+ vsiz = strlen(vbuf);
+ }
+ if(kbuf && vbuf){
+ rv = dostore(name, kbuf, ksiz, vbuf, vsiz, ins);
+ } else {
+ if(vf){
+ fprintf(stderr, "%s: %s: cannot read\n", progname, val);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ }
+ rv = 1;
+ }
+ free(kbuf);
+ free(vbuf);
+ return rv;
+}
+
+
+/* parse arguments of delete command */
+int rundelete(int argc, char **argv){
+ char *name, *key, *kbuf;
+ int i, kx, ksiz, rv;
+ name = NULL;
+ kx = FALSE;
+ key = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = strlen(kbuf);
+ }
+ if(kbuf){
+ rv = dodelete(name, kbuf, ksiz);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ rv = 1;
+ }
+ free(kbuf);
+ return rv;
+}
+
+
+/* parse arguments of fetch command */
+int runfetch(int argc, char **argv){
+ char *name, *key, *kbuf;
+ int i, kx, ox, nb, ksiz, rv;
+ name = NULL;
+ kx = FALSE;
+ ox = FALSE;
+ nb = FALSE;
+ key = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ox")){
+ ox = TRUE;
+ } else if(!strcmp(argv[i], "-n")){
+ nb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = strlen(kbuf);
+ }
+ if(kbuf){
+ rv = dofetch(name, kbuf, ksiz, ox, nb);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ rv = 1;
+ }
+ free(kbuf);
+ return rv;
+}
+
+
+/* parse arguments of list command */
+int runlist(int argc, char **argv){
+ char *name;
+ int i, ox, rv;
+ name = NULL;
+ ox = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-ox")){
+ ox = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = dolist(name, ox);
+ return rv;
+}
+
+
+/* print an error message */
+void pmyerror(const char *name, const char *msg){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, msg);
+}
+
+
+/* print an object */
+void printobj(const char *obj, int size){
+ int i;
+ for(i = 0; i < size; i++){
+ putchar(obj[i]);
+ }
+}
+
+
+/* print an object as a hexadecimal string */
+void printobjhex(const char *obj, int size){
+ int i;
+ for(i = 0; i < size; i++){
+ printf("%s%02X", i > 0 ? " " : "", ((const unsigned char *)obj)[i]);
+ }
+}
+
+
+/* perform create command */
+int docreate(char *name){
+ DBM *db;
+ if(!(db = dbm_open(name, O_RDWR | O_CREAT | O_TRUNC, 00644))){
+ pmyerror(name, "dbm_open failed");
+ return 1;
+ }
+ dbm_close(db);
+ return 0;
+}
+
+
+/* perform store command */
+int dostore(char *name, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int ins){
+ DBM *db;
+ datum key, content;
+ int rv;
+ if(!(db = dbm_open(name, O_RDWR, 00644))){
+ pmyerror(name, "dbm_open failed");
+ return 1;
+ }
+ key.dptr = (char *)kbuf;
+ key.dsize = ksiz;
+ content.dptr = (char *)vbuf;
+ content.dsize = vsiz;
+ switch(dbm_store(db, key, content, ins ? DBM_INSERT : DBM_REPLACE)){
+ case 0:
+ rv = 0;
+ break;
+ case 1:
+ pmyerror(name, "dbm_store failed by insert");
+ rv = 1;
+ break;
+ default:
+ pmyerror(name, "dbm_store failed");
+ rv = 1;
+ break;
+ }
+ dbm_close(db);
+ return rv;
+}
+
+
+/* perform delete command */
+int dodelete(char *name, const char *kbuf, int ksiz){
+ DBM *db;
+ datum key;
+ int rv;
+ if(!(db = dbm_open((char *)name, O_RDWR, 00644))){
+ pmyerror(name, "dbm_open failed");
+ return 1;
+ }
+ key.dptr = (char *)kbuf;
+ key.dsize = ksiz;
+ if(dbm_delete(db, key) == 0){
+ rv = 0;
+ } else {
+ pmyerror(name, "dbm_delete failed");
+ rv = 1;
+ }
+ dbm_close(db);
+ return rv;
+}
+
+
+/* perform fetch command */
+int dofetch(char *name, const char *kbuf, int ksiz, int ox, int nb){
+ DBM *db;
+ datum key, content;
+ int rv;
+ if(!(db = dbm_open((char *)name, O_RDONLY, 00644))){
+ pmyerror(name, "dbm_open failed");
+ return 1;
+ }
+ key.dptr = (char *)kbuf;
+ key.dsize = ksiz;
+ content = dbm_fetch(db, key);
+ if(content.dptr){
+ if(ox){
+ printobjhex(content.dptr, content.dsize);
+ } else {
+ printobj(content.dptr, content.dsize);
+ }
+ if(!nb) putchar('\n');
+ rv = 0;
+ } else {
+ pmyerror(name, "dbm_fetch failed");
+ rv = 1;
+ }
+ dbm_close(db);
+ return rv;
+}
+
+
+/* perform list command */
+int dolist(char *name, int ox){
+ DBM *db;
+ datum key, val;
+ if(!(db = dbm_open((char *)name, O_RDONLY, 00644))){
+ pmyerror(name, "dbm_open failed");
+ return 1;
+ }
+ for(key = dbm_firstkey(db); key.dptr != NULL; key = dbm_nextkey(db)){
+ val = dbm_fetch(db, key);
+ if(!val.dptr) break;
+ if(ox){
+ printobjhex(key.dptr, key.dsize);
+ putchar('\t');
+ printobjhex(val.dptr, val.dsize);
+ } else {
+ printobj(key.dptr, key.dsize);
+ putchar('\t');
+ printobj(val.dptr, val.dsize);
+ }
+ putchar('\n');
+ }
+ dbm_close(db);
+ return 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/rltest.c b/qdbm/rltest.c
new file mode 100644
index 00000000..898855fb
--- /dev/null
+++ b/qdbm/rltest.c
@@ -0,0 +1,241 @@
+/*************************************************************************************************
+ * Test cases of Relic
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <relic.h>
+#include <cabin.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define RECBUFSIZ 32 /* buffer for records */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int printfflush(const char *format, ...);
+void pmyerror(const char *name, const char *msg);
+int dowrite(char *name, int rnum);
+int doread(char *name, int rnum);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "write")){
+ rv = runwrite(argc, argv);
+ } else if(!strcmp(argv[1], "read")){
+ rv = runread(argc, argv);
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: test cases for Relic\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s write name rnum\n", progname);
+ fprintf(stderr, " %s read name rnum\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+ char *name, *rstr;
+ int i, rnum, rv;
+ name = NULL;
+ rstr = NULL;
+ rnum = 0;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else if(!rstr){
+ rstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ rv = dowrite(name, rnum);
+ return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+ char *name, *rstr;
+ int i, rnum, rv;
+ name = NULL;
+ rstr = NULL;
+ rnum = 0;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else if(!rstr){
+ rstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ rv = doread(name, rnum);
+ return rv;
+}
+
+
+/* print formatted string and flush the buffer */
+int printfflush(const char *format, ...){
+ va_list ap;
+ int rv;
+ va_start(ap, format);
+ rv = vprintf(format, ap);
+ if(fflush(stdout) == EOF) rv = -1;
+ va_end(ap);
+ return rv;
+}
+
+
+/* print an error message */
+void pmyerror(const char *name, const char *msg){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, msg);
+}
+
+
+/* perform write command */
+int dowrite(char *name, int rnum){
+ DBM *db;
+ datum key, content;
+ int i, err, len;
+ char buf[RECBUFSIZ];
+ printfflush("<Writing Test>\n name=%s rnum=%d\n\n", name, rnum);
+ /* open a database */
+ if(!(db = dbm_open(name, O_RDWR | O_CREAT | O_TRUNC, 00644))){
+ pmyerror(name, "dbm_open failed");
+ return 1;
+ }
+ err = FALSE;
+ /* loop for each record */
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", i);
+ key.dptr = buf;
+ key.dsize = len;
+ content.dptr = buf;
+ content.dsize = len;
+ /* store a record */
+ if(dbm_store(db, key, content, DBM_REPLACE) < 0){
+ pmyerror(name, "dbm_store failed");
+ err = TRUE;
+ break;
+ }
+ /* print progression */
+ if(rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ /* close the database */
+ dbm_close(db);
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(char *name, int rnum){
+ DBM *db;
+ datum key, content;
+ int i, err, len;
+ char buf[RECBUFSIZ];
+ printfflush("<Reading Test>\n name=%s rnum=%d\n\n", name, rnum);
+ /* open a database */
+ if(!(db = dbm_open(name, O_RDONLY, 00644))){
+ pmyerror(name, "dbm_open failed");
+ return 1;
+ }
+ err = FALSE;
+ /* loop for each record */
+ for(i = 1; i <= rnum; i++){
+ /* retrieve a record */
+ len = sprintf(buf, "%08d", i);
+ key.dptr = buf;
+ key.dsize = len;
+ content = dbm_fetch(db, key);
+ if(!content.dptr){
+ pmyerror(name, "dbm_fetch failed");
+ err = TRUE;
+ break;
+ }
+ /* print progression */
+ if(rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ /* close the database */
+ dbm_close(db);
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/spex-ja.html b/qdbm/spex-ja.html
new file mode 100644
index 00000000..140167bf
--- /dev/null
+++ b/qdbm/spex-ja.html
@@ -0,0 +1,4348 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta http-equiv="Content-Style-Type" content="text/css" />
+<meta name="author" content="Mikio Hirabayashi" />
+<meta name="keywords" content="QDBM, DBM, database, hash, B+ tree" />
+<meta name="description" content="fundamental specifications of QDBM" />
+<link rel="contents" href="./" />
+<link rel="alternate" href="spex.html" hreflang="en" title="the English version" />
+<link rev="made" href="mailto:mikio@users.sourceforge.net" />
+<title>Specifications of QDBM Version 1 (Japanese)</title>
+<style type="text/css">html { margin: 0em 0em; padding: 0em 0em; background: #eeeeee none; }
+body { margin: 2em 2em; padding: 0em 0em;
+ background: #eeeeee none; color: #111111;
+ font-style: normal; font-weight: normal; }
+h1 { margin-top: 1.8em; margin-bottom: 1.3em; font-weight: bold; }
+h2 { margin-top: 1.8em; margin-bottom: 1.1em; font-weight: bold;
+ border-left: solid 0.6em #445555; border-bottom: solid 1pt #bbbbbb;
+ padding: 0.5em 0.5em; width: 60%; }
+h3 { margin-top: 1.8em; margin-bottom: 0.8em; font-weight: bold; }
+hr { margin-top: 2.5em; margin-bottom: 1.5em; height: 1pt;
+ color: #999999; background-color: #999999; border: none; }
+div.note,div.navi { text-align: right; }
+div.logo { text-align: center; margin: 3em 0em; }
+div.logo img { border: inset 2pt #ccccdd; }
+p { margin: 0.8em 0em; line-height: 140%; }
+p,dd { text-indent: 0.8em; }
+div,pre { margin-left: 1.7em; margin-right: 1.7em; }
+pre { background-color: #ddddee; padding: 0.2em; border: 1pt solid #bbbbcc; font-size: smaller; }
+kbd { color: #111111; font-style: normal; font-weight: bold; }
+a { color: #0022aa; text-decoration: none; }
+a:hover,a:focus { color: #0033ee; text-decoration: underline; }
+a.head { color: #111111; text-decoration: none; }
+table { padding: 1pt 2pt 1pt 2pt; border: none; margin-left: 1.7em; border-collapse: collapse; }
+th { padding: 1pt 4pt 1pt 4pt; border-style: none;
+ text-align: left; vertical-align: bottom; }
+td { padding: 1pt 4pt 1pt 4pt; border: 1pt solid #333333;
+ text-align: left; vertical-align: top; }
+ul,ol,dl { line-height: 140%; }
+dt { margin-left: 1.2em; }
+dd { margin-left: 2.0em; }
+ul.lines { list-style-type: none; }
+@media print {
+ html,body { margin: 0em 0em; background-color: #ffffff; color: #000000; }
+ h1 { padding: 8em 0em 0.5em 0em; text-align: center; }
+ h2 { page-break-before: always; }
+ div.note { text-align: center; }
+ div.navi,div.logo { display: none }
+ hr { display: none; }
+ pre { margin: 0.8em 0.8em; background-color: #ffffff;
+ border: 1pt solid #aaaaaa; font-size: smaller; }
+ a,kbd { color: #000000; text-decoration: none; }
+ h1,h2,h3 { font-family: sans-serif; }
+ p,div,li,dt,dd { font-family: serif; }
+ pre,kbd { font-family: monospace; }
+ dd { font-size: smaller; }
+}
+</style>
+</head>
+
+<body>
+
+<h1>QDBMバージョン1基本仕様書</h1>
+
+<div class="note">Copyright (C) 2000-2007 Mikio Hirabayashi</div>
+<div class="note">Last Update: Thu, 26 Oct 2006 15:00:20 +0900</div>
+<div class="navi">[<a href="spex.html" hreflang="en">English</a>] [<a href="http://qdbm.sourceforge.net/">Home</a>]</div>
+
+<hr />
+
+<h2>目次</h2>
+
+<ol>
+<li><a href="#overview">概要</a></li>
+<li><a href="#features">特徴</a></li>
+<li><a href="#installation">インストール</a></li>
+<li><a href="#depotapi">Depot: 基本API</a></li>
+<li><a href="#depotcli">Depot用コマンド</a></li>
+<li><a href="#curiaapi">Curia: 拡張API</a></li>
+<li><a href="#curiacli">Curia用コマンド</a></li>
+<li><a href="#relicapi">Relic: NDBM互換API</a></li>
+<li><a href="#reliccli">Relic用コマンド</a></li>
+<li><a href="#hovelapi">Hovel: GDBM互換API</a></li>
+<li><a href="#hovelcli">Hovel用コマンド</a></li>
+<li><a href="#cabinapi">Cabin: ユーティリティAPI</a></li>
+<li><a href="#cabincli">Cabin用コマンド</a></li>
+<li><a href="#villaapi">Villa: 上級API</a></li>
+<li><a href="#villacli">Villa用コマンド</a></li>
+<li><a href="#odeumapi">Odeum: 転置API</a></li>
+<li><a href="#odeumcli">Odeum用コマンド</a></li>
+<li><a href="#fileformat">ファイルフォーマット</a></li>
+<li><a href="#porting">移植方法</a></li>
+<li><a href="#bugs">バグ</a></li>
+<li><a href="#faq">よく聞かれる質問</a></li>
+<li><a href="#copying">ライセンス</a></li>
+</ol>
+
+<hr />
+
+<h2><a name="overview" id="overview" class="head">概要</a></h2>
+
+<p>QDBMはデータベースを扱うルーチン群のライブラリである。データベースといっても単純なものであり、キーと値のペアからなるレコード群を格納したデータファイルである。キーと値は任意の長さを持つ一連のバイト列であり、文字列でもバイナリでも扱うことができる。テーブルやデータ型の概念はない。レコードはハッシュ表またはB+木で編成される。</p>
+
+<p>ハッシュ表のデータベースでは、キーはデータベース内で一意であり、キーが重複する複数のレコードを格納することはできない。このデータベースに対しては、キーと値を指定してレコードを格納したり、キーを指定して対応するレコードを削除したり、キーを指定して対応するレコードを検索したりすることができる。また、データベースに格納してある全てのキーを順不同に一つずつ取り出すこともできる。このような操作は、UNIX標準で定義されているDBMライブラリおよびその追従であるNDBMやGDBMに類するものである。QDBMはDBMのより良い代替として利用することができる。</p>
+
+<p>B+木のデータベースでは、キーが重複する複数のレコードを格納することができる。このデータベースに対しては、ハッシュ表のデータベースと同様に、キーを指定してレコードを格納したり取り出したり削除したりすることができる。レコードはユーザが指示した比較関数に基づいて整列されて格納される。カーソルを用いて各レコードを昇順または降順で参照することができる。この機構によって、文字列の前方一致検索や数値の範囲検索が可能になる。また、B+木のデータベースではトランザクションが利用できる。</p>
+
+<p>QDBMはCで記述され、C、C++、Java、PerlおよびRubyのAPIとして提供される。QDBMはPOSIX準拠のAPIを備えるプラットフォームで利用できる。QDBMはGNU Lesser General Public Licenseに基づくフリーソフトウェアである。</p>
+
+<hr />
+
+<h2><a name="features" id="features" class="head">特徴</a></h2>
+
+<h3>効率的なハッシュデータベースの実装</h3>
+
+<p>QDBMはGDBMを参考に次の三点を目標として開発された。処理がより高速であること、データベースファイルがより小さいこと、APIがより単純であること。これらの目標は達成されている。また、GDBMと同様に、伝統的なDBMが抱える三つの制限事項を回避している。すなわち、プロセス内で複数のデータベースを扱うことができ、キーと値のサイズに制限がなく、データベースファイルがスパースではない。</p>
+
+<p>QDBMはレコードの探索にハッシュアルゴリズムを用いる。バケット配列に十分な要素数があれば、レコードの探索にかかる時間計算量は O(1) である。すなわち、レコードの探索に必要な時間はデータベースの規模に関わらず一定である。追加や削除に関しても同様である。ハッシュ値の衝突はセパレートチェーン法で管理する。チェーンのデータ構造は二分探索木である。バケット配列の要素数が著しく少ない場合でも、探索等の時間計算量は O(log n) に抑えられる。</p>
+
+<p>QDBMはバケット配列を全てRAM上に保持することによって、処理の高速化を図る。バケット配列がRAM上にあれば、ほぼ1パスのファイル操作でレコードに該当するファイル上の領域を参照することができる。ファイルに記録されたバケット配列は `read' コールでRAM上に読み込むのではなく、`mmap' コールでRAMに直接マッピングされる。したがって、データベースに接続する際の準備時間が極めて短く、また、複数のプロセスでメモリマップを共有することができる。</p>
+
+<p>バケット配列の要素数が格納するレコード数の半分ほどであれば、データの性質によって多少前後するが、ハッシュ値の衝突率は56.7%ほどである(等倍だと36.8%、2倍だと21.3%、4倍だと11.5%、8倍だと6.0%ほど)。そのような場合、平均2パス以下のファイル操作でレコードを探索することができる。これを性能指標とするならば、例えば100万個のレコードを格納するためには50万要素のバケット配列が求められる。バケット配列の各要素は4バイトである。すなわち、2MバイトのRAMが利用できれば100万レコードのデータベースが構築できる。</p>
+
+<p>QDBMにはデータベースに接続するモードとして、「リーダ」と「ライタ」の二種類がある。リーダは読み込み専用であり、ライタは読み書き両用である。データベースにはファイルロックによってプロセス間での排他制御が行われる。ライタが接続している間は、他のプロセスはリーダとしてもライタとしても接続できない。リーダが接続している間は、他のプロセスのリーダは接続できるが、ライタは接続できない。この機構によって、マルチタスク環境での同時接続に伴うデータの整合性が保証される。</p>
+
+<p>伝統的なDBMにはレコードの追加操作に関して「挿入」モードと「置換」モードがある。前者では、キーが既存のレコードと重複する際に既存の値を残す。後者では、キーが既存のレコードと重複した際に新しい値に置き換える。QDBMはその2つに加えて「連結」モードがある。既存の値の末尾に指定された値を連結して格納する操作である。レコードの値を配列として扱う場合、要素を追加するには連結モードが役に立つ。また、DBMではレコードの値を取り出す際にはその全ての領域を処理対象にするしか方法がないが、QDBMでは値の領域の一部のみを選択して取り出すことができる。レコードの値を配列として扱う場合にはこの機能も役に立つ。</p>
+
+<p>一般的に、データベースの更新処理を続けるとファイル内の利用可能領域の断片化が起き、ファイルのサイズが肥大化してしまう。QDBMは隣接する不要領域を連結して再利用し、またデータベースの最適化機能を備えることによってこの問題に対処する。既存のレコードの値をより大きなサイズの値に上書きする場合、そのレコードの領域をファイル中の別の位置に移動させる必要がある。この処理の時間計算量はレコードのサイズに依存するので、値を拡張していく場合には効率が悪い。しかし、QDBMはアラインメントによってこの問題に対処する。増分がパディングに収まれば領域を移動させる必要はない。</p>
+
+<p>多くのファイルシステムでは、2GBを越えるサイズのファイルを扱うことができない。この問題に対処するために、QDBMは複数のデータベースファイルを含むディレクトリからなるデータベースを扱う機能を提供する。レコードをどのファイルに格納するかはキーに別のハッシュ関数を適用することによって決められる。この機能によって、理論的にはレコードの合計サイズが1TBまでのデータベースを構築することができる。また、データベースファイルを複数のディスクに振り分けることができるため、RAID-0(ストライピング)に見られるような更新操作の高速化が期待できる。NFS等を利用すれば複数のファイルサーバにデータベースを分散させることもできる。</p>
+
+<h3>便利なB+木データベースの実装</h3>
+
+<p>B+木データベースはハッシュデータベースより遅いが、ユーザが定義した順序に基づいて各レコードを参照できることが特長である。B+木は複数のレコードを整列させた状態で論理的なページにまとめて管理する。各ページに対してはB木すなわち多進平衡木によって階層化された疎インデックスが維持される。したがって、各レコードの探索等にかかる時間計算量は O(log n) である。各レコードを順番に参照するためにカーソルが提供される。カーソルの場所はキーを指定して飛ばすことができ、また現在の場所から次のレコードに進めたり前のレコードに戻したりすることができる。各ページは双方向リンクリストで編成されるので、カーソルを前後に移動させる時間計算量は O(1) である。</p>
+
+<p>B+木データベースは上述のハッシュデータベースを基盤として実装される。B+木の各ページはハッシュデータベースのレコードとして記録されるので、ハッシュデータベースの記憶管理の効率性を継承している。B+木では各レコードのヘッダが小さく、各ページのアラインメントはページサイズに応じて調整されるので、ほとんどの場合、ハッシュデータベースに較べてデータベースファイルのサイズが半減する。B+木を更新する際には多くのページを操作する必要があるが、QDBMはページをキャッシュすることによってファイル操作を減らして処理を効率化する。ほとんどの場合、疎インデックス全体がメモリ上にキャッシュされるので、各レコードを参照するのに必要なファイル操作は平均1パス以下である。</p>
+
+<p>B+木データベースはトランザクション機構を提供する。トランザクションを開始してから終了するまでの一連の操作を一括してデータベースにコミットしたり、一連の更新操作を破棄してデータベースの状態をトランザクションの開始前の状態にロールバックしたりすることができる。トランザクションの間にアプリケーションのプロセスがクラッシュしてもデータベースファイルは破壊されない。</p>
+
+<p>可逆データ圧縮ライブラリであるZLIBかLZOかBZIP2を有効化してQDBMをビルドすると、B+木の各ページの内容は圧縮されてファイルに書き込まれる。同一ページ内の各レコードは似たようなパターンを持つため、Lempel-Zivなどのアルゴリズムを適用すると高い圧縮効率が期待できる。テキストデータを扱う場合、データベースのサイズが元の25%程度になる。データベースの規模が大きくディスクI/Oがボトルネックとなる場合は、圧縮機能を有効化すると処理速度が大幅に改善される。</p>
+
+<h3>単純だが多様なインタフェース群</h3>
+
+<p>QDBMのAPIは非常に単純である。ANSI Cで定義された `FILE' ポインタを用いた通常のファイル入出力と同じようにデータベースファイルに対する入出力を行うことができる。QDBMの基本APIでは、データベースの実体は単一のファイルに記録される。拡張APIでは、データベースの実体は単一のディレクトリに含まれる複数のファイルに記録される。二つのAPIは互いに酷似しているので、アプリケーションを一方から他方に移植することはたやすい。</p>
+
+<p>NDBMおよびGDBMに互換するAPIも提供される。NDBMやGDBMのアプリケーションは市場に数多く存在するが、それらをQDBMに移植するのはたやすい。ほとんどの場合、ヘッダファイルの取り込み(#include)を書き換えてコンパイルしなおせばよい。ただし、オリジナルのNDBMやGDBMで作成したデータベースファイルをQDBMで扱うことはできない。</p>
+
+<p>メモリ上でレコードを簡単に扱うために、ユーティリティAPIが提供される。メモリ確保関数とソート関数と拡張可能なデータと配列リストとハッシュマップ等の実装である。それらを用いると、C言語でもPerlやRuby等のスクリプト言語のような手軽さでレコードを扱うことができる。</p>
+
+<p>B+木データベースは上級APIを介して利用する。上級APIは基本APIとユーティリティAPIを利用して実装される。上級APIも基本APIや拡張APIに類似した書式を持つので、使い方を覚えるのは容易である。</p>
+
+<p>全文検索システムで利用される転置インデックスを扱うために、転置APIが提供される。文書群の転置インデックスを容易に扱うことができれば、アプリケーションはテキスト処理や自然言語処理に注力できる。このAPIは文字コードや言語に依存しないので、ユーザの多様な要求に応える全文検索システムを実装することが可能となる。</p>
+
+<p>QDBMはC言語の他にも、C++、Java、PerlおよびRubyのAPIを提供する。C言語のAPIには、基本API、拡張API、NDBM互換API、GDBM互換API、ユーティリティAPI、上級APIおよび転置APIの七種類がある。各APIに対応したコマンドラインインタフェースも用意されている。それらはプロトタイピングやテストやデバッグなどで活躍する。C++用APIは基本APIと拡張APIと上級APIのデータベース操作関数群をC++のクラス機構でカプセル化したものである。Java用APIはJava Native Interfaceを用いて基本APIと拡張APIと上級APIを呼び出すものである。Perl用APIはXS言語を用いて基本APIと拡張APIと上級APIを呼び出すものである。Ruby用APIはRubyのモジュールとして基本APIと拡張APIと上級APIを呼び出すものである。データベースの管理とファイルアップロードと全文検索のためのCGIスクリプトも提供される。</p>
+
+<h3>幅広い移植性</h3>
+
+<p>QDBMはANSI C(C89)の記法に従い、ANSI CまたはPOSIXで定義されたAPIのみを用いて実装される。したがって、ほとんどのUNIXおよびその互換をうたうOSで動作させることができる。C言語のAPIに関しては、少なくとも以下のプラットフォームで動作確認されている。</p>
+
+<ul>
+<li>Linux (2.2, 2.4, 2.6) (IA32, IA64, AMD64, PA-RISC, Alpha, PowerPC, M68000, ARM)</li>
+<li>FreeBSD (4.9, 5.0, 5.1, 5.2, 5.3) (IA32, IA64, SPARC, Alpha)</li>
+<li>NetBSD (1.6) (IA32)</li>
+<li>OpenBSD (3.4) (IA32)</li>
+<li>SunOS (5.6, 5.7, 5.8, 5.9, 5.10) (IA32, SPARC)</li>
+<li>HP-UX (11.11, 11.23) (IA64, PA-RISC)</li>
+<li>AIX (5.2) (POWER)</li>
+<li>Windows (2000, XP) (IA32, IA64, AMD64) (Cygwin, MinGW, Visual C++)</li>
+<li>Mac OS X (10.2, 10.3, 10.4) (IA32, PowerPC)</li>
+<li>Tru64 (5.1) (Alpha)</li>
+<li>RISC OS (5.03) (ARM)</li>
+</ul>
+
+<p>QDBMが作成したデータベースファイルは処理系のバイトオーダに依存するが、その対策として、バイトオーダに依存しない形式のデータをダンプするユーティリティが提供される。</p>
+
+<hr />
+
+<h2><a name="installation" id="installation" class="head">インストール</a></h2>
+
+<h3>準備</h3>
+
+<p>ソースパッケージを用いてQDBMをインストールするには、GCCのバージョン2.8以降と `make' が必要である。</p>
+
+<p>QDBMの配布用アーカイブファイルを展開したら、生成されたディレクトリに入ってインストール作業を行う。</p>
+
+<h3>普通の手順</h3>
+
+<p>LinuxとBSDとSunOSでは以下の手順に従う。</p>
+
+<p>ビルド環境を設定する。</p>
+
+<pre>./configure
+</pre>
+
+<p>プログラムをビルドする。</p>
+
+<pre>make
+</pre>
+
+<p>プログラムの自己診断テストを行う。</p>
+
+<pre>make check
+</pre>
+
+<p>プログラムをインストールする。作業は `root' ユーザで行う。</p>
+
+<pre>make install
+</pre>
+
+<h3>GNU Libtoolを使う場合</h3>
+
+<p>上記の方法でうまくいかない場合、以下の手順に従う。この手順には、GNU Libtoolのバージョン1.5以降が必要である。</p>
+
+<p>ビルド環境を設定する。</p>
+
+<pre>./configure
+</pre>
+
+<p>プログラムをビルドする。</p>
+
+<pre>make -f LTmakefile
+</pre>
+
+<p>プログラムの自己診断テストを行う。</p>
+
+<pre>make -f LTmakefile check
+</pre>
+
+<p>プログラムをインストールする。作業は `root' ユーザで行う。</p>
+
+<pre>make -f LTmakefile install
+</pre>
+
+<h3>結果</h3>
+
+<p>一連の作業が終ると、以下のファイルがインストールされる。その他にも、マニュアルが `/usr/local/man/man1' と `/usr/local/man/man3' に、それ以外の文書が `/usr/local/share/qdbm' に、`pkg-config' 用の設定ファイルが `/usr/local/lib/pkgconfig' にインストールされる。</p>
+
+<pre>/usr/local/include/depot.h
+/usr/local/include/curia.h
+/usr/local/include/relic.h
+/usr/local/include/hovel.h
+/usr/local/include/cabin.h
+/usr/local/include/villa.h
+/usr/local/include/vista.h
+/usr/local/include/odeum.h
+/usr/local/lib/libqdbm.a
+/usr/local/lib/libqdbm.so.14.13.0
+/usr/local/lib/libqdbm.so.14
+/usr/local/lib/libqdbm.so
+/usr/local/bin/dpmgr
+/usr/local/bin/dptest
+/usr/local/bin/dptsv
+/usr/local/bin/crmgr
+/usr/local/bin/crtest
+/usr/local/bin/crtsv
+/usr/local/bin/rlmgr
+/usr/local/bin/rltest
+/usr/local/bin/hvmgr
+/usr/local/bin/hvtest
+/usr/local/bin/cbtest
+/usr/local/bin/cbcodec
+/usr/local/bin/vlmgr
+/usr/local/bin/vltest
+/usr/local/bin/vltsv
+/usr/local/bin/odmgr
+/usr/local/bin/odtest
+/usr/local/bin/odidx
+/usr/local/bin/qmttest
+</pre>
+
+<p>`libqdbm.so' と動的にリンクしたプログラムを実行する際には、ライブラリの検索パスに `/usr/local/lib' を含めるべきである。環境変数 `LD_LIBRARY_PATH' でライブラリの検索パスを設定することができる。</p>
+
+<p>QDBMをアンインストールするには、`./configure' をした後の状態で以下のコマンドを実行する。作業は `root' ユーザで行う。</p>
+
+<pre>make uninstall
+</pre>
+
+<p>QDBMの古いバージョンがインストールされている場合、それをアンインストールしてからインストール作業を行うべきである。</p>
+
+<p>C言語以外のAPIとCGIスクリプトはデフォルトではインストールされない。C++用APIのインストール方法については、サブディレクトリ `plus' にある `xspex-ja.html' を参照すること。JAVA用APIのインストール方法については、サブディレクトリ `java' にある `jspex-ja.html' を参照すること。Perl用APIのインストール方法については、サブディレクトリ `perl' にある `plspex-ja.html' を参照すること。Ruby用APIのインストール方法については、サブディレクトリ `ruby' にある `rbspex-ja.html' を参照すること。CGIスクリプトのインストール方法については、サブディレクトリ `cgi' にある `cgispex.html' を参照すること。</p>
+
+<p>RPM等のバイナリパッケージを用いてインストールを行う場合は、それぞれのパッケージマネージャのマニュアルを参照すること。例えば、RPMを用いる場合、以下のようなコマンドを `root' ユーザで実行する。</p>
+
+<pre>rpm -ivh qdbm-1.x.x-x.i386.rpm
+</pre>
+
+<h3>Windowsの場合</h3>
+
+<p>Windows(Cygwin)にインストールする場合、以下の手順に従う。</p>
+
+<p>ビルド環境を設定する。</p>
+
+<pre>./configure
+</pre>
+
+<p>プログラムをビルドする。</p>
+
+<pre>make win
+</pre>
+
+<p>プログラムの自己診断テストを行う。</p>
+
+<pre>make check-win
+</pre>
+
+<p>プログラムをインストールする。なお、アンインストールする場合は `make uninstall-win' とする。</p>
+
+<pre>make install-win
+</pre>
+
+<p>Windowsでは、静的ライブラリ `libqdbm.a' に加えてインポートライブラリ `libqdbm.dll.a' が生成され、動的ライブラリ `libqdbm.so' 等の代わりにダイナミックリンクライブラリ `qdbm.dll' が生成される。`qdbm.dll' は `/usr/local/bin' にインストールされる。</p>
+
+<p>Cygwin環境でMinGWを用いてビルドするには、`make win' の代わりに `make mingw' を用いる。CygwinのUNIXエミュレーション層を用いる場合、生成されるプログラムは `cygwin1.dll' に依存したものになる(GNU GPLの影響を受ける)。MinGWによってWin32のネイティブDLLとリンクさせればこの問題を回避できる。</p>
+
+<p>Visual C++を用いてビルドするには、`VCmakefile' を編集してヘッダとライブラリの検索パスを設定した上で、`nmake /f VCMakefile' とすればよい。生成された `qdbm.dll' とリンクするアプリケーションは、コンパイラの `/MD' または `/MDd' オプションを用いて `msvcrt.dll' とリンクさせる必要がある。詳細設定に関しては `VCmakefile' を参照のこと。</p>
+
+<h3>Mac OS Xの場合</h3>
+
+<p>Mac OS X(Darwin)にインストールする場合、以下の手順に従う。</p>
+
+<p>ビルド環境を設定する。</p>
+
+<pre>./configure
+</pre>
+
+<p>プログラムをビルドする。</p>
+
+<pre>make mac
+</pre>
+
+<p>プログラムの自己診断テストを行う。</p>
+
+<pre>make check-mac
+</pre>
+
+<p>プログラムをインストールする。なお、アンインストールする場合は `make uninstall-mac' とする。</p>
+
+<pre>make install-mac
+</pre>
+
+<p>Mac OS Xでは、`libqdbm.so' 等の代わりに `libqdbm.dylib' 等が生成される。ライブラリの検索パスの指定は環境変数 `DYLD_LIBRARY_PATH' で行うことができる。</p>
+
+<h3>HP-UXの場合</h3>
+
+<p>HP-UXにインストールする場合、以下の手順に従う。</p>
+
+<p>ビルド環境を設定する。</p>
+
+<pre>./configure
+</pre>
+
+<p>プログラムをビルドする。</p>
+
+<pre>make hpux
+</pre>
+
+<p>プログラムの自己診断テストを行う。</p>
+
+<pre>make check-hpux
+</pre>
+
+<p>プログラムをインストールする。なお、アンインストールする場合は `make uninstall-hpux' とする。</p>
+
+<pre>make install-hpux
+</pre>
+
+<p>HP-UXでは、`libqdbm.so' 等の代わりに `libqdbm.sl' が生成される。ライブラリの検索パスの指定は環境変数 `SHLIB_PATH' で行うことができる。</p>
+
+<h3>RISC OSの場合</h3>
+
+<p>RISC OSにインストールする場合、以下の手順に従う。</p>
+
+<p>プログラムをビルドする。デフォルトではコンパイラに `cc' を用いるようになっているが、`gcc' を用いたければ `CC=gcc' という引数を加えればよい。</p>
+
+<pre>make -f RISCmakefile
+</pre>
+
+<p>一連の作業が終ると、`libqdbm' というライブラリファイルと `dpmgr' 等のコマンドが生成される。それらのインストール方法は定義されていないので、手動で任意の場所にコピーしてインストールすること。`depot.h' 等のヘッダファイルも同様に手動でインストールすること。</p>
+
+<h3>詳細設定</h3>
+
+<p>`./configure' を実行する際に以下のオプション引数を指定することで、ビルド方法の詳細な設定を行うことができる。</p>
+
+<ul class="lines">
+<li><kbd>--enable-debug</kbd> : デバッグ用にビルドする。デバッグシンボルを有効化し、最適化を行わず、静的にリンクする。</li>
+<li><kbd>--enable-devel</kbd> : 開発用にビルドする。デバッグシンボルを有効化し、最適化を行い、動的にリンクする。</li>
+<li><kbd>--enable-stable</kbd> : 安定版のリリース用にビルドする。保守的な最適化を行い、動的にリンクする。</li>
+<li><kbd>--enable-pthread</kbd> : POSIXスレッドを用い、グローバル変数をスレッド固有データとして扱う。</li>
+<li><kbd>--disable-lock</kbd> : ファイルロッキングが実装されていない環境用にビルドする。</li>
+<li><kbd>--disable-mmap</kbd> : メモリマッピングが実装されていない環境用にビルドする。</li>
+<li><kbd>--enable-zlib</kbd> : ZLIBによるB+木と転置インデックスのレコード圧縮を機能させる。</li>
+<li><kbd>--enable-lzo</kbd> : LZOによるB+木と転置インデックスのレコード圧縮を機能させる。</li>
+<li><kbd>--enable-bzip</kbd> : BZIP2によるB+木と転置インデックスのレコード圧縮を機能させる。</li>
+<li><kbd>--enable-iconv</kbd> : ICONVによる文字コード変換ユーティリティを機能させる。</li>
+</ul>
+
+<p>通常、QDBMとそのアプリケーションは `libqdbm.*' 以外の非標準のライブラリには依存しないでビルドすることができる。ただし、POSIXスレッドを有効にした場合は `libpthread.*' に依存し、ZLIBを有効にした場合は `libz.*' に依存し、LZOを有効にした場合は `liblzo2.*' に依存し、BZIP2を有効にした場合は `libbz2.*' に依存し、ICONVを有効にした場合は `libiconv.*' に依存するようになる。</p>
+
+<p>LZOのライセンスはGNU GPLなので、`liblzo2.*' とリンクしたアプリケーションはGNU GPLの制約を受けることに注意すること。</p>
+
+<hr />
+
+<h2><a name="depotapi" id="depotapi" class="head">Depot: 基本API</a></h2>
+
+<h3>概要</h3>
+
+<p>DepotはQDBMの基本APIである。QDBMが提供するデータベース管理機能のほぼ全てがDepotによって実装される。その他のAPIはDepotのラッパーにすぎない。したがって、QDBMのAPIの中でDepotが最も高速に動作する。</p>
+
+<p>Depotを使うためには、`depot.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。</p>
+
+<dl>
+<dt><kbd>#include &lt;depot.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+</dl>
+
+<p>Depotでデータベースを扱う際には、`DEPOT' 型へのポインタをハンドルとして用いる。これは、`stdio.h' の各種ルーチンがファイル入出力に `FILE' 型へのポインタを用いるのに似ている。ハンドルは、関数 `dpopen' で開き、関数 `dpclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。データベースに致命的なエラーが起きた場合は、以後そのハンドルに対する `dpclose' を除く全ての操作は何もせずにエラーを返す。ひとつのプロセスで複数のデータベースファイルを同時に利用することは可能であるが、同じデータベースファイルの複数のハンドルを利用してはならない。</p>
+
+<h3>API</h3>
+
+<p>外部変数 `dpversion' はバージョン情報の文字列である。</p>
+
+<dl>
+<dt><kbd>extern const char *dpversion;</kbd></dt>
+<dd>この変数の指す領域は書き込み禁止である。</dd>
+</dl>
+
+<p>外部変数 `dpecode' には直前のエラーコードが記録される。エラーコードの詳細については `depot.h' を参照すること。</p>
+
+<dl>
+<dt><kbd>extern int dpecode;</kbd></dt>
+<dd>この変数の初期値は `DP_ENOERR' である。その他の値として、`DP_EFATAL'、`DP_EMODE'、`DP_EBROKEN'、`DP_EKEEP'、`DP_ENOITEM'、`DP_EALLOC'、`DP_EMAP'、`DP_EOPEN'、`DP_ECLOSE'、`DP_ETRUNC'、`DP_ESYNC'、`DP_ESTAT'、`DP_ESEEK'、`DP_EREAD'、`DP_EWRITE'、`DP_ELOCK'、`DP_EUNLINK'、`DP_EMKDIR'、`DP_ERMDIR' および `DP_EMISC' がある。</dd>
+</dl>
+
+<p>エラーコードに対応するメッセージ文字列を得るには、関数 `dperrmsg' を用いる。</p>
+
+<dl>
+<dt><kbd>const char *dperrmsg(int <var>ecode</var>);</kbd></dt>
+<dd>`ecode' はエラーコードを指定する。戻り値はエラーメッセージの文字列であり、その領域は書き込み禁止である。</dd>
+</dl>
+
+<p>データベースのハンドルを作成するには、関数 `dpopen' を用いる。</p>
+
+<dl>
+<dt><kbd>DEPOT *dpopen(const char *<var>name</var>, int <var>omode</var>, int <var>bnum</var>);</kbd></dt>
+<dd>`name' はデータベースファイルの名前を指定する。`omode' は接続モードを指定し、`DP_OREADER' ならリーダ、`DP_OWRITER' ならライタとなる。`DP_OWRITER' の場合、`DP_OCREAT' または `DP_OTRUNC' とのビット論理和にすることができる。`DP_OCREAT' はファイルが無い場合に新規作成することを指示し、`DP_OTRUNC' はファイルが存在しても作り直すことを指示する。`DP_OREADER' と `DP_OWRITER' の両方で `DP_ONOLCK' または `DP_OLCKNB' とのビット論理和にすることができるが、前者はファイルロックをかけずにデータベースを開くことを指示し、後者はブロックせずにロックをかけることを指示する。`DP_OCREAT' は `DP_OSPARSE' とのビット論理和にすることができるが、それは生成されるファイルをスパースにすることを指示する。`bnum' はバケット配列の要素数の目安を指定するが、0 以下ならデフォルト値が使われる。バケット配列の要素数はデータベースを作成する時に決められ、最適化以外の手段で変更することはできない。バケット配列の要素数は、格納するレコード数の半分から4倍程度にするのがよい。戻り値はデータベースハンドルであるか、エラーなら `NULL' である。ライタ(読み書き両用モード)でデータベースファイルを開く際にはそのファイルに対して排他ロックがかけられ、リーダ(読み込み専用モード)で開く際には共有ロックがかけられる。その際には該当のロックがかけられるまで制御がブロックする。`DP_ONOLCK' を使う場合、アプリケーションが排他制御の責任を負う。</dd>
+</dl>
+
+<p>データベースとの接続を閉じてハンドルを破棄するには、関数 `dpclose' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpclose(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースの更新内容は、接続を閉じた時点で初めてファイルと同期される。ライタでデータベースを開いた場合、適切に接続を閉じないとデータベースが破壊される。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。</dd>
+</dl>
+
+<p>レコードを追加するには、関数 `dpput' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpput(DEPOT *<var>depot</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const char *<var>vbuf</var>, int <var>vsiz</var>, int <var>dmode</var>);</kbd></dt>
+<dd>`depot' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`dmode' はキーが既存レコードと重複した際の制御を指定する。`DP_DOVER' は既存のレコードの値を上書きし、`DP_DKEEP' は既存のレコードを残してエラーを返し、`DP_DCAT' は指定された値を既存の値の末尾に加える。戻り値は正常なら真であり、エラーなら偽である。</dd>
+</dl>
+
+<p>レコードを削除するには、関数 `dpout' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpout(DEPOT *<var>depot</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`depot' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。</dd>
+</dl>
+
+<p>レコードを取得するには、関数 `dpget' を用いる。</p>
+
+<dl>
+<dt><kbd>char *dpget(DEPOT *<var>depot</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>start</var>, int <var>max</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>レコードを取得してバッファに書き込むには、関数 `dpgetwb' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpgetwb(DEPOT *<var>depot</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>start</var>, int <var>max</var>, char *<var>vbuf</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定する。それは書き込み用のバッファのサイズ以下である必要がある。`vbuf' は抽出したデータを書き込むバッファへのポインタを指定する。戻り値は正常ならバッファに書き込まれたデータのサイズであり、エラーなら -1 である。該当のレコードがない場合も -1 を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。書き込み用バッファの末尾に終端文字が追加されないことに注意すべきである。</dd>
+</dl>
+
+<p>レコードの値のサイズを取得するには、関数 `dpvsiz' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpvsiz(DEPOT *<var>depot</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値のサイズであるが、該当がない場合やエラーの場合は -1 である。この関数はレコードの有無を調べるのにも便利である。`dpget' と違って実データを読み込まないので効率がよい。</dd>
+</dl>
+
+<p>データベースのイテレータを初期化するには、関数 `dpiterinit' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpiterinit(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。イテレータは、データベースに格納された全てのレコードを参照するために用いられる。</dd>
+</dl>
+
+<p>データベースのイテレータから次のレコードのキーを取り出すには、関数 `dpiternext' を用いる。</p>
+
+<dl>
+<dt><kbd>char *dpiternext(DEPOT *<var>depot</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常ならキーを格納した領域へのポインタであり、エラーなら `NULL' である。イテレータが最後まできて該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数を繰り返して呼ぶことによって全てのレコードを一度ずつ参照することができる。ただし、繰り返しの間にデータベースの更新があった場合はその限りではない。なお、取り出すレコードの順序は制御できず、格納した順番でレコードを取り出せるとは限らない。</dd>
+</dl>
+
+<p>データベースのアラインメントを設定するには、関数 `dpsetalign' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpsetalign(DEPOT *<var>depot</var>, int <var>align</var>);</kbd></dt>
+<dd>`depot' はライタで接続したデータベースハンドルを指定する。`align' はアラインメントのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。アラインメントを設定しておくと、レコードの上書きを頻繁にする場合の処理効率が良くなる。アラインメントには、一連の更新操作をした後の状態での標準的な値のサイズを指定するのがよい。アラインメントが正数の場合、レコードの領域のサイズがアラインメントの倍数になるようにパディングがとられる。アラインメントが負数の場合、`vsiz' を値のサイズとして、パディングのサイズは `(vsiz / pow(2, abs(align) - 1))' として算出される。アラインメントの設定はデータベースに保存されないので、データベースを開く度に指定する必要がある。</dd>
+</dl>
+
+<p>データベースのフリーブロックプールのサイズ設定するには、関数 `dpsetfbpsiz' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpsetfbpsiz(DEPOT *<var>depot</var>, int <var>size</var>);</kbd></dt>
+<dd>`depot' はライタで接続したデータベースハンドルを指定する。`size' はフリーブロックプールのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。フリーブロックプールのデフォルトのサイズは16である。サイズをより大きくすると、レコードの上書きを繰り返す際の空間効率は上がるが、時間効率が下がる。</dd>
+</dl>
+
+<p>データベースを更新した内容をファイルとデバイスに同期させるには、関数 `dpsync' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpsync(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はデータベースを閉じないうちに別プロセスにデータベースファイルを利用させる場合に役立つ。</dd>
+</dl>
+
+<p>データベースを最適化するには、関数 `dpoptimize' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpoptimize(DEPOT *<var>depot</var>, int <var>bnum</var>);</kbd></dt>
+<dd>`depot' はライタで接続したデータベースハンドルを指定する。`bnum' は新たなバケット配列の要素数を指定するが、0 以下なら現在のレコード数に最適な値が指定される。戻り値は正常なら真であり、エラーなら偽である。レコードを削除したり、置換モードや連結モードで書き込みを繰り返したりする場合は、データベース内に不要な領域が蓄積するが、この関数はそれを解消するのに役立つ。</dd>
+</dl>
+
+<p>データベースの名前を得るには、関数 `dpname' を用いる。</p>
+
+<dl>
+<dt><kbd>char *dpname(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。戻り値は正常なら名前を格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>データベースファイルのサイズを得るには、関数 `dpfsiz' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpfsiz(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズであり、エラーなら -1 である。</dd>
+</dl>
+
+<p>データベースのバケット配列の要素数を得るには、関数 `dpbnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpbnum(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。戻り値は正常ならデータベースのバケット配列の要素数であり、エラーなら -1 である。</dd>
+</dl>
+
+<p>データベースのバケット配列の利用済みの要素数を得るには、関数 `dpbusenum' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpbusenum(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。戻り値は正常ならバケット配列の利用済みの要素数であり、エラーなら -1 である。この関数はバケット配列の全ての要素を参照するので、効率が悪い。</dd>
+</dl>
+
+<p>データベースのレコード数を得るには、関数 `dprnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int dprnum(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。戻り値は正常ならデータベースのレコード数であり、エラーなら -1 である。</dd>
+</dl>
+
+<p>データベースハンドルがライタかどうかを調べるには、関数 `dpwritable' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpwritable(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。戻り値はライタなら真であり、そうでなければ偽である。</dd>
+</dl>
+
+<p>データベースに致命的エラーが起きたかどうかを調べるには、関数 `dpfatalerror' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpfatalerror(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。</dd>
+</dl>
+
+<p>データベースファイルのinode番号を得るには、関数 `dpinode' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpinode(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。戻り値はデータベースファイルのinode番号である。</dd>
+</dl>
+
+<p>データベースの最終更新時刻を得るには、関数 `dpmtime' を用いる。</p>
+
+<dl>
+<dt><kbd>time_t dpmtime(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。戻り値はデータベースの最終更新時刻である。</dd>
+</dl>
+
+<p>データベースファイルのファイルディスクリプタを得るには、関数 `dpfdesc' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpfdesc(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。戻り値はデータベースファイルのファイルディスクリプタである。データベースのファイルディスクリプタを直接操ることは推奨されない。</dd>
+</dl>
+
+<p>データベースファイルを削除するには、関数 `dpremove' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpremove(const char *<var>name</var>);</kbd></dt>
+<dd>`name' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。</dd>
+</dl>
+
+<p>壊れたデータベースファイルを修復するには、関数 `dprepair' を用いる。</p>
+
+<dl>
+<dt><kbd>int dprepair(const char *<var>name</var>);</kbd></dt>
+<dd>`name' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。修復されたデータベースの全レコードが元来もしくは期待される状態に戻る保証はない。</dd>
+</dl>
+
+<p>全てのレコードをエンディアン非依存のデータとしてダンプするには、関数 `dpexportdb' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpexportdb(DEPOT *<var>depot</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`depot' はデータベースハンドルを指定する。`name' は出力ファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。</dd>
+</dl>
+
+<p>エンディアン非依存データから全てのレコードをロードするには、関数 `dpimportdb' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpimportdb(DEPOT *<var>depot</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`depot' はライタで接続したデータベースハンドルを指定する。データベースは空でなければならない。`name' は入力ファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。</dd>
+</dl>
+
+<p>データベースファイルからレコードを直接取得するには、関数 `dpsnaffle' を用いる。</p>
+
+<dl>
+<dt><kbd>char *dpsnaffle(const char *<var>name</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`name' はデータベースファイルの名前を指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はデータベースファイルが別のプロセスにロックされていても利用できるが、最新の更新が反映されている保証はない。</dd>
+</dl>
+
+<p>データベースの内部で用いるハッシュ関数として、関数 `dpinnerhash' がある。</p>
+
+<dl>
+<dt><kbd>int dpinnerhash(const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値はキーから31ビット長のハッシュ値を算出した値である。この関数はアプリケーションがバケット配列の状態を予測する際に役立つ。</dd>
+</dl>
+
+<p>データベースの内部で用いるハッシュ関数と独立したハッシュ関数として、関数 `dpouterhash' がある。</p>
+
+<dl>
+<dt><kbd>int dpouterhash(const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値はキーから31ビット長のハッシュ値を算出した値である。この関数はアプリケーションがデータベースの更に上でハッシュアルゴリズムを利用する際に役立つ。</dd>
+</dl>
+
+<p>ある数以上の自然数の素数を得るには、関数 `dpprimenum' を用いる。</p>
+
+<dl>
+<dt><kbd>int dpprimenum(int <var>num</var>);</kbd></dt>
+<dd>`num' は適当な自然数を指定する。戻り値は、指定した数と同じかより大きくかつなるべく小さい自然数の素数である。この関数はアプリケーションが利用するバケット配列のサイズを決める場合に役立つ。</dd>
+</dl>
+
+<h3>サンプルコード</h3>
+
+<p>名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+#define NAME "mikio"
+#define NUMBER "000-1234-5678"
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ DEPOT *depot;
+ char *val;
+
+ /* データベースを開く */
+ if(!(depot = dpopen(DBNAME, DP_OWRITER | DP_OCREAT, -1))){
+ fprintf(stderr, "dpopen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* レコードを格納する */
+ if(!dpput(depot, NAME, -1, NUMBER, -1, DP_DOVER)){
+ fprintf(stderr, "dpput: %s\n", dperrmsg(dpecode));
+ }
+
+ /* レコードを取得する */
+ if(!(val = dpget(depot, NAME, -1, 0, -1, NULL))){
+ fprintf(stderr, "dpget: %s\n", dperrmsg(dpecode));
+ } else {
+ printf("Name: %s\n", NAME);
+ printf("Number: %s\n", val);
+ free(val);
+ }
+
+ /* データベースを閉じる */
+ if(!dpclose(depot)){
+ fprintf(stderr, "dpclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<p>データベースの全てのレコードを表示するアプリケーションのサンプルコードを以下に示す。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ DEPOT *depot;
+ char *key, *val;
+
+ /* データベースを開く */
+ if(!(depot = dpopen(DBNAME, DP_OREADER, -1))){
+ fprintf(stderr, "dpopen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* イテレータを初期化する */
+ if(!dpiterinit(depot)){
+ fprintf(stderr, "dpiterinit: %s\n", dperrmsg(dpecode));
+ }
+
+ /* イテレータを走査する */
+ while((key = dpiternext(depot, NULL)) != NULL){
+ if(!(val = dpget(depot, key, -1, 0, -1, NULL))){
+ fprintf(stderr, "dpget: %s\n", dperrmsg(dpecode));
+ free(key);
+ break;
+ }
+ printf("%s: %s\n", key, val);
+ free(val);
+ free(key);
+ }
+
+ /* データベースを閉じる */
+ if(!dpclose(depot)){
+ fprintf(stderr, "dpclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<h3>注記</h3>
+
+<p>Depotを利用したプログラムをビルドするには、ライブラリ `libqdbm.a' または `libqdbm.so' をリンク対象に加える必要がある。例えば、`sample.c' から `sample' を作るには、以下のようにビルドを行う。</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>POSIXスレッドを有効にしてQDBMをビルドした場合、外部変数 `dpecode' はスレッド固有データへの参照として扱われ、Depotの各関数はリエントラントになる。その場合、スレッド間で同時に同じハンドルにアクセスしない限りは、各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。</p>
+
+<hr />
+
+<h2><a name="depotcli" id="depotcli" class="head">Depot用コマンド</a></h2>
+
+<p>Depotに対応するコマンドラインインタフェースは以下のものである。</p>
+
+<p>コマンド `dpmgr' はDepotやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりする機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。</p>
+
+<dl>
+<dt><kbd>dpmgr create [-s] [-bnum <var>num</var>] <var>name</var></kbd></dt>
+<dd>データベースファイルを作成する。</dd>
+<dt><kbd>dpmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-na] <var>name</var> <var>key</var> <var>val</var></kbd></dt>
+<dd>キーと値に対応するレコードを追加する。</dd>
+<dt><kbd>dpmgr out [-kx|-ki] <var>name</var> <var>key</var></kbd></dt>
+<dd>キーに対応するレコードを削除する。</dd>
+<dt><kbd>dpmgr get [-nl] [-kx|-ki] [-start <var>num</var>] [-max <var>num</var>] [-ox] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>キーに対応するレコードの値を取得して標準出力する。</dd>
+<dt><kbd>dpmgr list [-nl] [-k|-v] [-ox] <var>name</var></kbd></dt>
+<dd>データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。</dd>
+<dt><kbd>dpmgr optimize [-bnum <var>num</var>] [-na] <var>name</var></kbd></dt>
+<dd>データベースを最適化する。</dd>
+<dt><kbd>dpmgr inform [-nl] <var>name</var></kbd></dt>
+<dd>データベースの雑多な情報を出力する。</dd>
+<dt><kbd>dpmgr remove <var>name</var></kbd></dt>
+<dd>データベースファイルを削除する。</dd>
+<dt><kbd>dpmgr repair <var>name</var></kbd></dt>
+<dd>壊れたデータベースファイルを修復する。</dd>
+<dt><kbd>dpmgr exportdb <var>name</var> <var>file</var></kbd></dt>
+<dd>全てのレコードをエンディアン非依存のデータとしてダンプする。</dd>
+<dt><kbd>dpmgr importdb [-bnum <var>num</var>] <var>name</var> <var>file</var></kbd></dt>
+<dd>エンディアン非依存データから全てのレコードをロードする。</dd>
+<dt><kbd>dpmgr snaffle [-kx|-ki] [-ox] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>ロックされたデータベースからキーに対応するレコードの値を取得して標準出力する。</dd>
+<dt><kbd>dpmgr version</kbd></dt>
+<dd>QDBMのバージョン情報を標準出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-s</kbd> : ファイルをスパースにする。</li>
+<li><kbd>-bnum <var>num</var></kbd> : バケット配列の要素数を `num' に指定する。</li>
+<li><kbd>-kx</kbd> : 2桁単位の16進数によるバイナリ表現として `key' を扱う。</li>
+<li><kbd>-ki</kbd> : 10進数による数値表現として `key' を扱う。</li>
+<li><kbd>-vx</kbd> : 2桁単位の16進数によるバイナリ表現として `val' を扱う。</li>
+<li><kbd>-vi</kbd> : 10進数による数値表現として `val' を扱う。</li>
+<li><kbd>-vf</kbd> : 名前が `val' のファイルのデータを値として読み込む。</li>
+<li><kbd>-keep</kbd> : 既存のレコードとキーが重複時に上書きせずにエラーにする。</li>
+<li><kbd>-cat</kbd> : 既存のレコードとキーが重複時に値を末尾に追加する。</li>
+<li><kbd>-na</kbd> : アラインメントを設定しない。</li>
+<li><kbd>-nl</kbd> : ファイルロックをかけずにデータベースを開く。</li>
+<li><kbd>-start</kbd> : 値から取り出すデータの開始オフセットを指定する。</li>
+<li><kbd>-max</kbd> : 値から取り出すデータの最大の長さを指定する。</li>
+<li><kbd>-ox</kbd> : 2桁単位の16進数によるバイナリ表現として標準出力を行う。</li>
+<li><kbd>-n</kbd> : 標準出力の末尾に付加される改行文字の出力を抑制する。</li>
+<li><kbd>-k</kbd> : キーのみを出力する。</li>
+<li><kbd>-v</kbd> : 値のみを出力する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。</p>
+
+<p>コマンド `dptest' はDepotの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `dpmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数、`bnum' はバケット配列の要素数、`pnum' はキーのパターン数、`align' はアラインメントの基本サイズ、`fbpsiz' はフリーブロックプールのサイズを指定する。</p>
+
+<dl>
+<dt><kbd>dptest write [-s] <var>name</var> <var>rnum</var> <var>bnum</var></kbd></dt>
+<dd>`00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。</dd>
+<dt><kbd>dptest read [-wb] <var>name</var></kbd></dt>
+<dd>上記で生成したデータベースの全レコードを検索する。</dd>
+<dt><kbd>dptest rcat [-c] <var>name</var> <var>rnum</var> <var>bnum</var> <var>pnum</var> <var>align</var> <var>fbpsiz</var></kbd></dt>
+<dd>キーがある程度重複するようにレコードの追加を行い、連結モードで処理する。</dd>
+<dt><kbd>dptest combo <var>name</var></kbd></dt>
+<dd>各種操作の組み合わせテストを行う。</dd>
+<dt><kbd>dptest wicked [-c] <var>name</var> <var>rnum</var></kbd></dt>
+<dd>各種更新操作を無作為に選択して実行する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-s</kbd> : ファイルをスパースにする。</li>
+<li><kbd>-wb</kbd> : 関数 `dpget' の代わりに関数 `dpgetwb' を用いる。</li>
+<li><kbd>-c</kbd> : Cabinのマップを使って比較テストを行う。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。</p>
+
+<p>コマンド `dptsv' はタブ区切りでキーと値を表現した行からなるTSVファイルとDepotのデータベースを相互変換する。このコマンドは、QDBMの他のバージョンや他のDBMとの間でデータの交換を行う際に役立つ。また、バイトオーダの違うシステムの間でデータを交換する際にも役立つ。以下の書式で用いる。`name' はデータベース名を指定する。`export' サブコマンドではTSVのデータは標準入力から読み込む。キーが重複するレコードは後者を優先する。`-bnum' オプションの引数 `num' はバケット配列の要素数を指定する。`import' サブコマンドではTSVのデータが標準出力に書き出される。</p>
+
+<dl>
+<dt><kbd>dptsv import [-bnum <var>num</var>] [-bin] <var>name</var></kbd></dt>
+<dd>TSVファイルを読み込んでデータベースを作成する。</dd>
+<dt><kbd>dptsv export [-bin] <var>name</var></kbd></dt>
+<dd>データベースの全てのレコードをTSVファイルとして出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-bnum <var>num</var></kbd> : バケット配列の要素数を `num' に指定する。</li>
+<li><kbd>-bin</kbd> : Base64形式でレコードを扱う。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。</p>
+
+<p>Depotのコマンド群を駆使すると、簡単なデータベースシステムが構築できる。例えば `/etc/password' をユーザ名で検索するためのデータベースを作成するには、以下のようにする。</p>
+
+<pre>cat /etc/passwd | tr ':' '\t' | dptsv import casket
+</pre>
+
+<p>そして、`mikio' というユーザの情報を取り出すには、以下のようにする。</p>
+
+<pre>dpmgr get casket mikio
+</pre>
+
+<p>これらのコマンドと同等の機能をDepotのAPIを用いて実装することも容易である。</p>
+
+<hr />
+
+<h2><a name="curiaapi" id="curiaapi" class="head">Curia: 拡張API</a></h2>
+
+<h3>概要</h3>
+
+<p>CuriaはQDBMの拡張APIであり、複数のデータベースファイルをディレクトリで一括して扱う機能を提供する。データベースを複数のファイルに分割することで、ファイルシステムによるファイルサイズの制限を回避することができる。複数のデバイスにファイルを分散させれば、スケーラビリティを向上させることができる。</p>
+
+<p>Depotではファイル名を指定してデータベースを構築するが、Curiaではディレクトリ名を指定してデータベースを構築する。指定したディレクトリの直下には、`depot' という名前のデータベースファイルが生成される。これはディレクトリの属性を保持するものであり、レコードの実データは格納されない。それとは別に、データベースを分割した個数だけ、4桁の10進数値の名前を持つサブディレクトリが生成され、各々のサブディレクトリの中には `depot' という名前でデータベースファイルが生成される。レコードの実データはそれらに格納される。例えば、`casket' という名前のデータベースを作成し、分割数を3にする場合、`casket/depot'、`casket/0001/depot'、`casket/0002/depot'、`casket/0003/depot' が生成される。データベースを作成する際にすでにディレクトリが存在していてもエラーとはならない。したがって、予めサブディレクトリを生成しておいて、各々に異なるデバイスのファイルシステムをマウントしておけば、データベースファイルを複数のデバイスに分散させることができる。</p>
+
+<p>Curiaにはラージオブジェクトを扱う機能がある。通常のレコードのデータはデータベースファイルに格納されるが、ラージオブジェクトのレコードのデータは個別のファイルに格納される。ラージオブジェクトのファイルはハッシュ値を元にディレクトリに分けて格納されるので、通常のレコードには劣るが、それなりの速度で参照できる。サイズが大きく参照頻度が低いデータは、ラージオブジェクトとしてデータベースファイルから分離すべきである。そうすれば、通常のレコードに対する処理速度が向上する。ラージオブジェクトのディレクトリ階層はデータベースファイルが格納されるサブディレクトリの中の `lob' という名前のディレクトリの中に作られる。通常のデータベースとラージオブジェクトのデータベースはキー空間が異なり、互いに干渉することはない。</p>
+
+<p>Curiaを使うためには、`depot.h' と `curia.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。</p>
+
+<dl>
+<dt><kbd>#include &lt;depot.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;curia.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+</dl>
+
+<p>Curiaでデータベースを扱う際には、`CURIA' 型へのポインタをハンドルとして用いる。これは、`stdio.h' の各種ルーチンがファイル入出力に `FILE' 型へのポインタを用いるのに似ている。ハンドルは、関数 `cropen' で開き、関数 `crclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。データベースに致命的なエラーが起きた場合は、以後そのハンドルに対する `crclose' を除く全ての操作は何もせずにエラーを返す。ひとつのプロセスで複数のデータベースディレクトリを同時に利用することは可能であるが、同じデータベースディレクトリの複数のハンドルを利用してはならない。</p>
+
+<p>CuriaでもDepotと同じく外部変数 `dpecode' に直前のエラーコードが記録される。エラーコードに対応するメッセージ文字列を得るには、関数 `dperrmsg' を用いる。</p>
+
+<h3>API</h3>
+
+<p>データベースのハンドルを作成するには、関数 `cropen' を用いる。</p>
+
+<dl>
+<dt><kbd>CURIA *cropen(const char *<var>name</var>, int <var>omode</var>, int <var>bnum</var>, int <var>dnum</var>);</kbd></dt>
+<dd>`name' はデータベースディレクトリの名前を指定する。`omode' は接続モードを指定し、`CR_OREADER' ならリーダ、`CR_OWRITER' ならライタとなる。`CR_OWRITER' の場合、`CR_OCREAT' または `CR_OTRUNC' とのビット論理和にすることができる。`CR_OCREAT' はファイルが無い場合に新規作成することを指示し、`CR_OTRUNC' はファイルが存在しても作り直すことを指示する。`CR_OREADER' と `CR_OWRITER' の両方で `CR_ONOLCK' または `CR_OLCKNB' とのビット論理和にすることができるが、前者はファイルロックをかけずにデータベースを開くことを指示し、後者はブロックせずにロックをかけることを指示する。`CR_OCREAT' は `CR_OSPARSE' とのビット論理和にすることができるが、それは生成されるファイルをスパースにすることを指示する。`bnum' はバケット配列の要素数の目安を指定するが、0 以下ならデフォルト値が使われる。バケット配列の要素数はデータベースを作成する時に決められ、最適化以外の手段で変更することはできない。バケット配列の要素数は、格納するレコード数の半分から4倍程度にするのがよい。`dnum' は要素データベースの数を指定するが、0 以下ならデフォルト値が使われる。データベースファイルの分割数はデータベースを作成する時に指定したものから変更することはできない。データベースファイルの分割数の最大値は 512 個である。戻り値はデータベースハンドルであるか、エラーなら `NULL' である。ライタ(読み書き両用モード)でデータベースファイルを開く際にはそのファイルに対して排他ロックがかけられ、リーダ(読み込み専用モード)で開く際には共有ロックがかけられる。その際には該当のロックがかけられるまで制御がブロックする。`CR_ONOLCK' を使う場合、アプリケーションが排他制御の責任を負う。</dd>
+</dl>
+
+<p>データベースとの接続を閉じてハンドルを破棄するには、関数 `crclose' を用いる。</p>
+
+<dl>
+<dt><kbd>int crclose(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースの更新内容は、接続を閉じた時点で初めてファイルと同期される。ライタでデータベースを開いた場合、適切に接続を閉じないとデータベースが破壊される。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。</dd>
+</dl>
+
+<p>レコードを追加するには、関数 `crput' を用いる。</p>
+
+<dl>
+<dt><kbd>int crput(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const char *<var>vbuf</var>, int <var>vsiz</var>, int <var>dmode</var>);</kbd></dt>
+<dd>`curia' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`dmode' はキーが既存レコードと重複した際の制御を指定する。`CR_DOVER' は既存のレコードの値を上書きし、`CR_DKEEP' は既存のレコードを残してエラーを返し、`DP_DCAT' は指定された値を既存の値の末尾に加える。戻り値は正常なら真であり、エラーなら偽である。</dd>
+</dl>
+
+<p>レコードを削除するには、関数 `crout' を用いる。</p>
+
+<dl>
+<dt><kbd>int crout(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`curia' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。</dd>
+</dl>
+
+<p>レコードを取得するには、関数 `crget' を用いる。</p>
+
+<dl>
+<dt><kbd>char *crget(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>start</var>, int <var>max</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>レコードを取得してバッファに書き込むには、関数 `crgetwb' を用いる。</p>
+
+<dl>
+<dt><kbd>int crgetwb(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>start</var>, int <var>max</var>, char *<var>vbuf</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定する。それは書き込み用のバッファのサイズ以下である必要がある。`vbuf' は抽出したデータを書き込むバッファへのポインタを指定する。戻り値は正常ならバッファに書き込まれたデータのサイズであり、エラーなら -1 である。該当のレコードがない場合も -1 を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。書き込み用バッファの末尾に終端文字が追加されないことに注意すべきである。</dd>
+</dl>
+
+<p>レコードの値のサイズを取得するには、関数 `crvsiz' を用いる。</p>
+
+<dl>
+<dt><kbd>int crvsiz(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値のサイズであるが、該当がない場合やエラーの場合は -1 である。この関数はレコードの有無を調べるのにも便利である。`crget' と違って実データを読み込まないので効率がよい。</dd>
+</dl>
+
+<p>データベースのイテレータを初期化するには、関数 `criterinit' を用いる。</p>
+
+<dl>
+<dt><kbd>int criterinit(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。イテレータは、データベースに格納された全てのレコードを参照するために用いられる。</dd>
+</dl>
+
+<p>データベースのイテレータから次のレコードのキーを取り出すには、関数 `criternext' を用いる。</p>
+
+<dl>
+<dt><kbd>char *criternext(CURIA *<var>curia</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常ならキーを格納した領域へのポインタであり、エラーなら `NULL' である。イテレータが最後まできて該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数を繰り返して呼ぶことによって全てのレコードを一度ずつ参照することができる。ただし、繰り返しの間にデータベースの更新があった場合はその限りではない。なお、取り出すレコードの順序は制御できず、格納した順番でレコードを取り出せるとは限らない。</dd>
+</dl>
+
+<p>データベースのアラインメントを設定するには、関数 `crsetalign' を用いる。</p>
+
+<dl>
+<dt><kbd>int crsetalign(CURIA *<var>curia</var>, int <var>align</var>);</kbd></dt>
+<dd>`curia' はライタで接続したデータベースハンドルを指定する。`align' はアラインメントのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。アラインメントを設定しておくと、レコードの上書きを頻繁にする場合の処理効率が良くなる。アラインメントには、一連の更新操作をした後の状態での標準的な値のサイズを指定するのがよい。アラインメントが正数の場合、レコードの領域のサイズがアラインメントの倍数になるようにパディングがとられる。アラインメントが負数の場合、`vsiz' を値のサイズとして、パディングのサイズは `(vsiz / pow(2, abs(align) - 1))' として算出される。アラインメントの設定はデータベースに保存されないので、データベースを開く度に指定する必要がある。</dd>
+</dl>
+
+<p>データベースのフリーブロックプールのサイズ設定するには、関数 `crsetfbpsiz' を用いる。</p>
+
+<dl>
+<dt><kbd>int crsetfbpsiz(CURIA *<var>curia</var>, int <var>size</var>);</kbd></dt>
+<dd>`curia' はライタで接続したデータベースハンドルを指定する。`size' はフリーブロックプールのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。フリーブロックプールのデフォルトのサイズは16である。サイズをより大きくすると、レコードの上書きを繰り返す際の空間効率は上がるが、時間効率が下がる。</dd>
+</dl>
+
+<p>データベースを更新した内容をファイルとデバイスに同期させるには、関数 `crsync' を用いる。</p>
+
+<dl>
+<dt><kbd>int crsync(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はデータベースを閉じないうちに別プロセスにデータベースファイルを利用させる場合に役立つ。</dd>
+</dl>
+
+<p>データベースを最適化するには、関数 `croptimize' を用いる。</p>
+
+<dl>
+<dt><kbd>int croptimize(CURIA *<var>curia</var>, int <var>bnum</var>);</kbd></dt>
+<dd>`curia' はライタで接続したデータベースハンドルを指定する。`bnum' は新たなバケット配列の要素数を指定するが、0 以下なら現在のレコード数に最適な値が指定される。戻り値は正常なら真であり、エラーなら偽である。レコードを削除したり、置換モードや連結モードで書き込みを繰り返したりする場合は、データベース内に不要な領域が蓄積するが、この関数はそれを解消するのに役立つ。</dd>
+</dl>
+
+<p>データベースの名前を得るには、関数 `crname' を用いる。</p>
+
+<dl>
+<dt><kbd>char *crname(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。戻り値は正常なら名前を格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>データベースファイルのサイズの合計を得るには、関数 `crfsiz' を用いる。</p>
+
+<dl>
+<dt><kbd>int crfsiz(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズの合計であり、エラーなら -1 である。戻り値が2GBを越えた場合は桁溢れが起こる。</dd>
+</dl>
+
+<p>データベースファイルのサイズの合計を倍精度浮動小数として得るには、関数 `crfsizd' を用いる。</p>
+
+<dl>
+<dt><kbd>double crfsizd(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズの合計の倍精度値であり、エラーなら -1 である。</dd>
+</dl>
+
+<p>データベースのバケット配列の要素数の合計を得るには、関数 `crbnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int crbnum(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースのバケット配列の要素数の合計であり、エラーなら -1 である。</dd>
+</dl>
+
+<p>データベースのバケット配列の利用済みの要素数の合計を得るには、関数 `crbusenum' を用いる。</p>
+
+<dl>
+<dt><kbd>int crbusenum(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。戻り値は正常ならバケット配列の利用済みの要素数の合計であり、エラーなら -1 である。この関数はバケット配列の全ての要素を参照するので、効率が悪い。</dd>
+</dl>
+
+<p>データベースのレコード数を得るには、関数 `crrnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int crrnum(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースのレコード数であり、エラーなら -1 である。</dd>
+</dl>
+
+<p>データベースハンドルがライタかどうかを調べるには、関数 `crwritable' を用いる。</p>
+
+<dl>
+<dt><kbd>int crwritable(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。戻り値はライタなら真であり、そうでなければ偽である。</dd>
+</dl>
+
+<p>データベースに致命的エラーが起きたかどうかを調べるには、関数 `crfatalerror' を用いる。</p>
+
+<dl>
+<dt><kbd>int crfatalerror(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。</dd>
+</dl>
+
+<p>データベースディレクトリのinode番号を得るには、関数 `crinode' を用いる。</p>
+
+<dl>
+<dt><kbd>int crinode(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。戻り値はデータベースディレクトリのinode番号である。</dd>
+</dl>
+
+<p>データベースの最終更新時刻を得るには、関数 `crmtime' を用いる。</p>
+
+<dl>
+<dt><kbd>time_t crmtime(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。戻り値はデータベースの最終更新時刻である。</dd>
+</dl>
+
+<p>データベースディレクトリを削除するには、関数 `crremove' を用いる。</p>
+
+<dl>
+<dt><kbd>int crremove(const char *<var>name</var>);</kbd></dt>
+<dd>`name' はデータベースディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。</dd>
+</dl>
+
+<p>壊れたデータベースディレクトリを修復するには、関数 `crrepair' を用いる。</p>
+
+<dl>
+<dt><kbd>int crrepair(const char *<var>name</var>);</kbd></dt>
+<dd>`name' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。修復されたデータベースの全レコードが元来もしくは期待される状態に戻る保証はない。</dd>
+</dl>
+
+<p>全てのレコードをエンディアン非依存のデータとしてダンプするには、関数 `crexportdb' を用いる。</p>
+
+<dl>
+<dt><kbd>int crexportdb(CURIA *<var>curia</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。`name' は出力ディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。</dd>
+</dl>
+
+<p>エンディアン非依存データから全てのレコードをロードするには、関数 `crimportdb' を用いる。</p>
+
+<dl>
+<dt><kbd>int crimportdb(CURIA *<var>curia</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`curia' はライタで接続したデータベースハンドルを指定する。データベースは空でなければならない。`name' は入力ディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。</dd>
+</dl>
+
+<p>データベースディレクトリからレコードを直接取得するには、関数 `crsnaffle' を用いる。</p>
+
+<dl>
+<dt><kbd>char *crsnaffle(const char *<var>name</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`name' はデータベースディレクトリの名前を指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はデータベースディレクトリが別のプロセスにロックされていても利用できるが、最新の更新が反映されている保証はない。</dd>
+</dl>
+
+<p>ラージオブジェクト用データベースにレコードを追加するには、関数 `crputlob' を用いる。</p>
+
+<dl>
+<dt><kbd>int crputlob(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const char *<var>vbuf</var>, int <var>vsiz</var>, int <var>dmode</var>);</kbd></dt>
+<dd>`curia' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`dmode' はキーが既存レコードと重複した際の制御を指定する。`CR_DOVER' は既存のレコードの値を上書きし、`CR_DKEEP' は既存のレコードを残してエラーを返し、`DP_DCAT' は指定された値を既存の値の末尾に加える。戻り値は正常なら真であり、エラーなら偽である。</dd>
+</dl>
+
+<p>ラージオブジェクト用データベースからレコードを削除するには、関数 `croutlob' を用いる。</p>
+
+<dl>
+<dt><kbd>int croutlob(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`curia' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。</dd>
+</dl>
+
+<p>ラージオブジェクト用データベースからレコードの値を取得するには、関数 `crgetlob' を用いる。</p>
+
+<dl>
+<dt><kbd>char *crgetlob(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>start</var>, int <var>max</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>ラージオブジェクト用データベースにあるレコードのファイルディスクリプタを取得するには、関数 `crgetlobfd' を用いる。</p>
+
+<dl>
+<dt><kbd>int crgetlobfd(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら該当のファイルディスクリプタであり、エラーなら -1 である。該当のレコードがない場合も -1 を返す。戻り値のファイルディスクリプタは `open' コールで開かれる。データベースがライタで接続された場合はそのディスクリプタは書き込み可能(O_RDWR)であり、そうでなければ書き込み不可能(O_RDONLY)である。ディスクリプタが不要になったら `close' で閉じるべきである。</dd>
+</dl>
+
+<p>ラージオブジェクト用データベースにあるレコードの値のサイズを取得するには、関数 `crvsizlob' を用いる。</p>
+
+<dl>
+<dt><kbd>int crvsizlob(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値のサイズであり、該当がない場合やエラーの場合は -1 である。この関数はレコードの有無を調べるのにも便利である。`crgetlob' と違って実データを読み込まないので効率がよい。</dd>
+</dl>
+
+<p>ラージオブジェクト用データベースのレコード数の合計を得るには、関数 `crrnumlob' を用いる。</p>
+
+<dl>
+<dt><kbd>int crrnumlob(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースのレコード数の合計であり、エラーなら -1 である。</dd>
+</dl>
+
+<h3>サンプルコード</h3>
+
+<p>名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;curia.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+#define NAME "mikio"
+#define NUMBER "000-1234-5678"
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ CURIA *curia;
+ char *val;
+
+ /* データベースを開く */
+ if(!(curia = cropen(DBNAME, CR_OWRITER | CR_OCREAT, -1, -1))){
+ fprintf(stderr, "cropen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* レコードを格納する */
+ if(!crput(curia, NAME, -1, NUMBER, -1, CR_DOVER)){
+ fprintf(stderr, "crput: %s\n", dperrmsg(dpecode));
+ }
+
+ /* レコードを取得する */
+ if(!(val = crget(curia, NAME, -1, 0, -1, NULL))){
+ fprintf(stderr, "crget: %s\n", dperrmsg(dpecode));
+ } else {
+ printf("Name: %s\n", NAME);
+ printf("Number: %s\n", val);
+ free(val);
+ }
+
+ /* データベースを閉じる */
+ if(!crclose(curia)){
+ fprintf(stderr, "crclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<p>データベースの全てのレコードを表示するアプリケーションのサンプルコードを以下に示す。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;curia.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ CURIA *curia;
+ char *key, *val;
+
+ /* データベースを開く */
+ if(!(curia = cropen(DBNAME, CR_OREADER, -1, -1))){
+ fprintf(stderr, "cropen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* イテレータを初期化する */
+ if(!criterinit(curia)){
+ fprintf(stderr, "criterinit: %s\n", dperrmsg(dpecode));
+ }
+
+ /* イテレータを走査する */
+ while((key = criternext(curia, NULL)) != NULL){
+ if(!(val = crget(curia, key, -1, 0, -1, NULL))){
+ fprintf(stderr, "crget: %s\n", dperrmsg(dpecode));
+ free(key);
+ break;
+ }
+ printf("%s: %s\n", key, val);
+ free(val);
+ free(key);
+ }
+
+ /* データベースを閉じる */
+ if(!crclose(curia)){
+ fprintf(stderr, "crclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<h3>注記</h3>
+
+<p>Curiaを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>POSIXスレッドを有効にしてQDBMをビルドした場合、外部変数 `dpecode' はスレッド固有データへの参照として扱われ、Curiaの各関数はリエントラントになる。その場合、スレッド間で同時に同じハンドルにアクセスしない限りは、各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。</p>
+
+<hr />
+
+<h2><a name="curiacli" id="curiacli" class="head">Curia用コマンド</a></h2>
+
+<p>Curiaに対応するコマンドラインインタフェースは以下のものである。</p>
+
+<p>コマンド `crmgr' はCuriaやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりする機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。</p>
+
+<dl>
+<dt><kbd>crmgr create [-s] [-bnum <var>num</var>] [-dnum <var>num</var>] <var>name</var></kbd></dt>
+<dd>データベースディレクトリを作成する。</dd>
+<dt><kbd>crmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-lob] [-na] <var>name</var> <var>key</var> <var>val</var></kbd></dt>
+<dd>キーと値に対応するレコードを追加する。</dd>
+<dt><kbd>crmgr out [-kx|-ki] [-lob] <var>name</var> <var>key</var></kbd></dt>
+<dd>キーに対応するレコードを削除する。</dd>
+<dt><kbd>crmgr get [-nl] [-kx|-ki] [-start <var>num</var>] [-max <var>num</var>] [-ox] [-lob] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>キーに対応するレコードの値を取得して標準出力する。</dd>
+<dt><kbd>crmgr list [-nl] [-k|-v] [-ox] <var>name</var></kbd></dt>
+<dd>データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。</dd>
+<dt><kbd>crmgr optimize [-bnum <var>num</var>] [-na] <var>name</var></kbd></dt>
+<dd>データベースを最適化する。</dd>
+<dt><kbd>crmgr inform [-nl] <var>name</var></kbd></dt>
+<dd>データベースの雑多な情報を出力する。</dd>
+<dt><kbd>crmgr remove <var>name</var></kbd></dt>
+<dd>データベースディレクトリを削除する。</dd>
+<dt><kbd>crmgr repair <var>name</var></kbd></dt>
+<dd>壊れたデータベースディレクトリを修復する。</dd>
+<dt><kbd>crmgr exportdb <var>name</var> <var>dir</var></kbd></dt>
+<dd>全てのレコードをエンディアン非依存のデータとしてダンプする。</dd>
+<dt><kbd>crmgr importdb [-bnum <var>num</var>] [-dnum <var>num</var>] <var>name</var> <var>dir</var></kbd></dt>
+<dd>エンディアン非依存データから全てのレコードをロードする。</dd>
+<dt><kbd>crmgr snaffle [-kx|-ki] [-ox] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>ロックされたデータベースからキーに対応するレコードの値を取得して標準出力する。</dd>
+<dt><kbd>crmgr version</kbd></dt>
+<dd>QDBMのバージョン情報を標準出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-s</kbd> : ファイルをスパースにする。</li>
+<li><kbd>-bnum <var>num</var></kbd> : バケット配列の要素数を `num' に指定する。</li>
+<li><kbd>-dnum <var>num</var></kbd> : データベースファイルの分割数を `num' に指定する。</li>
+<li><kbd>-kx</kbd> : 2桁単位の16進数によるバイナリ表現として `key' を扱う。</li>
+<li><kbd>-ki</kbd> : 10進数による数値表現として `key' を扱う。</li>
+<li><kbd>-vx</kbd> : 2桁単位の16進数によるバイナリ表現として `val' を扱う。</li>
+<li><kbd>-vi</kbd> : 10進数による数値表現として `val' を扱う。</li>
+<li><kbd>-vf</kbd> : 名前が `val' のファイルのデータを値として読み込む。</li>
+<li><kbd>-keep</kbd> : 既存のレコードとキーが重複時に上書きせずにエラーにする。</li>
+<li><kbd>-cat</kbd> : 既存のレコードとキーが重複時に値を末尾に追加する。</li>
+<li><kbd>-na</kbd> : アラインメントを設定しない。</li>
+<li><kbd>-nl</kbd> : ファイルロックをかけずにデータベースを開く。</li>
+<li><kbd>-start</kbd> : 値から取り出すデータの開始オフセットを指定する。</li>
+<li><kbd>-max</kbd> : 値から取り出すデータの最大の長さを指定する。</li>
+<li><kbd>-ox</kbd> : 2桁単位の16進数によるバイナリ表現として標準出力を行う。</li>
+<li><kbd>-lob</kbd> : ラージオブジェクトを扱う。</li>
+<li><kbd>-n</kbd> : 標準出力の末尾に付加される改行文字の出力を抑制する。</li>
+<li><kbd>-k</kbd> : キーのみを出力する。</li>
+<li><kbd>-v</kbd> : 値のみを出力する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。</p>
+
+<p>コマンド `crtest' はCuriaの機能テストや性能テストに用いるツールである。`crtest' によって生成されたデータベースディレクトリを `crmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数、`bnum' はバケット配列の要素数、`dnum' はデータベースファイルの分割数、`pnum' はキーのパターン数、`align' はアラインメントの基本サイズ、`fbpsiz' はフリーブロックプールのサイズを指定する。</p>
+
+<dl>
+<dt><kbd>crtest write [-s] [-lob] <var>name</var> <var>rnum</var> <var>bnum</var> <var>dnum</var></kbd></dt>
+<dd>`00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。</dd>
+<dt><kbd>crtest read [-wb] [-lob] <var>name</var></kbd></dt>
+<dd>上記で生成したデータベースの全レコードを検索する。</dd>
+<dt><kbd>crtest rcat [-c] <var>name</var> <var>rnum</var> <var>bnum</var> <var>dnum</var> <var>pnum</var> <var>align</var> <var>fbpsiz</var></kbd></dt>
+<dd>キーがある程度重複するようにレコードの追加を行い、連結モードで処理する。</dd>
+<dt><kbd>crtest combo <var>name</var></kbd></dt>
+<dd>各種操作の組み合わせテストを行う。</dd>
+<dt><kbd>crtest wicked [-c] <var>name</var> <var>rnum</var></kbd></dt>
+<dd>各種更新操作を無作為に選択して実行する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-s</kbd> : ファイルをスパースにする。</li>
+<li><kbd>-lob</kbd> : ラージオブジェクトを扱う。</li>
+<li><kbd>-wb</kbd> : 関数 `crget' の代わりに関数 `crgetwb' を用いる。</li>
+<li><kbd>-c</kbd> : Cabinのマップを使って比較テストを行う。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。</p>
+
+<p>コマンド `crtsv' はタブ区切りでキーと値を表現した行からなるTSVファイルとCuriaのデータベースを相互変換する。このコマンドは、QDBMの他のバージョンや他のDBMとの間でデータの交換を行う際に役立つ。また、バイトオーダの違うシステムの間でデータを交換する際にも役立つ。以下の書式で用いる。`name' はデータベース名を指定する。`export' サブコマンドではTSVのデータは標準入力から読み込む。キーが重複するレコードは後者を優先する。`-bnum' オプションの引数 `num' はバケット配列の要素数を指定する。`-dnum' オプションの引数 `num' は要素データベースの数を指定する。`import' サブコマンドではTSVのデータが標準出力に書き出される。</p>
+
+<dl>
+<dt><kbd>crtsv import [-bnum <var>num</var>] [-dnum <var>num</var>] [-bin] <var>name</var></kbd></dt>
+<dd>TSVファイルを読み込んでデータベースを作成する。</dd>
+<dt><kbd>crtsv export [-bin] <var>name</var></kbd></dt>
+<dd>データベースの全てのレコードをTSVファイルとして出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-bnum <var>num</var></kbd> : バケット配列の要素数を `num' に指定する。</li>
+<li><kbd>-dnum <var>num</var></kbd> : データベースファイルの分割数を `num' に指定する。</li>
+<li><kbd>-bin</kbd> : Base64形式でレコードを扱う。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。</p>
+
+<p>Curiaのコマンド群を駆使すると、簡単なデータベースシステムが構築できる。例えば `/etc/password' をユーザ名で検索するためのデータベースを作成するには、以下のようにする。</p>
+
+<pre>cat /etc/passwd | tr ':' '\t' | crtsv import casket
+</pre>
+
+<p>そして、`mikio' というユーザの情報を取り出すには、以下のようにする。</p>
+
+<pre>crmgr get casket mikio
+</pre>
+
+<p>これらのコマンドと同等の機能をCuriaのAPIを用いて実装することも容易である。</p>
+
+<hr />
+
+<h2><a name="relicapi" id="relicapi" class="head">Relic: NDBM互換API</a></h2>
+
+<h3>概要</h3>
+
+<p>Relicは、NDBMと互換するAPIである。すなわち、Depotの関数群をNDBMのAPIで包んだものである。Relicを使ってNDBMのアプリケーションをQDBMに移植するのはたやすい。ほとんどの場合、インクルードするヘッダファイルを `ndbm.h' から `relic.h' に換え、ビルドの際のリンカオプションを `-lndbm' から `-lqdbm' に換えるだけでよい。</p>
+
+<p>オリジナルのNDBMでは、データベースは二つのファイルの対からなる。ひとつは接尾辞に `.dir' がつく名前で、キーのビットマップを格納する「ディレクトリファイル」である。もうひとつは接尾辞に `.pag' がつく名前で、データの実体を格納する「データファイル」である。Relicではディレクトリファイルは単なるダミーとして作成し、データファイルをデータベースとする。RelicではオリジナルのNDBMと違い、格納するデータのサイズに制限はない。なお、オリジナルのNDBMで生成したデータベースファイルをRelicで扱うことはできない。</p>
+
+<p>Relicを使うためには、`relic.h' と `stdlib.h' と `sys/types.h' と `sys/stat.h' と `fcntl.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。</p>
+
+<dl>
+<dt><kbd>#include &lt;relic.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;sys/types.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;sys/stat.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;fcntl.h&gt;</kbd></dt>
+</dl>
+
+<p>Relicでデータベースを扱う際には、`DBM' 型へのポインタをハンドルとして用いる。ハンドルは、関数 `dbm_open' で開き、関数 `dbm_close' で閉じる。ハンドルのメンバを直接参照することは推奨されない。</p>
+
+<h3>API</h3>
+
+<p>データの格納、削除、検索に用いる関数とのデータの授受には、キーと値を表現するのに `datum' 型の構造体を用いる。</p>
+
+<dl>
+<dt><kbd>typedef struct { void *dptr; size_t dsize; } datum;</kbd></dt>
+<dd>`dptr' はデータ領域へのポインタである。`dsize' はデータ領域のサイズである。</dd>
+</dl>
+
+<p>データベースのハンドルを作成するには、関数 `dbm_open' を用いる。</p>
+
+<dl>
+<dt><kbd>DBM *dbm_open(char *<var>name</var>, int <var>flags</var>, int <var>mode</var>);</kbd></dt>
+<dd>`name' はデータベースの名前を指定するが、ファイル名はそれに接尾辞をつけたものになる。`flags' は `open' コールに渡すものと同じだが、`O_WRONLY' は `O_RDWR' と同じになり、追加フラグでは `O_CREAT' と `O_TRUNC' のみが有効である。`mode' は `open' コールに渡すものと同じでファイルのモードを指定する。戻り値は正常ならデータベースハンドルであり、エラーなら `NULL' である。</dd>
+</dl>
+
+<p>データベースとの接続を閉じてハンドルを破棄するには、関数 `dbm_close' を用いる。</p>
+
+<dl>
+<dt><kbd>void dbm_close(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' はデータベースハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。</dd>
+</dl>
+
+<p>レコードを追加するには、関数 `dbm_store' を用いる。</p>
+
+<dl>
+<dt><kbd>int dbm_store(DBM *<var>db</var>, datum <var>key</var>, datum <var>content</var>, int <var>flags</var>);</kbd></dt>
+<dd>`db' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。`content' は値の構造体を指定する。`frags' が `DBM_INSERT' ならキーの重複時に書き込みを断念し、`DBM_REPLACE' なら上書きを行う。戻り値は正常なら 0 であり、重複での断念なら 1 であり、その他のエラーなら -1 である。</dd>
+</dl>
+
+<p>レコードを削除するには、関数 `dbm_delete' を用いる。</p>
+
+<dl>
+<dt><kbd>int dbm_delete(DBM *<var>db</var>, datum <var>key</var>);</kbd></dt>
+<dd>`db' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は正常なら 0 であり、エラーなら -1 である。</dd>
+</dl>
+
+<p>レコードを取得するには、関数 `dbm_fetch' を用いる。</p>
+
+<dl>
+<dt><kbd>datum dbm_fetch(DBM *<var>db</var>, datum <var>key</var>);</kbd></dt>
+<dd>`db' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は値の構造体である。該当があればメンバ `dptr' がその領域を指し、メンバ `dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。`dptr' の指す領域はハンドルに関連づけられて確保され、同じハンドルに対して次にこの関数を呼び出すか、ハンドルを閉じるまで、有効なデータを保持する。</dd>
+</dl>
+
+<p>最初のレコードのキーを得るには、関数 `dbm_firstkey' を用いる。</p>
+
+<dl>
+<dt><kbd>datum dbm_firstkey(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' はデータベースハンドルを指定する。戻り値はキーの構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。`dptr' の指す領域はハンドルに関連づけられて確保され、同じハンドルに対して次にこの関数もしくは関数 `dbm_nextkey' を呼び出すか、ハンドルを閉じるまで、有効なデータを保持する。</dd>
+</dl>
+
+<p>次レコードのキーを得るには、関数 `dbm_nextkey' を用いる。</p>
+
+<dl>
+<dt><kbd>datum dbm_nextkey(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' はデータベースハンドルを指定する。戻り値はキーの構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。`dptr' の指す領域はハンドルに関連づけられて確保され、同じハンドルに対して次にこの関数もしくは関数 `dbm_firstkey' を呼び出すか、ハンドルを閉じるまで、有効なデータを保持する。</dd>
+</dl>
+
+<p>データベースに致命的エラーが起きたかどうかを調べるには、関数 `dbm_error' を用いる。</p>
+
+<dl>
+<dt><kbd>int dbm_error(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。</dd>
+</dl>
+
+<p>関数 `dbm_clearerr' は何もしない。</p>
+
+<dl>
+<dt><kbd>int dbm_clearerr(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' はデータベースハンドルを指定する。戻り値は 0 である。この関数は互換性のためにのみ存在する。</dd>
+</dl>
+
+<p>データベースが読み込み専用かどうかを調べるには、関数 `dbm_rdonly' を用いる。</p>
+
+<dl>
+<dt><kbd>int dbm_rdonly(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' はデータベースハンドルを指定する。戻り値は読み込み専用なら真であり、そうでなければ偽である。</dd>
+</dl>
+
+<p>ディレクトリファイルのファイルディスクリプタを得るには、関数 `dbm_dirfno' を用いる。</p>
+
+<dl>
+<dt><kbd>int dbm_dirfno(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' はデータベースハンドルを指定する。戻り値はディレクトリファイルのファイルディスクリプタである。</dd>
+</dl>
+
+<p>データファイルのファイルディスクリプタを得るには、関数 `dbm_pagfno' を用いる。</p>
+
+<dl>
+<dt><kbd>int dbm_pagfno(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' はデータベースハンドルを指定する。戻り値はデータファイルのファイルディスクリプタである。</dd>
+</dl>
+
+<h3>サンプルコード</h3>
+
+<p>名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。</p>
+
+<pre>#include &lt;relic.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;sys/types.h&gt;
+#include &lt;sys/stat.h&gt;
+#include &lt;stdio.h&gt;
+#include &lt;string.h&gt;
+
+#define NAME "mikio"
+#define NUMBER "000-1234-5678"
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ DBM *db;
+ datum key, val;
+ int i;
+
+ /* データベースを開く */
+ if(!(db = dbm_open(DBNAME, O_RDWR | O_CREAT, 00644))){
+ perror("dbm_open");
+ return 1;
+ }
+
+ /* レコードを準備する */
+ key.dptr = NAME;
+ key.dsize = strlen(NAME);
+ val.dptr = NUMBER;
+ val.dsize = strlen(NUMBER);
+
+ /* レコードを格納する */
+ if(dbm_store(db, key, val, DBM_REPLACE) != 0){
+ perror("dbm_store");
+ }
+
+ /* レコードを検索する */
+ val = dbm_fetch(db, key);
+ if(val.dptr){
+ printf("Name: %s\n", NAME);
+ printf("Number: ");
+ for(i = 0; i &lt; val.dsize; i++){
+ putchar(((char *)val.dptr)[i]);
+ }
+ putchar('\n');
+ } else {
+ perror("dbm_fetch");
+ }
+
+ /* データベースを閉じる */
+ dbm_close(db);
+
+ return 0;
+}
+</pre>
+
+<h3>注記</h3>
+
+<p>Relicを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。リンカに渡すオプションは `-lndbm' ではなく `-lqdbm' である。</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>スレッド間で同時に同じハンドルにアクセスしない限りは、Relicの各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。</p>
+
+<hr />
+
+<h2><a name="reliccli" id="reliccli" class="head">Relic用コマンド</a></h2>
+
+<p>Relicに対応するコマンドラインインタフェースは以下のものである。</p>
+
+<p>コマンド `rlmgr' はRelicやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりする機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。</p>
+
+<dl>
+<dt><kbd>rlmgr create <var>name</var></kbd></dt>
+<dd>データベースファイルを作成する。</dd>
+<dt><kbd>rlmgr store [-kx] [-vx|-vf] [-insert] <var>name</var> <var>key</var> <var>val</var></kbd></dt>
+<dd>キーと値に対応するレコードを追加する。</dd>
+<dt><kbd>rlmgr delete [-kx] <var>name</var> <var>key</var></kbd></dt>
+<dd>キーに対応するレコードを削除する。</dd>
+<dt><kbd>rlmgr fetch [-kx] [-ox] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>キーに対応するレコードの値を取得して標準出力する。</dd>
+<dt><kbd>rlmgr list [-ox] <var>name</var></kbd></dt>
+<dd>データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-kx</kbd> : 2桁単位の16進数によるバイナリ表現として `key' を扱う。</li>
+<li><kbd>-vx</kbd> : 2桁単位の16進数によるバイナリ表現として `val' を扱う。</li>
+<li><kbd>-vf</kbd> : 名前が `val' のファイルのデータを値として読み込む。</li>
+<li><kbd>-insert</kbd> : 既存のレコードとキーが重複時に上書きせずにエラーにする。</li>
+<li><kbd>-ox</kbd> : 2桁単位の16進数によるバイナリ表現として標準出力を行う。</li>
+<li><kbd>-n</kbd> : 標準出力の末尾に付加される改行文字の出力を抑制する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。</p>
+
+<p>コマンド `rltest' はRelicの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `rlmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数を指定する。</p>
+
+<dl>
+<dt><kbd>rltest write <var>name</var> <var>rnum</var></kbd></dt>
+<dd>`00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。</dd>
+<dt><kbd>rltest read <var>name</var> <var>rnum</var></kbd></dt>
+<dd>上記で生成したデータベースを検索する。</dd>
+</dl>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。</p>
+
+<hr />
+
+<h2><a name="hovelapi" id="hovelapi" class="head">Hovel: GDBM互換API</a></h2>
+
+<h3>概要</h3>
+
+<p>Hovelは、GDBMと互換するAPIである。すなわち、DepotおよびCuriaの関数群をGDBMのAPIで包んだものである。Hovelを使ってGDBMのアプリケーションをQDBMに移植するのはたやすい。ほとんどの場合、インクルードするヘッダファイルを `gdbm.h' から `hovel.h' に換え、ビルドの際のリンカオプションを `-lgdbm' から `-lqdbm' に換えるだけでよい。なお、オリジナルのGDBMで生成したデータベースファイルをHovelで扱うことはできない。</p>
+
+<p>Hovelを使うためには、`hovel.h' と `stdlib.h' と `sys/types.h' と `sys/stat.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。</p>
+
+<dl>
+<dt><kbd>#include &lt;hovel.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;sys/types.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;sys/stat.h&gt;</kbd></dt>
+</dl>
+
+<p>Hovelでデータベースを扱う際には、`GDBM_FILE' 型のオブジェクト(それ自体がポインタ型)をハンドルとして用いる。ハンドルは、関数 `gdbm_open' で開き、関数 `gdbm_close' で閉じる。ハンドルのメンバを直接参照することは推奨されない。Hovelは通常はDepotのラッパーとして動作してデータベースファイルを扱うが、ハンドルを開く際に関数 `gdbm_open2' を用いることによってCuriaのラッパーとしてデータベースディレクトリを扱うようにすることができる。</p>
+
+<h3>API</h3>
+
+<p>データの格納、削除、検索に用いる関数とのデータの授受には、キーと値を表現するのに `datum' 型の構造体を用いる。</p>
+
+<dl>
+<dt><kbd>typedef struct { char *dptr; size_t dsize; } datum;</kbd></dt>
+<dd>`dptr' はデータ領域へのポインタである。`dsize' はデータ領域のサイズである。</dd>
+</dl>
+
+<p>外部変数 `gdbm_version' はバージョン情報の文字列である。</p>
+
+<dl>
+<dt><kbd>extern char *gdbm_version;</kbd></dt>
+<dd>この変数の指す領域は書き込み禁止である。</dd>
+</dl>
+
+<p>外部変数 `gdbm_errno' には直前のエラーコードが記録される。エラーコードの詳細については `hovel.h' を参照すること。</p>
+
+<dl>
+<dt><kbd>extern gdbm_error gdbm_errno;</kbd></dt>
+<dd>この変数の初期値は `GDBM_NO_ERROR' である。その他の値として、`GDBM_MALLOC_ERROR'、`GDBM_BLOCK_SIZE_ERROR'、`GDBM_FILE_OPEN_ERROR'、`GDBM_FILE_WRITE_ERROR'、`GDBM_FILE_SEEK_ERROR'、`GDBM_FILE_READ_ERROR'、`GDBM_BAD_MAGIC_NUMBER'、`GDBM_EMPTY_DATABASE'、`GDBM_CANT_BE_READER'、`GDBM_CANT_BE_WRITER'、`GDBM_READER_CANT_DELETE'、`GDBM_READER_CANT_STORE'、`GDBM_READER_CANT_REORGANIZE'、`GDBM_UNKNOWN_UPDATE'、`GDBM_ITEM_NOT_FOUND'、`GDBM_REORGANIZE_FAILED'、`GDBM_CANNOT_REPLACE'、`GDBM_ILLEGAL_DATA'、`GDBM_OPT_ALREADY_SET' および `GDBM_OPT_ILLEGAL' がある。</dd>
+</dl>
+
+<p>エラーコードに対応するメッセージ文字列を得るには、関数 `gdbm_strerror' を用いる。</p>
+
+<dl>
+<dt><kbd>char *gdbm_strerror(gdbm_error <var>gdbmerrno</var>);</kbd></dt>
+<dd>`gdbmerrno' はエラーコードを指定する。戻り値はエラーメッセージの文字列であり、その領域は書き込み禁止領域である。</dd>
+</dl>
+
+<p>GDBM流にデータベースのハンドルを作成するには、関数 `gdbm_open' を用いる。</p>
+
+<dl>
+<dt><kbd>GDBM_FILE gdbm_open(char *<var>name</var>, int <var>block_size</var>, int <var>read_write</var>, int <var>mode</var>, void (*<var>fatal_func</var>)(void));</kbd></dt>
+<dd>`name' はデータベースの名前を指定する。`block_size' は無視される。`read_write' は接続モードを指定し、`GDBM_READER' ならリーダ、`GDBM_WRITER' と `GDBM_WRCREAT' と `GDBM_NEWDB' ならライタとなる。`GDBM_WRCREAT' の場合はデータベースが存在しなければ作成し、`GDBM_NEWDB' の場合は既に存在していても新しいデータベースを作成する。ライタに対しては、`GDBM_SYNC' か `GDBM_NOLOCK' か `GDBM_LOCKNB' か `GDBM_FAST' か `GDBM_SPARSE' とのビット論理和にすることができる。`GDBM_SYNC' は全てのデータベース操作をディスクと同期させ、`GDBM_NOLOCK' はファイルロックを伴わずにデータベースを開き、`GDBM_LOCKNB' はブロックなしのロックを行い、`GDBM_FAST' は無視される。`GDBM_SPARSE' はQDBM独自のものであり、作成するファイルをスパースにする。`mode' は `open' コールに渡すものと同じでファイルのモードを指定する。`fatal_func' は無視される。戻り値は正常ならデータベースハンドルであり、エラーなら `NULL' である。</dd>
+</dl>
+
+<p>QDBM流にデータベースのハンドルを作成するには、関数 `gdbm_open2' を用いる。</p>
+
+<dl>
+<dt><kbd>GDBM_FILE gdbm_open2(char *<var>name</var>, int <var>read_write</var>, int <var>mode</var>, int <var>bnum</var>, int <var>dnum</var>, int <var>align</var>);</kbd></dt>
+<dd>`name' はデータベースの名前を指定する。`read_write' は接続モードを指定し、`GDBM_READER' ならリーダ、`GDBM_WRITER' と `GDBM_WRCREAT' と `GDBM_NEWDB' ならライタとなる。`GDBM_WRCREAT' の場合はデータベースが存在しなければ作成し、`GDBM_NEWDB' の場合は既に存在していても新しいデータベースを作成する。ライタに対しては、`GDBM_SYNC' か `GDBM_NOLOCK' か `GDBM_LOCKNB' か `GDBM_FAST' か `GDBM_SPARSE' とのビット論理和にすることができる。`GDBM_SYNC' は全てのデータベース操作をディスクと同期させ、`GDBM_NOLOCK' はファイルロックを伴わずにデータベースを開き、`GDBM_LOCKNB' はブロックなしのロックを行い、`GDBM_FAST' は無視される。`GDBM_SPARSE' はQDBM独自のものであり、作成するファイルをスパースにする。`mode' は `open' コールもしくは `mkdir' コールに渡すものと同じでファイルやディレクトリのモードを指定する。`bnum' はバケット配列の要素数の目安を指定するが、0 以下ならデフォルト値が使われる。`dnum' は要素データベースの数を指定するが、0 以下なら返されるハンドルはDepotのラッパーとして生成され、そうでなければCuriaのラッパーになる。`align' はアラインメントの基本サイズを指定する。戻り値は正常ならデータベースハンドルであり、エラーなら `NULL' である。既にデータベースが存在する場合、それがDepotのものかCuriaのものかが自動的に判断される。</dd>
+</dl>
+
+<p>データベースとの接続を閉じてハンドルを破棄するには、関数 `gdbm_close' を用いる。</p>
+
+<dl>
+<dt><kbd>void gdbm_close(GDBM_FILE <var>dbf</var>);</kbd></dt>
+<dd>`dbf' はデータベースハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。</dd>
+</dl>
+
+<p>レコードを追加するには、関数 `gdbm_store' を用いる。</p>
+
+<dl>
+<dt><kbd>int gdbm_store(GDBM_FILE <var>dbf</var>, datum <var>key</var>, datum <var>content</var>, int <var>flag</var>);</kbd></dt>
+<dd>`dbf' はライタで接続したデータベースハンドルを指定する。`key' はキーの構造体を指定する。`content' は値の構造体を指定する。`frags' が `GDBM_INSERT' ならキーの重複時に書き込みを断念し、`GDBM_REPLACE' なら上書きを行う。戻り値は正常なら 0 、重複での断念なら 1 、その他のエラーなら -1 である。</dd>
+</dl>
+
+<p>レコードを削除するには、関数 `gdbm_delete' を用いる。</p>
+
+<dl>
+<dt><kbd>int gdbm_delete(GDBM_FILE <var>dbf</var>, datum <var>key</var>);</kbd></dt>
+<dd>`dbf' はライタで接続したデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は正常なら 0 、エラーなら -1 である。</dd>
+</dl>
+
+<p>レコードを取得するには、関数 `gdbm_fetch' を用いる。</p>
+
+<dl>
+<dt><kbd>datum gdbm_fetch(GDBM_FILE <var>dbf</var>, datum <var>key</var>);</kbd></dt>
+<dd>`dbf' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は値の構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。戻り値のメンバ `dptr' の指す領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>レコードが存在するか調べるには、関数 `gdbm_exists' を用いる。</p>
+
+<dl>
+<dt><kbd>int gdbm_exists(GDBM_FILE <var>dbf</var>, datum <var>key</var>);</kbd></dt>
+<dd>`dbf' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は該当があれば真であり、該当がない場合やエラーの場合は偽である。</dd>
+</dl>
+
+<p>最初のレコードのキーを得るには、関数 `gdbm_firstkey' を用いる。</p>
+
+<dl>
+<dt><kbd>datum gdbm_firstkey(GDBM_FILE <var>dbf</var>);</kbd></dt>
+<dd>`dbf' はデータベースハンドルを指定する。戻り値はキーの構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。戻り値のメンバ `dptr' の指す領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>次のレコードのキーを得るには、関数 gdbm_nextkey を用いる。</p>
+
+<dl>
+<dt><kbd>datum gdbm_nextkey(GDBM_FILE <var>dbf</var>, datum <var>key</var>);</kbd></dt>
+<dd>`dbf' はデータベースハンドルを指定する。`key' は無視される。戻り値はキーの構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。戻り値のメンバ `dptr' の指す領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>データベースを更新した内容をファイルとデバイスに同期させるには、関数 `gdbm_sync' を用いる。</p>
+
+<dl>
+<dt><kbd>void gdbm_sync(GDBM_FILE <var>dbf</var>);</kbd></dt>
+<dd>`dbf' はライタで接続したデータベースハンドルを指定する。</dd>
+</dl>
+
+<p>データベースを最適化するには、関数 `gdbm_reorganize' を用いる。</p>
+
+<dl>
+<dt><kbd>int gdbm_reorganize(GDBM_FILE <var>dbf</var>);</kbd></dt>
+<dd>`dbf' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら 0 であり、エラーなら -1 である。</dd>
+</dl>
+
+<p>データベースファイルのファイルディスクリプタを得るには、関数 `gdbm_fdesc' を用いる。</p>
+
+<dl>
+<dt><kbd>int gdbm_fdesc(GDBM_FILE <var>dbf</var>);</kbd></dt>
+<dd>`dbf' はデータベースハンドルを指定する。戻り値はデータベースファイルのファイルディスクリプタである。データベースがディレクトリなら、戻り値は -1 である。</dd>
+</dl>
+
+<p>関数 `gdbm_setopt' は何もしない。</p>
+
+<dl>
+<dt><kbd>int gdbm_setopt(GDBM_FILE <var>dbf</var>, int <var>option</var>, int *<var>value</var>, int <var>size</var>);</kbd></dt>
+<dd>`dbf' はデータベースハンドルを指定する。`option' は無視される。`value' は無視される。`size' は無視される。戻り値は 0 である。この関数は互換性のためにのみ存在する。</dd>
+</dl>
+
+<h3>サンプルコード</h3>
+
+<p>名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。</p>
+
+<pre>#include &lt;hovel.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;sys/types.h&gt;
+#include &lt;sys/stat.h&gt;
+#include &lt;stdio.h&gt;
+#include &lt;string.h&gt;
+
+#define NAME "mikio"
+#define NUMBER "000-1234-5678"
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ GDBM_FILE dbf;
+ datum key, val;
+ int i;
+
+ /* データベースを開く */
+ if(!(dbf = gdbm_open(DBNAME, 0, GDBM_WRCREAT, 00644, NULL))){
+ fprintf(stderr, "gdbm_open: %s\n", gdbm_strerror(gdbm_errno));
+ return 1;
+ }
+
+ /* レコードを準備する */
+ key.dptr = NAME;
+ key.dsize = strlen(NAME);
+ val.dptr = NUMBER;
+ val.dsize = strlen(NUMBER);
+
+ /* レコードを格納する */
+ if(gdbm_store(dbf, key, val, GDBM_REPLACE) != 0){
+ fprintf(stderr, "gdbm_store: %s\n", gdbm_strerror(gdbm_errno));
+ }
+
+ /* レコードを検索する */
+ val = gdbm_fetch(dbf, key);
+ if(val.dptr){
+ printf("Name: %s\n", NAME);
+ printf("Number: ");
+ for(i = 0; i &lt; val.dsize; i++){
+ putchar(val.dptr[i]);
+ }
+ putchar('\n');
+ free(val.dptr);
+ } else {
+ fprintf(stderr, "gdbm_fetch: %s\n", gdbm_strerror(gdbm_errno));
+ }
+
+ /* データベースを閉じる */
+ gdbm_close(dbf);
+
+ return 0;
+}
+</pre>
+
+<h3>注記</h3>
+
+<p>Hovelを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。リンカに渡すオプションは `-lgdbm' ではなく `-lqdbm' である。</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>POSIXスレッドを有効にしてQDBMをビルドした場合、外部変数 `gdbm_errno' はスレッド固有データへの参照として扱われ、Hovelの各関数はリエントラントになる。その場合、スレッド間で同時に同じハンドルにアクセスしない限りは、各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。</p>
+
+<hr />
+
+<h2><a name="hovelcli" id="hovelcli" class="head">Hovel用コマンド</a></h2>
+
+<p>Hovelに対応するコマンドラインインタフェースは以下のものである。</p>
+
+<p>コマンド `hvmgr' はHovelやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりす機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。</p>
+
+<dl>
+<dt><kbd>hvmgr [-qdbm bnum dnum] [-s] create <var>name</var></kbd></dt>
+<dd>データベースファイルを作成する。</dd>
+<dt><kbd>hvmgr store [-qdbm] [-kx] [-vx|-vf] [-insert] <var>name</var> <var>key</var> <var>val</var></kbd></dt>
+<dd>キーと値に対応するレコードを追加する。</dd>
+<dt><kbd>hvmgr delete [-qdbm] [-kx] <var>name</var> <var>key</var></kbd></dt>
+<dd>キーに対応するレコードを削除する。</dd>
+<dt><kbd>hvmgr fetch [-qdbm] [-kx] [-ox] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>キーに対応するレコードの値を取得して標準出力する。</dd>
+<dt><kbd>hvmgr list [-qdbm] [-ox] <var>name</var></kbd></dt>
+<dd>データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。</dd>
+<dt><kbd>hvmgr optimize [-qdbm] <var>name</var></kbd></dt>
+<dd>データベースを最適化する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-qdbm [<var>bnum</var> <var>dnum</var>]</kbd> : `gdbm_open2' でデータベースを開く。`bnum' と `dnum' はバケット配列の要素数とデータベースの分割数を指定する。</li>
+<li><kbd>-s</kbd> : ファイルをスパースにする。</li>
+<li><kbd>-kx</kbd> : 2桁単位の16進数によるバイナリ表現として `key' を扱う。</li>
+<li><kbd>-vx</kbd> : 2桁単位の16進数によるバイナリ表現として `val' を扱う。</li>
+<li><kbd>-vf</kbd> : 名前が `val' のファイルのデータを値として読み込む。</li>
+<li><kbd>-insert</kbd> : 既存のレコードとキーが重複時に上書きせずにエラーにする。</li>
+<li><kbd>-ox</kbd> : 2桁単位の16進数によるバイナリ表現として標準出力を行う。</li>
+<li><kbd>-n</kbd> : 標準出力の末尾に付加される改行文字の出力を抑制する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。</p>
+
+<p>コマンド `hvtest' はHovelの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `hvmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数を指定する。</p>
+
+<dl>
+<dt><kbd>hvtest write [-qdbm] [-s] <var>name</var> <var>rnum</var></kbd></dt>
+<dd>`00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。</dd>
+<dt><kbd>hvtest read [-qdbm] <var>name</var> <var>rnum</var></kbd></dt>
+<dd>上記で生成したデータベースを検索する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-qdbm</kbd> : `gdbm_open2' を用いてCuriaのハンドルを開く。</li>
+<li><kbd>-s</kbd> : ファイルをスパースにする。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。</p>
+
+<hr />
+
+<h2><a name="cabinapi" id="cabinapi" class="head">Cabin: ユーティリティAPI</a></h2>
+
+<h3>概要</h3>
+
+<p>Cabinはメモリ上で簡単にレコードを扱うためのメモリ確保関数や整列関数や拡張可能なデータや配列リストやハッシュマップやヒープ配列など提供するユーティリティのAPIである。MIMEやCSVやXMLを解析する機能や、各種の符号化と復号を行う機能も備える。</p>
+
+<p>Cabinを使うためには、`cabin.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。</p>
+
+<dl>
+<dt><kbd>#include &lt;cabin.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+</dl>
+
+<p>拡張可能なデータを扱う際には、`CBDATUM' 型へのポインタをハンドルとして用いる。データハンドルは、関数 `cbdatumopen' で開き、関数 `cbdatumclose' で閉じる。リストを扱う際には、`CBLIST' 型へのポインタをハンドルとして用いる。リストハンドルは、関数 `cblistopen' で開き、関数 `cblistclose' で閉じる。マップを扱う際には、`CBMAP' 型へのポインタをハンドルとして用いる。マップハンドルは、関数 `cbmapopen' で開き、関数 `cbmapclose' で閉じる。ヒープ配列を扱う際には `CBHEAP' 型へのポインタをハンドルとして用いる。ヒープハンドルは関数 `cbheapopen' で開き、関数 `cbheapclose' で閉じる。各ハンドルのメンバを直接参照することは推奨されない。
+
+</p>
+
+<h3>API</h3>
+
+<p>外部変数 `cbfatalfunc' は致命的エラーをハンドリングするコールバック関数である。</p>
+
+<dl>
+<dt><kbd>extern void (*cbfatalfunc)(const char *<var>message</var>);</kbd></dt>
+<dd>引数はエラーメッセージを指定する。この変数の初期値は `NULL' であり、`NULL' ならば致命的エラーの発生時にはデフォルトの関数が呼ばれる。致命的エラーはメモリの割り当てに失敗した際に起こる。</dd>
+</dl>
+
+<p>メモリ上に領域を確保するには、関数 `cbmalloc' を用いる。</p>
+
+<dl>
+<dt><kbd>void *cbmalloc(size_t <var>size</var>);</kbd></dt>
+<dd>`size' は領域のサイズを指定する。戻り値は確保した領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>メモリ上の領域を再確保するには、関数 `cbrealloc' を用いる。</p>
+
+<dl>
+<dt><kbd>void *cbrealloc(void *<var>ptr</var>, size_t <var>size</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`size' は領域のサイズを指定する。戻り値は再確保した領域へのポインタである。戻り値の領域は `remalloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>メモリ上の領域を複製するには、関数 `cbmemdup' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbmemdup(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`size' は領域のサイズを指定する。戻り値は再確保した領域へのポインタである。戻り値は複製の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>メモリ上の開放を解放するには、関数 `cbfree' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbfree(void *<var>ptr</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定するが、`NULL' の場合は何もしない。この関数は `free' のラッパーに過ぎないが、`malloc' シリーズの別のパッケージを使うアプリケーションにおいてQDBMが確保した領域を解放するのに便利である。</dd>
+</dl>
+
+<p>オブジェクトのポインタかハンドルをグローバルガベージコレクタに登録するには、関数 `cbglobalgc' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbglobalgc(void *<var>ptr</var>, void (*<var>func</var>)(void *));</kbd></dt>
+<dd>`ptr' はオブジェクトのポインタかハンドルを指定する。`func' はオブジェクトのリソースを解放する関数を指定する。その引数は解放するオブジェクトのポインタかハンドルである。この関数は、`main' 関数がリターンするか `exit' 関数が呼ばれてプロセスが正常終了する際に、オブジェクトのリソースが解放されることを保証する。</dd>
+</dl>
+
+<p>グローバルガベージコレクタを明示的に発動させるには、関数 `cbggcsweep' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbggcsweep(void);</kbd></dt>
+<dd>この関数を呼んだ後はグローバルガベージコレクタに登録してあったオブジェクトは利用することができなくなることに注意すること。グローバルガベージコレクタは初期化されるので、新しいオブジェクトを入れることができるようになる。</dd>
+</dl>
+
+<p>仮想メモリの割り当て可能性を調べるには、関数 `cbvmemavail' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbvmemavail(size_t <var>size</var>);</kbd></dt>
+<dd>`size' は新たに割り当て可能であるべき領域のサイズを指定する。戻り値は割り当てが可能であれば真、そうでなければ偽である。</dd>
+</dl>
+
+<p>配列の各要素を挿入ソートで整列させるには、関数 `cbisort' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbisort(void *<var>base</var>, int <var>nmemb</var>, int <var>size</var>, int(*<var>compar</var>)(const void *, const void *));</kbd></dt>
+<dd>`base' は配列のポインタを指定する。`nmemb' は配列の要素数を指定する。`size' は各要素のサイズを指定する。`compar' は比較関数を指定する。二つの引数は要素へのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。挿入ソートは、ほとんどの要素が既に整列済みの場合にのみ有用である。</dd>
+</dl>
+
+<p>配列の各要素をシェルソートで整列させるには、関数 `cbssort' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbssort(void *<var>base</var>, int <var>nmemb</var>, int <var>size</var>, int(*<var>compar</var>)(const void *, const void *));</kbd></dt>
+<dd>`base' は配列のポインタを指定する。`nmemb' は配列の要素数を指定する。`size' は各要素のサイズを指定する。`compar' は比較関数を指定する。二つの引数は要素へのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。ほとんどの要素が整列済みの場合、シェルソートの方がヒープソートやクイックソートより速いかもしれない。</dd>
+</dl>
+
+<p>配列の各要素をヒープソートで整列させるには、関数 `cbhsort' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbhsort(void *<var>base</var>, int <var>nmemb</var>, int <var>size</var>, int(*<var>compar</var>)(const void *, const void *));</kbd></dt>
+<dd>`base' は配列のポインタを指定する。`nmemb' は配列の要素数を指定する。`size' は各要素のサイズを指定する。`compar' は比較関数を指定する。二つの引数は要素へのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。ヒープソートは入力の偏りに対して頑丈であるが、ほとんどの場合でクイックソートの方が速い。</dd>
+</dl>
+
+<p>配列の各要素をクイックソートで整列させるには、関数 `cbqsort' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbqsort(void *<var>base</var>, int <var>nmemb</var>, int <var>size</var>, int(*<var>compar</var>)(const void *, const void *));</kbd></dt>
+<dd>`base' は配列のポインタを指定する。`nmemb' は配列の要素数を指定する。`size' は各要素のサイズを指定する。`compar' は比較関数を指定する。二つの引数は要素へのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。入力の偏りに敏感ではあるが、クイックソートは最速の整列アルゴリズムである。</dd>
+</dl>
+
+<p>大文字と小文字の違いを無視して二つの文字列を比較するには、関数 `cbstricmp' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbstricmp(const char *<var>astr</var>, const char *<var>bstr</var>);</kbd></dt>
+<dd>`astr' は一方の文字列へのポインタを指定する。`bstr' は他方の文字列へのポインタを指定する。戻り値は前者が大きければ正、後者が大きければ負、両者が等価なら 0 である。ASCIIコード中のアルファベットの大文字と小文字は区別されない。</dd>
+</dl>
+
+<p>文字列があるキーで始まっているか調べるには、関数 `cbstrfwmatch' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbstrfwmatch(const char *<var>str</var>, const char *<var>key</var>);</kbd></dt>
+<dd>`str' は対象の文字列へのポインタを指定する。`key' は前方一致のキーの文字列へのポインタを指定する。戻り値は対象の文字列がキーで始まっていれば真、そうでなければ偽である。</dd>
+</dl>
+
+<p>大文字と小文字の違いを無視しつつ、文字列があるキーで始まっているか調べるには、関数 `cbstrfwimatch' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbstrfwimatch(const char *<var>str</var>, const char *<var>key</var>);</kbd></dt>
+<dd>`str' は対象の文字列へのポインタを指定する。`key' は前方一致のキーの文字列へのポインタを指定する。戻り値は対象の文字列がキーで始まっていれば真、そうでなければ偽である。ASCIIコード中のアルファベットの大文字と小文字は区別されない。</dd>
+</dl>
+
+<p>文字列があるキーで終っているか調べるには、関数 `cbstrbwmatch' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbstrbwmatch(const char *<var>str</var>, const char *<var>key</var>);</kbd></dt>
+<dd>`str' は対象の文字列へのポインタを指定する。`key' は後方一致のキーの文字列へのポインタを指定する。戻り値は対象の文字列がキーで終っていれば真、そうでなければ偽である。</dd>
+</dl>
+
+<p>大文字と小文字の違いを無視しつつ、文字列があるキーで終っているか調べるには、関数 `cbstrbwimatch' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbstrbwimatch(const char *<var>str</var>, const char *<var>key</var>);</kbd></dt>
+<dd>`str' は対象の文字列へのポインタを指定する。`key' は後方一致のキーの文字列へのポインタを指定する。戻り値は対象の文字列がキーで終っていれば真、そうでなければ偽である。ASCIIコード中のアルファベットの大文字と小文字は区別されない。</dd>
+</dl>
+
+<p>KMP法を用いて文字列の部分文字列の位置を得るには、関数 `cbstrstrkmp' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbstrstrkmp(const char *<var>haystack</var>, const char *<var>needle</var>);</kbd></dt>
+<dd>`haystack' は文字列へのポインタを指定する。`needle' は探すべき部分文字列へのポインタを指定する。戻り値は部分文字列の開始を指すポインタか、見つからなければ `NULL' である。大抵の場合、この関数よりコンパイラのビルドインである `strstr' の方が高速である。</dd>
+</dl>
+
+<p>BM法を用いて文字列の部分文字列の位置を得るには、関数 `cbstrstrbm' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbstrstrbm(const char *<var>haystack</var>, const char *<var>needle</var>);</kbd></dt>
+<dd>`haystack' は文字列へのポインタを指定する。`needle' は探すべき部分文字列へのポインタを指定する。戻り値は部分文字列の開始を指すポインタか、見つからなければ `NULL' である。大抵の場合、この関数よりコンパイラのビルドインである `strstr' の方が高速である。</dd>
+</dl>
+
+<p>文字列の全ての文字を大文字に変換するには、関数 `cbstrtoupper' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbstrtoupper(char *<var>str</var>);</kbd></dt>
+<dd>`str' は変換対象の文字列へのポインタを指定する。戻り値はその文字列へのポインタである。</dd>
+</dl>
+
+<p>文字列の全ての文字を小文字に変換するには、関数 `cbstrtolower' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbstrtolower(char *<var>str</var>);</kbd></dt>
+<dd>`str' は変換対象の文字列へのポインタを指定する。戻り値はその文字列へのポインタである。</dd>
+</dl>
+
+<p>文字列の先頭と末尾にある空白文字を削除するには、関数 `cbstrtrim' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbstrtrim(char *<var>str</var>);</kbd></dt>
+<dd>`str' は変換対象の文字列へのポインタを指定する。戻り値はその文字列へのポインタである。</dd>
+</dl>
+
+<p>文字列内の連続する空白を絞って整形するには、関数 `cbstrsqzcpc' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbstrsqzspc(char *<var>str</var>);</kbd></dt>
+<dd>`str' は変換対象の文字列へのポインタを指定する。戻り値はその文字列へのポインタである。</dd>
+</dl>
+
+<p>UTF-8の文字列に含まれる文字数を数えるには、関数 `cbstrcountutf' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbstrcountutf(const char *<var>str</var>);</kbd></dt>
+<dd>`str' はUTF-8の文字列へのポインタを指定する。戻り値はその文字列に含まれる文字数である。</dd>
+</dl>
+
+<p>UTF-8の文字列を指定した文字数で切るには、関数 `cbstrcututf' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbstrcututf(char *<var>str</var>, int <var>num</var>);</kbd></dt>
+<dd>`str' はUTF-8の文字列へのポインタを指定する。`num' は保持する文字数を指定する。戻り値はその文字列へのポインタである。</dd>
+</dl>
+
+<p>データハンドルを作成するには、関数 `cbdatumopen' を用いる。</p>
+
+<dl>
+<dt><kbd>CBDATUM *cbdatumopen(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' は初期内容の領域へのポインタを指定するか、`NULL' なら空のデータを作成する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値はデータハンドルである。</dd>
+</dl>
+
+<p>データを複製するには、関数 `cbdatumdup' を用いる。</p>
+
+<dl>
+<dt><kbd>CBDATUM *cbdatumdup(const CBDATUM *<var>datum</var>);</kbd></dt>
+<dd>`datum' はデータハンドルを指定する。戻り値は新しいデータハンドルである。</dd>
+</dl>
+
+<p>データハンドルを破棄するには、関数 `cbdatumclose' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbdatumclose(CBDATUM *<var>datum</var>);</kbd></dt>
+<dd>`datum' はデータハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。</dd>
+</dl>
+
+<p>データに別の領域を連結するには、関数 `cbdatumcat' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbdatumcat(CBDATUM *<var>datum</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`datum' はデータハンドルを指定する。`ptr' は連結する領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。</dd>
+</dl>
+
+<p>データの領域へのポインタを得るには、関数 `cbdatumptr' を用いる。</p>
+
+<dl>
+<dt><kbd>const char *cbdatumptr(const CBDATUM *<var>datum</var>);</kbd></dt>
+<dd>`datum' はデータハンドルを指定する。戻り値はデータの領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。</dd>
+</dl>
+
+<p>データの領域のサイズを得るには、関数 `cbdatumsize' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbdatumsize(const CBDATUM *<var>datum</var>);</kbd></dt>
+<dd>`datum' はデータハンドルを指定する。戻り値はデータの領域のサイズである。</dd>
+</dl>
+
+<p>データの領域のサイズを変更するには、関数 `cbdatumsetsize' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbdatumsetsize(CBDATUM *<var>datum</var>, int <var>size</var>);</kbd></dt>
+<dd>`datum' はデータハンドルを指定する。`size' は領域の新しいサイズを指定する。新しいサイズが既存のサイズより大きい場合、余った領域は終端文字で埋められる。</dd>
+</dl>
+
+<p>データに書式出力を行なうには、関数 `cbdatumprintf' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbdatumprintf(CBDATUM *<var>datum</var>, const char *<var>format</var>, ...);</kbd></dt>
+<dd>`format' はprintf風の書式文字列を指定する。変換文字 `%' を `s'、`d'、`o'、`u'、`x'、`X'、`c'、`e'、`E'、`f'、`g'、`G'、`@'、`?'、`:'、`%' と併せて利用することができる。`@' は `s' と同様に機能するが、XMLのメタ文字をエスケープする。`?' は `s' と同様に機能するが、URLのメタ文字をエスケープする。`:' は `s' と同様に機能するが、UTF-8としてのMIMEエンコーディングを施す。それ以外の変換文字は元来のものと同様に機能する。</dd>
+</dl>
+
+<p>データを確保された領域に変換するには、関数 `cbdatumtomalloc' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbdatumtomalloc(CBDATUM *<var>datum</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`datum' はデータハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値はデータの領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。元のデータの領域は解放されるので、それを再び解放してはならない。</dd>
+</dl>
+
+<p>リストハンドルを作成するには、関数 `cblistopen' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *cblistopen(void);</kbd></dt>
+<dd>戻り値はリストハンドルである。</dd>
+</dl>
+
+<p>リストを複製するには、関数 `cblistdup' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *cblistdup(const CBLIST *<var>list</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。戻り値は新しいリストハンドルである。</dd>
+</dl>
+
+<p>リストハンドルを破棄するには、関数 `cblistclose' を用いる。</p>
+
+<dl>
+<dt><kbd>void cblistclose(CBLIST *<var>list</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。</dd>
+</dl>
+
+<p>リストに格納された要素数を得るには、関数 `cblistnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int cblistnum(const CBLIST *<var>list</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。戻り値はリストに格納された要素数である。</dd>
+</dl>
+
+<p>リスト内のある要素の領域へのポインタを得るには、関数 `cblistval' を用いる。</p>
+
+<dl>
+<dt><kbd>const char *cblistval(const CBLIST *<var>list</var>, int <var>index</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。`index' は取り出す要素のインデックスを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は該当要素の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。`index' が要素数以上ならば、戻り値は `NULL' である。</dd>
+</dl>
+
+<p>要素をリストの末尾に加えるには、関数 `cblistpush' を用いる。</p>
+
+<dl>
+<dt><kbd>void cblistpush(CBLIST *<var>list</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。`ptr' は追加する要素の領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。</dd>
+</dl>
+
+<p>リストの末尾の要素を削除するには、関数 `cblistpop' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cblistpop(CBLIST *<var>list</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は該当要素の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。リストが空ならば、戻り値は `NULL' である。</dd>
+</dl>
+
+<p>要素をリストの先頭に加えるには、関数 `cblistunshift' を用いる。</p>
+
+<dl>
+<dt><kbd>void cblistunshift(CBLIST *<var>list</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。`ptr' は追加する要素の領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。</dd>
+</dl>
+
+<p>リストの先頭の要素を削除するには、関数 `cblistshift' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cblistshift(CBLIST *<var>list</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は該当要素の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。リストが空ならば、戻り値は `NULL' である。</dd>
+</dl>
+
+<p>リスト内の指定した位置に要素を加えるには、関数 `cblistinsert' を用いる。</p>
+
+<dl>
+<dt><kbd>void cblistinsert(CBLIST *<var>list</var>, int <var>index</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。`index' は追加する要素のインデックスを指定する。`ptr' は追加する要素の領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。</dd>
+</dl>
+
+<p>リスト内の指定した位置の要素を削除するには、関数 `cblistremove' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cblistremove(CBLIST *<var>list</var>, int <var>index</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。`index' は削除する要素のインデックスを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は該当要素の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。`index' が要素数以上ならば、要素は削除されず、戻り値は `NULL' である。</dd>
+</dl>
+
+<p>リスト内の指定した位置の要素を上書きするには、関数 `cblistover' を用いる。</p>
+
+<dl>
+<dt><kbd>void cblistover(CBLIST *<var>list</var>, int <var>index</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。`index' は削除する要素のインデックスを指定する。`ptr' は新しい内容の領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。`index' が要素数以上ならば、この関数は何もしない。</dd>
+</dl>
+
+<p>リストの要素を辞書順で整列させるには、関数 `cblistsort' を用いる。</p>
+
+<dl>
+<dt><kbd>void cblistsort(CBLIST *<var>list</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。整列にはクイックソートが用いられる。</dd>
+</dl>
+
+<p>リストの要素を線形探索を使って検索するには、関数 `cblistlsearch' を用いる。</p>
+
+<dl>
+<dt><kbd>int cblistlsearch(const CBLIST *<var>list</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。`ptr' は検索キーの領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。戻り値は該当の要素のインデックスであるが、該当がなければ -1 である。複数の要素が該当した場合、前者が返される。</dd>
+</dl>
+
+<p>リストの要素を二分探索を使って検索するには、関数 `cblistbsearch' を用いる。</p>
+
+<dl>
+<dt><kbd>int cblistbsearch(const CBLIST *<var>list</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。リストは辞書順にソートされている必要がある。`ptr' は検索キーの領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。戻り値は該当の要素のインデックスであるが、該当がなければ -1 である。複数の要素が該当した場合にどちらが返るかは未定義である。</dd>
+</dl>
+
+<p>リストを直列化してバイト配列にするには、関数 `cblistdump' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cblistdump(const CBLIST *<var>list</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`list' はリストハンドルを指定する。`sp' は抽出した領域のサイズを格納する領域へのポインタを指定する。戻り値は直列化された領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>直列化されたリストを復元するには、関数 `cblistload' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *cblistload(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' はバイト配列へのポインタを指定する。`size' はその領域のサイズを指定する。戻り値は新しいリストハンドルである。</dd>
+</dl>
+
+<p>マップハンドルを作成するには、関数 `cbmapopen' を用いる。</p>
+
+<dl>
+<dt><kbd>CBMAP *cbmapopen(void);</kbd></dt>
+<dd>戻り値はマップハンドルである。</dd>
+</dl>
+
+<p>マップを複製するには、関数 `cbmapdup' を用いる。</p>
+
+<dl>
+<dt><kbd>CBMAP *cbmapdup(CBMAP *<var>map</var>);</kbd></dt>
+<dd>`map' はマップハンドルを指定する。戻り値は新しいマップハンドルである。コピー元のマップのイテレータは初期化される。</dd>
+</dl>
+
+<p>マップハンドルを破棄するには、関数 `cbmapclose' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbmapclose(CBMAP *<var>map</var>);</kbd></dt>
+<dd>`map' はマップハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。</dd>
+</dl>
+
+<p>マップにレコードを追加するには、関数 `cbmapput' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbmapput(CBMAP *<var>map</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const char *<var>vbuf</var>, int <var>vsiz</var>, int <var>over</var>);</kbd></dt>
+<dd>`map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`over' は重複したレコードを上書きするか否かを指定する。`over' が偽でキーが重複した場合は戻り値は偽であるが、そうでない場合は真である。</dd>
+</dl>
+
+<p>既存のレコードの値の末尾に値を連結するには、関数 `cbmapputcat' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbmapputcat(CBMAP *<var>map</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const char *<var>vbuf</var>, int <var>vsiz</var>);</kbd></dt>
+<dd>`map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。該当のレコードが存在しない場合は新しいレコードが作られる。</dd>
+</dl>
+
+<p>マップのレコードを削除するには、関数 `cbmapout' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbmapout(CBMAP *<var>map</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、該当のレコードがない場合は偽である。</dd>
+</dl>
+
+<p>マップのレコードを取得するには、関数 `cbmapget' を用いる。</p>
+
+<dl>
+<dt><kbd>const char *cbmapget(const CBMAP *<var>map</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、該当のレコードがない場合は `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。</dd>
+</dl>
+
+<p>レコードをマップの端に移動させるには、関数 `cbmapmove' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbmapmove(CBMAP *<var>map</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>head</var>);</kbd></dt>
+<dd>`map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`head' は移動先を指定し、真なら先頭、偽なら末尾となる。戻り値は正常なら真であり、該当のレコードがない場合は偽である。</dd>
+</dl>
+
+<p>マップのイテレータを初期化するには、関数 `cbmapiterinit' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbmapiterinit(CBMAP *<var>map</var>);</kbd></dt>
+<dd>`map' はマップハンドルを指定する。イテレータは、マップに格納された全てのレコードを参照するために用いられる。</dd>
+</dl>
+
+<p>マップのイテレータから次のレコードのキーを取り出すには、関数 `cbmapiternext' を用いる。</p>
+
+<dl>
+<dt><kbd>const char *cbmapiternext(CBMAP *<var>map</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`map' はマップハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常ならキーを格納した領域へのポインタであり、エラーなら `NULL' である。イテレータが最後まできて該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。取り出す順番は格納した際の順番に一致することが保証されている。</dd>
+</dl>
+
+<p>マップのイテレータから取り出したキーに対応する値を取り出すには、関数 `cbmapiterval' を用いる。</p>
+
+<dl>
+<dt><kbd>const char *cbmapiterval(const char *<var>kbuf</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`kbuf' はイテレータから取り出したキーの領域へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は値を格納した領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。</dd>
+</dl>
+
+<p>マップのレコード数を得るには、関数 `cbmaprnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbmaprnum(const CBMAP *<var>map</var>);</kbd></dt>
+<dd>`map' はマップハンドルを指定する。戻り値はデータベースのレコード数である。</dd>
+</dl>
+
+<p>マップに含まれる全てのキーを含むリストを得るには、関数 `cbmapkeys' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *cbmapkeys(CBMAP *<var>map</var>);</kbd></dt>
+<dd>`map' はマップハンドルを指定する。戻り値はマップに含まれる全てのキーを含むリストハンドルである。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。</dd>
+</dl>
+
+<p>マップに含まれる全ての値を含むリストを得るには、関数 `cbmapvals' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *cbmapvals(CBMAP *<var>map</var>);</kbd></dt>
+<dd>`map' はマップハンドルを指定する。戻り値はマップに含まれる全ての値を含むリストハンドルである。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。</dd>
+</dl>
+
+<p>マップを直列化してバイト配列にするには、関数 `cbmapdump' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbmapdump(const CBMAP *<var>map</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`map' はマップハンドルを指定する。`sp' は抽出した領域のサイズを格納する領域へのポインタを指定する。戻り値は直列化された領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>直列化されたマップを復元するには、関数 `cbmapload' を用いる。</p>
+
+<dl>
+<dt><kbd>CBMAP *cbmapload(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' はバイト配列へのポインタを指定する。`size' はその領域のサイズを指定する。戻り値は新しいマップハンドルである。</dd>
+</dl>
+
+<p>直列化したマップからひとつのレコードを抽出するには、関数 `cbmaploadone' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbmaploadone(const char *<var>ptr</var>, int <var>size</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' はバイト配列へのポインタを指定する。`size' はその領域のサイズを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、該当のレコードがない場合は `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。</dd>
+</dl>
+
+<p>ヒープハンドルを作成するには、関数 `cbheapopen' を用いる。</p>
+
+<dl>
+<dt><kbd>CBHEAP *cbheapopen(int <var>size</var>, int <var>max</var>, int(*<var>compar</var>)(const void *, const void *));</kbd></dt>
+<dd>`size' は各レコードの領域のサイズを指定する。`max' はヒープに格納するレコードの最大数を指定する。`compar' は比較関数を指定する。二つの引数はレコードへのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。戻り値はヒープハンドル。</dd>
+</dl>
+
+<p>ヒープハンドルを複製するには、関数 `cbheapdup' を用いる。</p>
+
+<dl>
+<dt><kbd>CBHEAP *cbheapdup(CBHEAP *<var>heap</var>);</kbd></dt>
+<dd>`heap' はヒープハンドルを指定する。戻り値は新しいヒープハンドルである。</dd>
+</dl>
+
+<p>ヒープハンドルを破棄するには、関数 `cbheapclose' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbheapclose(CBHEAP *<var>heap</var>);</kbd></dt>
+<dd>`heap' はヒープハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。</dd>
+</dl>
+
+<p>ヒープに格納されたレコード数を得るには、関数 `cbheapnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbheapnum(CBHEAP *<var>heap</var>);</kbd></dt>
+<dd>`heap' はヒープハンドルを指定する。戻り値はリストに格納されたレコード数である。</dd>
+</dl>
+
+<p>ヒープにレコードを挿入するには、関数 `cbheapinsert' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbheapinsert(CBHEAP *<var>heap</var>, const void *<var>ptr</var>);</kbd></dt>
+<dd>`heap' はヒープハンドルを指定する。`ptr' は追加するレコードの領域へのポインタを指定する。戻り値はレコードが追加されれば真であり、そうでなければ偽である。新しいレコードの値が既存のレコードの最大値より大きければ、新しいレコードは追加されない。新しいレコードが追加されてレコード数が最大数を越えた場合、既存のレコードの最大値のものが削除される。</dd>
+</dl>
+
+<p>ヒープ内のあるレコードの領域へのポインタを取得するには、関数 `cbheapget' を用いる。</p>
+
+<dl>
+<dt><kbd>void *cbheapval(CBHEAP *<var>heap</var>, int <var>index</var>);</kbd></dt>
+<dd>`heap' はヒープハンドルを指定する。`index' は取り出すレコードのインデックスを指定する。戻り値は該当レコードの領域へのポインタである。`index' が要素数以上ならば、戻り値は `NULL' である。レコードは内部的には比較関数の負値に基づいて組織化されていることに注意すべきである。</dd>
+</dl>
+
+<p>ヒープを確保された領域に変換するには、関数 `cbheaptomalloc' を用いる。</p>
+
+<dl>
+<dt><kbd>void *cbheaptomalloc(CBHEAP *<var>heap</var>, int *<var>np</var>);</kbd></dt>
+<dd>`heap' はヒープハンドルを指定する。`np' が `NULL' でなければ、その参照先に抽出した領域のレコード数を格納する。戻り値はヒープの領域へのポインタである。レコードはソート済みになる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。元のデータの領域は解放されるので、それを再び解放してはならない。</dd>
+</dl>
+
+<p>書式に基づいた文字列をメモリ上で確保するには、関数 `cbsprintf' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbsprintf(const char *<var>format</var>, ...);</kbd></dt>
+<dd>`format' はprintf風の書式文字列を指定する。変換文字 `%' をフラグ文字 `d'、`o'、`u'、`x'、`X'、`e'、`E'、`f'、`g'、`G'、`c'、`s' および `%' を伴わせて使用することができる。フィールドの幅と精度の指示子を変換文字とフラグ文字の間に置くことができる。その指示子は10進数字、`.'、`+'、`-' およびスペース文字からなる。その他の引数は書式文字列によって利用される。戻り値は結果の文字列の領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>文字列中のパターンを置換するには、関数 `cbreplace' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbreplace(const char *<var>str</var>, CBMAP *<var>pairs</var>);</kbd></dt>
+<dd>`str' は置換前の文字列を指定する。`pairs' は置換のペアからなるマップのハンドルを指定する。各ペアのキーは置換前のパターンを指定し、値は置換後のパターンを指定する。戻り値は結果の文字列の領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>一連のデータを分割してリストを作成するには、関数 `cbsplit' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *cbsplit(const char *<var>ptr</var>, int <var>size</var>, const char *<var>delim</var>);</kbd></dt>
+<dd>`ptr' は内容の領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`delim' は区切り文字を含む文字列を指定するか、`NULL' なら終端文字を区切り文字とする。戻り値はリストハンドルである。区切り文字が連続している場合でも、その間に空の要素があるとみなす。戻り値のハンドルは、関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。</dd>
+</dl>
+
+<p>ファイルの全データを読み込むには、関数 `cbreadfile' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbreadfile(const char *<var>name</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`name' はファイルの名前を指定するが、`NULL' なら標準入力を読み込む。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら読み込んだデータを格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>一連のデータをファイルに書き込むには、関数 `cbwritefile' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbwritefile(const char *<var>name</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`name' はファイル名を指定するが、`NULL' なら標準出力に書き出される。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値は正常なら真であり、該当のエラーなら偽である。ファイルが存在する場合には上書きされ、そうでない場合は新しいファイルが生成される。</dd>
+</dl>
+
+<p>ファイルの各行を読み込むには、関数 `cbreadlines' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *cbreadlines(const char *<var>name</var>);</kbd></dt>
+<dd>`name' はファイルの名前を指定するが、`NULL' なら標準入力を読み込む。成功すれば戻り値は各行のデータを保持するリストのハンドルであり、失敗なら `NULL' である。改行文字は削除される。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。</dd>
+</dl>
+
+<p>ディレクトリに含まれるファイルの名前のリストを得るには、関数 `cbdirlist' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *cbdirlist(const char *<var>name</var>);</kbd></dt>
+<dd>`name' はディレクトリの名前を指定する。成功すれば戻り値は各ファイルの名前を保持するリストのハンドルであり、失敗なら `NULL' である。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。</dd>
+</dl>
+
+<p>ファイルやディレクトリの状態を得るには、関数 `cbfilestat' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbfilestat(const char *<var>name</var>, int *<var>isdirp</var>, int *<var>sizep</var>, int *<var>mtimep</var>);</kbd></dt>
+<dd>`name' はファイルやディレクトリの名前を指定する。`dirp' が `NULL' でなければ、その参照先にファイルがディレクトリか否かを格納する。`sizep' が `NULL' でなければ、その参照先にファイルのサイズを格納する。`mtimep' が `NULL' でなければ、その参照先にファイルの最終更新時刻を格納する。戻り値は正常なら真であり、エラーなら偽である。ファイルが存在しない場合やパーミッションがない場合も偽を返す。</dd>
+</dl>
+
+<p>ファイルかディレクトリとその内容を削除するには、関数 `cbremove' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbremove(const char *<var>name</var>);</kbd></dt>
+<dd>`name' はファイルやディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。ファイルが存在しない場合やパーミッションがない場合も偽を返す。</dd>
+</dl>
+
+<p>URLを構成要素に分解するには、関数 `cburlbreak' を用いる。</p>
+
+<dl>
+<dt><kbd>CBMAP *cburlbreak(const char *<var>str</var>);</kbd></dt>
+<dd>`str' はURLの文字列へのポインタを指定する。戻り値はマップハンドルである。マップの各キーは要素名である。キー "self" はURLそれ自体を指示する。キー "scheme" はURLのスキームを指示する。キー "host" はサーバのホスト名を指示する。キー "port" はサーバのポート番号を指示する。キー "authority" は認証情報を指示する。キー "path" はリソースのパスを指示する。キー "file" はディレクトリ部分を除いたファイル名を指示する。キー "query" はクエリ文字列を指示する。キー "fragment" はフラグメント文字列を指示する。サポートされるスキームはHTTPとHTTPSとFTPとFILEである。絶対URLにも相対URLにも対応する。戻り値のハンドルは関数 `cbmapopen' で開かれるので、不要になったら `cbmapclose' で閉じるべきである。</dd>
+</dl>
+
+<p>相対URLを絶対URLを用いて解決するには、関数 `cburlresolve' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cburlresolve(const char *<var>base</var>, const char *<var>target</var>);</kbd></dt>
+<dd>`base' はベースロケーションの絶対URLを指定する。`target' は解決すべきURLを指定する。戻り値は解決されたURLである。ターゲットURLが相対URLの場合、ベースロケーションからの相対位置のURLを返す。ターゲットURLが絶対URLの場合、ターゲットURLのコピーを返す。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>一連のオブジェクトをURLエンコーディングで符号化するには、関数 `cburlencode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cburlencode(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値は結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>URLエンコーディングで符号化された文字列を復元するには、関数 `cburldecode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cburldecode(const char *<var>str</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`str' は符号化された文字列へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は結果の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>一連のオブジェクトをBase64エンコーディングで符号化するには、関数 `cbbaseencode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbbaseencode(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値は結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>Base64エンコーディングで符号化された文字列を復元するには、関数 `cbbasedecode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbbasedecode(const char *<var>str</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`str' は符号化された文字列へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は結果の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>一連のオブジェクトをquoted-printableエンコーディングで符号化するには、関数 `cbquoteencode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbquoteencode(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値は結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>quoted-printableエンコーディングで符号化された文字列を復元するには、関数 `cbquotedecode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbquotedecode(const char *<var>str</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`str' は符号化された文字列へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は結果の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>MIMEの文字列をヘッダとボディに分割するには、関数 `cbmimebreak' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbmimebreak(const char *<var>ptr</var>, int <var>size</var>, CBMAP *<var>attrs</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' はMIMEのデータの領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`attrs' は属性を保存するためのマップハンドルを指定するが、`NULL' の場合には利用されない。マップの各キーは小文字に正規化された属性名である。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値はボディの文字列である。コンテントタイプが指定されている場合、マップのキー "TYPE" はそのタイプを指示する。文字コードが指定されている場合、キー "CHARSET" はそのコード名を指示する。マルチパートの区切り文字列が指定されている場合、キー "BOUNDARY" はその文字列を指示する。コンテントディスポジションが指定されている場合、キー "DISPOSITION" はその方針を指示する。ファイル名が指定されている場合、キー "FILENAME" はその名前を指示する。属性名が指定されている場合、キー "NAME" はその名前を指示する。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>MIMEのマルチパートデータの文字列を各パートに分割するには、関数 `cbmimeparts' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *cbmimeparts(const char *<var>ptr</var>, int <var>size</var>, const char *<var>boundary</var>);</kbd></dt>
+<dd>`ptr' はMIMEのデータの領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`boundary' は区切り文字列へのポインタを指定する。戻り値はリストハンドルである。リストの各要素はパートの文字列である。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。</dd>
+</dl>
+
+<p>文字列をMIMEエンコーディングで符号化するには、関数 `cbmimeencode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbmimeencode(const char *<var>str</var>, const char *<var>encname</var>, int <var>base</var>);</kbd></dt>
+<dd>`str' は文字列へのポインタを指定する。`encname' は文字コード名の文字列を指定する。`base' はBase64エンコードを使うか否かを指定する。偽の場合はquoted-printableが用いられる。戻り値は結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>MIMEエンコーディングで符号化された文字列を復元するには、関数 `cbmimedecode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbmimedecode(const char *<var>str</var>, char *<var>enp</var>);</kbd></dt>
+<dd>`str' は符号化された文字列へのポインタを指定する。`enp' は文字コード名を書き込むための領域へのポインタを指定するが、`NULL' の場合は利用されない。バッファのサイズは32バイト以上でなければならない。戻り値は結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>CSVの文字列を行に分割するには、関数 `cbcsvrows' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *cbcsvrows(const char *<var>str</var>);</kbd></dt>
+<dd>`str' はCSVの文字列へのポインタを指定する。戻り値はリストハンドルである。リストの各要素は行の文字列である。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。入力文字列の文字コードはUS-ASCII、UTF-8、ISO-8859-*、EUC-*、Shift_JISのどれかである必要がある。MS-Excelと互換して、これらCSV用関数群は、ダブルクォートで囲んでコンマなどのメタ文字を含めたセルを扱うことができる。</dd>
+</dl>
+
+<p>CSVの行の文字列をセルに分割するには、関数 `cbcsvcells' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *cbcsvcells(const char *<var>str</var>);</kbd></dt>
+<dd>`str' はCSVの行の文字列へのポインタを指定する。戻り値はリストハンドルである。リストの各要素はセル内容をアンエスケープした文字列である。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。</dd>
+</dl>
+
+<p>CSVのメタ文字をエスケープした文字列を得るには、関数 `cbcsvescape' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbcsvescape(const char *<var>str</var>);</kbd></dt>
+<dd>`str' は対象の文字列へのポインタを指定する。戻り値はメタ文字を無効化した結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>XMLの実体参照をアンエスケープした文字列を得るには、関数 `cbcsvunescape' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbcsvunescape(const char *<var>str</var>);</kbd></dt>
+<dd>`str' は対象の文字列へのポインタを指定する。戻り値はメタ文字を伴った結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>XMLの文字列をタグとテキストセクションに分割するには、関数 `cbxmlbreak' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *cbxmlbreak(const char *<var>str</var>, int <var>cr</var>);</kbd></dt>
+<dd>`str' はXMLの文字列へのポインタを指定する。`cr' はコメントを削除するか否かを指定する。戻り値はリストハンドルである。リストの各要素はタグかテキストセクションの文字列である。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。入力文字列の文字コードはUS-ASCII、UTF-8、ISO-8859-*、EUC-*、Shift_JISのどれかである必要がある。これらXML用関数群は妥当性検証を行うXMLパーザではないので、HTMLやSGMLも扱うことができる。</dd>
+</dl>
+
+<p>XMLのタグの属性のマップを得るには、関数 `cbxmlattrs' を用いる。</p>
+
+<dl>
+<dt><kbd>CBMAP *cbxmlattrs(const char *<var>str</var>);</kbd></dt>
+<dd>`str' はタグの文字列へのポインタを指定する。戻り値はマップハンドルである。マップの各キーは属性の名前である。各値はアンエスケープされる。空文字列をキーにするとタグの名前を取り出すことができる。戻り値のハンドルは関数 `cbmapopen' で開かれるので、不要になったら `cbmapclose' で閉じるべきである。</dd>
+</dl>
+
+<p>XMLのメタ文字をエスケープした文字列を得るには、関数 `cbxmlescape' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbxmlescape(const char *<var>str</var>);</kbd></dt>
+<dd>`str' は対象の文字列へのポインタを指定する。戻り値はメタ文字を無効化した結果の文字列へのポインタである。この関数は `&amp;'、 `&lt;'、`&gt;'、`&quot;' のみを変換する。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>XMLの実体参照をアンエスケープした文字列を得るには、関数 `cbxmlunescape' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbxmlunescape(const char *<var>str</var>);</kbd></dt>
+<dd>`str' は対象の文字列へのポインタを指定する。戻り値はメタ文字を伴った結果の文字列へのポインタである。この関数は `&amp;amp;'、`&amp;lt;'、`&amp;gt;'、`&amp;quot;' のみを復元する。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>ZLIBを用いて一連のオブジェクトを圧縮するには、関数 `cbdeflate' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbdeflate(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`sp' の参照先には抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがZLIBを有効にしてビルドされた場合のみ利用できる。</dd>
+</dl>
+
+<p>ZLIBを用いて圧縮されたオブジェクトを伸長するには、関数 `cbinflate' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbinflate(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがZLIBを有効にしてビルドされた場合のみ利用できる。</dd>
+</dl>
+
+<p>GZIPを用いて一連のオブジェクトを圧縮するには、関数 `cbgzencode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbgzencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`sp' の参照先には抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがZLIBを有効にしてビルドされた場合のみ利用できる。</dd>
+</dl>
+
+<p>GZIPを用いて圧縮されたオブジェクトを伸長するには、関数 `cbgzdecode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbgzdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがZLIBを有効にしてビルドされた場合のみ利用できる。</dd>
+</dl>
+
+<p>一連のオブジェクトのCRC32チェックサムを得るには、関数 `cbgetcrc' を用いる。</p>
+
+<dl>
+<dt><kbd>unsigned int cbgetcrc(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値はオブジェクトのCRC32チェックサムである。この関数はQDBMがZLIBを有効にしてビルドされた場合のみ利用できる。</dd>
+</dl>
+
+<p>LZOを用いて一連のオブジェクトを圧縮するには、関数 `cblzoencode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cblzoencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`sp' の参照先には抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがLZOを有効にしてビルドされた場合のみ利用できる。</dd>
+</dl>
+
+<p>LZOを用いて圧縮されたオブジェクトを伸長するには、関数 `cblzodecode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cblzodecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがLZOを有効にしてビルドされた場合のみ利用できる。</dd>
+</dl>
+
+<p>BZIP2を用いて一連のオブジェクトを圧縮するには、関数 `cbbzencode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbbzencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`sp' の参照先には抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがBZIP2を有効にしてビルドされた場合のみ利用できる。</dd>
+</dl>
+
+<p>BZIP2を用いて圧縮されたオブジェクトを伸長するには、関数 `cbbzdecode' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbbzdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがBZIP2を有効にしてビルドされた場合のみ利用できる。</dd>
+</dl>
+
+<p>文字列の文字コードを変換するには、関数 `cbiconv' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbiconv(const char *<var>ptr</var>, int <var>size</var>, const char *<var>icode</var>, const char *<var>ocode</var>, int *<var>sp</var>, int *<var>mp</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`icode' は入力文字列の文字コードの名前を指定する。`outcode' は出力文字列の文字コードの名前を指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。`mp' が `NULL' でなければ、その参照先に変換に失敗した文字数を格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがICONVを有効にしてビルドされた場合のみ利用できる。</dd>
+</dl>
+
+<p>文字列の文字コードを自動判定するには、関数 `cbencname' を用いる。</p>
+
+<dl>
+<dt><kbd>const char *cbencname(const char *<var>str</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`icode' は入力文字列の文字コードの名前を指定する。戻り値はエンコードの名前の文字列である。現状では、US-ASCII、ISO-2022-JP、Shift_JIS、CP932、EUC-JP、UTF-8、UTF-16、UTF-16BEおよびUTF-16LEに対応している。そのいずれでもない場合は、ISO-8859-1と判定する。この関数はQDBMがICONVを有効にしてビルドされた場合のみ利用できる。</dd>
+</dl>
+
+<p>ローカル時間の秒単位の時差を得るには、関数 `cbjetlag' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbjetlag(void);</kbd></dt>
+<dd>戻り値はローカル時間の秒単位の時差である。</dd>
+</dl>
+
+<p>ある時間のグレゴリオ暦を得るには、関数 `cbcalendar' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbcalendar(time_t <var>t</var>, int <var>jl</var>, int *<var>yearp</var>, int *<var>monp</var>, int *<var>dayp</var>, int *<var>hourp</var>, int *<var>minp</var>, int *<var>secp</var>);</kbd></dt>
+<dd>`t' は対象の時間を指定するが、負数なら現在時間が使われる。`jl' はある場所の時差を秒単位で指定する。`yearp' が `NULL' でなければ、その参照先に年を格納する。`monp' が `NULL' でなければ、その参照先に月を格納する。1は1月を意味し、12は12月を意味する。`dayp' が `NULL' でなければ、その参照先に日を格納する。`hourp' が `NULL' でなければ、その参照先に時を格納する。`minp' が `NULL' でなければ、その参照先に分を格納する。`secp' が `NULL' でなければ、その参照先に秒を格納する。</dd>
+</dl>
+
+<p>ある日付の曜日を得るには、関数 `cbdayofweek' を用いる。</p>
+
+<dl>
+<dt><kbd>int cbdayofweek(int <var>year</var>, int <var>mon</var>, int <var>day</var>);</kbd></dt>
+<dd>`year' は日付の年を指定する。`mon' は日付の月を指定する。`day' は日付の日を指定する。戻り値は曜日の値である。0は日曜を意味し、6は土曜を意味する。</dd>
+</dl>
+
+<p>ある日付をW3CDTFの書式で表した文字列を得るには、関数 `cbdatestrwww' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbdatestrwww(time_t <var>t</var>, int <var>jl</var>);</kbd></dt>
+<dd>`t' は対象の時間を指定するが、負数なら現在時間が使われる。`jl' はある場所の時差を秒単位で指定する。戻り値はW3CDTFの書式(YYYY-MM-DDThh:mm:ddTZD)の文字列である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>ある日付をRFC 1123の書式で表した文字列を得るには、関数 `cbdatestrhttp' を用いる。</p>
+
+<dl>
+<dt><kbd>char *cbdatestrhttp(time_t <var>t</var>, int <var>jl</var>);</kbd></dt>
+<dd>`t' は対象の時間を指定するが、負数なら現在時間が使われる。`jl' はある場所の時差を秒単位で指定する。戻り値はRFC 1123の書式(Wdy, DD-Mon-YYYY hh:mm:dd TZD)の文字列である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>10進数か16進数かW3CDTFかRFC 822(1123)の書式で表した文字列から時間の値を得るには、関数 `cbstrmktime' を用いる。</p>
+
+<dl>
+<dt><kbd>time_t cbstrmktime(const char *<var>str</var>);</kbd></dt>
+<dd>`str' は10進数か16進数かW3CDTFかRFC 822(1123)の書式で表した文字列を指定する。戻り値はその時間の値か、書式が不正な場合は -1 である。10進数に "s" を後置すると秒単位を意味し、"m" を後置すると分単位を意味し、"h" を後置すると時単位を意味し、"d" を後置すると日単位を意味する。</dd>
+</dl>
+
+<p>ユーザとシステムのプロセス時間を得るには、関数 `cbproctime' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbproctime(double *<var>usrp</var>, double *<var>sysp</var>);</kbd></dt>
+<dd>`usrp' が `NULL' でなければ、その参照先にユーザ時間を格納する。時間の単位は秒である。`sysp' が `NULL' でなければ、その参照先にシステム時間を格納する。時間の単位は秒である。</dd>
+</dl>
+
+<p>標準入出力がバイナリモードであることを保証するには、関数 `cbstdiobin' を用いる。</p>
+
+<dl>
+<dt><kbd>void cbstdiobin(void);</kbd></dt>
+<dd>この関数はDOS的なファイルシステムの上のアプリケーションで有用である。</dd>
+</dl>
+
+<h3>サンプルコード</h3>
+
+<p>以下のサンプルコードは典型的な利用例である。</p>
+
+<pre>#include &lt;cabin.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+int main(int argc, char **argv){
+ CBDATUM *datum;
+ CBLIST *list;
+ CBMAP *map;
+ char *buf1, *buf2;
+ int i;
+
+ /* データハンドルを開く */
+ datum = cbdatumopen("123", -1);
+ /* データを連結する */
+ cbdatumcat(datum, "abc", -1);
+ /* データを表示する */
+ printf("%s\n", cbdatumptr(datum));
+ /* データハンドルを閉じる */
+ cbdatumclose(datum);
+
+ /* リストハンドルを開く */
+ list = cblistopen();
+ /* リストに要素を追加する */
+ cblistpush(list, "apple", -1);
+ cblistpush(list, "orange", -1);
+ /* 全ての要素を表示する */
+ for(i = 0; i &lt; cblistnum(list); i++){
+ printf("%s\n", cblistval(list, i, NULL));
+ }
+ /* リストハンドルを閉じる */
+ cblistclose(list);
+
+ /* マップハンドルを開く */
+ map = cbmapopen();
+ /* マップにレコードを追加する */
+ cbmapput(map, "dog", -1, "bowwow", -1, 1);
+ cbmapput(map, "cat", -1, "meow", -1, 1);
+ /* 値を取得して表示する */
+ printf("%s\n", cbmapget(map, "dog", -1, NULL));
+ printf("%s\n", cbmapget(map, "cat", -1, NULL));
+ /* マップハンドルを閉じる */
+ cbmapclose(map);
+
+ /* Base64の符号化を行う */
+ buf1 = cbbaseencode("I miss you.", -1);
+ printf("%s\n", buf1);
+ /* Base64の復元を行う */
+ buf2 = cbbasedecode(buf1, NULL);
+ printf("%s\n", buf2);
+ /* リソースを解放する */
+ free(buf2);
+ free(buf1);
+
+ /* 単純なポインタをグローバルガベージコレクタに登録する */
+ buf1 = cbmemdup("Take it easy.", -1);
+ cbglobalgc(buf1, free);
+ /* ポインタは利用できるが解放する必要はない */
+ printf("%s\n", buf1);
+
+ /* リストをグローバルガベージコレクタに登録する */
+ list = cblistopen();
+ cbglobalgc(list, (void (*)(void *))cblistclose);
+ /* ハンドルは利用できるが閉じる必要はない */
+ cblistpush(list, "Don't hesitate.", -1);
+ for(i = 0; i &lt; cblistnum(list); i++){
+ printf("%s\n", cblistval(list, i, NULL));
+ }
+
+ return 0;
+}
+</pre>
+
+<h3>注記</h3>
+
+<p>Cabinを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>スレッド間で同時に同じハンドルにアクセスしない限りは、`cbglobalgc' を除いたCabinの各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。</p>
+
+<hr />
+
+<h2><a name="cabincli" id="cabincli" class="head">Cabin用コマンド</a></h2>
+
+<p>Cabinに対応するコマンドラインインタフェースは以下のものである。</p>
+
+<p>コマンド `cbtest' はCabinの機能テストや性能テストに用いるツールである。`time' コマンドによってこのコマンドの実行時間を計るとよい。以下の書式で用いる。`rnum' はレコード数を指定する。</p>
+
+<dl>
+<dt><kbd>cbtest sort [-d] <var>rnum</var></kbd></dt>
+<dd>ソートアルゴリズムのテストを行う。</dd>
+<dt><kbd>cbtest strstr [-d] <var>rnum</var></kbd></dt>
+<dd>文字列探索アルゴリズムのテストを行う。</dd>
+<dt><kbd>cbtest list [-d] <var>rnum</var></kbd></dt>
+<dd>リストの書き込みテストを行う。</dd>
+<dt><kbd>cbtest map [-d] <var>rnum</var></kbd></dt>
+<dd>マップの書き込みテストを行う。</dd>
+<dt><kbd>cbtest wicked <var>rnum</var></kbd></dt>
+<dd>リストとマップの各種更新操作を無作為に選択して実行する。</dd>
+<dt><kbd>cbtest misc</kbd></dt>
+<dd>雑多なルーチンのテストを実行する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-d</kbd> : 結果のデータを読み込んで表示する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。</p>
+
+<p>コマンド `cbcodec' はCabinが提供するエンコードおよびデコードの機能を利用するツールである。以下の書式で用いる。`file' は入力ファイルを指定するが、省略されれば標準入力を読み込む。</p>
+
+<dl>
+<dt><kbd>cbcodec url [-d] [-br] [-rs <var>base</var> <var>target</var>] [-l] [-e <var>expr</var>] [<var>file</var>]</kbd></dt>
+<dd>URLエンコードとそのデコードを行う。</dd>
+<dt><kbd>cbcodec base [-d] [-l] [-c <var>num</var>] [-e <var>expr</var>] [<var>file</var>]</kbd></dt>
+<dd>Base64エンコードとそのデコードを行う。</dd>
+<dt><kbd>cbcodec quote [-d] [-l] [-c <var>num</var>] [-e <var>expr</var>] [<var>file</var>]</kbd></dt>
+<dd>quoted-printableエンコードとそのデコードを行う。</dd>
+<dt><kbd>cbcodec mime [-d] [-hd] [-bd] [-part <var>num</var>] [-l] [-ec <var>code</var>] [-qp] [-dc] [-e <var>expr</var>] [<var>file</var>]</kbd></dt>
+<dd>MIMEエンコードとそのデコードを行う。</dd>
+<dt><kbd>cbcodec csv [-d] [-t] [-l] [-e <var>expr</var>] [-html] [<var>file</var>]</kbd></dt>
+<dd>CSVの処理を行う。デフォルトではメタ文字のエスケープを行う。</dd>
+<dt><kbd>cbcodec xml [-d] [-p] [-l] [-e <var>expr</var>] [-tsv] [<var>file</var>]</kbd></dt>
+<dd>XMLの処理を行う。デフォルトではメタ文字のエスケープを行う。</dd>
+<dt><kbd>cbcodec zlib [-d] [-gz] [-crc] [<var>file</var>]</kbd></dt>
+<dd>ZLIBの圧縮とその伸長を行う。ZLIBを有効化してQDBMをビルドした場合にのみ利用可能である。</dd>
+<dt><kbd>cbcodec lzo [-d] [<var>file</var>]</kbd></dt>
+<dd>LZOの圧縮とその伸長を行う。LZOを有効化してQDBMをビルドした場合にのみ利用可能である。</dd>
+<dt><kbd>cbcodec bzip [-d] [<var>file</var>]</kbd></dt>
+<dd>BZIP2の圧縮とその伸長を行う。BZIP2を有効化してQDBMをビルドした場合にのみ利用可能である。</dd>
+<dt><kbd>cbcodec iconv [-ic <var>code</var>] [-oc <var>code</var>] [-ol <var>ltype</var>] [-cn] [-um] [-wc] [<var>file</var>]</kbd></dt>
+<dd>ICONVによる文字コードの変換を行う。ICONVを有効化してQDBMをビルドした場合にのみ利用可能である。</dd>
+<dt><kbd>cbcodec date [-wf] [-rf] [-utc] [<var>str</var>]</kbd></dt>
+<dd>`str' で指定した日付の文字列の書式を変換する。デフォルトでは、UNIX時間を出力する。`str' が省略されると、現在日時を扱う。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-d</kbd> : エンコード(エスケープ)ではなく、デコード(アンエスケープ)を行う。</li>
+<li><kbd>-br</kbd> : URLを構成要素に分解する。</li>
+<li><kbd>-rs</kbd> : 相対URLを解決する。</li>
+<li><kbd>-l</kbd> : 出力の末尾に改行文字を加える。</li>
+<li><kbd>-e <var>expr</var></kbd> : 入力データを直接指定する。</li>
+<li><kbd>-c <var>num</var></kbd> : エンコードの際の桁数制限を指定する。</li>
+<li><kbd>-hd</kbd> : MIMEの構文解析を行い、ヘッダをTSV形式で抽出する。</li>
+<li><kbd>-bd</kbd> : MIMEの構文解析を行い、ボディを抽出する。</li>
+<li><kbd>-part <var>num</var></kbd> : MIMEの構文解析を行い、特定のパートを抽出する。</li>
+<li><kbd>-ec <var>code</var></kbd> : 入力の文字コードを指定する。デフォルトはUTF-8である。</li>
+<li><kbd>-qp</kbd> : quoted-printableエンコードを用いる。デフォルトはBase64である。</li>
+<li><kbd>-dc</kbd> : デコード結果の文字列でなく、文字コード名を出力する。</li>
+<li><kbd>-t</kbd> : CSVの構造を解析する。TSVに変換して表示する。セル内のタブと改行は削除される。</li>
+<li><kbd>-html</kbd> : CSVの構造を解析する。HTMLに変換して出力する。</li>
+<li><kbd>-p</kbd> : XMLの構文解析を行う。タグとテキストセクションをヘッダで分けて表示する。</li>
+<li><kbd>-tsv</kbd> : XMLの構文解析を行う。結果をTSV形式で出力する。テキストセクションのタブと改行はURLエンコードされる。</li>
+<li><kbd>-gz</kbd> : GZIP形式を用いる。</li>
+<li><kbd>-crc</kbd> : CRC32のチェックサムをビッグエンディアンの16進数で出力する。</li>
+<li><kbd>-ic <var>code</var></kbd> : 入力の文字コードを指定する。デフォルトだと自動判定する。</li>
+<li><kbd>-oc <var>code</var></kbd> : 出力の文字コードを指定する。デフォルトだとUTF-8になる。</li>
+<li><kbd>-ol <var>ltype</var></kbd> : 改行文字を変換する。`unix'(LF)、`dos'(CRLF)、`mac'(CR) のどれかを指定する。</li>
+<li><kbd>-cn</kbd> : 入力の文字コードを自動判定し、その名前を表示する。</li>
+<li><kbd>-wc</kbd> : 入力の文字コードをUTF-8と仮定し、その文字数を表示する。</li>
+<li><kbd>-um</kbd> : UCS-2の文字と、C言語の文字列表現でのUTF-16BEとUTF-8の対応表を出力する。</li>
+<li><kbd>-wf</kbd> : W3CDTFの書式で出力する。</li>
+<li><kbd>-rf</kbd> : RFC 1123の書式で出力する。</li>
+<li><kbd>-utc</kbd> : 協定世界時を出力する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。</p>
+
+<hr />
+
+<h2><a name="villaapi" id="villaapi" class="head">Villa: 上級API</a></h2>
+
+<h3>概要</h3>
+
+<p>VillaはQDBMの上級APIであり、B+木のデータベースを管理するルーチンを提供する。各レコードはユーザが指定した順序で整列されて格納される。ハッシュデータベースではレコードの検索はキーの完全一致によるしかなかった。しかし、Villaを用いると範囲を指定してレコードを検索することができる。各レコードを順番に参照するにはカーソルを用いる。データベースにはキーが重複する複数のレコードを格納することができる。また、トランザクション機構によってデータベースの操作を一括して反映させたり破棄したりすることができる。</p>
+
+<p>VillaはDepotおよびCabinを基盤として実装される。VillaのデータベースファイルはDepotのデータベースファイルそのものである。レコードの検索や格納の処理速度はDepotより遅いが、データベースファイルのサイズはより小さい。</p>
+
+<p>Villaを使うためには、`depot.h' と `cabin.h' と `villa.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。</p>
+
+<dl>
+<dt><kbd>#include &lt;depot.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;cabin.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;villa.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+</dl>
+
+<p>Villaでデータベースを扱う際には、`VILLA' 型へのポインタをハンドルとして用いる。これは、`stdio.h' の各種ルーチンがファイル入出力に `FILE' 型へのポインタを用いるのに似ている。ハンドルは、関数 `vlopen' で開き、関数 `vlclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。データベースに致命的なエラーが起きた場合は、以後そのハンドルに対する `vlclose' を除く全ての操作は何もせずにエラーを返す。ひとつのプロセスで複数のデータベースファイルを同時に利用することは可能であるが、同じデータベースファイルの複数のハンドルを利用してはならない。カーソルを使う前には `vlcurfirst' か `vlcurlast' か `vlcurjump' のどれかで初期化する必要がある。`vlcurput' と `vlcurout' 以外の関数でレコードの更新や削除をした後にもカーソルを初期化する必要がある。</p>
+
+<p>VillaでもDepotと同じく外部変数 `dpecode' に直前のエラーコードが記録される。エラーコードに対応するメッセージ文字列を得るには、関数 `dperrmsg' を用いる。</p>
+
+<h3>API</h3>
+
+<p>レコードの順番を指定するためには、比較関数を定義する。比較関数には以下の型を用いる。</p>
+
+<dl>
+<dt><kbd>typedef int(*VLCFUNC)(const char *<var>aptr</var>, int <var>asiz</var>, const char *<var>bptr</var>, int <var>bsiz</var>);</kbd></dt>
+<dd>`aptr' は一方のキーのデータ領域へのポインタを指定する。`asiz' はそのキーのデータ領域のサイズを指定する。`bptr' は他方のキーのデータ領域へのポインタを指定する。`bsiz' はそのキーのデータ領域のサイズを指定する。戻り値は前者が大きければ正、後者が大きければ負、両者が等価なら 0 である。</dd>
+</dl>
+
+<p>データベースのハンドルを作成するには、関数 `vlopen' を用いる。</p>
+
+<dl>
+<dt><kbd>VILLA *vlopen(const char *<var>name</var>, int <var>omode</var>, VLCFUNC <var>cmp</var>);</kbd></dt>
+<dd>`name' はデータベースファイルの名前を指定する。`omode' は接続モードを指定し、`VL_OREADER' ならリーダ、`VL_OWRITER' ならライタとなる。`VL_OWRITER' の場合、`VL_OCREAT' または `VL_OTRUNC' とのビット論理和にすることができる。`VL_OCREAT' はファイルが無い場合に新規作成することを指示し、`VL_OTRUNC' はファイルが存在しても作り直すことを指示し、`VL_OZCOMP' はデータベース内のリーフをZLIBで圧縮することを指示し、`VL_OYCOMP' はデータベース内のリーフをLZOで圧縮することを指示し、`VL_OXCOMP' はデータベース内のリーフをBZIP2で圧縮することを指示する。`VL_OREADER' と `VL_OWRITER' の両方で `VL_ONOLCK' または `VL_OLCKNB' とのビット論理和にすることができるが、前者はファイルロックをかけずにデータベースを開くことを指示し、後者はブロックせずにロックをかけることを指示する。`cmp' は比較関数を指定する。`VL_CMPLEX' はキーを辞書順で比較する。`VL_CMPINT' はキーを `int' 型のオブジェクトとみなして比較する。`VL_CMPNUM' はキーをビッグエンディアンの数値とみなして比較する。`VL_CMPDEC' はキーを10進数の数値を表す文字列とみなして比較する。`VLCFUNC' 型の宣言に基づく関数であれば比較関数として用いることができる。同じデータベースには常に同じ比較関数を用いる必要がある。戻り値はデータベースハンドルであるか、エラーなら `NULL' である。ライタ(読み書き両用モード)でデータベースファイルを開く際にはそのファイルに対して排他ロックがかけられ、リーダ(読み込み専用モード)で開く際には共有ロックがかけられる。その際には該当のロックがかけられるまで制御がブロックする。`VL_OZCOMP' と `VL_OYCOMP' と `VL_OXCOMP' はQDBMがそれぞれZLIBとLZOとBZIP2を有効にしてビルドされた場合にのみ利用できる。`VL_ONOLCK' を使う場合、アプリケーションが排他制御の責任を負う。</dd>
+</dl>
+
+<p>データベースとの接続を閉じてハンドルを破棄するには、関数 `vlclose' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlclose(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースの更新内容は、接続を閉じた時点で初めてファイルと同期される。ライタでデータベースを開いた場合、適切に接続を閉じないとデータベースが破壊される。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。トランザクションが有効でコミットされていない場合、それは破棄される。</dd>
+</dl>
+
+<p>レコードを追加するには、関数 `vlput' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlput(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const char *<var>vbuf</var>, int <var>vsiz</var>, int <var>dmode</var>);</kbd></dt>
+<dd>`villa' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`dmode' はキーが既存レコードと重複した際の制御を指定する。`VL_DOVER' は既存のレコードの値を上書きし、`VL_DKEEP' は既存のレコードを残してエラーを返し、`VL_DCAT' は指定された値を既存の値の末尾に加え、`VL_DDUP' はキーの重複を許して指定された値を最後の値として加え、`VL_DDUPR' はキーの重複を許して指定された値を最初の値として加える。戻り値は正常なら真であり、エラーなら偽である。データベースの更新によってカーソルは使用不能になる。</dd>
+</dl>
+
+<p>レコードを削除するには、関数 `vlout' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlout(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`villa' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。重複するレコード群のキーが指定された場合、その最初のものが削除される。データベースの更新によってカーソルは使用不能になる。</dd>
+</dl>
+
+<p>レコードを取得するには、関数 `vlget' を用いる。</p>
+
+<dl>
+<dt><kbd>char *vlget(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。重複するレコード群のキーが指定された場合、その最初のものが選択される。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>レコードの値のサイズを取得するには、関数 `vlvsiz' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlvsiz(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値のサイズであるが、該当がない場合やエラーの場合は -1 である。複数のレコードが該当する場合は、最初のレコードの値のサイズを返す。</dd>
+</dl>
+
+<p>キーに一致するレコードの数を取得するには、関数 `vlvnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlvnum(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値の数であり、該当がない場合やエラーの場合は 0 である。</dd>
+</dl>
+
+<p>キーに一致する複数のレコードを追加するには、関数 `vlputlist' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlputlist(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const CBLIST *<var>vals</var>);</kbd></dt>
+<dd>`villa' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vals' は値のリストのハンドルを指定する。リストは空であってはならない。戻り値は正常なら真であり、エラーなら偽である。データベースの更新によってカーソルは使用不能になる。</dd>
+</dl>
+
+<p>キーに一致する全てのレコードを削除するには、関数 `vloutlist' を用いる。</p>
+
+<dl>
+<dt><kbd>int vloutlist(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`villa' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。データベースの更新によってカーソルは使用不能になる。</dd>
+</dl>
+
+<p>キーに一致する全てのレコードを取得するには、関数 `vlgetlist' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *vlgetlist(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は一致するレコードの値のリストのハンドルであるか、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。</dd>
+</dl>
+
+<p>キーに一致する全てのレコードの連結した値を取得するには、関数 `vlgetcat' を用いる。</p>
+
+<dl>
+<dt><kbd>char *vlgetcat(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら連結した値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>カーソルを最初のレコードに移動させるには、関数 `vlcurfirst' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlcurfirst(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースが空の場合も偽を返す。</dd>
+</dl>
+
+<p>カーソルを最後のレコードに移動させるには、関数 `vlcurlast' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlcurlast(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースが空の場合も偽を返す。</dd>
+</dl>
+
+<p>カーソルを前のレコードに移動させるには、関数 `vlcurprev' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlcurprev(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。前のレコードがない場合も偽を返す。</dd>
+</dl>
+
+<p>カーソルを次のレコードに移動させるには、関数 `vlcurnext' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlcurnext(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。次のレコードがない場合も偽を返す。</dd>
+</dl>
+
+<p>カーソルを特定のレコードの前後に移動させるには、関数 `vlcurjump' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlcurjump(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>jmode</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`jmode' は詳細な調整を指定する。`VL_JFORWARD' はキーが同じレコード群の最初のレコードにカーソルが設定され、また完全に一致するレコードがない場合は次の候補にカーソルが設定されることを意味する。`VL_JBACKWORD' はキーが同じレコード群の最後のレコードにカーソルが設定され、また完全に一致するレコードがない場合は前の候補にカーソルが設定されることを意味する。戻り値は正常なら真であり、エラーなら偽である。条件に一致するレコードがない場合も偽を返す。</dd>
+</dl>
+
+<p>カーソルのあるレコードのキーを取得するには、関数 `vlcurkey' を用いる。</p>
+
+<dl>
+<dt><kbd>char *vlcurkey(VILLA *<var>villa</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>カーソルのあるレコードの値を取得するには、関数 `vlcurkey' を用いる。</p>
+
+<dl>
+<dt><kbd>char *vlcurval(VILLA *<var>villa</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>カーソルのあるレコードの周辺にレコードを挿入するには、関数 `vlcurput' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlcurput(VILLA *<var>villa</var>, const char *<var>vbuf</var>, int <var>vsiz</var>, int <var>cpmode</var>);</kbd></dt>
+<dd>`villa' はライタで接続したデータベースハンドルを指定する。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`cpmode' は詳細な調整を指定する。`VL_CPCURRENT' は現在のレコードの値を上書きすることを指示し、`VL_CPBEFORE' はカーソルの直前にレコードを挿入することを指示し、`VL_CPAFTER' はカーソルの直後にレコードを挿入することを指示する。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。挿入操作の後には、カーソルは挿入されたレコードの位置に移動する。</dd>
+</dl>
+
+<p>カーソルのあるレコードを削除するには、関数 `vlcurout' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlcurout(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。削除操作の後には、可能であればカーソルは次のレコードに移動する。</dd>
+</dl>
+
+<p>性能を調整するパラメータを指定するには、関数 `vlsettuning' を用いる。</p>
+
+<dl>
+<dt><kbd>void vlsettuning(VILLA *<var>villa</var>, int <var>lrecmax</var>, int <var>nidxmax</var>, int <var>lcnum</var>, int <var>ncnum</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。`lrecmax' はB+木のひとつのリーフに入れるレコードの最大数を指定するが、0 以下ならデフォルト値が使われる。`nidxmax' はB+木の非リーフノードに入れるインデックスの最大数を指定するが、0 以下ならデフォルト値が使われる。`lcnum' はキャッシュに入れるリーフの最大数を指定するが、0 以下ならデフォルト値が使われる。`ncnum' はキャッシュに入れる非リーフノードの最大数を指定するが、0 以下ならデフォルト値が使われる。デフォルトの設定は `vlsettuning(49, 192, 1024, 512)' に相当する。性能調整のパラメータはデータベースに保存されないので、データベースを開く度に指定する必要がある。</dd>
+</dl>
+
+<p>データベースのフリーブロックプールのサイズ設定するには、関数 `vlsetfbpsiz' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlsetfbpsiz(VILLA *<var>villa</var>, int <var>size</var>);</kbd></dt>
+<dd>`villa' はライタで接続したデータベースハンドルを指定する。`size' はフリーブロックプールのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。フリーブロックプールのデフォルトのサイズは256である。サイズをより大きくすると、レコードの上書きを繰り返す際の空間効率は上がるが、時間効率が下がる。</dd>
+</dl>
+
+<p>データベースを更新した内容をファイルとデバイスに同期させるには、関数 `vlsync' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlsync(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はデータベースを閉じないうちに別プロセスにデータベースファイルを利用させる場合に役立つ。トランザクションが有効な間はこの関数を使用すべきではない。</dd>
+</dl>
+
+<p>データベースを最適化するには、関数 `vloptimize' を用いる。</p>
+
+<dl>
+<dt><kbd>int vloptimize(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。レコードを削除したり、置換モードや連結モードで書き込みを繰り返したりする場合は、データベース内に不要な領域が蓄積するが、この関数はそれを解消するのに役立つ。トランザクションが有効な間はこの関数を使用すべきではない。</dd>
+</dl>
+
+<p>データベースの名前を得るには、関数 `vlname' を用いる。</p>
+
+<dl>
+<dt><kbd>char *vlname(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値は正常なら名前を格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>データベースファイルのサイズを得るには、関数 `vlfsiz' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlfsiz(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズであり、エラーなら -1 である。I/Oバッファにより、戻り値は実際のサイズより小さくなる場合がある。</dd>
+</dl>
+
+<p>B+木のリーフノードの数を得るには、関数 `vllnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int vllnum(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値は正常ならリーフノードの数であり、エラーなら -1 である。</dd>
+</dl>
+
+<p>B+木の非リーフノードの数を得るには、関数 `vlnnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlnnum(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値は正常なら非リーフノードの数であり、エラーなら -1 である。</dd>
+</dl>
+
+<p>データベースのレコード数を得るには、関数 `vlrnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlrnum(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値は正常ならデータベースのレコード数であり、エラーなら -1 である。</dd>
+</dl>
+
+<p>データベースハンドルがライタかどうかを調べるには、関数 `vlwritable' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlwritable(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値はライタなら真であり、そうでなければ偽である。</dd>
+</dl>
+
+<p>データベースに致命的エラーが起きたかどうかを調べるには、関数 `vlfatalerror' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlfatalerror(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。</dd>
+</dl>
+
+<p>データベースファイルのinode番号を得るには、関数 `vlinode' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlinode(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値はデータベースファイルのinode番号である。</dd>
+</dl>
+
+<p>データベースの最終更新時刻を得るには、関数 `vlmtime' を用いる。</p>
+
+<dl>
+<dt><kbd>time_t vlmtime(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。戻り値はデータベースの最終更新時刻である。</dd>
+</dl>
+
+<p>トランザクションを開始するには、関数 `vltranbegin' を用いる。</p>
+
+<dl>
+<dt><kbd>int vltranbegin(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はマルチスレッドでの相互排他制御を行わないので、アプリケーションがその責任を負う。一つのデータベースハンドルで同時に有効にできるトランザクションは一つだけである。</dd>
+</dl>
+
+<p>トランザクションをコミットするには、関数 `vltrancommit' を用いる。</p>
+
+<dl>
+<dt><kbd>int vltrancommit(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。トランザクションの中でのデータベースの更新はコミットが成功した時に確定する。</dd>
+</dl>
+
+<p>トランザクションを破棄するには、関数 `vltranabort' を用いる。</p>
+
+<dl>
+<dt><kbd>int vltranabort(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。トランザクションの中でのデータベースの更新はアボートした時には破棄される。データベースの状態はトランザクションの前の状態にロールバックされる。</dd>
+</dl>
+
+<p>データベースファイルを削除するには、関数 `vlremove' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlremove(const char *<var>name</var>);</kbd></dt>
+<dd>`name' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。</dd>
+</dl>
+
+<p>壊れたデータベースファイルを修復するには、関数 `vlrepair' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlrepair(const char *<var>name</var>, VLCFUNC <var>cmp</var>);</kbd></dt>
+<dd>`name' はデータベースファイルの名前を指定する。`cmp' はデータベースファイルの比較関数を指定する。戻り値は正常なら真であり、エラーなら偽である。修復されたデータベースの全レコードが元来もしくは期待される状態に戻る保証はない。</dd>
+</dl>
+
+<p>全てのレコードをエンディアン非依存のデータとしてダンプするには、関数 `vlexportdb' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlexportdb(VILLA *<var>villa</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`villa' はデータベースハンドルを指定する。`name' は出力ファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。</dd>
+</dl>
+
+<p>エンディアン非依存データから全てのレコードをロードするには、関数 `vlimportdb' を用いる。</p>
+
+<dl>
+<dt><kbd>int vlimportdb(VILLA *<var>villa</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`villa' はライタで接続したデータベースハンドルを指定する。データベースは空でなければならない。`name' は入力ファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。</dd>
+</dl>
+
+<h3>サンプルコード</h3>
+
+<p>名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;cabin.h&gt;
+#include &lt;villa.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+#define NAME "mikio"
+#define NUMBER "000-1234-5678"
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ VILLA *villa;
+ char *val;
+
+ /* データベースを開く */
+ if(!(villa = vlopen(DBNAME, VL_OWRITER | VL_OCREAT, VL_CMPLEX))){
+ fprintf(stderr, "vlopen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* レコードを格納する */
+ if(!vlput(villa, NAME, -1, NUMBER, -1, VL_DOVER)){
+ fprintf(stderr, "vlput: %s\n", dperrmsg(dpecode));
+ }
+
+ /* レコードを取得する */
+ if(!(val = vlget(villa, NAME, -1, NULL))){
+ fprintf(stderr, "vlget: %s\n", dperrmsg(dpecode));
+ } else {
+ printf("Name: %s\n", NAME);
+ printf("Number: %s\n", val);
+ free(val);
+ }
+
+ /* データベースを閉じる */
+ if(!vlclose(villa)){
+ fprintf(stderr, "vlclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<p>文字列の前方一致検索を行うアプリケーションのサンプルコードを以下に示す。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;cabin.h&gt;
+#include &lt;villa.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+#include &lt;string.h&gt;
+
+#define DBNAME "words"
+#define PREFIX "apple"
+
+int main(int argc, char **argv){
+ VILLA *villa;
+ char *key, *val;
+
+ /* データベースを開く */
+ if(!(villa = vlopen(DBNAME, VL_OWRITER | VL_OCREAT, VL_CMPLEX))){
+ fprintf(stderr, "vlopen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* レコードを格納する */
+ if(!vlput(villa, "applet", -1, "little application", -1, VL_DDUP) ||
+ !vlput(villa, "aurora", -1, "polar wonderwork", -1, VL_DDUP) ||
+ !vlput(villa, "apple", -1, "delicious fruit", -1, VL_DDUP) ||
+ !vlput(villa, "amigo", -1, "good friend", -1, VL_DDUP) ||
+ !vlput(villa, "apple", -1, "big city", -1, VL_DDUP)){
+ fprintf(stderr, "vlput: %s\n", dperrmsg(dpecode));
+ }
+
+ /* カーソルを候補の先頭に置く */
+ vlcurjump(villa, PREFIX, -1, VL_JFORWARD);
+
+ /* カーソルを走査する */
+ while((key = vlcurkey(villa, NULL)) != NULL){
+ if(strstr(key, PREFIX) != key){
+ free(key);
+ break;
+ }
+ if(!(val = vlcurval(villa, NULL))){
+ fprintf(stderr, "vlcurval: %s\n", dperrmsg(dpecode));
+ free(key);
+ break;
+ }
+ printf("%s: %s\n", key, val);
+ free(val);
+ free(key);
+ vlcurnext(villa);
+ }
+
+ /* データベースを閉じる */
+ if(!vlclose(villa)){
+ fprintf(stderr, "vlclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<h3>注記</h3>
+
+<p>Villaを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>POSIXスレッドを有効にしてQDBMをビルドした場合、外部変数 `dpecode' はスレッド固有データへの参照として扱われ、Villaの各関数はリエントラントになる。その場合、スレッド間で同時に同じハンドルにアクセスしない限りは、各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。</p>
+
+<h3>Vista: 拡張上級API</h3>
+
+<p>VistaはVillaを拡張したAPIである。Villaが2GB以上のファイルを扱うことができないという欠点を補うために、VistaではDepotではなくCuriaを用いて内部データベースを管理する。VistaはVillaと同じくB+木のデータ構造とその操作を提供するが、そのデータベースはディレクトリで実現される。</p>
+
+<p>Vistaを使うには、`villa.h' の代わりに `vista.h' をインクルードすればよい。VistaはVillaのシンボルをマクロでオーバーライドして実装されているため、Villaと全く同様のAPIで利用することができる。すなわち、双方のシグネチャは全く同じである。ただし、その副作用として、Vistaを使うモジュール(コンパイルユニット)はVillaを利用することができない(`villa.h' をインクルードしてはならない)。</p>
+
+<hr />
+
+<h2><a name="villacli" id="villacli" class="head">Villa用コマンド</a></h2>
+
+<p>Villaに対応するコマンドラインインタフェースは以下のものである。</p>
+
+<p>コマンド `vlmgr' はVillaやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりする機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。</p>
+
+<dl>
+<dt><kbd>vlmgr create [-cz|-cy|-cx] <var>name</var></kbd></dt>
+<dd>データベースファイルを作成する。</dd>
+<dt><kbd>vlmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat|-dup] <var>name</var> <var>key</var> <var>val</var></kbd></dt>
+<dd>キーと値に対応するレコードを追加する。</dd>
+<dt><kbd>vlmgr out [-l] [-kx|-ki] <var>name</var> <var>key</var></kbd></dt>
+<dd>キーに対応するレコードを削除する。</dd>
+<dt><kbd>vlmgr get [-nl] [-l] [-kx|-ki] [-ox] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>キーに対応するレコードの値を取得して標準出力する。</dd>
+<dt><kbd>vlmgr list [-nl] [-k|-v] [-kx|-ki] [-ox] [-top <var>key</var>] [-bot <var>key</var>] [-gt] [-lt] [-max <var>num</var>] [-desc] <var>name</var></kbd></dt>
+<dd>データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。</dd>
+<dt><kbd>vlmgr optimize <var>name</var></kbd></dt>
+<dd>データベースを最適化する。</dd>
+<dt><kbd>vlmgr inform [-nl] <var>name</var></kbd></dt>
+<dd>データベースの雑多な情報を出力する。</dd>
+<dt><kbd>vlmgr remove <var>name</var></kbd></dt>
+<dd>データベースファイルを削除する。</dd>
+<dt><kbd>vlmgr repair [-ki] <var>name</var></kbd></dt>
+<dd>壊れたデータベースファイルを修復する。</dd>
+<dt><kbd>vlmgr exportdb [-ki] <var>name</var> <var>file</var></kbd></dt>
+<dd>全てのレコードをエンディアン非依存のデータとしてダンプする。</dd>
+<dt><kbd>vlmgr importdb [-ki] <var>name</var> <var>file</var></kbd></dt>
+<dd>エンディアン非依存データから全てのレコードをロードする。</dd>
+<dt><kbd>vlmgr version</kbd></dt>
+<dd>QDBMのバージョン情報を標準出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-cz</kbd> : データベースのリーフをZLIBで圧縮する。</li>
+<li><kbd>-cy</kbd> : データベースのリーフをLZOで圧縮する。</li>
+<li><kbd>-cx</kbd> : データベースのリーフをBZIP2で圧縮する。</li>
+<li><kbd>-l</kbd> : キーに一致する全てのレコードを処理対象とする。</li>
+<li><kbd>-kx</kbd> : 2桁単位の16進数によるバイナリ表現として `key' を扱う。</li>
+<li><kbd>-ki</kbd> : 10進数による数値表現として `key' を扱う。</li>
+<li><kbd>-vx</kbd> : 2桁単位の16進数によるバイナリ表現として `val' を扱う。</li>
+<li><kbd>-vi</kbd> : 10進数による数値表現として `val' を扱う。</li>
+<li><kbd>-vf</kbd> : 名前が `val' のファイルのデータを値として読み込む。</li>
+<li><kbd>-keep</kbd> : 既存のレコードとキーが重複時に上書きせずにエラーにする。</li>
+<li><kbd>-cat</kbd> : 既存のレコードとキーが重複時に値を末尾に追加する。</li>
+<li><kbd>-dup</kbd> : レコードのキーが重複するのを許す。</li>
+<li><kbd>-nl</kbd> : ファイルロックをかけずにデータベースを開く。</li>
+<li><kbd>-top <var>key</var></kbd> : リストの最小のキーを指定する。</li>
+<li><kbd>-bot <var>key</var></kbd> : リストの最大のキーを指定する。</li>
+<li><kbd>-gt</kbd> : 最小のキーをリストに含めない。</li>
+<li><kbd>-lt</kbd> : 最大のキーをリストに含めない。</li>
+<li><kbd>-max <var>num</var></kbd> : リストする最大数を指定する。</li>
+<li><kbd>-desc</kbd> : リストを降順で行う。</li>
+<li><kbd>-ox</kbd> : 2桁単位の16進数によるバイナリ表現として標準出力を行う。</li>
+<li><kbd>-n</kbd> : 標準出力の末尾に付加される改行文字の出力を抑制する。</li>
+<li><kbd>-k</kbd> : キーのみを出力する。</li>
+<li><kbd>-v</kbd> : 値のみを出力する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。</p>
+
+<p>コマンド `vltest' はVillaの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `vlmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数、`pnum' はキーのパターン数を指定する。</p>
+
+<dl>
+<dt><kbd>vltest write [-int] [-cz|-cy|-cx] [-tune <var>lrecmax</var> <var>nidxmax</var> <var>lcnum</var> <var>ncnum</var>] [-fbp <var>num</var>] <var>name</var> <var>rnum</var></kbd></dt>
+<dd>`00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。</dd>
+<dt><kbd>vltest read [-int] [-vc] <var>name</var></kbd></dt>
+<dd>上記で生成したデータベースの全レコードを検索する。</dd>
+<dt><kbd>vltest rdup [-int] [-cz|-cy|-cx] [-cc] [-tune <var>lrecmax</var> <var>nidxmax</var> <var>lcnum</var> <var>ncnum</var>] [-fbp <var>num</var>] <var>name</var> <var>rnum</var> <var>pnum</var></kbd></dt>
+<dd>キーがある程度重複するようにレコードの追加を行い、重複モードで処理する。</dd>
+<dt><kbd>vltest combo [-cz|-cy|-cx] <var>name</var></kbd></dt>
+<dd>各種操作の組み合わせテストを行う。</dd>
+<dt><kbd>vltest wicked [-cz|-cy|-cx] <var>name</var> <var>rnum</var></kbd></dt>
+<dd>各種更新操作を無作為に選択して実行する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-int</kbd> : `int' 型のオブジェクトをキーと値に用い、比較関数もそれに合わせる。</li>
+<li><kbd>-cz</kbd> : データベースのリーフをZLIBで圧縮する。</li>
+<li><kbd>-cy</kbd> : データベースのリーフをLZOで圧縮する。</li>
+<li><kbd>-cx</kbd> : データベースのリーフをBZIP2で圧縮する。</li>
+<li><kbd>-vc</kbd> : 揮発性キャッシュを参照する。</li>
+<li><kbd>-cc</kbd> : 連結モードと重複モードを無作為に選択する。</li>
+<li><kbd>-tune <var>lrecmax</var> <var>nidxmax</var> <var>lcnum</var> <var>ncnum</var></kbd> : 性能パラメータを指定する。</li>
+<li><kbd>-fbp <var>num</var></kbd> : フリーブロックプールのサイズを指定する。</li>
+<li><kbd>-c</kbd> : Cabinのマップを使って比較テストを行う。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。</p>
+
+<p>コマンド `vltsv' はタブ区切りでキーと値を表現した行からなるTSVファイルとVillaのデータベースを相互変換する。このコマンドは、QDBMの他のバージョンや他のDBMとの間でデータの交換を行う際に役立つ。また、バイトオーダの違うシステムの間でデータを交換する際にも役立つ。以下の書式で用いる。`name' はデータベース名を指定する。`export' サブコマンドではTSVのデータは標準入力から読み込む。`import' サブコマンドではTSVのデータが標準出力に書き出される。</p>
+
+<dl>
+<dt><kbd>vltsv import [-bin] <var>name</var></kbd></dt>
+<dd>TSVファイルを読み込んでデータベースを作成する。</dd>
+<dt><kbd>vltsv export [-bin] <var>name</var></kbd></dt>
+<dd>データベースの全てのレコードをTSVファイルとして出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-bin</kbd> : Base64形式でレコードを扱う。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。</p>
+
+<p>Villaのコマンド群を駆使すると、簡単なデータベースシステムが構築できる。例えば `/etc/password' をユーザ名で検索するためのデータベースを作成するには、以下のようにする。</p>
+
+<pre>cat /etc/passwd | tr ':' '\t' | vltsv import casket
+</pre>
+
+<p>そして、`mikio' というユーザの情報を取り出すには、以下のようにする。</p>
+
+<pre>vlmgr get casket mikio
+</pre>
+
+<p>これらのコマンドと同等の機能をVillaのAPIを用いて実装することも容易である。</p>
+
+<p>コマンド `qmttest' はDepotとCuriaとVillaのマルチスレッドにおける安全性を調査する。このコマンドはPOSIXスレッドを有効にしてQDBMをビルドした場合にのみマルチスレッドで動作する。以下の書式で用いる。`name' はデータベース名の接頭辞を指定する。`rnum' は各データベースに書き込むレコード数を指定する。`tnum' はスレッド数を指定する。</p>
+
+<dl>
+<dt><kbd>qmttest <var>name</var> <var>rnum</var> <var>tnum</var></kbd></dt>
+<dd>マルチスレッドにおける安全性を調査する。</dd>
+</dl>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。</p>
+
+<hr />
+
+<h2><a name="odeumapi" id="odeumapi" class="head">Odeum: 転置API</a></h2>
+
+<h3>概要</h3>
+
+<p>Odeumは転置インデックスを扱うAPIである。転置インデックスとは、母集団の文書群に含まれる語を抽出して、各語をキーとし、その語を含む文書のリストを検索するためのデータ構造である。転置インデックスを用いると、全文検索システムを容易に実現することができる。Odeumは文書を語や属性の集合として扱うための抽象データ型を提供する。それは、各アプリケーションがOdeumのデータベースに文書を格納する際や、データベースから文書を検索する際に用いられる。</p>
+
+<p>Odeumは元来の文書データからテキストを抽出する方法は提供しない。それはアプリケーションが実装する必要がある。Odeumはテキストを分解して語群を抽出するユーティリティを提供するが、それは空白で語が分割される英語などの言語を指向している。形態素解析やN-gram解析が必要な日本語などの言語を扱う際や、ステミングなどのより高度な自然言語処理を行う際には、アプリケーションは独自の解析方法を適用することができる。検索結果は文書のID番号とそのスコアをメンバに持つ構造体を要素とする配列として得られる。複数の語を用いて検索を行うために、Odeumは結果の配列の集合演算を行うユーティリティを提供する。</p>
+
+<p>OdeumはCuriaとCabinとVillaを基盤として実装される。Odeumではディレクトリ名を指定してデータベースを構築する。特定のディレクトリの直下にCuriaやVillaのデータベースを構築する。例えば、`casket' という名前のデータベースを作成する場合、`casket/docs'、`casket/index' および `casket/rdocs' が生成される。`docs' はCuriaのデータベースディレクトリである。キーは文書のID番号であり、値はURI等の文書属性である。`index' はCuriaのデータベースディレクトリである。キーは語の正規形であり、値はその語を含む文書のID番号とそのスコアを要素とする配列である。`rdocs' はVillaのデータベースファイルである。キーは文書のURIであり、値は文書のID番号である。</p>
+
+<p>Odeumを使うためには、`depot.h' と `cabin.h' と `odeum.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。</p>
+
+<dl>
+<dt><kbd>#include &lt;depot.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;cabin.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;odeum.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+</dl>
+
+<p>Odeumでデータベースを扱う際には、`ODEUM' 型へのポインタをハンドルとして用いる。ハンドルは、関数 `odopen' で開き、関数 `odclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。データベースに致命的なエラーが起きた場合は、以後そのハンドルに対する `odclose' を除く全ての操作は何もせずにエラーを返す。ひとつのプロセスで複数のデータベースファイルを同時に利用することは可能であるが、同じデータベースファイルの複数のハンドルを利用してはならない。</p>
+
+<p>各文書を扱う際には、`ODDOC' 型へのポインタをハンドルとして用いる。ハンドルは、関数 `oddocopen' で開き、関数 `oddocclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。文書は属性と語の集合からなる。語は正規形と出現形のペアとして表現される。</p>
+
+<p>OdeumでもDepotと同じく外部変数 `dpecode' に直前のエラーコードが記録される。エラーコードに対応するメッセージ文字列を得るには、関数 `dperrmsg' を用いる。</p>
+
+<h3>API</h3>
+
+<p>検索結果を扱うためには、`ODPAIR' 型の構造体を用いる。</p>
+
+<dl>
+<dt><kbd>typedef struct { int id; int score; } ODPAIR;</kbd></dt>
+<dd>`id' は文書のID番号である。`score' は文書に含まれる検索語の数を元に算出されるスコアである。</dd>
+</dl>
+
+<p>データベースのハンドルを作成するには、関数 `odopen' を用いる。</p>
+
+<dl>
+<dt><kbd>ODEUM *odopen(const char *<var>name</var>, int <var>omode</var>);</kbd></dt>
+<dd>`name' はデータベースディレクトリの名前を指定する。`omode' は接続モードを指定し、`OD_OREADER' ならリーダ、`OD_OWRITER' ならライタとなる。`OD_OWRITER' の場合、`OD_OCREAT' または `OD_OTRUNC' とのビット論理和にすることができる。`OD_OCREAT' はファイルが無い場合に新規作成することを指示し、`OD_OTRUNC' はファイルが存在しても作り直すことを指示する。`OD_OREADER' と `OD_OWRITER' の両方で `OD_ONOLCK' か `OD_OLCKNB' とのビット論理和にすることができるが、前者はファイルロックをかけずにデータベースを開くことを指示し、後者はブロックせずにロックをかけることを指示する。戻り値はデータベースハンドルであるか、エラーなら `NULL' である。ライタ(読み書き両用モード)でデータベースファイルを開く際にはそのファイルに対して排他ロックがかけられ、リーダ(読み込み専用モード)で開く際には共有ロックがかけられる。その際には該当のロックがかけられるまで制御がブロックする。`OD_ONOLCK' を使う場合、アプリケーションが排他制御の責任を負う。</dd>
+</dl>
+
+<p>データベースとの接続を閉じてハンドルを破棄するには、関数 `odclose' を用いる。</p>
+
+<dl>
+<dt><kbd>int odclose(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースの更新内容は、接続を閉じた時点で初めてファイルと同期される。ライタでデータベースを開いた場合、適切に接続を閉じないとデータベースが破壊される。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。</dd>
+</dl>
+
+<p>文書を追加するには、関数 `odput' を用いる。</p>
+
+<dl>
+<dt><kbd>int odput(ODEUM *<var>odeum</var>, const ODDOC *<var>doc</var>, int <var>wmax</var>, int <var>over</var>);</kbd></dt>
+<dd>`odeum' はライタで接続したデータベースハンドルを指定する。`doc' は文書ハンドルを指定する。`wmax' は文書データベースに格納する語の最大数を指定するが、負数なら無制限となる。`over' は重複した文書の上書きを行うか否かを指定する。それが偽で文書のURIが重複した場合はエラーとなる。戻り値は正常なら真であり、エラーなら偽である。</dd>
+</dl>
+
+<p>URIで指定した文書を削除するには、関数 `odout' を用いる。</p>
+
+<dl>
+<dt><kbd>int odout(ODEUM *<var>odeum</var>, const char *<var>uri</var>);</kbd></dt>
+<dd>`odeum' はライタで接続したデータベースハンドルを指定する。`uri' は文書のURIの文字列を指定する。戻り値は正常なら真であり、エラーなら偽である。該当する文書がない場合も偽を返す。</dd>
+</dl>
+
+<p>ID番号で指定した文書を削除するには、関数 `odoutbyid' を用いる。</p>
+
+<dl>
+<dt><kbd>int odoutbyid(ODEUM *<var>odeum</var>, int <var>id</var>);</kbd></dt>
+<dd>`odeum' はライタで接続したデータベースハンドルを指定する。`id' は文書のID番号を指定する。戻り値は正常なら真であり、エラーなら偽である。該当する文書がない場合も偽を返す。</dd>
+</dl>
+
+<p>URIで指定した文書を取得するには、関数 `odget' を用いる。</p>
+
+<dl>
+<dt><kbd>ODDOC *odget(ODEUM *<var>odeum</var>, const char *<var>uri</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。`uri' は文書のURIの文字列を指定する。戻り値は正常なら該当の文書のハンドルであり、エラーなら `NULL' である。該当の文書がない場合も `NULL' を返す。戻り値のハンドルは、関数 `oddocopen' で開かれるので、不要になったら `oddocclose' で閉じるべきである。</dd>
+</dl>
+
+<p>ID番号で指定した文書を取得するには、関数 `odgetbyid' を用いる。</p>
+
+<dl>
+<dt><kbd>ODDOC *odgetbyid(ODEUM *<var>odeum</var>, int <var>id</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。`id' は文書のID番号を指定する。戻り値は正常なら該当の文書のハンドルであり、エラーなら `NULL' である。該当の文書がない場合も `NULL' を返す。戻り値のハンドルは、関数 `oddocopen' で開かれるので、不要になったら `oddocclose' で閉じるべきである。</dd>
+</dl>
+
+<p>URIで指定した文書のIDを取得するには、関数 `odgetidbyuri' を用いる。</p>
+
+<dl>
+<dt><kbd>int odgetidbyuri(ODEUM *<var>odeum</var>, const char *<var>uri</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。`uri' は文書のURIの文字列を指定する。戻り値は正常なら該当の文書のIDであり、エラーなら -1 である。該当の文書がない場合も -1 を返す。</dd>
+</dl>
+
+<p>ID番号で指定した文書が存在しているか調べるには、関数 `odcheck' を用いる。</p>
+
+<dl>
+<dt><kbd>int odcheck(ODEUM *<var>odeum</var>, int <var>id</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。`id' は文書のID番号を指定する。戻り値は文書が存在すれば真、そうでなければ偽である。</dd>
+</dl>
+
+<p>転置インデックスを検索して特定の語を含む文書群を知るには、関数 `odsearch' を用いる。</p>
+
+<dl>
+<dt><kbd>ODPAIR *odsearch(ODEUM *<var>odeum</var>, const char *<var>word</var>, int <var>max</var>, int *<var>np</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。`word' は検索語を指定する。`max' は取り出す文書の最大数を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。戻り値は正常なら配列へのポインタであり、エラーなら `NULL' である。その配列の各要素は文書のID番号とスコアのペアであり、スコアの降順で並べられる。検索語に該当する文書が一つもなかったとしてもエラーにはならずに、空の配列を返す。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。配列の各要素には既に削除された文書のデータも含まれることに注意すべきである。</dd>
+</dl>
+
+<p>特定の語を含む文書の数を知るには、関数 `odsearchdnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int odsearchdnum(ODEUM *<var>odeum</var>, const char *<var>word</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。`word' は検索語を指定する。戻り値は正常なら検索語を含む文書の数であり、該当がない場合やエラーの場合は -1 である。この関数は転置インデックスの実データを読み込まないので効率がよい。</dd>
+</dl>
+
+<p>データベースのイテレータを初期化するには、関数 `oditerinit' を用いる。</p>
+
+<dl>
+<dt><kbd>int oditerinit(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。イテレータは、データベースに格納された全ての文書を参照するために用いられる。</dd>
+</dl>
+
+<p>データベースのイテレータから次の文書を取り出すには、関数 `oditernext' を用いる。</p>
+
+<dl>
+<dt><kbd>ODDOC *oditernext(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。戻り値は正常なら文書ハンドルであり、エラーなら `NULL' である。イテレータが最後まできて該当の文書がない場合も `NULL' を返す。この関数を繰り返して呼ぶことによって全ての文書を一度ずつ参照することができる。ただし、繰り返しの間にデータベースの更新があった場合はその限りではない。なお、取り出すレコードの順序は制御できず、格納した順番でレコードを取り出せるとは限らない。戻り値のハンドルは、関数 `oddocopen' で開かれるので、不要になったら `oddocclose' で閉じるべきである。</dd>
+</dl>
+
+<p>データベースを更新した内容をファイルとデバイスに同期させるには、関数 `odsync' を用いる。</p>
+
+<dl>
+<dt><kbd>int odsync(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はデータベースを閉じないうちに別プロセスにデータベースディレクトリを利用させる場合に役立つ。</dd>
+</dl>
+
+<p>データベースを最適化するには、関数 `odoptimize' を用いる。</p>
+
+<dl>
+<dt><kbd>int odoptimize(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。転置インデックスにおける削除された文書の要素は削除される。</dd>
+</dl>
+
+<p>データベースの名前を得るには、関数 `odname' を用いる。</p>
+
+<dl>
+<dt><kbd>char *odname(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。戻り値は正常なら名前を格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>データベースファイルのサイズの合計を得るには、関数 `odfsiz' を用いる。</p>
+
+<dl>
+<dt><kbd>double odfsiz(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズの合計であり、エラーなら -1.0 である。</dd>
+</dl>
+
+<p>転置インデックス内のバケット配列の要素数の合計を得るには、関数 `odbnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int odbnum(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。戻り値は正常ならバケット配列の要素数の合計であり、エラーなら -1 である。</dd>
+</dl>
+
+<p>転置インデックス内のバケット配列の利用済みの要素数の合計を得るには、関数 `odbusenum' を用いる。</p>
+
+<dl>
+<dt><kbd>int odbusenum(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。戻り値は正常ならバケット配列の利用済みの要素数の合計であり、エラーなら -1 である。</dd>
+</dl>
+
+
+<p>データベースに格納された文書数を得るには、関数 `oddnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int oddnum(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。戻り値は正常ならデータベースに格納された文書の数であり、エラーなら -1 である。</dd>
+</dl>
+
+<p>データベースに格納された単語数を得るには、関数 `odwnum' を用いる。</p>
+
+<dl>
+<dt><kbd>int odwnum(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。戻り値は正常ならデータベースに格納された語の数であり、エラーなら -1 である。I/Oバッファにより、戻り値は実際のサイズより小さくなる場合がある。</dd>
+</dl>
+
+<p>データベースハンドルがライタかどうかを調べるには、関数 `odwritable' を用いる。</p>
+
+<dl>
+<dt><kbd>int odwritable(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。戻り値はライタなら真であり、そうでなければ偽である。</dd>
+</dl>
+
+<p>データベースに致命的エラーが起きたかどうかを調べるには、関数 `odfatalerror' を用いる。</p>
+
+<dl>
+<dt><kbd>int odfatalerror(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。</dd>
+</dl>
+
+<p>データベースディレクトリのinode番号を得るには、関数 `odinode' を用いる。</p>
+
+<dl>
+<dt><kbd>int odinode(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。戻り値はデータベースディレクトリのinode番号である。</dd>
+</dl>
+
+<p>データベースの最終更新時刻を得るには、関数 `odmtime' を用いる。</p>
+
+<dl>
+<dt><kbd>time_t odmtime(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。戻り値はデータベースの最終更新時刻である。</dd>
+</dl>
+
+<p>複数のデータベースをマージするには、関数 `odmerge' を用いる。</p>
+
+<dl>
+<dt><kbd>int odmerge(const char *<var>name</var>, const CBLIST *<var>elemnames</var>);</kbd></dt>
+<dd>`name' は作成するデータベースディレクトリの名前を指定する。`elemnames' は要素データベースの名前のリストを指定する。戻り値は正常なら真であり、エラーなら偽である。同じURLを持つ複数の文書が現れた場合、最初に現れたものが採用され、残りは無視される。</dd>
+</dl>
+
+<p>データベースディレクトリを削除するには、関数 `odremove' を用いる。</p>
+
+<dl>
+<dt><kbd>int odremove(const char *<var>name</var>);</kbd></dt>
+<dd>`name' はデータベースディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。データベースディレクトリの中にはQDBMの他のAPIによるデータベースを置くことができるが、それらもこの関数によって削除される。</dd>
+</dl>
+
+<p>文書ハンドルを作成するには、関数 `oddocopen' を用いる。</p>
+
+<dl>
+<dt><kbd>ODDOC *oddocopen(const char *<var>uri</var>);</kbd></dt>
+<dd>`uri' は文書のURIを指定する。戻り値は文書ハンドルである。新しい文書のID番号は定義されない。それはデータベースに格納した際に定義される。</dd>
+</dl>
+
+<p>文書ハンドルを破棄するには、関数 `oddocclose' を用いる。</p>
+
+<dl>
+<dt><kbd>void oddocclose(ODDOC *<var>doc</var>);</kbd></dt>
+<dd>`doc' は文書ハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。</dd>
+</dl>
+
+<p>文書に属性を追加するには、関数 `oddocaddattr' を用いる。</p>
+
+<dl>
+<dt><kbd>void oddocaddattr(ODDOC *<var>doc</var>, const char *<var>name</var>, const char *<var>value</var>);</kbd></dt>
+<dd>`doc' は文書ハンドルを指定する。`name' は属性名の文字列を指定する。`value' は属性値の文字列を指定する。</dd>
+</dl>
+
+<p>文書に語を追加するには、関数 `oddocaddword' を用いる。</p>
+
+<dl>
+<dt><kbd>void oddocaddword(ODDOC *<var>doc</var>, const char *<var>normal</var>, const char *<var>asis</var>);</kbd></dt>
+<dd>`doc' は文書ハンドルを指定する。`normal' は語の正規形の文字列を指定する。正規形は転置インデックスのキーとして扱われる。正規形が空文字列の場合、その語は転置インデックスに反映されない。`asis' は語の出現形の文字列を指定する。出現形はアプリケーションが文書を取得した際に利用される。</dd>
+</dl>
+
+<p>文書のIDを得るには、関数 `oddocid' を用いる。</p>
+
+<dl>
+<dt><kbd>int oddocid(const ODDOC *<var>doc</var>);</kbd></dt>
+<dd>`doc' は文書ハンドルを指定する。戻り値は文書のID番号である。</dd>
+</dl>
+
+<p>文書のURIを得るには、関数 `oddocuri' を用いる。</p>
+
+<dl>
+<dt><kbd>const char *oddocuri(const ODDOC *<var>doc</var>);</kbd></dt>
+<dd>`doc' は文書ハンドルを指定する。戻り値は文書のURIの文字列である。</dd>
+</dl>
+
+<p>文書の属性値を得るには、関数 `oddocgetattr' を用いる。</p>
+
+<dl>
+<dt><kbd>const char *oddocgetattr(const ODDOC *<var>doc</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`doc' は文書ハンドルを指定する。`name' は属性名の文字列を指定する。戻り値は属性値の文字列であるか、該当がなければ `NULL' である。</dd>
+</dl>
+
+<p>文書内の語群の正規形のリストを得るには、関数 `oddocnwords' を用いる。</p>
+
+<dl>
+<dt><kbd>const CBLIST *oddocnwords(const ODDOC *<var>doc</var>);</kbd></dt>
+<dd>`doc' は文書ハンドルを指定する。戻り値は正規形の語群を格納したリストハンドルである。</dd>
+</dl>
+
+<p>文書内の語群の出現形のリストを得るには、関数 `oddocawords' を用いる。</p>
+
+<dl>
+<dt><kbd>const CBLIST *oddocawords(const ODDOC *<var>doc</var>);</kbd></dt>
+<dd>`doc' は文書ハンドルを指定する。戻り値は出現形の語群を格納したリストハンドルである。</dd>
+</dl>
+
+<p>文書のキーワードの正規形とそのスコアのマップを得るには、関数 `oddocscores' を用いる。</p>
+
+<dl>
+<dt><kbd>CBMAP *oddocscores(const ODDOC *<var>doc</var>, int <var>max</var>, ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`doc' は文書ハンドルを指定する。`max' は取得するキーワードの最大数を指定する。`odeum' が `NULL' でなければ、それを用いて重みづけのためのIDFが算出される。戻り値はキーワードとそのスコアを格納したマップハンドルである。スコアは10進数の文字列で表現される。戻り値のハンドルは関数 `cbmapopen' で開かれるので、不要になったら `cbmapclose' で閉じるべきである。</dd>
+</dl>
+
+<p>テキストを分解して語群の出現形のリストを得るには、関数 `odbreaktext' を用いる。</p>
+
+<dl>
+<dt><kbd>CBLIST *odbreaktext(const char *<var>text</var>);</kbd></dt>
+<dd>`text' はテキストの文字列を指定する。戻り値は語群の出現形のリストハンドルである。語群は空白文字とピリオドやコンマ等の区切り文字で分割される。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。</dd>
+</dl>
+
+<p>語の正規形を取得するには、関数 `odnormalizeword' を用いる。</p>
+
+<dl>
+<dt><kbd>char *odnormalizeword(const char *<var>asis</var>);</kbd></dt>
+<dd>`asis' は語の出現形の文字列を指定する。戻り値は語の正規形の文字列である。ASCIIコードのアルファベットは小文字に統一される。区切り文字のみからなる文字は空文字列として扱われる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>二つの文書集合からその共通集合を得るには、関数 `odpairsand' を用いる。</p>
+
+<dl>
+<dt><kbd>ODPAIR *odpairsand(ODPAIR *<var>apairs</var>, int <var>anum</var>, ODPAIR *<var>bpairs</var>, int <var>bnum</var>, int *<var>np</var>);</kbd></dt>
+<dd>`apairs' は前者の文書集合の配列を指定する。`anum' は前者の配列の要素数を指定する。`apairs' は後者の文書集合の配列を指定する。`anum' は後者の配列の要素数を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。戻り値は新しい文書集合の配列へのポインタであり、各要素は二つの集合に共通して属するものである。各要素はスコアの降順で並べられる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>二つの文書集合からその和集合を得るには、関数 `odpairsor' を用いる。</p>
+
+<dl>
+<dt><kbd>ODPAIR *odpairsor(ODPAIR *<var>apairs</var>, int <var>anum</var>, ODPAIR *<var>bpairs</var>, int <var>bnum</var>, int *<var>np</var>);</kbd></dt>
+<dd>`apairs' は前者の文書集合の配列を指定する。`anum' は前者の配列の要素数を指定する。`apairs' は後者の文書集合の配列を指定する。`anum' は後者の配列の要素数を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。戻り値は新しい文書集合の配列へのポインタであり、各要素は二つの集合の両方あるいはどちらか一方に属するものである。各要素はスコアの降順で並べられる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>二つの文書集合からその差集合を得るには、関数 `odpairsnotand' を用いる。</p>
+
+<dl>
+<dt><kbd>ODPAIR *odpairsnotand(ODPAIR *<var>apairs</var>, int <var>anum</var>, ODPAIR *<var>bpairs</var>, int <var>bnum</var>, int *<var>np</var>);</kbd></dt>
+<dd>`apairs' は前者の文書集合の配列を指定する。`anum' は前者の配列の要素数を指定する。`apairs' は後者の文書集合の配列を指定する。`anum' は後者の配列の要素数を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。戻り値は新しい文書集合の配列へのポインタであり、各要素は前者の集合には属するが後者の集合には属さないものである。各要素はスコアの降順で並べられる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。</dd>
+</dl>
+
+<p>文書集合をスコアの降順で並べるには、関数 `odpairssort' を用いる。</p>
+
+<dl>
+<dt><kbd>void odpairssort(ODPAIR *<var>pairs</var>, int <var>pnum</var>);</kbd></dt>
+<dd>`pairs' は文書集合の配列を指定する。`pnum' はその配列の要素数を指定する。</dd>
+</dl>
+
+<p>ある数の自然対数を得るには、関数 `odlogarithm' を用いる。</p>
+
+<dl>
+<dt><kbd>double odlogarithm(double <var>x</var>);</kbd></dt>
+<dd>`x' はある数を指定する。戻り値はその数の自然対数である。もしその数が 1.0 以下であれば、戻り値は 0.0 となる。この関数はアプリケーションが検索結果のIDFを算出する際に便利である。</dd>
+</dl>
+
+<p>二つのベクトルのなす角の余弦を得るには、関数 `odvectorcosine' を用いる。</p>
+
+<dl>
+<dt><kbd>double odvectorcosine(const int *<var>avec</var>, const int *<var>bvec</var>, int <var>vnum</var>);</kbd></dt>
+<dd>`avec' は前者の数値配列を指定する。`bvec' は後者の数値配列を指定する。`vnum' は各々の配列の要素数を指定する。戻り値は二つのベクトルのなす角の余弦である。この関数はアプリケーションが文書の類似度を算出する際に便利である。</dd>
+</dl>
+
+<p>性能を調整する大域的なパラメータを指定するには、関数 `odsettuning' を用いる。</p>
+
+<dl>
+<dt><kbd>void odsettuning(int <var>ibnum</var>, int <var>idnum</var>, int <var>cbnum</var>, int <var>csiz</var>);</kbd></dt>
+<dd>`ibnum' は転置インデックスのバケット数を指定する。`idnum' は転置インデックスの分割数を指定する。`cbnum' はダーティバッファのバケット数を指定する。`csiz' はダーティバッファに使うメモリの最大バイト数を指定する。デフォルトの設定は `odsettuning(32749, 7, 262139, 8388608)' に相当する。この関数はハンドルを開く前に呼び出すべきである。</dd>
+</dl>
+
+<p>テキストを分解して出現形と正規形を別々のリストに格納するには、関数 `odanalyzetext' を用いる。</p>
+
+<dl>
+<dt><kbd>void odanalyzetext(ODEUM *<var>odeum</var>, const char *<var>text</var>, CBLIST *<var>awords</var>, CBLIST *<var>nwords</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。`text' はテキストの文字列を指定する。`awords' は出現型を格納するリストハンドルを指定する。`nwords' は正規型を格納するリストハンドルを指定するが、`NULL' なら無視される。語群は空白文字とピリオドやコンマ等の区切り文字で分割される。</dd>
+</dl>
+
+<p>関数 `odanalyzetext' で使われる文字の分類を設定するには、関数 `odsetcharclass' を用いる。</p>
+
+<dl>
+<dt><kbd>void odsetcharclass(ODEUM *<var>odeum</var>, const char *<var>spacechars</var>, const char *<var>delimchars</var>, const char *<var>gluechars</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。`spachechars' は空白文字を含む文字列を指定する。`delimchars' は区切り文字を含む文字列を指定する。`gluechars' は接着文字を含む文字列を指定する。</dd>
+</dl>
+
+<p>小さな問い合わせ言語を使って検索を行うには、関数 `odquery' を用いる。</p>
+
+<dl>
+<dt><kbd>ODPAIR *odquery(ODEUM *<var>odeum</var>, const char *<var>query</var>, int *<var>np</var>, CBLIST *<var>errors</var>);</kbd></dt>
+<dd>`odeum' はデータベースハンドルを指定する。`query' は問い合わせ言語の文字列を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。`error' はエラーメッセージを格納するリストハンドルを指定する。戻り値は正常なら配列へのポインタであり、エラーなら `NULL' である。その配列の各要素は文書のID番号とスコアのペアであり、スコアの降順で並べられる。検索語に該当する文書が一つもなかったとしてもエラーにはならずに、空の配列を返す。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。配列の各要素には既に削除された文書のデータも含まれることに注意すべきである。</dd>
+</dl>
+
+<h3>サンプルコード</h3>
+
+<p>文書をデータベースに格納するサンプルコードを以下に示す。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;cabin.h&gt;
+#include &lt;odeum.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+#include &lt;string.h&gt;
+
+#define DBNAME "index"
+
+int main(int argc, char **argv){
+ ODEUM *odeum;
+ ODDOC *doc;
+ CBLIST *awords;
+ const char *asis;
+ char *normal;
+ int i;
+
+ /* データベースを開く */
+ if(!(odeum = odopen(DBNAME, OD_OWRITER | OD_OCREAT))){
+ fprintf(stderr, "odopen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* 文書ハンドルを取得する */
+ doc = oddocopen("http://www.foo.bar/baz.txt");
+
+ /* 文書の属性を設定する */
+ oddocaddattr(doc, "title", "Balcony Scene");
+ oddocaddattr(doc, "author", "Shakespeare");
+
+ /* テキストを分解して語のリストを得る */
+ awords = odbreaktext("Parting is such sweet sorrow.");
+
+ /* 各語を文書ハンドルに設定する */
+ for(i = 0; i &lt; cblistnum(awords); i++){
+ /* 語のリストから一語を取り出す */
+ asis = cblistval(awords, i, NULL);
+ /* 出現形から正規形を生成する */
+ normal = odnormalizeword(asis);
+ /* 語を文書ハンドルに設定する */
+ oddocaddword(doc, normal, asis);
+ /* 正規形の領域を解放する */
+ free(normal);
+ }
+
+ /* 文書をデータベースに登録する */
+ if(!odput(odeum, doc, -1, 1)){
+ fprintf(stderr, "odput: %s\n", dperrmsg(dpecode));
+ }
+
+ /* 語のリストを解放する */
+ cblistclose(awords);
+
+ /* 文書ハンドルを解放する */
+ oddocclose(doc);
+
+ /* データベースを閉じる */
+ if(!odclose(odeum)){
+ fprintf(stderr, "odclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<p>データベース内の文書を検索するサンプルコードを以下に示す。</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;cabin.h&gt;
+#include &lt;odeum.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+#include &lt;string.h&gt;
+
+#define DBNAME "index"
+
+int main(int argc, char **argv){
+ ODEUM *odeum;
+ ODPAIR *pairs;
+ ODDOC *doc;
+ const CBLIST *words;
+ const char *title, *author, *asis;
+ int i, j, pnum;
+
+ /* データベースを取得する */
+ if(!(odeum = odopen(DBNAME, OD_OREADER))){
+ fprintf(stderr, "odopen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* 文書の検索を行う */
+ if((pairs = odsearch(odeum, "sorrow", -1, &amp;pnum)) != NULL){
+
+ /* 文書の配列を走査する */
+ for(i = 0; i &lt; pnum; i++){
+ /* 文書ハンドルを取得する */
+ if(!(doc = odgetbyid(odeum, pairs[i].id))) continue;
+ /* 文書の属性を表示する */
+ printf("URI: %s\n", oddocuri(doc));
+ title = oddocgetattr(doc, "title");
+ if(title) printf("TITLE: %s\n", title);
+ author = oddocgetattr(doc, "author");
+ if(author) printf("AUTHOR: %s\n", author);
+ /* 文書内の語を出現形で表示する */
+ printf("WORDS:");
+ words = oddocawords(doc);
+ for(j = 0; j &lt; cblistnum(words); j++){
+ asis = cblistval(words, j, NULL);
+ printf(" %s", asis);
+ }
+ putchar('\n');
+ /* 文書ハンドルを解放する */
+ oddocclose(doc);
+ }
+
+ /* 文書の配列を解放する */
+ free(pairs);
+
+ } else {
+ fprintf(stderr, "odsearch: %s\n", dperrmsg(dpecode));
+ }
+
+ /* データベースを閉じる */
+ if(!odclose(odeum)){
+ fprintf(stderr, "odclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<h3>注記</h3>
+
+<p>Odeumを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>POSIXスレッドを有効にしてQDBMをビルドした場合、外部変数 `dpecode' はスレッド固有データへの参照として扱われ、Odeumの各関数はリエントラントになる。その場合、スレッド間で同時に同じハンドルにアクセスしない限りは、各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。</p>
+
+<p>ZLIBを有効にしてQDBMをビルドした場合、文書属性用データベース内のレコードは圧縮されて保存される。その場合、サイズが30%以下になる。したがって、Odeumを利用する場合はZLIBを有効にした方がよい。ZLIBを有効にして作成したOdeumのデータベースを、ZLIBを有効にしていない環境で利用することはできず、またその逆も同様である。ZLIBが有効でなくLZOが有効な場合は、ZLIBの変わりにLZOが用いられる。</p>
+
+<h3>問い合わせ言語</h3>
+
+<p>関数 `odquery' の問い合わせ言語は以下の文法に基づく。</p>
+
+<pre>expr ::= subexpr ( op subexpr )*
+subexpr ::= WORD
+subexpr ::= LPAREN expr RPAREN
+</pre>
+
+<p>演算子としては "&amp;"(AND)と "|"(OR)と "!"(NOTAND)が利用できる。また、括弧 "()" を使うことで演算子の評価順序を制御することができる。問い合わせの文字列は関数 `odanalyzetext' を用いて分解されるので、"&amp;"、"|"、"!"、"("、")" は区切り文字として設定されている必要がある。また、空白で単語を区切っても "&amp;" で区切ったのと同じことになる。つまり "joe blow" は "joe &amp; blow" と同じである。</p>
+
+<p>問い合わせ文字列の文字コードは対象文書の文字コードと一致している必要がある。また、空白文字と区切り文字と接着文字に指定できるのは1バイト文字だけである。</p>
+
+<hr />
+
+<h2><a name="odeumcli" id="odeumcli" class="head">Odeum用コマンド</a></h2>
+
+<p>Odeumに対応するコマンドラインインタフェースは以下のものである。</p>
+
+<p>コマンド `odmgr' はOdeumやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりする機能を持つ。シェルスクリプトで全文検索システムを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`file' はファイル名、`expr' は文書のURIかID番号、`words' は検索語、`elems' は要素データベースを指定する。</p>
+
+<dl>
+<dt><kbd>odmgr create <var>name</var></kbd></dt>
+<dd>データベースファイルを作成する。</dd>
+<dt><kbd>odmgr put [-uri <var>str</var>] [-title <var>str</var>] [-author <var>str</var>] [-date <var>str</var>] [-wmax <var>num</var>] [-keep] <var>name</var> [<var>file</var>]</kbd></dt>
+<dd>ファイルを読み込んで文書を追加する。`file' を省略すると標準入力を読み込むが、その場合はURIの指定が必須となる。</dd>
+<dt><kbd>odmgr out [-id] <var>name</var> <var>expr</var></kbd></dt>
+<dd>URIに対応する文書を削除する。</dd>
+<dt><kbd>odmgr get [-id] [-t|-h] <var>name</var> <var>expr</var></kbd></dt>
+<dd>URIに対応する文書を表示する。出力は文書のID番号とURIとスコアをタブで区切ったものである。</dd>
+<dt><kbd>odmgr search [-max <var>num</var>] [-or] [-idf] [-t|-h|-n] <var>name</var> <var>words</var>...</kbd></dt>
+<dd>指定した語を含む文書を検索する。出力の第1行は、検索語全体の該当数と各検索語およびその該当数をタブで区切ったものである。第2行以降は、該当の各文書のID番号とURIとスコアをタブで区切ったものである。</dd>
+<dt><kbd>odmgr list [-t|-h] <var>name</var></kbd></dt>
+<dd>データベース内の全ての文書を表示する。出力の各行は文書のID番号とURIとスコアをタブで区切ったものである。</dd>
+<dt><kbd>odmgr optimize <var>name</var></kbd></dt>
+<dd>データベースを最適化する。</dd>
+<dt><kbd>odmgr inform <var>name</var></kbd></dt>
+<dd>データベースの雑多な情報を出力する。</dd>
+<dt><kbd>odmgr merge <var>name</var> <var>elems</var>...</kbd></dt>
+<dd>複数のデータベースをマージする。</dd>
+<dt><kbd>odmgr remove <var>name</var></kbd></dt>
+<dd>データベースディレクトリを削除する。</dd>
+<dt><kbd>odmgr break [-h|-k|-s] [<var>file</var>]</kbd></dt>
+<dd>ファイルを読み込んで、テキストを語に分解して出力する。出力の各行は各語の正規形と出現形をタブで区切ったものである。</dd>
+<dt><kbd>odmgr version</kbd></dt>
+<dd>QDBMのバージョン情報を出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-uri <var>str</var></kbd> : 文書のURIを明示的に指定する。</li>
+<li><kbd>-title <var>str</var></kbd> : 文書のタイトルを指定する。</li>
+<li><kbd>-author <var>str</var></kbd> : 文書の著者名を指定する。</li>
+<li><kbd>-date <var>str</var></kbd> : 文書の更新日時を指定する。</li>
+<li><kbd>-wmax <var>num</var></kbd> : 格納する語の最大数を指定する。</li>
+<li><kbd>-keep</kbd> : 同じURIの文書が既存であれば上書きを行わない。</li>
+<li><kbd>-id</kbd> : URIでなくID番号で文書を指定する。</li>
+<li><kbd>-t</kbd> : 文書の詳細情報をタブ区切りで出力する。</li>
+<li><kbd>-h</kbd> : 文書の詳細情報を人間が読みやすい形式で出力する。</li>
+<li><kbd>-k</kbd> : 文書のキーワードのみを出力する。</li>
+<li><kbd>-s</kbd> : 文書の要約のみを出力する。</li>
+<li><kbd>-max <var>num</var></kbd> : 出力する文書の最大数を指定する。</li>
+<li><kbd>-or</kbd> : AND検索でなくOR検索を行う。</li>
+<li><kbd>-idf</kbd> : IDFでスコアを重みづけする。</li>
+<li><kbd>-n</kbd> : 文書のIDとスコアのみを表示する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。</p>
+
+<p>コマンド `odtest' はOdeumの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースディレクトリを `odmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`dnum' は文書数、`wnum' は文書毎の語数、`pnum' は語のパターン数を指定する。</p>
+
+<dl>
+<dt><kbd>odtest write [-tune <var>ibnum</var> <var>idnum</var> <var>cbnum</var> <var>csiz</var>] <var>name</var> <var>dnum</var> <var>wnum</var> <var>pnum</var></kbd></dt>
+<dd>無作為な属性と語を持つ文書を連続してデータベースに追加する。</dd>
+<dt><kbd>odtest read <var>name</var></kbd></dt>
+<dd>上記で生成したデータベースの全文書を検索する。</dd>
+<dt><kbd>odtest combo <var>name</var></kbd></dt>
+<dd>各種操作の組み合わせテストを行う。</dd>
+<dt><kbd>odtest wicked <var>name</var> <var>dnum</var></kbd></dt>
+<dd>各種更新操作を無作為に選択して実行する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-tune <var>ibnum</var> <var>idnum</var> <var>cbnum</var> <var>csiz</var></kbd> : 性能パラメータを指定する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。</p>
+
+<p>コマンド `odidx' はローカルファイルシステム上のファイルを読み込んでOdeumのデータベースに登録するユーティリティである。このコマンドはWebサイトの全文検索システムを構築する際に役立つ。サポートされるファイルフォーマットはプレーンテキストとHTMLである。サポートされる文字コードはUS-ASCIIとISO-8859-1である。各文書のURIにはファイルのパスが指定される。各文書には、`title' と `date' という属性が付与される。既にデータベース登録してあるファイルを登録しようとした場合、更新時刻が新しければ登録され、そうでなければ無視される。更新時刻はデータベースディレクトリの中の '_mtime' というサブデータベースに記録される。スコア情報はデータベースディレクトリの中の `_score' というサブデータベースに記録される。以下の書式で用いる。`name' はデータベース名、`dir' はディレクトリ名を指定する。</p>
+
+<dl>
+<dt><kbd>odidx register [-l <var>file</var>] [-wmax <var>num</var>] [-tsuf <var>sufs</var>] [-hsuf <var>sufs</var>] <var>name</var> [<var>dir</var>]</kbd></dt>
+<dd>特定のディレクトリ以下のファイル群をデータベース登録する。`dir' が省略された場合、カレントディレクトリが指定される。</dd>
+<dt><kbd>odidx relate <var>name</var></kbd></dt>
+<dd>データベースの各文書に関連文書検索のためのスコア情報を付加する。</dd>
+<dt><kbd>odidx purge <var>name</var></kbd></dt>
+<dd>ファイルシステムに存在しない文書をデータベースから削除する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持つ。</p>
+
+<ul class="lines">
+<li><kbd>-l <var>file</var></kbd> : 登録すべきファイルのパスのリストをファイルから読み込む。`-' を指定した場合、標準入力が読み込まれる。</li>
+<li><kbd>-wmax <var>num</var></kbd> : データベースに格納する語の最大数を指定する。</li>
+<li><kbd>-tsuf <var>sufs</var></kbd> : プレーンテキストファイルの拡張子をカンマ区切りで指定する。デフォルトは `-tsuf .txt,.text' と同意である。</li>
+<li><kbd>-hsuf <var>sufs</var></kbd> : HTMLファイルの拡張子をカンマ区切りで指定する。デフォルトは `-hsuf .html,.htm' と同意である。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。</p>
+
+<p>Odeumのコマンド群を駆使すると、全文検索システムを簡単に実現することができる。例えば `/home/mikio' 以下にあり、かつ `.txt' か `.c' か `.h' という接尾辞を持つファイル群を `casket' という名前のインデックスに登録するなら、以下のようにする。</p>
+
+<pre>odidx register -tsuf ".txt,.c,.h" -hsuf "" casket /home/mikio
+</pre>
+
+<p>そして、`unix' および `posix' という語を含む文書を検索し、上位8件を表示するには、以下のようにする。</p>
+
+<pre>odmgr search -max 8 -h casket "unix posix"
+</pre>
+
+<p>`odidx' で生成したデータベースは、QDBMに付録される全文検索のためのCGIスクリプト `qfts.cgi' でそのまま利用することができる。</p>
+
+<hr />
+
+<h2><a name="fileformat" id="fileformat" class="head">ファイルフォーマット</a></h2>
+
+<h3>Depotのファイルフォーマット</h3>
+
+<p>Depotが管理するデータベースファイルの内容は、ヘッダ部、バケット部、レコード部の三つに大別される。</p>
+
+<p>ヘッダ部はファイルの先頭から 48 バイトの固定長でとられ、以下の情報が記録される。</p>
+
+<ol>
+<li>マジックナンバ : オフセット 0 から始まる。ビッグエンディアン用なら文字列 "[DEPOT]\n\f" を内容とし、リトルエンディアン用なら文字列 "[depot]\n\f" を内容とする。</li>
+<li>バージョン番号 : オフセット 12 から始まる。ライブラリのバージョン番号を10進数で表現した文字列を内容とする。</li>
+<li>ラッパー用フラグ : オフセット 16 から始まる。`int' 型の整数である。</li>
+<li>ファイルサイズ : オフセット 24 から始まる。`int' 型の整数である。</li>
+<li>バケット配列の要素数 : オフセット 32 から始まる。`int' 型の整数である。</li>
+<li>レコード数 : オフセット 40 から始まる。`int' 型の整数である。</li>
+</ol>
+
+<p>バケット部はヘッダ部の直後にバケット配列の要素数に応じた大きさでとられ、チェーンの先頭要素のオフセットが各要素に記録される。</p>
+
+<p>レコード部はバケット部の直後からファイルの末尾までを占め、各レコードの以下の情報を持つ要素が記録される。</p>
+
+<ol>
+<li>フラグ : `int' 型の整数である。</li>
+<li>キーの第二ハッシュ値 : `int' 型の整数である。</li>
+<li>キーのサイズ : `int' 型の整数である。</li>
+<li>値のサイズ : `int' 型の整数である。</li>
+<li>パディングのサイズ : `int' 型の整数である。</li>
+<li>左の子の位置 : `int' 型の整数である。</li>
+<li>右の子の位置 : `int' 型の整数である。</li>
+<li>キーの実データ : キーのサイズで定義される長さを持つ一連のバイトである。</li>
+<li>値の実データ : 値のサイズで定義される長さを持つ一連のバイトである。</li>
+<li>パディング : 値のサイズとアラインメントにより算出される長さを持つ一連のバイトである。</li>
+</ol>
+
+<h3>Villaのファイルフォーマット</h3>
+
+<p>Villaの扱う全てのデータはDepotのデータベースに記録される。記録されるデータは、メタデータと論理ページに分類される。論理ページはリーフノードと非リーフノードに分類される。メタデータはレコード数等の管理情報を記録するもので、キーと値ともに `int' 型である。リーフノードはレコードを保持する。非リーフノードはページを参照する疎インデックスを保持する。</p>
+
+<p>Villaは、小さい自然数を直列化して扱う際に記憶領域を節約するために、可変長整数フォーマット(BER圧縮)を用いる。可変長整数のオブジェクトは、領域の先頭から解析し、値が正のバイトを読んだらそこで終端とする。各バイトは絶対値で評価され、リトルエンディアンの128進数として算出される。</p>
+
+<p>レコードはユーザデータの論理的な単位である。キーが重複する論理レコードは物理的には単一のレコードにまとめられる。物理レコードは以下の形式で直列化される。</p>
+
+<ol>
+<li>キーのサイズ : 可変長整数型である。</li>
+<li>キーの実データ : キーのサイズで定義される長さを持つ一連のバイトである。</li>
+<li>値の数 : 可変長整数型である。</li>
+<li>値のリスト : 以下の表現を値の数だけ繰り返した一連のバイトである。<ol>
+<li>サイズ : 可変長整数型である。</li>
+<li>実データ : サイズで定義される長さを持つ一連のバイトである。</li>
+</ol></li>
+</ol>
+
+<p>リーフノードはレコードの集合を格納するための物理的な単位である。リーフノードは `int' 型のIDをキーとし、以下の値を持つレコードとしてDepotのデータベースに格納される。レコードは常にキーの昇順に整列した状態で保持される。</p>
+
+<ol>
+<li>前のリーフのID : 可変長整数型である。</li>
+<li>次のリーフのID : 可変長整数型である。</li>
+<li>レコードのリスト : 直列化したレコードを連結したもの。</li>
+</ol>
+
+<p>インデックスはページを探索するためのポインタの論理的な単位である。インデックスは以下の形式で直列化される。</p>
+
+<ol>
+<li>参照先のページのID : 可変長整数型である。</li>
+<li>キーのサイズ : 可変長整数型である。</li>
+<li>キーの実データ : キーのサイズで定義される長さを持つ一連のバイトである。</li>
+</ol>
+
+<p>非リーフノードはインデックスの集合を格納するための物理的な単位である。非リーフノードは `int' 型のIDをキーとし、以下の値を持つレコードとしてDepotのデータベースに格納される。インデックスは常にキーの昇順に整列した状態で保持される。</p>
+
+<ol>
+<li>最初の子ノードのID : 可変長整数型である。</li>
+<li>インデックスのリスト : 直列化したインデックスを連結したもの。</li>
+</ol>
+
+<h3>注記</h3>
+
+<p>データベースファイルはスパースではないので、通常のファイルと同様に複製等の操作を行うことができる。Depotはバイトオーダの調整をしないでファイルの読み書きを行っているので、バイトオーダの異なる環境にデータベースファイルを移設してもそのままでは利用できない。</p>
+
+<p>DepotやVillaのデータベースファイルをネットワークで配布する際には、MIMEタイプを `application/x-qdbm' にしてほしい。ファイル名の接尾辞は `.qdb' にしてほしい。Curiaのデータベースディレクトリをネットワークで配布する際には、TAR形式等を用いたアーカイブに変換して行うことができる。</p>
+
+<p>データベースファイルのマジックナンバを `file' コマンドに識別させたい場合は、`magic' ファイルに以下の行を追記するとよい。</p>
+
+<pre>0 string [DEPOT]\n\f QDBM, big endian
+&gt;12 string x \b, version=%s
+&gt;19 byte ^1 \b, Hash
+&gt;19 byte &amp;1 \b, B+ tree
+&gt;19 byte &amp;2 \b (deflated:ZLIB)
+&gt;19 byte &amp;4 \b (deflated:LZO)
+&gt;19 byte &amp;8 \b (deflated:BZIP2)
+&gt;24 belong x \b, filesize=%d
+&gt;32 belong x \b, buckets=%d
+&gt;40 belong x \b, records=%d
+0 string [depot]\n\f QDBM, little endian
+&gt;12 string x \b, version=%s
+&gt;16 byte ^1 \b, Hash
+&gt;16 byte &amp;1 \b, B+ tree
+&gt;16 byte &amp;2 \b (deflated:ZLIB)
+&gt;16 byte &amp;4 \b (deflated:LZO)
+&gt;16 byte &amp;8 \b (deflated:BZIP2)
+&gt;24 lelong x \b, filesize=%d
+&gt;32 lelong x \b, buckets=%d
+&gt;40 lelong x \b, records=%d
+</pre>
+
+<hr />
+
+<h2><a name="porting" id="porting" class="head">移植方法</a></h2>
+
+<p>QDBMはPOSIX互換の全てのプラットフォームで動作することを目標としている。ただし、いくつかのAPIが実装されていないプラットフォームでも動作することが望ましい。また、GCC以外のコンパイラを利用してもビルドができることが望ましい。様々なプラットフォームへの移植作業は、新しい `Makefile' を追加したりソースファイルの一部を修正したりすることによってなされる。C言語のAPIであれば、おそらく以下のファイルのいくつかを修正することになる。もしくはそれらを基に新しいファイルを作ってもよい。</p>
+
+<ul class="lines">
+<li><kbd>Makefile.in</kbd> : `./configure' に利用され、`Makefile' のベースとなる。</li>
+<li><kbd>myconf.h</kbd> : システム依存の設定ファイル。</li>
+<li><kbd>depot.h</kbd> : 基本APIのヘッダ。</li>
+<li><kbd>curia.h</kbd> : 拡張APIのヘッダ。</li>
+<li><kbd>relic.h</kbd> : NDBM互換APIのヘッダ。</li>
+<li><kbd>hovel.h</kbd> : GDBM互換APIのヘッダ。</li>
+<li><kbd>cabin.h</kbd> : ユーティリティAPIのヘッダ。</li>
+<li><kbd>villa.h</kbd> : 上級APIのヘッダ。</li>
+<li><kbd>vista.h</kbd> : 拡張上級APIのヘッダ。</li>
+<li><kbd>odeum.h</kbd> : 転置APIのヘッダ。</li>
+<li><kbd>myconf.c</kbd> : システム依存の実装。</li>
+<li><kbd>depot.c</kbd> : 基本APIの実装。</li>
+<li><kbd>curia.c</kbd> : 拡張APIの実装。</li>
+<li><kbd>relic.c</kbd> : NDBM互換APIの実装。</li>
+<li><kbd>hovel.c</kbd> : GDBM互換APIの実装。</li>
+<li><kbd>cabin.c</kbd> : ユーティリティAPIの実装。</li>
+<li><kbd>villa.c</kbd> : 上級APIの実装。</li>
+<li><kbd>vista.c</kbd> : 拡張上級APIの実装。</li>
+<li><kbd>odeum.c</kbd> : 転置APIの実装。</li>
+</ul>
+
+<p>`fcntl' コールによるファイルロックがサポートされていないプラットフォームでは、`Makefile' で定義される `CFLAGS' マクロに `-DMYNOLOCK' を追加するとよい。その際にはプロセス間の排他制御を行う別の方法を考える必要がある。同様に、`mmap' コールがないプラットフォームでは、`CFLAGS' に `-DMYNOMMAP' を追加するとよい。`mmap' に関しては `malloc' 等を用いたエミュレーションが用意されている。その他のシステムコールが実装されていない場合は、`myconf.h' と `myconf.c' を修正して該当のシステムコールのエミュレーションを行えばよい。</p>
+
+<p>C++用のAPIではPOSIXスレッドを使っているので、そのパッケージが実装されていない環境にはC++用APIは移植できない。Java用のAPIではJNIを使っているので、そのヘッダやライブラリの場所に注意すべきである。また、`long long' や `int64' といった型定義にも注意すべきである。PerlやRuby用のAPIでは各々の言語処理系で用意されたビルドコマンドを用いているので、その仕様に精通すべきである。</p>
+
+<hr />
+
+<h2><a name="bugs" id="bugs" class="head">バグ</a></h2>
+
+<p>QDBMの各文書は英語を母国語とする人達によって校正されるべきである。</p>
+
+<p>segmentation faultによるクラッシュ、予期せぬデータの消失等の不整合、メモリリーク、その他諸々のバグに関して、既知のもので未修正のものはない。</p>
+
+<p>バグを発見したら、是非とも作者にフィードバックしてほしい。その際、QDBMのバージョンと、利用環境のOSとコンパイラのバージョンも教えてほしい。</p>
+
+<p>1.7.13より前のバージョンのQDBMで作成したデータベースは、それ以後のバージョンと互換性がない。</p>
+
+<hr />
+
+<h2><a name="faq" id="faq" class="head">よく聞かれる質問</a></h2>
+
+<dl>
+<dt>Q. : QDBMはSQLをサポートするか。</dt>
+<dd>A. : QDBMはSQLをサポートしない。QDBMはRDBMS(関係データベース管理システム)ではない。組み込みのRDBMSを求めるなら、SQLiteなどを利用するとよい。</dd>
+<dt>Q. : 結局のところ、GDBM(NDBM、SDBM、Berkeley DB)とどう違うのか。</dt>
+<dd>A. : 処理が速い。データベースファイルが小さい。APIが簡潔である。特筆すべきは、レコードの上書きを繰り返す場合の時間的および空間的効率がとてもよく、実用上のスケーラビリティが高いことである。また、レコード数が100万を越えるような大規模なデータベースを構築する際にも、処理が極端に遅くなったり、ファイルのサイズが極端に大きくなったりしない。とはいえ、用途によっては他のDBMやDBMSを使う方が適切かもしれないので、各自で性能や機能の比較をしてみてほしい。</dd>
+<dt>Q. : 参考文献は何か。</dt>
+<dd>A. : QDBMの各種アルゴリズムは、主にAho他の `Data Structures and Algorithms'(邦題は「データ構造とアルゴリズム」)およびSedgewickの `Algorithms in C'(邦題は「アルゴリズムC」)の記述に基礎を置いている。</dd>
+<dt>Q. : どのAPIを使えばよいのか。</dt>
+<dd>A. : レコードの検索が完全一致だけで済むのなら、Depotを試すとよい。その規模が大きいなら、Curiaを試すとよい。レコードを順序に基づいて参照したいなら、Villaを試すとよい。その規模が大きいなら、Vistaを試すとよい。最大のレコード数を追求するなら、ZLIBかLZOを有効にしてQDBMをビルドし、その上でVistaを用いるのがよい。</dd>
+<dt>Q. : アプリケーションの良いサンプルコードはあるか。</dt>
+<dd>A. : 各APIのコマンドのソースコードを参考にしてほしい。`dptsv.c' と `crtsv.c' と `vltsv.c' が最も簡潔である。</dd>
+<dt>Q. : データベースが壊れたのだが、どうしてか。</dt>
+<dd>A. : 大抵の場合、あなたのアプリケーションがきちんとデータベースを閉じていないのが原因である。デーモンプロセスであろうが、CGIスクリプトであろうが、アプリケーションが終了する際には必ずデータベースを閉じなければならない。なお、CGIのプロセスはSIGPIPEやSIGTERMによって殺されることがあることにも留意すべきである。</dd>
+<dt>Q. : QDBMのデータベースはどのくらい堅牢なのか。</dt>
+<dd>A. : QDBMは絶対的な堅牢性は保証しない。オペレーティングシステムが暴走した際にはデータベースが壊れる可能性がある。Villaのトランザクションはアプリケーションの暴走からデータベースを保護することができるが、オペレーティングシステムの暴走には対処できない。したがって、ミッションクリティカルな用途にQDBMを利用する場合は、データベースの多重化を考慮すべきである。</dd>
+<dt>Q. : DepotとCuriaのアラインメントの使い方がよくわからないが。</dt>
+<dd>A. : 上書きモードや連結モードでの書き込みを繰り返す場合に、アラインメントはデータベースファイルのサイズが急激に大きくなるのを防ぐ。アラインメントの適切なサイズはアプリケーションによって異なるので、各自で実験してみてほしい。さしあたりは32くらいにしておくとよい。</dd>
+<dt>Q. : Villaの性能パラメータの調整がよくわからないが。</dt>
+<dd>A. : レコードを順番に参照することが多いならば、`lrecmax' と `nidxmax' をより大きくした方がよい。レコードを無作為に参照することが多いならば、それらは小さくした方がよい。RAMに余裕があるならば、`lcnum' と `ncnum' を増やすと性能がかなり向上する。ZLIBやLZOやBZIP2を有効化した場合、`lrecmax' を大きくした方が圧縮効率がよくなる。</dd>
+<dt>Q. : Villaの圧縮方式としてはZLIBとLZOとBZIP2のどれがよいのか。</dt>
+<dd>A. : 圧縮率が最も良いのはBZIP2で、処理速度が最も高いのはLZOで、ZLIBはその中間的な特性を持つ。特に理由がない限りはZLIBを使うとよい。ただし、更新が頻繁なデータベースにはLZOが適切で、更新がほとんどないならばBZIP2が適切である。圧縮しないという選択肢よりはLZOを使う方がよい。LZOのライセンスはGNU GPLであることに注意すること。</dd>
+<dt>Q. : スパースファイルとは何か。</dt>
+<dd>A. : ホール(一度もデータが書き込まれていないブロック)があるファイルのことである。ファイルシステムがスパースファイルをサポートしている場合、ホールは物理的な記憶装置に割り当てられない。QDBMでは、DP_OSPARSEなどのフラグを用いると、ハッシュのバケット配列は初期化されずにホールとなる。このことを利用して、非常に巨大なハッシュ表を実現することができる。ただし、その性能はファイルシステムの設定に強く依存する。</dd>
+<dt>Q. : なぜDepotとCuriaにはトランザクション機能がないのか。</dt>
+<dd>A. : アプリケーションが独自のトランザクション機能を実装している場合には、データベース内部のトランザクションは邪魔になるからである。トランザクションはCabinのマップを使えば簡単に実装できる。</dd>
+<dt>Q. : 性能を引き出すシステムの設定はどうであるか。</dt>
+<dd>A. : データベースのサイズと同等以上のRAMをマシンに搭載することが望ましい。そして、I/Oバッファのサイズを大きくし、ダーティバッファをフラッシュする頻度が少なくするように設定するとよい。ファイルシステムの選択も重要である。Linux上では、通常はEXT2が最高速であるが、EXT3の `writeback' モードの方が速いこともある。ReiserFSはそれなりである。EXT3のその他のモードはかなり遅い。他のファイルシステムに関しては各自で実験してみてほしい。</dd>
+<dt>Q. : `gcc' の代わりに `cc' を使ってビルドできるか。</dt>
+<dd>A. : `LTmakefile' を使えばできる。</dd>
+<dt>Q. : Visual C++を使ってビルドできるか。</dt>
+<dd>A. : できる。`Makefile' の代わりに `VCmakefile' を使うこと。</dd>
+<dt>Q. : 他にQDBMを利用できる言語はあるか。</dt>
+<dd>A. : PHP、Scheme(Gauche)、OCaml用のインタフェースが既に公開されているようである。それ以外の言語については、必要なら自分で作ってほしい。</dd>
+<dt>Q. : 「QDBM」とはどういう意味なのか。</dt>
+<dd>A. : 「QDBM」は「Quick Database Manager」の略である。高速に動作するという意味と、アプリケーションの開発が迅速にできるという意味が込められている。</dd>
+<dt>Q. : 各APIの名前はどういう意味なのか。どう発音するのか。</dt>
+<dd>A. : 5文字の英単語から適当に選択しただけで、深い意味はない。なお、「depot」は、空港、倉庫、補給所など、物質が集まる場所を意味するらしい。発音を片仮名で表現するなら「ディーポゥ」が妥当だろう。「curia」は、宮廷、法廷など、権威が集まる場所を意味するらしい。発音を片仮名で表現するなら「キュリア」が妥当だろう。「relic」は、遺物、遺跡など、過去の残骸を意味するらしい。発音を片仮名で表現するなら「レリック」が妥当だろう。「hovel」は、小屋、物置、離れ家など、粗末な建物を意味するらしい。発音を片仮名で表現するなら「ハヴル」が妥当だろう。「cabin」は、機室、客室、小屋など、簡易的な居住空間を意味するらしい。発音を片仮名で表現するなら「キャビン」が妥当だろう。「villa」は、別荘、郊外住宅など、都会風でない住居を意味するらしい。発音を片仮名で表現するなら「ヴィラ」が妥当だろう。「vista」は、予想、展望など、遠くを見渡すことを意味するらしい。発音を片仮名で表現するなら「ヴィスタ」が妥当だろう。「odeum」は、音楽堂、劇場など、音楽や詩吟を行う建物を意味するらしい。発音を片仮名で表現するなら「オディアム」が妥当だろう。</dd>
+</dl>
+
+<hr />
+
+<h2><a name="copying" id="copying" class="head">ライセンス</a></h2>
+
+<p>QDBMはフリーソフトウェアである。あなたは、Free Software Foundationが公表したGNU Lesser General Public Licenseのバージョン2.1あるいはそれ以降の各バージョンの中からいずれかを選択し、そのバージョンが定める条項に従ってQDBMを再頒布または変更することができる。</p>
+
+<p>QDBMは有用であると思われるが、頒布にあたっては、市場性及び特定目的適合性についての暗黙の保証を含めて、いかなる保証も行なわない。詳細についてはGNU Lesser General Public Licenseを読んでほしい。</p>
+
+<p>あなたは、QDBMと一緒にGNU Lesser General Public Licenseの写しを受け取っているはずである(`COPYING' ファイルを参照)。そうでない場合は、Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA へ連絡してほしい。</p>
+
+<p>QDBMは平林幹雄が作成した。作者と連絡をとるには、`mikio@users.sourceforge.net' 宛に電子メールを送ってほしい。ただし、質問やバグレポートなど、他のユーザと共有できる話題はメーリングリストに送ってほしい。メーリングリストの参加方法については、`http://lists.sourceforge.net/lists/listinfo/qdbm-users' を参照すること。</p>
+
+<hr />
+
+</body>
+
+</html>
+
+<!-- END OF FILE -->
diff --git a/qdbm/spex.html b/qdbm/spex.html
new file mode 100644
index 00000000..9c1505f4
--- /dev/null
+++ b/qdbm/spex.html
@@ -0,0 +1,4343 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta http-equiv="Content-Style-Type" content="text/css" />
+<meta name="author" content="Mikio Hirabayashi" />
+<meta name="keywords" content="QDBM, DBM, database, hash, B+ tree" />
+<meta name="description" content="fundamental specifications of QDBM" />
+<link rel="contents" href="./" />
+<link rel="alternate" href="spex-ja.html" hreflang="ja" title="the Japanese version" />
+<link rev="made" href="mailto:mikio@users.sourceforge.net" />
+<title>Specifications of QDBM Version 1</title>
+<style type="text/css">html { margin: 0em 0em; padding: 0em 0em; background: #eeeeee none; }
+body { margin: 2em 2em; padding: 0em 0em;
+ background: #eeeeee none; color: #111111;
+ font-style: normal; font-weight: normal; }
+h1 { margin-top: 1.8em; margin-bottom: 1.3em; font-weight: bold; }
+h2 { margin-top: 1.8em; margin-bottom: 1.1em; font-weight: bold;
+ border-left: solid 0.6em #445555; border-bottom: solid 1pt #bbbbbb;
+ padding: 0.5em 0.5em; width: 60%; }
+h3 { margin-top: 1.8em; margin-bottom: 0.8em; font-weight: bold; }
+hr { margin-top: 2.5em; margin-bottom: 1.5em; height: 1pt;
+ color: #999999; background-color: #999999; border: none; }
+div.note,div.navi { text-align: right; }
+div.logo { text-align: center; margin: 3em 0em; }
+div.logo img { border: inset 2pt #ccccdd; }
+p { margin: 0.8em 0em; line-height: 140%; }
+p,dd { text-indent: 0.8em; }
+div,pre { margin-left: 1.7em; margin-right: 1.7em; }
+pre { background-color: #ddddee; padding: 0.2em; border: 1pt solid #bbbbcc; font-size: smaller; }
+kbd { color: #111111; font-style: normal; font-weight: bold; }
+a { color: #0022aa; text-decoration: none; }
+a:hover,a:focus { color: #0033ee; text-decoration: underline; }
+a.head { color: #111111; text-decoration: none; }
+table { padding: 1pt 2pt 1pt 2pt; border: none; margin-left: 1.7em; border-collapse: collapse; }
+th { padding: 1pt 4pt 1pt 4pt; border-style: none;
+ text-align: left; vertical-align: bottom; }
+td { padding: 1pt 4pt 1pt 4pt; border: 1pt solid #333333;
+ text-align: left; vertical-align: top; }
+ul,ol,dl { line-height: 140%; }
+dt { margin-left: 1.2em; }
+dd { margin-left: 2.0em; }
+ul.lines { list-style-type: none; }
+@media print {
+ html,body { margin: 0em 0em; background-color: #ffffff; color: #000000; }
+ h1 { padding: 8em 0em 0.5em 0em; text-align: center; }
+ h2 { page-break-before: always; }
+ div.note { text-align: center; }
+ div.navi,div.logo { display: none }
+ hr { display: none; }
+ pre { margin: 0.8em 0.8em; background-color: #ffffff;
+ border: 1pt solid #aaaaaa; font-size: smaller; }
+ a,kbd { color: #000000; text-decoration: none; }
+ h1,h2,h3 { font-family: sans-serif; }
+ p,div,li,dt,dd { font-family: serif; }
+ pre,kbd { font-family: monospace; }
+ dd { font-size: smaller; }
+}
+</style>
+</head>
+
+<body>
+
+<h1>Fundamental Specifications of QDBM Version 1</h1>
+
+<div class="note">Copyright (C) 2000-2007 Mikio Hirabayashi</div>
+<div class="note">Last Update: Thu, 26 Oct 2006 15:00:20 +0900</div>
+<div class="navi">[<a href="spex-ja.html" hreflang="ja">Japanese</a>] [<a href="http://qdbm.sourceforge.net/">Home</a>]</div>
+
+<hr />
+
+<h2>Table of Contents</h2>
+
+<ol>
+<li><a href="#overview">Overview</a></li>
+<li><a href="#features">Features</a></li>
+<li><a href="#installation">Installation</a></li>
+<li><a href="#depotapi">Depot: Basic API</a></li>
+<li><a href="#depotcli">Commands for Depot</a></li>
+<li><a href="#curiaapi">Curia: Extended API</a></li>
+<li><a href="#curiacli">Commands for Curia</a></li>
+<li><a href="#relicapi">Relic: NDBM-compatible API</a></li>
+<li><a href="#reliccli">Commands for Relic</a></li>
+<li><a href="#hovelapi">Hovel: GDBM-compatible API</a></li>
+<li><a href="#hovelcli">Commands for Hovel</a></li>
+<li><a href="#cabinapi">Cabin: Utility API</a></li>
+<li><a href="#cabincli">Commands for Cabin</a></li>
+<li><a href="#villaapi">Villa: Advanced API</a></li>
+<li><a href="#villacli">Commands for Villa</a></li>
+<li><a href="#odeumapi">Odeum: Inverted API</a></li>
+<li><a href="#odeumcli">Commands for Odeum</a></li>
+<li><a href="#fileformat">File Format</a></li>
+<li><a href="#porting">Porting</a></li>
+<li><a href="#bugs">Bugs</a></li>
+<li><a href="#faq">Frequently Asked Questions</a></li>
+<li><a href="#copying">Copying</a></li>
+</ol>
+
+<hr />
+
+<h2><a name="overview" id="overview" class="head">Overview</a></h2>
+
+<p>QDBM is a library of routines for managing a database. The database is a simple data file containing records, each is a pair of a key and a value. Every key and value is serial bytes with variable length. Both binary data and character string can be used as a key and a value. There is neither concept of data tables nor data types. Records are organized in hash table or B+ tree.</p>
+
+<p>As for database of hash table, each key must be unique within a database, so it is impossible to store two or more records with a key overlaps. The following access methods are provided to the database: storing a record with a key and a value, deleting a record by a key, retrieving a record by a key. Moreover, traversal access to every key are provided, although the order is arbitrary. These access methods are similar to ones of DBM (or its followers: NDBM and GDBM) library defined in the UNIX standard. QDBM is an alternative for DBM because of its higher performance.</p>
+
+<p>As for database of B+ tree, records whose keys are duplicated can be stored. Access methods of storing, deleting, and retrieving are provided as with the database of hash table. Records are stored in order by a comparing function assigned by a user. It is possible to access each record with the cursor in ascending or descending order. According to this mechanism, forward matching search for strings and range search for integers are realized. Moreover, transaction is available in database of B+ tree.</p>
+
+<p>QDBM is written in C, and provided as APIs of C, C++, Java, Perl, and Ruby. QDBM is available on platforms which have API conforming to POSIX. QDBM is a free software licensed under the GNU Lesser General Public License.</p>
+
+<hr />
+
+<h2><a name="features" id="features" class="head">Features</a></h2>
+
+<h3>Effective Implementation of Hash Database</h3>
+
+<p>QDBM is developed referring to GDBM for the purpose of the following three points: higher processing speed, smaller size of a database file, and simpler API. They have been achieved. Moreover, as with GDBM, the following three restrictions of traditional DBM: a process can handle only one database, the size of a key and a value is bounded, a database file is sparse, are cleared.</p>
+
+<p>QDBM uses hash algorithm to retrieve records. If a bucket array has sufficient number of elements, the time complexity of retrieval is `O(1)'. That is, time required for retrieving a record is constant, regardless of the scale of a database. It is also the same about storing and deleting. Collision of hash values is managed by separate chaining. Data structure of the chains is binary search tree. Even if a bucket array has unusually scarce elements, the time complexity of retrieval is `O(log n)'.</p>
+
+<p>QDBM attains improvement in retrieval by loading RAM with the whole of a bucket array. If a bucket array is on RAM, it is possible to access a region of a target record by about one path of file operations. A bucket array saved in a file is not read into RAM with the `read' call but directly mapped to RAM with the `mmap' call. Therefore, preparation time on connecting to a database is very short, and two or more processes can share the same memory map.</p>
+
+<p>If the number of elements of a bucket array is about half of records stored within a database, although it depends on characteristic of the input, the probability of collision of hash values is about 56.7% (36.8% if the same, 21.3% if twice, 11.5% if four times, 6.0% if eight times). In such case, it is possible to retrieve a record by two or less paths of file operations. If it is made into a performance index, in order to handle a database containing one million of records, a bucket array with half a million of elements is needed. The size of each element is 4 bytes. That is, if 2M bytes of RAM is available, a database containing one million records can be handled.</p>
+
+<p>QDBM provides two modes to connect to a database: `reader' and `writer'. A reader can perform retrieving but neither storing nor deleting. A writer can perform all access methods. Exclusion control between processes is performed when connecting to a database by file locking. While a writer is connected to a database, neither readers nor writers can be connected. While a reader is connected to a database, other readers can be connect, but writers can not. According to this mechanism, data consistency is guaranteed with simultaneous connections in multitasking environment.</p>
+
+<p>Traditional DBM provides two modes of the storing operations: `insert' and `replace'. In the case a key overlaps an existing record, the insert mode keeps the existing value, while the replace mode transposes it to the specified value. In addition to the two modes, QDBM provides `concatenate' mode. In the mode, the specified value is concatenated at the end of the existing value and stored. This feature is useful when adding a element to a value as an array. Moreover, although DBM has a method to fetch out a value from a database only by reading the whole of a region of a record, QDBM has a method to fetch out a part of a region of a value. When a value is treated as an array, this feature is also useful.</p>
+
+<p>Generally speaking, while succession of updating, fragmentation of available regions occurs, and the size of a database grows rapidly. QDBM deal with this problem by coalescence of dispensable regions and reuse of them, and featuring of optimization of a database. When overwriting a record with a value whose size is greater than the existing one, it is necessary to remove the region to another position of the file. Because the time complexity of the operation depends on the size of the region of a record, extending values successively is inefficient. However, QDBM deal with this problem by alignment. If increment can be put in padding, it is not necessary to remove the region.</p>
+
+<p>As for many file systems, it is impossible to handle a file whose size is more than 2GB. To deal with this problem, QDBM provides a directory database containing multiple database files. Due to this feature, it is possible to handle a database whose total size is up to 1TB in theory. Moreover, because database files can be deployed on multiple disks, the speed of updating operations can be improved as with RAID-0 (striping). It is also possible for the database files to deploy on multiple file servers using NFS and so on.</p>
+
+<h3>Useful Implementation of B+ Tree Database</h3>
+
+<p>Although B+ tree database is slower than hash database, it features ordering access to each record. The order can be assigned by users. Records of B+ tree are sorted and arranged in logical pages. Sparse index organized in B tree that is multiway balanced tree are maintained for each page. Thus, the time complexity of retrieval and so on is `O(log n)'. Cursor is provided to access each record in order. The cursor can jump to a position specified by a key and can step forward or backward from the current position. Because each page is arranged as double linked list, the time complexity of stepping cursor is `O(1)'.</p>
+
+<p>B+ tree database is implemented, based on above hash database. Because each page of B+ tree is stored as each record of hash database, B+ tree database inherits efficiency of storage management of hash database. Because the header of each record is smaller and alignment of each page is adjusted according to the page size, in most cases, the size of database file is cut by half compared to one of hash database. Although operation of many pages are required to update B+ tree, QDBM expedites the process by caching pages and reducing file operations. In most cases, because whole of the sparse index is cached on memory, it is possible to retrieve a record by one or less path of file operations.</p>
+
+<p>B+ tree database features transaction mechanism. It is possible to commit a series of operations between the beginning and the end of the transaction in a lump, or to abort the transaction and perform rollback to the state before the transaction. Even if the process of an application is crashed while the transaction, the database file is not broken.</p>
+
+<p>In case that QDBM was built with ZLIB, LZO, or BZIP2 enabled, a lossless data-compression library, the content of each page of B+ tree is compressed and stored in a file. Because each record in a page has similar patterns, high efficiency of compression is expected due to the Lempel-Ziv algorithm and the like. In case handling text data, the size of a database is reduced to about 25%. If the scale of a database is large and disk I/O is the bottleneck, featuring compression makes the processing speed improved to a large extent.</p>
+
+<h3>Simple but Various Interfaces</h3>
+
+<p>QDBM provides very simple APIs. You can perform database I/O as usual file I/O with `FILE' pointer defined in ANSI C. In the basic API of QDBM, entity of a database is recorded as one file. In the extended API, entity of a database is recorded as several files in one directory. Because the two APIs are very similar with each other, porting an application from one to the other is easy.</p>
+
+<p>APIs which are compatible with NDBM and GDBM are also provided. As there are a lot of applications using NDBM or GDBM, it is easy to port them onto QDBM. In most cases, it is completed only by replacement of header including (#include) and re-compiling. However, QDBM can not handle database files made by the original NDBM or GDBM.</p>
+
+<p>In order to handle records on memory easily, the utility API is provided. It implements memory allocating functions, sorting functions, extensible datum, array list, hash map, and so on. Using them, you can handle records in C language cheaply as in such script languages as Perl or Ruby.</p>
+
+<p>B+ tree database is used with the advanced API. The advanced API is implemented using the basic API and the utility API. Because the advanced API is also similar to the basic API and the extended API, it is easy to learn how to use it.</p>
+
+<p>In order to handle an inverted index which is used by full-text search systems, the inverted API is provided. If it is easy to handle an inverted index of documents, an application can focus on text processing and natural language processing. Because this API does not depend on character codes nor languages, it is possible to implement a full-text search system which can respond to various requests from users.</p>
+
+<p>Along with APIs for C, QDBM provides APIs for C++, Java, Perl, and Ruby. APIs for C are composed of seven kinds: the basic API, the extended API, the NDBM-compatible API, the GDBM-compatible API, the utility API, the advanced API, and the inverted API. Command line interfaces corresponding to each API are also provided. They are useful for prototyping, testing, debugging, and so on. The C++ API encapsulates database handling functions of the basic API, the extended API, and the advanced API with class mechanism of C++. The Java API has native methods calling the basic API, the extended API, and the advanced API with Java Native Interface. The Perl API has methods calling the basic API, the extended API, and the advanced API with XS language. The Ruby API has method calling the basic API, the extended API, and the advanced API as modules of Ruby. Moreover, CGI scripts for administration of databases, file uploading, and full-text search are provided.</p>
+
+<h3>Wide Portability</h3>
+
+<p>QDBM is implemented being based on syntax of ANSI C (C89) and using only APIs defined in ANSI C or POSIX. Thus, QDBM works on most UNIX and its compatible OSs. As for C API, checking operations have been done at least on the following platforms.</p>
+
+<ul>
+<li>Linux (2.2, 2.4, 2.6) (IA32, IA64, AMD64, PA-RISC, Alpha, PowerPC, M68000, ARM)</li>
+<li>FreeBSD (4.9, 5.0, 5.1, 5.2, 5.3) (IA32, IA64, SPARC, Alpha)</li>
+<li>NetBSD (1.6) (IA32)</li>
+<li>OpenBSD (3.4) (IA32)</li>
+<li>SunOS (5.6, 5.7, 5.8, 5.9, 5.10) (IA32, SPARC)</li>
+<li>HP-UX (11.11, 11.23) (IA64, PA-RISC)</li>
+<li>AIX (5.2) (POWER)</li>
+<li>Windows (2000, XP) (IA32, IA64, AMD64) (Cygwin, MinGW, Visual C++)</li>
+<li>Mac OS X (10.2, 10.3, 10.4) (IA32, PowerPC)</li>
+<li>Tru64 (5.1) (Alpha)</li>
+<li>RISC OS (5.03) (ARM)</li>
+</ul>
+
+<p>Although a database file created by QDBM depends on byte order of the processor, to do with it, utilities to dump data in format which is independent to byte orders are provided.</p>
+
+<hr />
+
+<h2><a name="installation" id="installation" class="head">Installation</a></h2>
+
+<h3>Preparation</h3>
+
+<p>To install QDBM from a source package, GCC of 2.8 or later version and `make' are required.</p>
+
+<p>When an archive file of QDBM is extracted, change the current working directory to the generated directory and perform installation.</p>
+
+<h3>Usual Steps</h3>
+
+<p>Follow the procedures below on Linux, BSD, or SunOS.</p>
+
+<p>Run the configuration script.</p>
+
+<pre>./configure
+</pre>
+
+<p>Build programs.</p>
+
+<pre>make
+</pre>
+
+<p>Perform self-diagnostic test.</p>
+
+<pre>make check
+</pre>
+
+<p>Install programs. This operation must be carried out by the root user.</p>
+
+<pre>make install
+</pre>
+
+<h3>Using GNU Libtool</h3>
+
+<p>If above steps do not work, try the following steps. This way needs GNU Libtool of 1.5 or later version.</p>
+
+<p>Run the configuration script.</p>
+
+<pre>./configure
+</pre>
+
+<p>Build programs.</p>
+
+<pre>make -f LTmakefile
+</pre>
+
+<p>Perform self-diagnostic test.</p>
+
+<pre>make -f LTmakefile check
+</pre>
+
+<p>Install programs. This operation must be carried out by the root user.</p>
+
+<pre>make -f LTmakefile install
+</pre>
+
+<h3>Result</h3>
+
+<p>When a series of work finishes, the following files will be installed. As for the rest, manuals will be installed under `/usr/local/man/man1' and '/usr/local/man/man3', other documents will be installed under `/usr/local/share/qdbm'. A configuration file for `pkg-config' will be installed under `/usr/local/lib/pkgconfig'.</p>
+
+<pre>/usr/local/include/depot.h
+/usr/local/include/curia.h
+/usr/local/include/relic.h
+/usr/local/include/hovel.h
+/usr/local/include/cabin.h
+/usr/local/include/villa.h
+/usr/local/include/vista.h
+/usr/local/include/odeum.h
+/usr/local/lib/libqdbm.a
+/usr/local/lib/libqdbm.so.14.13.0
+/usr/local/lib/libqdbm.so.14
+/usr/local/lib/libqdbm.so
+/usr/local/bin/dpmgr
+/usr/local/bin/dptest
+/usr/local/bin/dptsv
+/usr/local/bin/crmgr
+/usr/local/bin/crtest
+/usr/local/bin/crtsv
+/usr/local/bin/rlmgr
+/usr/local/bin/rltest
+/usr/local/bin/hvmgr
+/usr/local/bin/hvtest
+/usr/local/bin/cbtest
+/usr/local/bin/cbcodec
+/usr/local/bin/vlmgr
+/usr/local/bin/vltest
+/usr/local/bin/vltsv
+/usr/local/bin/odmgr
+/usr/local/bin/odtest
+/usr/local/bin/odidx
+/usr/local/bin/qmttest
+</pre>
+
+<p>When you run a program linked dynamically to `libqdbm.so', the library search path should include `/usr/local/lib'. You can set the library search path with the environment variable `LD_LIBRARY_PATH'.</p>
+
+<p>To uninstall QDBM, execute the following command after `./configure'. This operation must be carried out by the root user.</p>
+
+<pre>make uninstall
+</pre>
+
+<p>If an old version of QDBM is installed on your system, uninstall it before installation of a new one.</p>
+
+<p>The other APIs except for C nor CGI scripts are not installed by default. Refer to `plus/xspex.html' to know how to install the C++ API. Refer to `java/jspex.html' to know how to install the Java API. Refer to `perl/plspex.html' to know how to install the Perl API. Refer to `ruby/rbspex.html' to know how to install the Ruby API. Refer to `cgi/cgispex.html' to know how to install the CGI script.</p>
+
+<p>To install QDBM from such a binary package as RPM, refer to the manual of the package manager. For example, if you use RPM, execute like the following command by the root user.</p>
+
+<pre>rpm -ivh qdbm-1.x.x-x.i386.rpm
+</pre>
+
+<h3>For Windows</h3>
+
+<p>On Windows (Cygwin), you should follow the procedures below for installation.</p>
+
+<p>Run the configuration script.</p>
+
+<pre>./configure
+</pre>
+
+<p>Build programs.</p>
+
+<pre>make win
+</pre>
+
+<p>Perform self-diagnostic test.</p>
+
+<pre>make check-win
+</pre>
+
+<p>Install programs. As well, perform `make uninstall-win' to uninstall them.</p>
+
+<pre>make install-win
+</pre>
+
+<p>On Windows, the import library `libqdbm.dll.a' is created as well as the static library `libqdbm.a', and the dynamic linking library `qdbm.dll' is created instead of such shared libraries as `libqdbm.so'. `qdbm.dll' is installed into `/usr/local/bin'.</p>
+
+<p>In order to build QDBM using MinGW on Cygwin, you should perform `make mingw' instead of `make win'. With the UNIX emulation layer of Cygwin, generated programs depend on `cygwin1.dll' (they come under GNU GPL). This problem is solved by linking them to the Win32 native DLL with MinGW.</p>
+
+<p>In order to build QDBM using Visual C++, you should edit `VCmakefile' and set the search paths for libraries and headers. And perform `nmake /f VCMakefile'. Applications linking to `qdbm.dll' should link to `msvcrt.dll' by `/MD' or `/MDd' option of the compiler. Refer to `VCmakefile' for detail configurations.</p>
+
+<h3>For Mac OS X</h3>
+
+<p>On Mac OS X (Darwin), you should follow the procedures below for installation.</p>
+
+<p>Run the configuration script.</p>
+
+<pre>./configure
+</pre>
+
+<p>Build programs.</p>
+
+<pre>make mac
+</pre>
+
+<p>Perform self-diagnostic test.</p>
+
+<pre>make check-mac
+</pre>
+
+<p>Install programs. As well, perform `make uninstall-mac' to uninstall them.</p>
+
+<pre>make install-mac
+</pre>
+
+<p>On Mac OS X, `libqdbm.dylib' and so on are created instead of `libqdbm.so' and so on. You can set the library search path with the environment variable `DYLD_LIBRARY_PATH'.</p>
+
+<h3>For HP-UX</h3>
+
+<p>On HP-UX, you should follow the procedures below for installation.</p>
+
+<p>Run the configuration script.</p>
+
+<pre>./configure
+</pre>
+
+<p>Build programs.</p>
+
+<pre>make hpux
+</pre>
+
+<p>Perform self-diagnostic test.</p>
+
+<pre>make check-hpux
+</pre>
+
+<p>Install programs. As well, perform `make uninstall-hpux' to uninstall them.</p>
+
+<pre>make install-hpux
+</pre>
+
+<p>On HP-UX, `libqdbm.sl' is created instead of `libqdbm.so' and so on. You can set the library search path with the environment variable `SHLIB_PATH'.</p>
+
+<h3>For RISC OS</h3>
+
+<p>On RISC OS, you should follow the procedures below for installation.</p>
+
+<p>Build programs. As `cc' is used for compilation by default, if you want to use `gcc', add the argument `CC=gcc'.</p>
+
+<pre>make -f RISCmakefile
+</pre>
+
+<p>When a series of work finishes, the library file `libqdbm' and such commands as `dpmgr' are generated. Because how to install them is not defined, copy them manually for installation. As with it, such header files as `depot.h' should be installed manually.</p>
+
+<h3>Detail Configurations</h3>
+
+<p>You can configure building processes by the following optional arguments of `./configure'.</p>
+
+<ul class="lines">
+<li><kbd>--enable-debug</kbd> : build for debugging. Enable debugging symbols, do not perform optimization, and perform static linking.</li>
+<li><kbd>--enable-devel</kbd> : build for development. Enable debugging symbols, perform optimization, and perform dynamic linking.</li>
+<li><kbd>--enable-stable</kbd> : build for stable release. Perform conservative optimization, and perform dynamic linking.</li>
+<li><kbd>--enable-pthread</kbd> : feature POSIX thread and treat global variables as thread specific data.</li>
+<li><kbd>--disable-lock</kbd> : build for environments without file locking.</li>
+<li><kbd>--disable-mmap</kbd> : build for environments without memory mapping.</li>
+<li><kbd>--enable-zlib</kbd> : feature ZLIB compression for B+ tree and inverted index.</li>
+<li><kbd>--enable-lzo</kbd> : feature LZO compression for B+ tree and inverted index.</li>
+<li><kbd>--enable-bzip</kbd> : feature BZIP2 compression for B+ tree and inverted index.</li>
+<li><kbd>--enable-iconv</kbd> : feature ICONV utilities for conversion of character encodings.</li>
+</ul>
+
+<p>Usually, QDBM and its applications can be built without any dependency on non-standard libraries except for `libqdbm.*'. However, they depend on `libpthread.*' if POSIX thread is enabled, and they depend on `libz.*' if ZLIB is enabled, and they depend on `liblzo2.*' if LZO is enabled, and they depend on `libbz2.*' if BZIP2 is enabled, and they depend on `libiconv.*' if ICONV is enabled.</p>
+
+<p>Because the license of LZO is GNU GPL, note that applications linking to `liblzo2.*' should meet commitments of GNU GPL.</p>
+
+<hr />
+
+<h2><a name="depotapi" id="depotapi" class="head">Depot: Basic API</a></h2>
+
+<h3>Overview</h3>
+
+<p>Depot is the basic API of QDBM. Almost all features for managing a database provided by QDBM are implemented by Depot. Other APIs are no more than wrappers of Depot. Depot is the fastest in all APIs of QDBM.</p>
+
+<p>In order to use Depot, you should include `depot.h' and `stdlib.h' in the source files. Usually, the following description will be near the beginning of a source file.</p>
+
+<dl>
+<dt><kbd>#include &lt;depot.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+</dl>
+
+<p>A pointer to `DEPOT' is used as a database handle. It is like that some file I/O routines of `stdio.h' use a pointer to `FILE'. A database handle is opened with the function `dpopen' and closed with `dpclose'. You should not refer directly to any member of the handle. If a fatal error occurs in a database, any access method via the handle except `dpclose' will not work and return error status. Although a process is allowed to use multiple database handles at the same time, handles of the same database file should not be used.</p>
+
+<h3>API</h3>
+
+<p>The external variable `dpversion' is the string containing the version information.</p>
+
+<dl>
+<dt><kbd>extern const char *dpversion;</kbd></dt>
+</dl>
+
+<p>The external variable `dpecode' is assigned with the last happened error code. Refer to `depot.h' for details of the error codes.</p>
+
+<dl>
+<dt><kbd>extern int dpecode;</kbd></dt>
+<dd>The initial value of this variable is `DP_ENOERR'. The other values are `DP_EFATAL', `DP_EMODE', `DP_EBROKEN', `DP_EKEEP', `DP_ENOITEM', `DP_EALLOC', `DP_EMAP', `DP_EOPEN', `DP_ECLOSE', `DP_ETRUNC', `DP_ESYNC', `DP_ESTAT', `DP_ESEEK', `DP_EREAD', `DP_EWRITE', `DP_ELOCK', `DP_EUNLINK', `DP_EMKDIR', `DP_ERMDIR', and `DP_EMISC'.</dd>
+</dl>
+
+<p>The function `dperrmsg' is used in order to get a message string corresponding to an error code.</p>
+
+<dl>
+<dt><kbd>const char *dperrmsg(int <var>ecode</var>);</kbd></dt>
+<dd>`ecode' specifies an error code. The return value is the message string of the error code. The region of the return value is not writable.</dd>
+</dl>
+
+<p>The function `dpopen' is used in order to get a database handle.</p>
+
+<dl>
+<dt><kbd>DEPOT *dpopen(const char *<var>name</var>, int <var>omode</var>, int <var>bnum</var>);</kbd></dt>
+<dd>`name' specifies the name of a database file. `omode' specifies the connection mode: `DP_OWRITER' as a writer, `DP_OREADER' as a reader. If the mode is `DP_OWRITER', the following may be added by bitwise or: `DP_OCREAT', which means it creates a new database if not exist, `DP_OTRUNC', which means it creates a new database regardless if one exists. Both of `DP_OREADER' and `DP_OWRITER' can be added to by bitwise or: `DP_ONOLCK', which means it opens a database file without file locking, or `DP_OLCKNB', which means locking is performed without blocking. `DP_OCREAT' can be added to by bitwise or: `DP_OSPARSE', which means it creates a database file as a sparse file. `bnum' specifies the number of elements of the bucket array. If it is not more than 0, the default value is specified. The size of a bucket array is determined on creating, and can not be changed except for by optimization of the database. Suggested size of a bucket array is about from 0.5 to 4 times of the number of all records to store. The return value is the database handle or `NULL' if it is not successful. While connecting as a writer, an exclusive lock is invoked to the database file. While connecting as a reader, a shared lock is invoked to the database file. The thread blocks until the lock is achieved. If `DP_ONOLCK' is used, the application is responsible for exclusion control.</dd>
+</dl>
+
+<p>The function `dpclose' is used in order to close a database handle.</p>
+
+<dl>
+<dt><kbd>int dpclose(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. If successful, the return value is true, else, it is false. Because the region of a closed handle is released, it becomes impossible to use the handle. Updating a database is assured to be written when the handle is closed. If a writer opens a database but does not close it appropriately, the database will be broken.</dd>
+</dl>
+
+<p>The function `dpput' is used in order to store a record.</p>
+
+<dl>
+<dt><kbd>int dpput(DEPOT *<var>depot</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const char *<var>vbuf</var>, int <var>vsiz</var>, int <var>dmode</var>);</kbd></dt>
+<dd>`depot' specifies a database handle connected as a writer. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `vbuf' specifies the pointer to the region of a value. `vsiz' specifies the size of the region of the value. If it is negative, the size is assigned with `strlen(vbuf)'. `dmode' specifies behavior when the key overlaps, by the following values: `DP_DOVER', which means the specified value overwrites the existing one, `DP_DKEEP', which means the existing value is kept, `DP_DCAT', which means the specified value is concatenated at the end of the existing value. If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `dpout' is used in order to delete a record.</p>
+
+<dl>
+<dt><kbd>int dpout(DEPOT *<var>depot</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`depot' specifies a database handle connected as a writer. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. If successful, the return value is true, else, it is false. False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `dpget' is used in order to retrieve a record.</p>
+
+<dl>
+<dt><kbd>char *dpget(DEPOT *<var>depot</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>start</var>, int <var>max</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `start' specifies the offset address of the beginning of the region of the value to be read. `max' specifies the max size to be read. If it is negative, the size to read is unlimited. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the value of the corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to the specified key or the size of the value of the corresponding record is less than `start'. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `dpgetwb' is used in order to retrieve a record and write the value into a buffer.</p>
+
+<dl>
+<dt><kbd>int dpgetwb(DEPOT *<var>depot</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>start</var>, int <var>max</var>, char *<var>vbuf</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `start' specifies the offset address of the beginning of the region of the value to be read. `max' specifies the max size to be read. It shuld be equal to or less than the size of the writing buffer. `vbuf' specifies the pointer to a buffer into which the value of the corresponding record is written. If successful, the return value is the size of the written data, else, it is -1. -1 is returned when no record corresponds to the specified key or the size of the value of the corresponding record is less than `start'. Note that no additional zero code is appended at the end of the region of the writing buffer.</dd>
+</dl>
+
+<p>The function `dpvsiz' is used in order to get the size of the value of a record.</p>
+
+<dl>
+<dt><kbd>int dpvsiz(DEPOT *<var>depot</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. If successful, the return value is the size of the value of the corresponding record, else, it is -1. Because this function does not read the entity of a record, it is faster than `dpget'.</dd>
+</dl>
+
+<p>The function `dpiterinit' is used in order to initialize the iterator of a database handle.</p>
+
+<dl>
+<dt><kbd>int dpiterinit(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. If successful, the return value is true, else, it is false. The iterator is used in order to access the key of every record stored in a database.</dd>
+</dl>
+
+<p>The function `dpiternext' is used in order to get the next key of the iterator.</p>
+
+<dl>
+<dt><kbd>char *dpiternext(DEPOT *<var>depot</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the next key, else, it is `NULL'. `NULL' is returned when no record is to be get out of the iterator. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. It is possible to access every record by iteration of calling this function. However, it is not assured if updating the database is occurred while the iteration. Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `dpsetalign' is used in order to set alignment of a database handle.</p>
+
+<dl>
+<dt><kbd>int dpsetalign(DEPOT *<var>depot</var>, int <var>align</var>);</kbd></dt>
+<dd>`depot' specifies a database handle connected as a writer. `align' specifies the size of alignment. If successful, the return value is true, else, it is false. If alignment is set to a database, the efficiency of overwriting values is improved. The size of alignment is suggested to be average size of the values of the records to be stored. If alignment is positive, padding whose size is multiple number of the alignment is placed. If alignment is negative, as `vsiz' is the size of a value, the size of padding is calculated with `(vsiz / pow(2, abs(align) - 1))'. Because alignment setting is not saved in a database, you should specify alignment every opening a database.</dd>
+</dl>
+
+<p>The function `dpsetfbpsiz' is used in order to set the size of the free block pool of a database handle.</p>
+
+<dl>
+<dt><kbd>int dpsetfbpsiz(DEPOT *<var>depot</var>, int <var>size</var>);</kbd></dt>
+<dd>`depot' specifies a database handle connected as a writer. `size' specifies the size of the free block pool of a database. If successful, the return value is true, else, it is false. The default size of the free block pool is 16. If the size is greater, the space efficiency of overwriting values is improved with the time efficiency sacrificed.</dd>
+</dl>
+
+<p>The function `dpsync' is used in order to synchronize updating contents with the file and the device.</p>
+
+<dl>
+<dt><kbd>int dpsync(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' specifies a database handle connected as a writer. If successful, the return value is true, else, it is false. This function is useful when another process uses the connected database file.</dd>
+</dl>
+
+<p>The function `dpoptimize' is used in order to optimize a database.</p>
+
+<dl>
+<dt><kbd>int dpoptimize(DEPOT *<var>depot</var>, int <var>bnum</var>);</kbd></dt>
+<dd>`depot' specifies a database handle connected as a writer. `bnum' specifies the number of the elements of the bucket array. If it is not more than 0, the default value is specified. If successful, the return value is true, else, it is false. In an alternating succession of deleting and storing with overwrite or concatenate, dispensable regions accumulate. This function is useful to do away with them.</dd>
+</dl>
+
+<p>The function `dpname' is used in order to get the name of a database.</p>
+
+<dl>
+<dt><kbd>char *dpname(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. If successful, the return value is the pointer to the region of the name of the database, else, it is `NULL'. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `dpfsiz' is used in order to get the size of a database file.</p>
+
+<dl>
+<dt><kbd>int dpfsiz(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. If successful, the return value is the size of the database file, else, it is -1.</dd>
+</dl>
+
+<p>The function `dpbnum' is used in order to get the number of the elements of the bucket array.</p>
+
+<dl>
+<dt><kbd>int dpbnum(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. If successful, the return value is the number of the elements of the bucket array, else, it is -1.</dd>
+</dl>
+
+<p>The function `dpbusenum' is used in order to get the number of the used elements of the bucket array.</p>
+
+<dl>
+<dt><kbd>int dpbusenum(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. If successful, the return value is the number of the used elements of the bucket array, else, it is -1. This function is inefficient because it accesses all elements of the bucket array.</dd>
+</dl>
+
+<p>The function `dprnum' is used in order to get the number of the records stored in a database.</p>
+
+<dl>
+<dt><kbd>int dprnum(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. If successful, the return value is the number of the records stored in the database, else, it is -1.</dd>
+</dl>
+
+<p>The function `dpwritable' is used in order to check whether a database handle is a writer or not.</p>
+
+<dl>
+<dt><kbd>int dpwritable(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. The return value is true if the handle is a writer, false if not.</dd>
+</dl>
+
+<p>The function `dpfatalerror' is used in order to check whether a database has a fatal error or not.</p>
+
+<dl>
+<dt><kbd>int dpfatalerror(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. The return value is true if the database has a fatal error, false if not.</dd>
+</dl>
+
+<p>The function `dpinode' is used in order to get the inode number of a database file.</p>
+
+<dl>
+<dt><kbd>int dpinode(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. The return value is the inode number of the database file.</dd>
+</dl>
+
+<p>The function `dpmtime' is used in order to get the last modified time of a database.</p>
+
+<dl>
+<dt><kbd>time_t dpmtime(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. The return value is the last modified time of the database.</dd>
+</dl>
+
+<p>The function `dpfdesc' is used in order to get the file descriptor of a database file.</p>
+
+<dl>
+<dt><kbd>int dpfdesc(DEPOT *<var>depot</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. The return value is the file descriptor of the database file. Handling the file descriptor of a database file directly is not suggested.</dd>
+</dl>
+
+<p>The function `dpremove' is used in order to remove a database file.</p>
+
+<dl>
+<dt><kbd>int dpremove(const char *<var>name</var>);</kbd></dt>
+<dd>`name' specifies the name of a database file. If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `dprepair' is used in order to repair a broken database file.</p>
+
+<dl>
+<dt><kbd>int dprepair(const char *<var>name</var>);</kbd></dt>
+<dd>`name' specifies the name of a database file. If successful, the return value is true, else, it is false. There is no guarantee that all records in a repaired database file correspond to the original or expected state.</dd>
+</dl>
+
+<p>The function `dpexportdb' is used in order to dump all records as endian independent data.</p>
+
+<dl>
+<dt><kbd>int dpexportdb(DEPOT *<var>depot</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`depot' specifies a database handle. `name' specifies the name of an output file. If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `dpimportdb' is used in order to load all records from endian independent data.</p>
+
+<dl>
+<dt><kbd>int dpimportdb(DEPOT *<var>depot</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`depot' specifies a database handle connected as a writer. The database of the handle must be empty. `name' specifies the name of an input file. If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `dpsnaffle' is used in order to retrieve a record directly from a database file.</p>
+
+<dl>
+<dt><kbd>char *dpsnaffle(const char *<var>name</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`name' specifies the name of a database file. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the value of the corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to the specified key. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. Although this function can be used even while the database file is locked by another process, it is not assured that recent updated is reflected.</dd>
+</dl>
+
+<p>The function `dpinnerhash' is a hash function used inside Depot.</p>
+
+<dl>
+<dt><kbd>int dpinnerhash(const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. The return value is the hash value of 31 bits length computed from the key. This function is useful when an application calculates the state of the inside bucket array.</dd>
+</dl>
+
+<p>The function `dpouterhash' is a hash function which is independent from the hash functions used inside Depot.</p>
+
+<dl>
+<dt><kbd>int dpouterhash(const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. The return value is the hash value of 31 bits length computed from the key. This function is useful when an application uses its own hash algorithm outside Depot.</dd>
+</dl>
+
+<p>The function `dpprimenum' is used in order to get a natural prime number not less than a number.</p>
+
+<dl>
+<dt><kbd>int dpprimenum(int <var>num</var>);</kbd></dt>
+<dd>`num' specified a natural number. The return value is a natural prime number not less than the specified number. This function is useful when an application determines the size of a bucket array of its own hash algorithm.</dd>
+</dl>
+
+<h3>Examples</h3>
+
+<p>The following example stores and retrieves a phone number, using the name as the key.</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+#define NAME "mikio"
+#define NUMBER "000-1234-5678"
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ DEPOT *depot;
+ char *val;
+
+ /* open the database */
+ if(!(depot = dpopen(DBNAME, DP_OWRITER | DP_OCREAT, -1))){
+ fprintf(stderr, "dpopen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* store the record */
+ if(!dpput(depot, NAME, -1, NUMBER, -1, DP_DOVER)){
+ fprintf(stderr, "dpput: %s\n", dperrmsg(dpecode));
+ }
+
+ /* retrieve the record */
+ if(!(val = dpget(depot, NAME, -1, 0, -1, NULL))){
+ fprintf(stderr, "dpget: %s\n", dperrmsg(dpecode));
+ } else {
+ printf("Name: %s\n", NAME);
+ printf("Number: %s\n", val);
+ free(val);
+ }
+
+ /* close the database */
+ if(!dpclose(depot)){
+ fprintf(stderr, "dpclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<p>The following example shows all records of the database.</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ DEPOT *depot;
+ char *key, *val;
+
+ /* open the database */
+ if(!(depot = dpopen(DBNAME, DP_OREADER, -1))){
+ fprintf(stderr, "dpopen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* initialize the iterator */
+ if(!dpiterinit(depot)){
+ fprintf(stderr, "dpiterinit: %s\n", dperrmsg(dpecode));
+ }
+
+ /* scan with the iterator */
+ while((key = dpiternext(depot, NULL)) != NULL){
+ if(!(val = dpget(depot, key, -1, 0, -1, NULL))){
+ fprintf(stderr, "dpget: %s\n", dperrmsg(dpecode));
+ free(key);
+ break;
+ }
+ printf("%s: %s\n", key, val);
+ free(val);
+ free(key);
+ }
+
+ /* close the database */
+ if(!dpclose(depot)){
+ fprintf(stderr, "dpclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<h3>Notes</h3>
+
+<p>For building a program using Depot, the program should be linked with a library file `libqdbm.a' or `libqdbm.so'. For example, the following command is executed to build `sample' from `sample.c'.</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>If QDBM was built with POSIX thread enabled, the global variable `dpecode' is treated as thread specific data, and functions of Depot are reentrant. In that case, they are thread-safe as long as a handle is not accessed by threads at the same time, on the assumption that `errno', `malloc', and so on are thread-safe.</p>
+
+<hr />
+
+<h2><a name="depotcli" id="depotcli" class="head">Commands for Depot</a></h2>
+
+<p>Depot has the following command line interfaces.</p>
+
+<p>The command `dpmgr' is a utility for debugging Depot and its applications. It features editing and checking of a database. It can be used for database applications with shell scripts. This command is used in the following format. `name' specifies a database name. `key' specifies the key of a record. `val' specifies the value of a record.</p>
+
+<dl>
+<dt><kbd>dpmgr create [-s] [-bnum <var>num</var>] <var>name</var></kbd></dt>
+<dd>Create a database file.</dd>
+<dt><kbd>dpmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-na] <var>name</var> <var>key</var> <var>val</var></kbd></dt>
+<dd>Store a record with a key and a value.</dd>
+<dt><kbd>dpmgr out [-kx|-ki] <var>name</var> <var>key</var></kbd></dt>
+<dd>Delete a record with a key.</dd>
+<dt><kbd>dpmgr get [-nl] [-kx|-ki] [-start <var>num</var>] [-max <var>num</var>] [-ox] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>Retrieve a record with a key and output it to the standard output.</dd>
+<dt><kbd>dpmgr list [-nl] [-k|-v] [-ox] <var>name</var></kbd></dt>
+<dd>List all keys and values delimited with tab and line-feed to the standard output.</dd>
+<dt><kbd>dpmgr optimize [-bnum <var>num</var>] [-na] <var>name</var></kbd></dt>
+<dd>Optimize a database.</dd>
+<dt><kbd>dpmgr inform [-nl] <var>name</var></kbd></dt>
+<dd>Output miscellaneous information to the standard output.</dd>
+<dt><kbd>dpmgr remove <var>name</var></kbd></dt>
+<dd>Remove a database file.</dd>
+<dt><kbd>dpmgr repair <var>name</var></kbd></dt>
+<dd>Repair a broken database file.</dd>
+<dt><kbd>dpmgr exportdb <var>name</var> <var>file</var></kbd></dt>
+<dd>Dump all records as endian independent data.</dd>
+<dt><kbd>dpmgr importdb [-bnum <var>num</var>] <var>name</var> <var>file</var></kbd></dt>
+<dd>Load all records from endian independent data.</dd>
+<dt><kbd>dpmgr snaffle [-kx|-ki] [-ox] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>Retrieve a record from a locked database with a key and output it to the standard output.</dd>
+<dt><kbd>dpmgr version</kbd></dt>
+<dd>Output version information of QDBM to the standard output.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-s</kbd> : make the file sparse.</li>
+<li><kbd>-bnum <var>num</var></kbd> : specify the number of the elements of the bucket array.</li>
+<li><kbd>-kx</kbd> : treat `key' as a binary expression of hexadecimal notation.</li>
+<li><kbd>-ki</kbd> : treat `key' as an integer expression of decimal notation.</li>
+<li><kbd>-vx</kbd> : treat `val' as a binary expression of hexadecimal notation.</li>
+<li><kbd>-vi</kbd> : treat `val' as an integer expression of decimal notation.</li>
+<li><kbd>-vf</kbd> : read the value from a file specified with `val'.</li>
+<li><kbd>-keep</kbd> : specify the storing mode for `DP_DKEEP'.</li>
+<li><kbd>-cat</kbd> : specify the storing mode for `DP_DCAT'.</li>
+<li><kbd>-na</kbd> : do not set alignment.</li>
+<li><kbd>-nl</kbd> : open the database without file locking.</li>
+<li><kbd>-start</kbd> : specify the beginning offset of a value to fetch.</li>
+<li><kbd>-max</kbd> : specify the max size of a value to fetch.</li>
+<li><kbd>-ox</kbd> : treat the output as a binary expression of hexadecimal notation.</li>
+<li><kbd>-n</kbd> : do not output the tailing newline.</li>
+<li><kbd>-k</kbd> : output keys only.</li>
+<li><kbd>-v</kbd> : output values only.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure. The environment variable `QDBMDBGFD' specifies the file descriptor to output the history of updating the variable `dpecode'.</p>
+
+<p>The command `dptest' is a utility for facility test and performance test. Check a database generated by the command or measure the execution time of the command. This command is used in the following format. `name' specifies a database name. `rnum' specifies the number of the records. `bnum' specifies the number of the elements of the bucket array. `pnum' specifies the number of patterns of the keys. `align' specifies the basic size of alignment. `fbpsiz' specifies the size of the free block pool.</p>
+
+<dl>
+<dt><kbd>dptest write [-s] <var>name</var> <var>rnum</var> <var>bnum</var></kbd></dt>
+<dd>Store records with keys of 8 bytes. They change as `00000001', `00000002'...</dd>
+<dt><kbd>dptest read [-wb] <var>name</var></kbd></dt>
+<dd>Retrieve all records of the database above.</dd>
+<dt><kbd>dptest rcat [-c] <var>name</var> <var>rnum</var> <var>bnum</var> <var>pnum</var> <var>align</var> <var>fbpsiz</var></kbd></dt>
+<dd>Store records with partway duplicated keys using concatenate mode.</dd>
+<dt><kbd>dptest combo <var>name</var></kbd></dt>
+<dd>Perform combination test of various operations.</dd>
+<dt><kbd>dptest wicked [-c] <var>name</var> <var>rnum</var></kbd></dt>
+<dd>Perform updating operations selected at random.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-s</kbd> : make the file sparse.</li>
+<li><kbd>-wb</kbd> : use the function `dpgetwb' instead of the function `dpget'.</li>
+<li><kbd>-c</kbd> : perform comparison test with map of Cabin.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure. The environment variable `QDBMDBGFD' specifies the file descriptor to output the history of updating the variable `dpecode'.</p>
+
+<p>The command `dptsv' features mutual conversion between a database of Depot and a TSV text. This command is useful when data exchange with another version of QDBM or another DBM, or when data exchange between systems which have different byte orders. This command is used in the following format. `name' specifies a database name. The subcommand `export' reads TSV data from the standard input. If a key overlaps, the latter is adopted. `-bnum' specifies the number of the elements of the bucket array. The subcommand `import' writes TSV data to the standard output.</p>
+
+<dl>
+<dt><kbd>dptsv import [-bnum <var>num</var>] [-bin] <var>name</var></kbd></dt>
+<dd>Create a database from TSV.</dd>
+<dt><kbd>dptsv export [-bin] <var>name</var></kbd></dt>
+<dd>Write TSV data of a database.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-bnum <var>num</var></kbd> : specify the number of the elements of the bucket array.</li>
+<li><kbd>-bin</kbd> : treat records as Base64 format.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>Commands of Depot realize a simple database system. For example, to make a database to search `/etc/password' by a user name, perform the following command.</p>
+
+<pre>cat /etc/passwd | tr ':' '\t' | dptsv import casket
+</pre>
+
+<p>Thus, to retrieve the information of a user `mikio', perform the following command.</p>
+
+<pre>dpmgr get casket mikio
+</pre>
+
+<p>It is easy to implement functions upsides with these commands, using the API of Depot.</p>
+
+<hr />
+
+<h2><a name="curiaapi" id="curiaapi" class="head">Curia: Extended API</a></h2>
+
+<h3>Overview</h3>
+
+<p>Curia is the extended API of QDBM. It provides routines for managing multiple database files in a directory. Restrictions of some file systems that the size of each file is limited are escaped by dividing a database file into two or more. If the database files deploy on multiple devices, the scalability is improved.</p>
+
+<p>Although Depot creates a database with a file name, Curia creates a database with a directory name. A database file named as `depot' is placed in the specified directory. Although it keeps the attribute of the database, it does not keep the entities of the records. Besides, sub directories are created by the number of division of the database, named with 4 digits. The database files are placed in the subdirectories. The entities of the records are stored in the database file. For example, in the case that a database directory named as `casket' and the number of division is 3, `casket/depot', `casket/0001/depot', `casket/0002/depot' and `casket/0003/depot' are created. No error occurs even if the namesake directory exists when creating a database. So, if sub directories exists and some devices are mounted on the sub directories, the database files deploy on the multiple devices.</p>
+
+<p>Curia features managing large objects. Although usual records are stored in some database files, records of large objects are stored in individual files. Because the files of large objects are deployed in different directories named with the hash values, the access speed is part-way robust although it is slower than the speed of usual records. Large and not often accessed data should be secluded as large objects. By doing this, the access speed of usual records is improved. The directory hierarchies of large objects are placed in the directory named as `lob' in the sub directories of the database. Because the key spaces of the usual records and the large objects are different, the operations keep out of each other.</p>
+
+<p>In order to use Curia, you should include `depot.h', `curia.h' and `stdlib.h' in the source files. Usually, the following description will be near the beginning of a source file.</p>
+
+<dl>
+<dt><kbd>#include &lt;depot.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;curia.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+</dl>
+
+<p>A pointer to `CURIA' is used as a database handle. It is like that some file I/O routines of `stdio.h' use a pointer to `FILE'. A database handle is opened with the function `cropen' and closed with `crclose'. You should not refer directly to any member of the handle. If a fatal error occurs in a database, any access method via the handle except `crclose' will not work and return error status. Although a process is allowed to use multiple database handles at the same time, handles of the same database directory should not be used.</p>
+
+<p>Curia also assign the external variable `dpecode' with the error code. The function `dperrmsg' is used in order to get the message of the error code.</p>
+
+<h3>API</h3>
+
+<p>The function `cropen' is used in order to get a database handle.</p>
+
+<dl>
+<dt><kbd>CURIA *cropen(const char *<var>name</var>, int <var>omode</var>, int <var>bnum</var>, int <var>dnum</var>);</kbd></dt>
+<dd>`name' specifies the name of a database directory. `omode' specifies the connection mode: `CR_OWRITER' as a writer, `CR_OREADER' as a reader. If the mode is `CR_OWRITER', the following may be added by bitwise or: `CR_OCREAT', which means it creates a new database if not exist, `CR_OTRUNC', which means it creates a new database regardless if one exists. Both of `CR_OREADER' and `CR_OWRITER' can be added to by bitwise or: `CR_ONOLCK', which means it opens a database directory without file locking, or `CR_OLCKNB', which means locking is performed without blocking. `CR_OCREAT' can be added to by bitwise or: `CR_OSPARSE', which means it creates database files as sparse files. `bnum' specifies the number of elements of each bucket array. If it is not more than 0, the default value is specified. The size of each bucket array is determined on creating, and can not be changed except for by optimization of the database. Suggested size of each bucket array is about from 0.5 to 4 times of the number of all records to store. `dnum' specifies the number of division of the database. If it is not more than 0, the default value is specified. The number of division can not be changed from the initial value. The max number of division is 512. The return value is the database handle or `NULL' if it is not successful. While connecting as a writer, an exclusive lock is invoked to the database directory. While connecting as a reader, a shared lock is invoked to the database directory. The thread blocks until the lock is achieved. If `CR_ONOLCK' is used, the application is responsible for exclusion control.</dd>
+</dl>
+
+<p>The function `crclose' is used in order to close a database handle.</p>
+
+<dl>
+<dt><kbd>int crclose(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. If successful, the return value is true, else, it is false. Because the region of a closed handle is released, it becomes impossible to use the handle. Updating a database is assured to be written when the handle is closed. If a writer opens a database but does not close it appropriately, the database will be broken.</dd>
+</dl>
+
+<p>The function `crput' is used in order to store a record.</p>
+
+<dl>
+<dt><kbd>int crput(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const char *<var>vbuf</var>, int <var>vsiz</var>, int <var>dmode</var>);</kbd></dt>
+<dd>`curia' specifies a database handle connected as a writer. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `vbuf' specifies the pointer to the region of a value. `vsiz' specifies the size of the region of the value. If it is negative, the size is assigned with `strlen(vbuf)'. `dmode' specifies behavior when the key overlaps, by the following values: `CR_DOVER', which means the specified value overwrites the existing one, `CR_DKEEP', which means the existing value is kept, `CR_DCAT', which means the specified value is concatenated at the end of the existing value. If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `crout' is used in order to delete a record.</p>
+
+<dl>
+<dt><kbd>int crout(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`curia' specifies a database handle connected as a writer. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. If successful, the return value is true, else, it is false. False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `crget' is used in order to retrieve a record.</p>
+
+<dl>
+<dt><kbd>char *crget(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>start</var>, int <var>max</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `start' specifies the offset address of the beginning of the region of the value to be read. `max' specifies the max size to be read. If it is negative, the size to read is unlimited. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the value of the corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to the specified key or the size of the value of the corresponding record is less than `start'. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `crgetwb' is used in order to retrieve a record and write the value into a buffer.</p>
+
+<dl>
+<dt><kbd>int crgetwb(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>start</var>, int <var>max</var>, char *<var>vbuf</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `start' specifies the offset address of the beginning of the region of the value to be read. `max' specifies the max size to be read. It shuld be equal to or less than the size of the writing buffer. `vbuf' specifies the pointer to a buffer into which the value of the corresponding record is written. If successful, the return value is the size of the written data, else, it is -1. -1 is returned when no record corresponds to the specified key or the size of the value of the corresponding record is less than `start'. Note that no additional zero code is appended at the end of the region of the writing buffer.</dd>
+</dl>
+
+<p>The function `crvsiz' is used in order to get the size of the value of a record.</p>
+
+<dl>
+<dt><kbd>int crvsiz(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. If successful, the return value is the size of the value of the corresponding record, else, it is -1. Because this function does not read the entity of a record, it is faster than `crget'.</dd>
+</dl>
+
+<p>The function `criterinit' is used in order to initialize the iterator of a database handle.</p>
+
+<dl>
+<dt><kbd>int criterinit(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. If successful, the return value is true, else, it is false. The iterator is used in order to access the key of every record stored in a database.</dd>
+</dl>
+
+<p>The function `criternext' is used in order to get the next key of the iterator.</p>
+
+<dl>
+<dt><kbd>char *criternext(CURIA *<var>curia</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the next key, else, it is `NULL'. `NULL' is returned when no record is to be get out of the iterator. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. It is possible to access every record by iteration of calling this function. However, it is not assured if updating the database is occurred while the iteration. Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `crsetalign' is used in order to set alignment of a database handle.</p>
+
+<dl>
+<dt><kbd>int crsetalign(CURIA *<var>curia</var>, int <var>align</var>);</kbd></dt>
+<dd>`curia' specifies a database handle connected as a writer. `align' specifies the size of alignment. If successful, the return value is true, else, it is false. If alignment is set to a database, the efficiency of overwriting values is improved. The size of alignment is suggested to be average size of the values of the records to be stored. If alignment is positive, padding whose size is multiple number of the alignment is placed. If alignment is negative, as `vsiz' is the size of a value, the size of padding is calculated with `(vsiz / pow(2, abs(align) - 1))'. Because alignment setting is not saved in a database, you should specify alignment every opening a database.</dd>
+</dl>
+
+<p>The function `crsetfbpsiz' is used in order to set the size of the free block pool of a database handle.</p>
+
+<dl>
+<dt><kbd>int crsetfbpsiz(CURIA *<var>curia</var>, int <var>size</var>);</kbd></dt>
+<dd>`curia' specifies a database handle connected as a writer. `size' specifies the size of the free block pool of a database. If successful, the return value is true, else, it is false. The default size of the free block pool is 16. If the size is greater, the space efficiency of overwriting values is improved with the time efficiency sacrificed.</dd>
+</dl>
+
+<p>The function `crsync' is used in order to synchronize updating contents with the files and the devices.</p>
+
+<dl>
+<dt><kbd>int crsync(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle connected as a writer. If successful, the return value is true, else, it is false. This function is useful when another process uses the connected database directory.</dd>
+</dl>
+
+<p>The function `croptimize' is used in order to optimize a database.</p>
+
+<dl>
+<dt><kbd>int croptimize(CURIA *<var>curia</var>, int <var>bnum</var>);</kbd></dt>
+<dd>`curia' specifies a database handle connected as a writer. `bnum' specifies the number of the elements of each bucket array. If it is not more than 0, the default value is specified. In an alternating succession of deleting and storing with overwrite or concatenate, dispensable regions accumulate. This function is useful to do away with them.</dd>
+</dl>
+
+<p>The function `crname' is used in order to get the name of a database.</p>
+
+<dl>
+<dt><kbd>char *crname(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. If successful, the return value is the pointer to the region of the name of the database, else, it is `NULL'. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `crfsiz' is used in order to get the total size of database files.</p>
+
+<dl>
+<dt><kbd>int crfsiz(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. If successful, the return value is the total size of the database files, else, it is -1. If the total size is more than 2GB, the return value overflows.</dd>
+</dl>
+
+<p>The function `crfsizd' is used in order to get the total size of database files as double-precision floating-point number.</p>
+
+<dl>
+<dt><kbd>double crfsizd(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. If successful, the return value is the total size of the database files, else, it is -1.0.</dd>
+</dl>
+
+<p>The function `crbnum' is used in order to get the total number of the elements of each bucket array.</p>
+
+<dl>
+<dt><kbd>int crbnum(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. If successful, the return value is the total number of the elements of each bucket array, else, it is -1.</dd>
+</dl>
+
+<p>The function `crbusenum' is used in order to get the total number of the used elements of each bucket array.</p>
+
+<dl>
+<dt><kbd>int crbusenum(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. If successful, the return value is the total number of the used elements of each bucket array, else, it is -1. This function is inefficient because it accesses all elements of each bucket array.</dd>
+</dl>
+
+<p>The function `crrnum' is used in order to get the number of the records stored in a database.</p>
+
+<dl>
+<dt><kbd>int crrnum(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. If successful, the return value is the number of the records stored in the database, else, it is -1.</dd>
+</dl>
+
+<p>The function `crwritable' is used in order to check whether a database handle is a writer or not.</p>
+
+<dl>
+<dt><kbd>int crwritable(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. The return value is true if the handle is a writer, false if not.</dd>
+</dl>
+
+<p>The function `crfatalerror' is used in order to check whether a database has a fatal error or not.</p>
+
+<dl>
+<dt><kbd>int crfatalerror(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. The return value is true if the database has a fatal error, false if not.</dd>
+</dl>
+
+<p>The function `crinode' is used in order to get the inode number of a database directory.</p>
+
+<dl>
+<dt><kbd>int crinode(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. The return value is the inode number of the database directory.</dd>
+</dl>
+
+<p>The function `crmtime' is used in order to get the last modified time of a database.</p>
+
+<dl>
+<dt><kbd>time_t crmtime(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. The return value is the last modified time of the database.</dd>
+</dl>
+
+<p>The function `crremove' is used in order to remove a database directory.</p>
+
+<dl>
+<dt><kbd>int crremove(const char *<var>name</var>);</kbd></dt>
+<dd>`name' specifies the name of a database directory. If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `crrepair' is used in order to repair a broken database directory.</p>
+
+<dl>
+<dt><kbd>int crrepair(const char *<var>name</var>);</kbd></dt>
+<dd>`name' specifies the name of a database directory. If successful, the return value is true, else, it is false. There is no guarantee that all records in a repaired database directory correspond to the original or expected state.</dd>
+</dl>
+
+<p>The function `crexportdb' is used in order to dump all records as endian independent data.</p>
+
+<dl>
+<dt><kbd>int crexportdb(CURIA *<var>curia</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. `name' specifies the name of an output directory. If successful, the return value is true, else, it is false. Note that large objects are ignored.</dd>
+</dl>
+
+<p>The function `crimportdb' is used in order to load all records from endian independent data.</p>
+
+<dl>
+<dt><kbd>int crimportdb(CURIA *<var>curia</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`curia' specifies a database handle connected as a writer. The database of the handle must be empty. `name' specifies the name of an input directory. If successful, the return value is true, else, it is false. Note that large objects are ignored.</dd>
+</dl>
+
+<p>The function `crsnaffle' is used in order to retrieve a record directly from a database directory.</p>
+
+<dl>
+<dt><kbd>char *crsnaffle(const char *<var>name</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`name' specifies the name of a database directory. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the value of the corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to the specified key. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. Although this function can be used even while the database directory is locked by another process, it is not assured that recent updated is reflected.</dd>
+</dl>
+
+<p>The function `crputlob' is used in order to store a large object.</p>
+
+<dl>
+<dt><kbd>int crputlob(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const char *<var>vbuf</var>, int <var>vsiz</var>, int <var>dmode</var>);</kbd></dt>
+<dd>`curia' specifies a database handle connected as a writer. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `vbuf' specifies the pointer to the region of a value. `vsiz' specifies the size of the region of the value. If it is negative, the size is assigned with `strlen(vbuf)'. `dmode' specifies behavior when the key overlaps, by the following values: `CR_DOVER', which means the specified value overwrites the existing one, `CR_DKEEP', which means the existing value is kept, `CR_DCAT', which means the specified value is concatenated at the end of the existing value. If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `croutlob' is used in order to delete a large object.</p>
+
+<dl>
+<dt><kbd>int croutlob(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`curia' specifies a database handle connected as a writer. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. If successful, the return value is true, else, it is false. false is returned when no large object corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `crgetlob' is used in order to retrieve a large object.</p>
+
+<dl>
+<dt><kbd>char *crgetlob(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>start</var>, int <var>max</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `start' specifies the offset address of the beginning of the region of the value to be read. `max' specifies the max size to be read. If it is negative, the size to read is unlimited. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the value of the corresponding large object, else, it is `NULL'. `NULL' is returned when no large object corresponds to the specified key or the size of the value of the corresponding large object is less than `start'. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `crgetlobfd' is used in order to get the file descriptor of a large object.</p>
+
+<dl>
+<dt><kbd>int crgetlobfd(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. If successful, the return value is the file descriptor of the corresponding large object, else, it is -1. -1 is returned when no large object corresponds to the specified key. The returned file descriptor is opened with the `open' call. If the database handle was opened as a writer, the descriptor is writable (O_RDWR), else, it is not writable (O_RDONLY). The descriptor should be closed with the `close' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `crvsizlob' is used in order to get the size of the value of a large object.</p>
+
+<dl>
+<dt><kbd>int crvsizlob(CURIA *<var>curia</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. If successful, the return value is the size of the value of the corresponding large object, else, it is -1. Because this function does not read the entity of a large object, it is faster than `crgetlob'.</dd>
+</dl>
+
+<p>The function `crrnumlob' is used in order to get the number of the large objects stored in a database.</p>
+
+<dl>
+<dt><kbd>int crrnumlob(CURIA *<var>curia</var>);</kbd></dt>
+<dd>`curia' specifies a database handle. If successful, the return value is the number of the large objects stored in the database, else, it is -1.</dd>
+</dl>
+
+<h3>Examples</h3>
+
+<p>The following example stores and retrieves a phone number, using the name as the key.</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;curia.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+#define NAME "mikio"
+#define NUMBER "000-1234-5678"
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ CURIA *curia;
+ char *val;
+
+ /* open the database */
+ if(!(curia = cropen(DBNAME, CR_OWRITER | CR_OCREAT, -1, -1))){
+ fprintf(stderr, "cropen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* store the record */
+ if(!crput(curia, NAME, -1, NUMBER, -1, CR_DOVER)){
+ fprintf(stderr, "crput: %s\n", dperrmsg(dpecode));
+ }
+
+ /* retrieve the record */
+ if(!(val = crget(curia, NAME, -1, 0, -1, NULL))){
+ fprintf(stderr, "crget: %s\n", dperrmsg(dpecode));
+ } else {
+ printf("Name: %s\n", NAME);
+ printf("Number: %s\n", val);
+ free(val);
+ }
+
+ /* close the database */
+ if(!crclose(curia)){
+ fprintf(stderr, "crclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<p>The following example shows all records of the database.</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;curia.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ CURIA *curia;
+ char *key, *val;
+
+ /* open the database */
+ if(!(curia = cropen(DBNAME, CR_OREADER, -1, -1))){
+ fprintf(stderr, "cropen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* initialize the iterator */
+ if(!criterinit(curia)){
+ fprintf(stderr, "criterinit: %s\n", dperrmsg(dpecode));
+ }
+
+ /* scan with the iterator */
+ while((key = criternext(curia, NULL)) != NULL){
+ if(!(val = crget(curia, key, -1, 0, -1, NULL))){
+ fprintf(stderr, "crget: %s\n", dperrmsg(dpecode));
+ free(key);
+ break;
+ }
+ printf("%s: %s\n", key, val);
+ free(val);
+ free(key);
+ }
+
+ /* close the iterator */
+ if(!crclose(curia)){
+ fprintf(stderr, "crclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<h3>Notes</h3>
+
+<p>How to build programs using Curia is the same as the case of Depot.</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>If QDBM was built with POSIX thread enabled, the global variable `dpecode' is treated as thread specific data, and functions of Curia are reentrant. In that case, they are thread-safe as long as a handle is not accessed by threads at the same time, on the assumption that `errno', `malloc', and so on are thread-safe.</p>
+
+<hr />
+
+<h2><a name="curiacli" id="curiacli" class="head">Commands for Curia</a></h2>
+
+<p>Curia has the following command line interfaces.</p>
+
+<p>The command `crmgr' is a utility for debugging Curia and its applications. It features editing and checking of a database. It can be used for the database applications with shell scripts. This command is used in the following format. `name' specifies a database name. `key' specifies the key of a record. `val' specifies the value of a record.</p>
+
+<dl>
+<dt><kbd>crmgr create [-s] [-bnum <var>num</var>] [-dnum <var>num</var>] <var>name</var></kbd></dt>
+<dd>Create a database file.</dd>
+<dt><kbd>crmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-lob] [-na] <var>name</var> <var>key</var> <var>val</var></kbd></dt>
+<dd>Store a record with a key and a value.</dd>
+<dt><kbd>crmgr out [-kx|-ki] [-lob] <var>name</var> <var>key</var></kbd></dt>
+<dd>Delete a record with a key.</dd>
+<dt><kbd>crmgr get [-nl] [-kx|-ki] [-start <var>num</var>] [-max <var>num</var>] [-ox] [-lob] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>Retrieve a record with a key and output it to the standard output.</dd>
+<dt><kbd>crmgr list [-nl] [-k|-v] [-ox] <var>name</var></kbd></dt>
+<dd>List all keys and values delimited with tab and line-feed to the standard output.</dd>
+<dt><kbd>crmgr optimize [-bnum <var>num</var>] [-na] <var>name</var></kbd></dt>
+<dd>Optimize a database.</dd>
+<dt><kbd>crmgr inform [-nl] <var>name</var></kbd></dt>
+<dd>Output miscellaneous information to the standard output.</dd>
+<dt><kbd>crmgr remove <var>name</var></kbd></dt>
+<dd>Remove a database directory.</dd>
+<dt><kbd>crmgr repair <var>name</var></kbd></dt>
+<dd>Repair a broken database directory.</dd>
+<dt><kbd>crmgr exportdb <var>name</var> <var>dir</var></kbd></dt>
+<dd>Dump all records as endian independent data.</dd>
+<dt><kbd>crmgr importdb [-bnum <var>num</var>] [-dnum <var>num</var>] <var>name</var> <var>dir</var></kbd></dt>
+<dd>Load all records from endian independent data.</dd>
+<dt><kbd>crmgr snaffle [-kx|-ki] [-ox] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>Retrieve a record from a locked database with a key and output it to the standard output.</dd>
+<dt><kbd>crmgr version</kbd></dt>
+<dd>Output version information of QDBM to the standard output.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-s</kbd> : make the files sparse.</li>
+<li><kbd>-bnum <var>num</var></kbd> : specify the number of elements of each bucket array.</li>
+<li><kbd>-dnum <var>num</var></kbd> : specify the number of division of the database.</li>
+<li><kbd>-kx</kbd> : treat `key' as a binary expression of hexadecimal notation.</li>
+<li><kbd>-ki</kbd> : treat `key' as an integer expression of decimal notation.</li>
+<li><kbd>-vx</kbd> : treat `val' as a binary expression of hexadecimal notation.</li>
+<li><kbd>-vi</kbd> : treat `val' as an integer expression of decimal notation.</li>
+<li><kbd>-vf</kbd> : read the value from a file specified with `val'.</li>
+<li><kbd>-keep</kbd> : specify the storing mode for `CR_DKEEP'.</li>
+<li><kbd>-cat</kbd> : specify the storing mode for `CR_DCAT'.</li>
+<li><kbd>-na</kbd> : do not set alignment.</li>
+<li><kbd>-nl</kbd> : open the database without file locking.</li>
+<li><kbd>-start</kbd> : specify the beginning offset of a value to fetch.</li>
+<li><kbd>-max</kbd> : specify the max size of a value to fetch.</li>
+<li><kbd>-ox</kbd> : treat the output as a binary expression of hexadecimal notation.</li>
+<li><kbd>-lob</kbd> : handle large objects.</li>
+<li><kbd>-n</kbd> : do not output the tailing newline.</li>
+<li><kbd>-k</kbd> : output keys only.</li>
+<li><kbd>-v</kbd> : output values only.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure. The environment variable `QDBMDBGFD' specifies the file descriptor to output the history of updating the variable `dpecode'.</p>
+
+<p>The command `crtest' is a utility for facility test and performance test. Check a database generated by the command or measure the execution time of the command. This command is used in the following format. `name' specifies a database name. `rnum' specifies the number of records. `bnum' specifies the number of elements of a bucket array. `dnum' specifies the number of division of a database. `pnum' specifies the number of patterns of the keys. `align' specifies the basic size of alignment. `fbpsiz' specifies the size of the free block pool.</p>
+
+<dl>
+<dt><kbd>crtest write [-s] [-lob] <var>name</var> <var>rnum</var> <var>bnum</var> <var>dnum</var></kbd></dt>
+<dd>Store records with keys of 8 bytes. They change as `00000001', `00000002'...</dd>
+<dt><kbd>crtest read [-wb] [-lob] <var>name</var></kbd></dt>
+<dd>Retrieve all records of the database above.</dd>
+<dt><kbd>crtest rcat [-c] <var>name</var> <var>rnum</var> <var>bnum</var> <var>dnum</var> <var>pnum</var> <var>align</var> <var>fbpsiz</var></kbd></dt>
+<dd>Store records with partway duplicated keys using concatenate mode.</dd>
+<dt><kbd>crtest combo <var>name</var></kbd></dt>
+<dd>Perform combination test of various operations.</dd>
+<dt><kbd>crtest wicked [-c] <var>name</var> <var>rnum</var></kbd></dt>
+<dd>Perform updating operations selected at random.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-s</kbd> : make the files sparse.</li>
+<li><kbd>-lob</kbd> : handle large objects.</li>
+<li><kbd>-wb</kbd> : use the function `crgetwb' instead of the function `crget'.</li>
+<li><kbd>-c</kbd> : perform comparison test with map of Cabin.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure. The environment variable `QDBMDBGFD' specifies the file descriptor to output the history of updating the variable `dpecode'.</p>
+
+<p>The command `crtsv' features mutual conversion between a database of Curia and a TSV text. This command is useful when data exchange with another version of QDBM or another DBM, or when data exchange between systems which have different byte orders. This command is used in the following format. `name' specifies a database name. The subcommand `export' reads TSV data from the standard input. If a key overlaps, the latter is adopted. `-bnum' specifies the number of the elements of the bucket array. `-dnum' specifies the number of division of the database. The subcommand `import' writes TSV data to the standard output.</p>
+
+<dl>
+<dt><kbd>crtsv import [-bnum <var>num</var>] [-dnum <var>num</var>] [-bin] <var>name</var></kbd></dt>
+<dd>Create a database from TSV.</dd>
+<dt><kbd>crtsv export [-bin] <var>name</var></kbd></dt>
+<dd>Write TSV data of a database.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-bnum <var>num</var></kbd> : specify the number of the elements of the bucket array.</li>
+<li><kbd>-dnum <var>num</var></kbd> : specify the number of division of the database.</li>
+<li><kbd>-bin</kbd> : treat records as Base64 format.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>Commands of Curia realize a simple database system. For example, to make a database to search `/etc/password' by a user name, perform the following command.</p>
+
+<pre>cat /etc/passwd | tr ':' '\t' | crtsv import casket
+</pre>
+
+<p>Thus, to retrieve the information of a user `mikio', perform the following command.</p>
+
+<pre>crmgr get casket mikio
+</pre>
+
+<p>It is easy to implement functions upsides with these commands, using the API of Curia.</p>
+
+<hr />
+
+<h2><a name="relicapi" id="relicapi" class="head">Relic: NDBM-compatible API</a></h2>
+
+<h3>Overview</h3>
+
+<p>Relic is the API which is compatible with NDBM. So, Relic wraps functions of Depot as API of NDBM. It is easy to port an application from NDBM to QDBM. In most cases, you should only replace the includings of `ndbm.h' with `relic.h' and replace the linking option `-lndbm' with `-lqdbm'.</p>
+
+<p>The original NDBM treats a database as a pair of files. One, `a directory file', has a name with suffix `.dir' and stores a bit map of keys. The other, `a data file', has a name with suffix `.pag' and stores entities of each records. Relic creates the directory file as a mere dummy file and creates the data file as a database. Relic has no restriction about the size of each record. Relic can not handle database files made by the original NDBM.</p>
+
+<p>In order to use Relic, you should include `relic.h', `stdlib.h', `sys/types.h', `sys/stat.h' and `fcntl.h' in the source files. Usually, the following description will be near the beginning of a source file.</p>
+
+<dl>
+<dt><kbd>#include &lt;relic.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;sys/types.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;sys/stat.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;fcntl.h&gt;</kbd></dt>
+</dl>
+
+<p>A pointer to `DBM' is used as a database handle. A database handle is opened with the function `dbm_open' and closed with `dbm_close'. You should not refer directly to any member of a handle.</p>
+
+<h3>API</h3>
+
+<p>Structures of `datum' type is used in order to give and receive data of keys and values with functions of Relic.</p>
+
+<dl>
+<dt><kbd>typedef struct { void *dptr; size_t dsize; } datum;</kbd></dt>
+<dd>`dptr' specifies the pointer to the region of a key or a value. `dsize' specifies the size of the region.</dd>
+</dl>
+
+<p>The function `dbm_open' is used in order to get a database handle.</p>
+
+<dl>
+<dt><kbd>DBM *dbm_open(char *<var>name</var>, int <var>flags</var>, int <var>mode</var>);</kbd></dt>
+<dd>`name' specifies the name of a database. The file names are concatenated with suffixes. `flags' is the same as the one of `open' call, although `O_WRONLY' is treated as `O_RDWR' and additional flags except for `O_CREAT' and `O_TRUNC' have no effect. `mode' specifies the mode of the database file as the one of `open' call does. The return value is the database handle or `NULL' if it is not successful.</dd>
+</dl>
+
+<p>The function `dbm_close' is used in order to close a database handle.</p>
+
+<dl>
+<dt><kbd>void dbm_close(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' specifies a database handle. Because the region of the closed handle is released, it becomes impossible to use the handle.</dd>
+</dl>
+
+<p>The function `dbm_store' is used in order to store a record.</p>
+
+<dl>
+<dt><kbd>int dbm_store(DBM *<var>db</var>, datum <var>key</var>, datum <var>content</var>, int <var>flags</var>);</kbd></dt>
+<dd>`db' specifies a database handle. `key' specifies a structure of a key. `content' specifies a structure of a value. `flags' specifies behavior when the key overlaps, by the following values: `DBM_REPLACE', which means the specified value overwrites the existing one, `DBM_INSERT', which means the existing value is kept. The return value is 0 if it is successful, 1 if it gives up because of overlaps of the key, -1 if other error occurs.</dd>
+</dl>
+
+<p>The function `dbm_delete' is used in order to delete a record.</p>
+
+<dl>
+<dt><kbd>int dbm_delete(DBM *<var>db</var>, datum <var>key</var>);</kbd></dt>
+<dd>`db' specifies a database handle. `key' specifies a structure of a key. The return value is 0 if it is successful, -1 if some errors occur.</dd>
+</dl>
+
+<p>The function `dbm_fetch' is used in order to retrieve a record.</p>
+
+<dl>
+<dt><kbd>datum dbm_fetch(DBM *<var>db</var>, datum <var>key</var>);</kbd></dt>
+<dd>`db' specifies a database handle. `key' specifies a structure of a key. The return value is a structure of the result. If a record corresponds, the member `dptr' of the structure is the pointer to the region of the value. If no record corresponds or some errors occur, `dptr' is `NULL'. `dptr' points to the region related with the handle. The region is available until the next time of calling this function with the same handle.</dd>
+</dl>
+
+<p>The function `dbm_firstkey' is used in order to get the first key of a database.</p>
+
+<dl>
+<dt><kbd>datum dbm_firstkey(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' specifies a database handle. The return value is a structure of the result. If a record corresponds, the member `dptr' of the structure is the pointer to the region of the first key. If no record corresponds or some errors occur, `dptr' is `NULL'. `dptr' points to the region related with the handle. The region is available until the next time of calling this function or the function `dbm_nextkey' with the same handle.</dd>
+</dl>
+
+<p>The function `dbm_nextkey' is used in order to get the next key of a database.</p>
+
+<dl>
+<dt><kbd>datum dbm_nextkey(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' specifies a database handle. The return value is a structure of the result. If a record corresponds, the member `dptr' of the structure is the pointer to the region of the next key. If no record corresponds or some errors occur, `dptr' is `NULL'. `dptr' points to the region related with the handle. The region is available until the next time of calling this function or the function `dbm_firstkey' with the same handle.</dd>
+</dl>
+
+<p>The function `dbm_error' is used in order to check whether a database has a fatal error or not.</p>
+
+<dl>
+<dt><kbd>int dbm_error(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' specifies a database handle. The return value is true if the database has a fatal error, false if not.</dd>
+</dl>
+
+<p>The function `dbm_clearerr' has no effect.</p>
+
+<dl>
+<dt><kbd>int dbm_clearerr(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' specifies a database handle. The return value is 0. The function is only for compatibility.</dd>
+</dl>
+
+<p>The function `dbm_rdonly' is used in order to check whether a handle is read-only or not.</p>
+
+<dl>
+<dt><kbd>int dbm_rdonly(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' specifies a database handle. The return value is true if the handle is read-only, or false if not read-only.</dd>
+</dl>
+
+<p>The function `dbm_dirfno' is used in order to get the file descriptor of a directory file.</p>
+
+<dl>
+<dt><kbd>int dbm_dirfno(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' specifies a database handle. The return value is the file descriptor of the directory file.</dd>
+</dl>
+
+<p>The function `dbm_pagfno' is used in order to get the file descriptor of a data file.</p>
+
+<dl>
+<dt><kbd>int dbm_pagfno(DBM *<var>db</var>);</kbd></dt>
+<dd>`db' specifies a database handle. The return value is the file descriptor of the data file.</dd>
+</dl>
+
+<h3>Examples</h3>
+
+<p>The following example stores and retrieves a phone number, using the name as the key.</p>
+
+<pre>#include &lt;relic.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;sys/types.h&gt;
+#include &lt;sys/stat.h&gt;
+#include &lt;stdio.h&gt;
+#include &lt;string.h&gt;
+
+#define NAME "mikio"
+#define NUMBER "000-1234-5678"
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ DBM *db;
+ datum key, val;
+ int i;
+
+ /* open the database */
+ if(!(db = dbm_open(DBNAME, O_RDWR | O_CREAT, 00644))){
+ perror("dbm_open");
+ return 1;
+ }
+
+ /* prepare the record */
+ key.dptr = NAME;
+ key.dsize = strlen(NAME);
+ val.dptr = NUMBER;
+ val.dsize = strlen(NUMBER);
+
+ /* store the record */
+ if(dbm_store(db, key, val, DBM_REPLACE) != 0){
+ perror("dbm_store");
+ }
+
+ /* retrieve the record */
+ val = dbm_fetch(db, key);
+ if(val.dptr){
+ printf("Name: %s\n", NAME);
+ printf("Number: ");
+ for(i = 0; i &lt; val.dsize; i++){
+ putchar(((char *)val.dptr)[i]);
+ }
+ putchar('\n');
+ } else {
+ perror("dbm_fetch");
+ }
+
+ /* close the database */
+ dbm_close(db);
+
+ return 0;
+}
+</pre>
+
+<h3>Notes</h3>
+
+<p>How to build programs using Relic is the same as the case of Depot. Note that an option to be given to a linker is not `-lndbm', but `-lqdbm'.</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>Functions of Relic are thread-safe as long as a handle is not accessed by threads at the same time, on the assumption that `errno', `malloc', and so on are thread-safe.</p>
+
+<hr />
+
+<h2><a name="reliccli" id="reliccli" class="head">Commands for Relic</a></h2>
+
+<p>Relic has the following command line interfaces.</p>
+
+<p>The command `rlmgr' is a utility for debugging Relic and its applications. It features editing and checking of a database. It can be used for database applications with shell scripts. This command is used in the following format. `name' specifies a database name. `key' specifies the key of a record. `val' specifies the value of a record.</p>
+
+<dl>
+<dt><kbd>rlmgr create <var>name</var></kbd></dt>
+<dd>Create a database file.</dd>
+<dt><kbd>rlmgr store [-kx] [-vx|-vf] [-insert] <var>name</var> <var>key</var> <var>val</var></kbd></dt>
+<dd>Store a record with a key and a value.</dd>
+<dt><kbd>rlmgr delete [-kx] <var>name</var> <var>key</var></kbd></dt>
+<dd>Delete a record with a key.</dd>
+<dt><kbd>rlmgr fetch [-kx] [-ox] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>Retrieve a record with a key and output to the standard output.</dd>
+<dt><kbd>rlmgr list [-ox] <var>name</var></kbd></dt>
+<dd>List all keys and values delimited with tab and line-feed to the standard output.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-kx</kbd> : treat `key' as a binary expression of hexadecimal notation.</li>
+<li><kbd>-vx</kbd> : treat `val' as a binary expression of hexadecimal notation.</li>
+<li><kbd>-vf</kbd> : read the value from a file specified with `val'.</li>
+<li><kbd>-insert</kbd> : specify the storing mode for `DBM_INSERT'.</li>
+<li><kbd>-ox</kbd> : treat the output as a binary expression of hexadecimal notation.</li>
+<li><kbd>-n</kbd> : do not output the tailing newline.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `rltest' is a utility for facility test and performance test. Check a database generated by the command or measure the execution time of the command. This command is used in the following format. `name' specifies a database name. `rnum' specifies the number of records.</p>
+
+<dl>
+<dt><kbd>rltest write <var>name</var> <var>rnum</var></kbd></dt>
+<dd>Store records with keys of 8 bytes. They change as `00000001', `00000002'...</dd>
+<dt><kbd>rltest read <var>name</var> <var>rnum</var></kbd></dt>
+<dd>Retrieve records of the database above.</dd>
+</dl>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<hr />
+
+<h2><a name="hovelapi" id="hovelapi" class="head">Hovel: GDBM-compatible API</a></h2>
+
+<h3>Overview</h3>
+
+<p>Hovel is the API which is compatible with GDBM. So, Hovel wraps functions of Depot and Curia as API of GDBM. It is easy to port an application from GDBM to QDBM. In most cases, you should only replace the includings of `gdbm.h' with `hovel.h' and replace the linking option `-lgdbm' with `-lqdbm'. Hovel can not handle database files made by the original GDBM.</p>
+
+<p>In order to use Hovel, you should include `hovel.h', `stdlib.h', `sys/types.h' and `sys/stat.h' in the source files. Usually, the following description will be near the beginning of a source file.</p>
+
+<dl>
+<dt><kbd>#include &lt;hovel.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;sys/types.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;sys/stat.h&gt;</kbd></dt>
+</dl>
+
+<p>An object of `GDBM_FILE' is used as a database handle. A database handle is opened with the function `gdbm_open' and closed with `gdbm_close'. You should not refer directly to any member of a handle. Although Hovel works as a wrapper of Depot and handles a database file usually, if you use the function `gdbm_open2' to open the handle, it is possible to make behavior of a handle as a wrapper of Curia and treat a database directory.</p>
+
+<h3>API</h3>
+
+<p>Structures of `datum' type is used in order to give and receive data of keys and values with functions of Hovel.</p>
+
+<dl>
+<dt><kbd>typedef struct { char *dptr; size_t dsize; } datum;</kbd></dt>
+<dd>`dptr' specifies the pointer to the region of a key or a value. `dsize' specifies the size of the region.</dd>
+</dl>
+
+<p>The external variable `gdbm_version' is the string containing the version information.</p>
+
+<dl>
+<dt><kbd>extern char *gdbm_version;</kbd></dt>
+</dl>
+
+<p>The external variable `gdbm_errno' is assigned with the last happened error code. Refer to `hovel.h' for details of the error codes.</p>
+
+<dl>
+<dt><kbd>extern gdbm_error gdbm_errno;</kbd></dt>
+<dd>The initial value of this variable is `GDBM_NO_ERROR'. The other values are `GDBM_MALLOC_ERROR', `GDBM_BLOCK_SIZE_ERROR', `GDBM_FILE_OPEN_ERROR', `GDBM_FILE_WRITE_ERROR', `GDBM_FILE_SEEK_ERROR', `GDBM_FILE_READ_ERROR', `GDBM_BAD_MAGIC_NUMBER', `GDBM_EMPTY_DATABASE', `GDBM_CANT_BE_READER', `GDBM_CANT_BE_WRITER', `GDBM_READER_CANT_DELETE', `GDBM_READER_CANT_STORE', `GDBM_READER_CANT_REORGANIZE', `GDBM_UNKNOWN_UPDATE', `GDBM_ITEM_NOT_FOUND', `GDBM_REORGANIZE_FAILED', `GDBM_CANNOT_REPLACE', `GDBM_ILLEGAL_DATA', `GDBM_OPT_ALREADY_SET', and `GDBM_OPT_ILLEGAL'.</dd>
+</dl>
+
+<p>The function `gdbm_strerror' is used in order to get a message string corresponding to an error code.</p>
+
+<dl>
+<dt><kbd>char *gdbm_strerror(gdbm_error <var>gdbmerrno</var>);</kbd></dt>
+<dd>`gdbmerrno' specifies an error code. The return value is the message string of the error code. The region of the return value is not writable.</dd>
+</dl>
+
+<p>The function `gdbm_open' is used in order to get a database handle after the fashion of GDBM.</p>
+
+<dl>
+<dt><kbd>GDBM_FILE gdbm_open(char *<var>name</var>, int <var>block_size</var>, int <var>read_write</var>, int <var>mode</var>, void (*<var>fatal_func</var>)(void));</kbd></dt>
+<dd>`name' specifies the name of a database. `block_size' is ignored. `read_write' specifies the connection mode: `GDBM_READER' as a reader, `GDBM_WRITER', `GDBM_WRCREAT' and `GDBM_NEWDB' as a writer. `GDBM_WRCREAT' makes a database file or directory if it does not exist. `GDBM_NEWDB' makes a new database even if it exists. You can add the following to writer modes by bitwise or: `GDBM_SYNC', `GDBM_NOLOCK', `GDBM_LOCKNB', `GDBM_FAST', and `GDBM_SPARSE'. `GDBM_SYNC' means a database is synchronized after every updating method. `GDBM_NOLOCK' means a database is opened without file locking. `GDBM_LOCKNB' means file locking is performed without blocking. `GDBM_FAST' is ignored. `GDBM_SPARSE' is an original mode of QDBM and makes database a sparse file. `mode' specifies mode of a database file as the one of `open' call does. `fatal_func' is ignored. The return value is the database handle or `NULL' if it is not successful.</dd>
+</dl>
+
+<p>The function `gdbm_open2' is used in order to get a database handle after the fashion of QDBM.</p>
+
+<dl>
+<dt><kbd>GDBM_FILE gdbm_open2(char *<var>name</var>, int <var>read_write</var>, int <var>mode</var>, int <var>bnum</var>, int <var>dnum</var>, int <var>align</var>);</kbd></dt>
+<dd>`name' specifies the name of a database. `read_write' specifies the connection mode: `GDBM_READER' as a reader, `GDBM_WRITER', `GDBM_WRCREAT' and `GDBM_NEWDB' as a writer. `GDBM_WRCREAT' makes a database file or directory if it does not exist. `GDBM_NEWDB' makes a new database even if it exists. You can add the following to writer modes by bitwise or: `GDBM_SYNC', `GDBM_NOLOCK', 'GDBM_LOCKNB', `GDBM_FAST', and `GDBM_SPARSE'. `GDBM_SYNC' means a database is synchronized after every updating method. `GDBM_NOLOCK' means a database is opened without file locking. `GDBM_LOCKNB' means file locking is performed without blocking. `GDBM_FAST' is ignored. `GDBM_SPARSE' is an original mode of QDBM and makes database sparse files. `mode' specifies a mode of a database file or a database directory as the one of `open' or `mkdir' call does. `bnum' specifies the number of elements of each bucket array. If it is not more than 0, the default value is specified. `dnum' specifies the number of division of the database. If it is not more than 0, the returning handle is created as a wrapper of Depot, else, it is as a wrapper of Curia. `align' specifies the basic size of alignment. The return value is the database handle or `NULL' if it is not successful. If the database already exists, whether it is one of Depot or Curia is measured automatically.</dd>
+</dl>
+
+<p>The function `gdbm_close' is used in order to close a database handle.</p>
+
+<dl>
+<dt><kbd>void gdbm_close(GDBM_FILE <var>dbf</var>);</kbd></dt>
+<dd>`dbf' specifies a database handle. Because the region of the closed handle is released, it becomes impossible to use the handle.</dd>
+</dl>
+
+<p>The function `gdbm_store' is used in order to store a record.</p>
+
+<dl>
+<dt><kbd>int gdbm_store(GDBM_FILE <var>dbf</var>, datum <var>key</var>, datum <var>content</var>, int <var>flag</var>);</kbd></dt>
+<dd>`dbf' specifies a database handle connected as a writer. `key' specifies a structure of a key. `content' specifies a structure of a value. `flag' specifies behavior when the key overlaps, by the following values: `GDBM_REPLACE', which means the specified value overwrites the existing one, `GDBM_INSERT', which means the existing value is kept. The return value is 0 if it is successful, 1 if it gives up because of overlaps of the key, -1 if other error occurs.</dd>
+</dl>
+
+<p>The function `gdbm_delete' is used in order to delete a record.</p>
+
+<dl>
+<dt><kbd>int gdbm_delete(GDBM_FILE <var>dbf</var>, datum <var>key</var>);</kbd></dt>
+<dd>`dbf' specifies a database handle connected as a writer. `key' specifies a structure of a key. The return value is 0 if it is successful, -1 if some errors occur.</dd>
+</dl>
+
+<p>The function `gdbm_fetch' is used in order to retrieve a record.</p>
+
+<dl>
+<dt><kbd>datum gdbm_fetch(GDBM_FILE <var>dbf</var>, datum <var>key</var>);</kbd></dt>
+<dd>`dbf' specifies a database handle. `key' specifies a structure of a key. The return value is a structure of the result. If a record corresponds, the member `dptr' of the structure is the pointer to the region of the value. If no record corresponds or some errors occur, `dptr' is `NULL'. Because the region pointed to by `dptr' is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `gdbm_exists' is used in order to check whether a record exists or not.</p>
+
+<dl>
+<dt><kbd>int gdbm_exists(GDBM_FILE <var>dbf</var>, datum <var>key</var>);</kbd></dt>
+<dd>`dbf' specifies a database handle. `key' specifies a structure of a key. The return value is true if a record corresponds and no error occurs, or false, else, it is false.</dd>
+</dl>
+
+<p>The function `gdbm_firstkey' is used in order to get the first key of a database.</p>
+
+<dl>
+<dt><kbd>datum gdbm_firstkey(GDBM_FILE <var>dbf</var>);</kbd></dt>
+<dd>`dbf' specifies a database handle. The return value is a structure of the result. If a record corresponds, the member `dptr' of the structure is the pointer to the region of the first key. If no record corresponds or some errors occur, `dptr' is `NULL'. Because the region pointed to by `dptr' is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `gdbm_nextkey' is used in order to get the next key of a database.</p>
+
+<dl>
+<dt><kbd>datum gdbm_nextkey(GDBM_FILE <var>dbf</var>, datum <var>key</var>);</kbd></dt>
+<dd>`dbf' specifies a database handle. The return value is a structure of the result. If a record corresponds, the member `dptr' of the structure is the pointer to the region of the next key. If no record corresponds or some errors occur, `dptr' is `NULL'. Because the region pointed to by `dptr' is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `gdbm_sync' is used in order to synchronize updating contents with the file and the device.</p>
+
+<dl>
+<dt><kbd>void gdbm_sync(GDBM_FILE <var>dbf</var>);</kbd></dt>
+<dd>`dbf' specifies a database handle connected as a writer.</dd>
+</dl>
+
+<p>The function `gdbm_reorganize' is used in order to reorganize a database.</p>
+
+<dl>
+<dt><kbd>int gdbm_reorganize(GDBM_FILE <var>dbf</var>);</kbd></dt>
+<dd>`dbf' specifies a database handle connected as a writer. If successful, the return value is 0, else -1.</dd>
+</dl>
+
+<p>The function `gdbm_fdesc' is used in order to get the file descriptor of a database file.</p>
+
+<dl>
+<dt><kbd>int gdbm_fdesc(GDBM_FILE <var>dbf</var>);</kbd></dt>
+<dd>`dbf' specifies a database handle connected as a writer. The return value is the file descriptor of the database file. If the database is a directory the return value is -1.</dd>
+</dl>
+
+<p>The function `gdbm_setopt' has no effect.</p>
+
+<dl>
+<dt><kbd>int gdbm_setopt(GDBM_FILE <var>dbf</var>, int <var>option</var>, int *<var>value</var>, int <var>size</var>);</kbd></dt>
+<dd>`dbf' specifies a database handle. `option' is ignored. `size' is ignored. The return value is 0. The function is only for compatibility.</dd>
+</dl>
+
+<h3>Examples</h3>
+
+<p>The following example stores and retrieves a phone number, using the name as the key.</p>
+
+<pre>#include &lt;hovel.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;sys/types.h&gt;
+#include &lt;sys/stat.h&gt;
+#include &lt;stdio.h&gt;
+#include &lt;string.h&gt;
+
+#define NAME "mikio"
+#define NUMBER "000-1234-5678"
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ GDBM_FILE dbf;
+ datum key, val;
+ int i;
+
+ /* open the database */
+ if(!(dbf = gdbm_open(DBNAME, 0, GDBM_WRCREAT, 00644, NULL))){
+ fprintf(stderr, "gdbm_open: %s\n", gdbm_strerror(gdbm_errno));
+ return 1;
+ }
+
+ /* prepare the record */
+ key.dptr = NAME;
+ key.dsize = strlen(NAME);
+ val.dptr = NUMBER;
+ val.dsize = strlen(NUMBER);
+
+ /* store the record */
+ if(gdbm_store(dbf, key, val, GDBM_REPLACE) != 0){
+ fprintf(stderr, "gdbm_store: %s\n", gdbm_strerror(gdbm_errno));
+ }
+
+ /* retrieve the record */
+ val = gdbm_fetch(dbf, key);
+ if(val.dptr){
+ printf("Name: %s\n", NAME);
+ printf("Number: ");
+ for(i = 0; i &lt; val.dsize; i++){
+ putchar(val.dptr[i]);
+ }
+ putchar('\n');
+ free(val.dptr);
+ } else {
+ fprintf(stderr, "gdbm_fetch: %s\n", gdbm_strerror(gdbm_errno));
+ }
+
+ /* close the database */
+ gdbm_close(dbf);
+
+ return 0;
+}
+</pre>
+
+<h3>Notes</h3>
+
+<p>How to build programs using Hovel is the same as the case of Depot. Note that an option to be given to a linker is not `-lgdbm', but `-lqdbm'.</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>If QDBM was built with POSIX thread enabled, the global variable `gdbm_errno' is treated as thread specific data, and functions of Hovel are reentrant. In that case, they are thread-safe as long as a handle is not accessed by threads at the same time, on the assumption that `errno', `malloc', and so on are thread-safe.</p>
+
+<hr />
+
+<h2><a name="hovelcli" id="hovelcli" class="head">Commands for Hovel</a></h2>
+
+<p>Hovel has the following command line interfaces.</p>
+
+<p>The command `hvmgr' is a utility for debugging Hovel and its applications. It features editing and checking of a database. It can be used for database applications with shell scripts. This command is used in the following format. `name' specifies a database name. `key' specifies the key of a record. `val' specifies the value of a record.</p>
+
+<dl>
+<dt><kbd>hvmgr create [-qdbm bnum dnum] [-s] <var>name</var></kbd></dt>
+<dd>Create a database file.</dd>
+<dt><kbd>hvmgr store [-qdbm] [-kx] [-vx|-vf] [-insert] <var>name</var> <var>key</var> <var>val</var></kbd></dt>
+<dd>Store a record with a key and a value.</dd>
+<dt><kbd>hvmgr delete [-qdbm] [-kx] <var>name</var> <var>key</var></kbd></dt>
+<dd>Delete a record with a key.</dd>
+<dt><kbd>hvmgr fetch [-qdbm] [-kx] [-ox] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>Retrieve a record with a key and output to the standard output.</dd>
+<dt><kbd>hvmgr list [-qdbm] [-ox] <var>name</var></kbd></dt>
+<dd>List all keys and values delimited with tab and line-feed to the standard output.</dd>
+<dt><kbd>hvmgr optimize [-qdbm] <var>name</var></kbd></dt>
+<dd>Optimize a database.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-qdbm [<var>bnum</var> <var>dnum</var>]</kbd> : use `gdbm_open2' to open the database. `bnum' specifies the number of the elements of the bucket array. `dnum' specifies the number of division of the database.</li>
+<li><kbd>-s</kbd> : make the file sparse.</li>
+<li><kbd>-kx</kbd> : treat `key' as a binary expression of hexadecimal notation.</li>
+<li><kbd>-vx</kbd> : treat `val' as a binary expression of hexadecimal notation.</li>
+<li><kbd>-vf</kbd> : read the value from a file specified with `val'.</li>
+<li><kbd>-insert</kbd> : specify the storing mode for `GDBM_INSERT'.</li>
+<li><kbd>-ox</kbd> : treat the output as a binary expression of hexadecimal notation.</li>
+<li><kbd>-n</kbd> : do not output the trailing newline.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `hvtest' is a utility for facility test and performance test. Check a database generated by the command or measure the execution time of the command. This command is used in the following format. `name' specifies a database name. `rnum' specifies the number of records.</p>
+
+<dl>
+<dt><kbd>hvtest write [-qdbm] [-s] <var>name</var> <var>rnum</var></kbd></dt>
+<dd>Store records with keys of 8 bytes. They changes as `00000001', `00000002'...</dd>
+<dt><kbd>hvtest read [-qdbm] <var>name</var> <var>rnum</var></kbd></dt>
+<dd>Retrieve records of the database above.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-qdbm</kbd> : use `gdbm_open2' and open the handle as Curia.</li>
+<li><kbd>-s</kbd> : make the file sparse.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<hr />
+
+<h2><a name="cabinapi" id="cabinapi" class="head">Cabin: Utility API</a></h2>
+
+<h3>Overview</h3>
+
+<p>Cabin is the utility API which provides memory allocating functions, sorting functions, extensible datum, array list, hash map, heap array, and so on for handling records easily on memory. This API features also parsing MIME, CSV, and XML, and features various types of encoding and decoding.</p>
+
+<p>In order to use Cabin, you should include `cabin.h' and `stdlib.h' in the source files. Usually, the following description will be near the beginning of a source file.</p>
+
+<dl>
+<dt><kbd>#include &lt;cabin.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+</dl>
+
+<p>A pointer to `CBDATUM' is used as a handle of an extensible datum. A datum handle is opened with the function `cbdatumopen' and closed with `cbdatumclose'. A pointer to `CBLIST' is used as a handle of an array list. A list handle is opened with the function `cblistopen' and closed with `cblistclose'. A pointer to `CBMAP' is used as a handle of a hash map. A map handle is opened with the function `cbmapopen' and closed with `cbmapclose'. A pointer to `CBHEAP' is used as a handle of a heap array. A heap handle is opened with the function `cbheapopen' and closed with `cbheapclose'. You should not refer directly to any member of each handles.</p>
+
+<h3>API</h3>
+
+<p>The external variable `cbfatalfunc' is the pointer to call back function for handling a fatal error.</p>
+
+<dl>
+<dt><kbd>extern void (*cbfatalfunc)(const char *);</kbd></dt>
+<dd>The argument specifies the error message. The initial value of this variable is `NULL'. If the value is `NULL', the default function is called when a fatal error occurs. A fatal error occurs when memory allocation is failed.</dd>
+</dl>
+
+<p>The function `cbmalloc' is used in order to allocate a region on memory.</p>
+
+<dl>
+<dt><kbd>void *cbmalloc(size_t <var>size</var>);</kbd></dt>
+<dd>`size' specifies the size of the region. The return value is the pointer to the allocated region. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbrealloc' is used in order to re-allocate a region on memory.</p>
+
+<dl>
+<dt><kbd>void *cbrealloc(void *<var>ptr</var>, size_t <var>size</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. The return value is the pointer to the re-allocated region. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbmemdup' is used in order to duplicate a region on memory.</p>
+
+<dl>
+<dt><kbd>char *cbmemdup(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. The return value is the pointer to the allocated region of the duplicate. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbfree' is used in order to free a region on memory.</p>
+
+<dl>
+<dt><kbd>void cbfree(void *<var>ptr</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. If it is `NULL', this function has no effect. Although this function is just a wrapper of `free' call, this is useful in applications using another package of the `malloc' series.</dd>
+</dl>
+
+<p>The function `cbglobalgc' is used in order to register the pointer or handle of an object to the global garbage collector.</p>
+
+<dl>
+<dt><kbd>void cbglobalgc(void *<var>ptr</var>, void (*<var>func</var>)(void *));</kbd></dt>
+<dd>`ptr' specifies the pointer or handle of an object. `func' specifies the pointer to a function to release resources of the object. Its argument is the pointer or handle of the object to release. This function assures that resources of an object are released when the process exits normally by returning from the `main' function or calling the `exit' function.</dd>
+</dl>
+
+<p>The function `cbggcsweep' is used in order to exercise the global garbage collector explicitly.</p>
+
+<dl>
+<dt><kbd>void cbggcsweep(void);</kbd></dt>
+<dd>Note that you should not use objects registered to the global garbage collector any longer after calling this function. Because the global garbage collector is initialized and you can register new objects into it.</dd>
+</dl>
+
+<p>The function `cbvmemavail' is used in order to check availability of allocation of the virtual memory.</p>
+
+<dl>
+<dt><kbd>int cbvmemavail(size_t <var>size</var>);</kbd></dt>
+<dd>`size' specifies the size of region to be allocated newly. The return value is true if allocation should be success, or false if not.</dd>
+</dl>
+
+<p>The function `cbisort' is used in order to sort an array using insert sort.</p>
+
+<dl>
+<dt><kbd>void cbisort(void *<var>base</var>, int <var>nmemb</var>, int <var>size</var>, int(*<var>compar</var>)(const void *, const void *));</kbd></dt>
+<dd>`base' specifies the pointer to an array. `nmemb' specifies the number of elements of the array. `size' specifies the size of each element. `compar' specifies the pointer to comparing function. The two arguments specify the pointers of elements. The comparing function should returns positive if the former is big, negative if the latter is big, 0 if both are equal. Insert sort is useful only if most elements have been sorted already.</dd>
+</dl>
+
+<p>The function `cbssort' is used in order to sort an array using shell sort.</p>
+
+<dl>
+<dt><kbd>void cbssort(void *<var>base</var>, int <var>nmemb</var>, int <var>size</var>, int(*<var>compar</var>)(const void *, const void *));</kbd></dt>
+<dd>`base' specifies the pointer to an array. `nmemb' specifies the number of elements of the array. `size' specifies the size of each element. `compar' specifies the pointer to comparing function. The two arguments specify the pointers of elements. The comparing function should returns positive if the former is big, negative if the latter is big, 0 if both are equal. If most elements have been sorted, shell sort may be faster than heap sort or quick sort.</dd>
+</dl>
+
+<p>The function `cbhsort' is used in order to sort an array using heap sort.</p>
+
+<dl>
+<dt><kbd>void cbhsort(void *<var>base</var>, int <var>nmemb</var>, int <var>size</var>, int(*<var>compar</var>)(const void *, const void *));</kbd></dt>
+<dd>`base' specifies the pointer to an array. `nmemb' specifies the number of elements of the array. `size' specifies the size of each element. `compar' specifies the pointer to comparing function. The two arguments specify the pointers of elements. The comparing function should returns positive if the former is big, negative if the latter is big, 0 if both are equal. Although heap sort is robust against bias of input, quick sort is faster in most cases.</dd>
+</dl>
+
+<p>The function `cbqsort' is used in order to sort an array using quick sort.</p>
+
+<dl>
+<dt><kbd>void cbqsort(void *<var>base</var>, int <var>nmemb</var>, int <var>size</var>, int(*<var>compar</var>)(const void *, const void *));</kbd></dt>
+<dd>`base' specifies the pointer to an array. `nmemb' specifies the number of elements of the array. `size' specifies the size of each element. `compar' specifies the pointer to comparing function. The two arguments specify the pointers of elements. The comparing function should returns positive if the former is big, negative if the latter is big, 0 if both are equal. Being sensitive to bias of input, quick sort is the fastest sorting algorithm.</dd>
+</dl>
+
+<p>The function `cbstricmp' is used in order to compare two strings with case insensitive evaluation.</p>
+
+<dl>
+<dt><kbd>int cbstricmp(const char *<var>astr</var>, const char *<var>bstr</var>);</kbd></dt>
+<dd>`astr' specifies the pointer of one string. `astr' specifies the pointer of the other string. The return value is positive if the former is big, negative if the latter is big, 0 if both are equivalent. Upper cases and lower cases of alphabets in ASCII code are not distinguished.</dd>
+</dl>
+
+<p>The function `cbstrfwmatch' is used in order to check whether a string begins with a key.</p>
+
+<dl>
+<dt><kbd>int cbstrfwmatch(const char *<var>str</var>, const char *<var>key</var>);</kbd></dt>
+<dd>`str' specifies the pointer of a target string. `key' specifies the pointer of a forward matching key string. The return value is true if the target string begins with the key, else, it is false.</dd>
+</dl>
+
+<p>The function `cbstrfwimatch' is used in order to check whether a string begins with a key, with case insensitive evaluation.</p>
+
+<dl>
+<dt><kbd>int cbstrfwimatch(const char *<var>str</var>, const char *<var>key</var>);</kbd></dt>
+<dd>`str' specifies the pointer of a target string. `key' specifies the pointer of a forward matching key string. The return value is true if the target string begins with the key, else, it is false. Upper cases and lower cases of alphabets in ASCII code are not distinguished.</dd>
+</dl>
+
+<p>The function `cbstrbwmatch' is used in order to check whether a string ends with a key.</p>
+
+<dl>
+<dt><kbd>int cbstrbwmatch(const char *<var>str</var>, const char *<var>key</var>);</kbd></dt>
+<dd>`str' specifies the pointer of a target string. `key' specifies the pointer of a backward matching key string. The return value is true if the target string ends with the key, else, it is false.</dd>
+</dl>
+
+<p>The function `cbstrbwimatch' is used in order to check whether a string ends with a key, with case insensitive evaluation.</p>
+
+<dl>
+<dt><kbd>int cbstrbwimatch(const char *<var>str</var>, const char *<var>key</var>);</kbd></dt>
+<dd>`str' specifies the pointer of a target string. `key' specifies the pointer of a backward matching key string. The return value is true if the target string ends with the key, else, it is false. Upper cases and lower cases of alphabets in ASCII code are not distinguished.</dd>
+</dl>
+
+<p>The function `cbstrstrkmp' is used in order to locate a substring in a string using KMP method.</p>
+
+<dl>
+<dt><kbd>char *cbstrstrkmp(const char *<var>haystack</var>, const char *<var>needle</var>);</kbd></dt>
+<dd>`haystack' specifies the pointer of a target string. `needle' specifies the pointer of a substring to be found. The return value is the pointer to the beginning of the substring or `NULL' if the substring is not found. In most cases, `strstr' as a built-in function of the compiler is faster than this function.</dd>
+</dl>
+
+<p>The function `cbstrstrkmp' is used in order to locate a substring in a string using BM method.</p>
+
+<dl>
+<dt><kbd>char *cbstrstrbm(const char *<var>haystack</var>, const char *<var>needle</var>);</kbd></dt>
+<dd>`haystack' specifies the pointer of a target string. `needle' specifies the pointer of a substring to be found. The return value is the pointer to the beginning of the substring or `NULL' if the substring is not found. In most cases, `strstr' as a built-in function of the compiler is faster than this function.</dd>
+</dl>
+
+<p>The function `cbstrtoupper' is used in order to convert the letters of a string to upper case.</p>
+
+<dl>
+<dt><kbd>char *cbstrtoupper(char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies the pointer of a string to convert. The return value is the pointer to the string.</dd>
+</dl>
+
+<p>The function `cbstrtolower' is used in order to convert the letters of a string to lower case.</p>
+
+<dl>
+<dt><kbd>char *cbstrtolower(char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies the pointer of a string to convert. The return value is the pointer to the string.</dd>
+</dl>
+
+<p>The function `cbstrtrim' is used in order to cut space characters at head or tail of a string.</p>
+
+<dl>
+<dt><kbd>char *cbstrtrim(char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies the pointer of a string to convert. The return value is the pointer to the string.</dd>
+</dl>
+
+<p>The function `cbstrsqzspc' is used in order to squeeze space characters in a string and trim it.</p>
+
+<dl>
+<dt><kbd>char *cbstrsqzspc(char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies the pointer of a string to convert. The return value is the pointer to the string.</dd>
+</dl>
+
+<p>The function `cbstrcountutf' is used in order to count the number of characters in a string of UTF-8.</p>
+
+<dl>
+<dt><kbd>int cbstrcountutf(const char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies the pointer of a string of UTF-8. The return value is the number of characters in the string.</dd>
+</dl>
+
+<p>The function `cbstrcututf' is used in order to cut a string of UTF-8 at the specified number of characters.</p>
+
+<dl>
+<dt><kbd>char *cbstrcututf(char *<var>str</var>, int <var>num</var>);</kbd></dt>
+<dd>`str' specifies the pointer of a string of UTF-8. `num' specifies the number of characters to be kept. The return value is the pointer to the string.</dd>
+</dl>
+
+<p>The function `cbdatumopen' is used in order to get a datum handle.</p>
+
+<dl>
+<dt><kbd>CBDATUM *cbdatumopen(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to the region of the initial content. If it is `NULL', an empty datum is created. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. The return value is a datum handle.</dd>
+</dl>
+
+<p>The function `cbdatumdup' is used in order to copy a datum.</p>
+
+<dl>
+<dt><kbd>CBDATUM *cbdatumdup(const CBDATUM *<var>datum</var>);</kbd></dt>
+<dd>`datum' specifies a datum handle. The return value is a new datum handle.</dd>
+</dl>
+
+<p>The function `cbdatumclose' is used in order to free a datum handle.</p>
+
+<dl>
+<dt><kbd>void cbdatumclose(CBDATUM *<var>datum</var>);</kbd></dt>
+<dd>`datum' specifies a datum handle. Because the region of a closed handle is released, it becomes impossible to use the handle.</dd>
+</dl>
+
+<p>The function `cbdatumcat' is used in order to concatenate a datum and a region.</p>
+
+<dl>
+<dt><kbd>void cbdatumcat(CBDATUM *<var>datum</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`datum' specifies a datum handle. `ptr' specifies the pointer to the region to be appended. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'.</dd>
+</dl>
+
+<p>The function `cbdatumptr' is used in order to get the pointer of the region of a datum.</p>
+
+<dl>
+<dt><kbd>const char *cbdatumptr(const CBDATUM *<var>datum</var>);</kbd></dt>
+<dd>`datum' specifies a datum handle. The return value is the pointer of the region of a datum. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.</dd>
+</dl>
+
+<p>The function `cbdatumsize' is used in order to get the size of the region of a datum.</p>
+
+<dl>
+<dt><kbd>int cbdatumsize(const CBDATUM *<var>datum</var>);</kbd></dt>
+<dd>`datum' specifies a datum handle. The return value is the size of the region of a datum.</dd>
+</dl>
+
+<p>The function `cbdatumsetsize' is used in order to change the size of the region of a datum.</p>
+
+<dl>
+<dt><kbd>void cbdatumsetsize(CBDATUM *<var>datum</var>, int <var>size</var>);</kbd></dt>
+<dd>`datum' specifies a datum handle. `size' specifies the new size of the region. If the new size is bigger than the one of old, the surplus region is filled with zero codes.</dd>
+</dl>
+
+<p>The function `cbdatumprintf' is used in order to perform formatted output into a datum.</p>
+
+<dl>
+<dt><kbd>void cbdatumprintf(CBDATUM *<var>datum</var>, const char *<var>format</var>, ...);</kbd></dt>
+<dd>`format' specifies a printf-like format string. The conversion character `%' can be used with such flag characters as `s', `d', `o', `u', `x', `X', `c', `e', `E', `f', `g', `G', `@', `?', `:', `%'. `@' works as with `s' but escapes meta characters of XML. `?' works as with `s' but escapes meta characters of URL. `:' works as with `s' but performs MIME encoding as UTF-8. The other conversion character work as with each original.</dd>
+</dl>
+
+<p>The function `cbdatumtomalloc' is used in order to convert a datum to an allocated region.</p>
+
+<dl>
+<dt><kbd>char *cbdatumtomalloc(CBDATUM *<var>datum</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`datum' specifies a datum handle. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. The return value is the pointer to the region of the datum. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. Because the region of the original datum is released, it should not be released again.</dd>
+</dl>
+
+<p>The function `cblistopen' is used in order to get a list handle.</p>
+
+<dl>
+<dt><kbd>CBLIST *cblistopen(void);</kbd></dt>
+<dd>The return value is a list handle.</dd>
+</dl>
+
+<p>The function `cblistdup' is used in order to copy a list.</p>
+
+<dl>
+<dt><kbd>CBLIST *cblistdup(const CBLIST *<var>list</var>);</kbd></dt>
+<dd>`list' specifies a list handle. The return value is a new list handle.</dd>
+</dl>
+
+<p>The function `cblistclose' is used in order to close a list handle.</p>
+
+<dl>
+<dt><kbd>void cblistclose(CBLIST *<var>list</var>);</kbd></dt>
+<dd>`list' specifies a list handle. Because the region of a closed handle is released, it becomes impossible to use the handle.</dd>
+</dl>
+
+<p>The function `cblistnum' is used in order to get the number of elements of a list.</p>
+
+<dl>
+<dt><kbd>int cblistnum(const CBLIST *<var>list</var>);</kbd></dt>
+<dd>`list' specifies a list handle. The return value is the number of elements of the list.</dd>
+</dl>
+
+<p>The function `cblistval' is used in order to get the pointer to the region of an element of a list.</p>
+
+<dl>
+<dt><kbd>const char *cblistval(const CBLIST *<var>list</var>, int <var>index</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`list' specifies a list handle. `index' specifies the index of an element. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. The return value is the pointer to the region of the element. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. If `index' is equal to or more than the number of elements, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `cblistpush' is used in order to add an element at the end of a list.</p>
+
+<dl>
+<dt><kbd>void cblistpush(CBLIST *<var>list</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`list' specifies a list handle. `ptr' specifies the pointer to the region of an element. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'.</dd>
+</dl>
+
+<p>The function `cblistpop' is used in order to remove an element of the end of a list.</p>
+
+<dl>
+<dt><kbd>char *cblistpop(CBLIST *<var>list</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`list' specifies a list handle. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. The return value is the pointer to the region of the value. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. If the list is empty, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `cblistunshift' is used in order to add an element at the top of a list.</p>
+
+<dl>
+<dt><kbd>void cblistunshift(CBLIST *<var>list</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`list' specifies a list handle. `ptr' specifies the pointer to the region of an element. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'.</dd>
+</dl>
+
+<p>The function `cblistshift' is used in order to remove an element of the top of a list.</p>
+
+<dl>
+<dt><kbd>char *cblistshift(CBLIST *<var>list</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`list' specifies a list handle. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. The return value is the pointer to the region of the value. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. If the list is empty, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `cblistinsert' is used in order to add an element at the specified location of a list.</p>
+
+<dl>
+<dt><kbd>void cblistinsert(CBLIST *<var>list</var>, int <var>index</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`list' specifies a list handle. `index' specifies the index of an element. `ptr' specifies the pointer to the region of the element. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'.</dd>
+</dl>
+
+<p>The function `cblistremove' is used in order to remove an element at the specified location of a list.</p>
+
+<dl>
+<dt><kbd>char *cblistremove(CBLIST *<var>list</var>, int <var>index</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`list' specifies a list handle. `index' specifies the index of an element. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. The return value is the pointer to the region of the value. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. If `index' is equal to or more than the number of elements, no element is removed and the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `cblistover' is used in order to overwrite an element at the specified location of a list.</p>
+
+<dl>
+<dt><kbd>void cblistover(CBLIST *<var>list</var>, int <var>index</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`list' specifies a list handle. `index' specifies the index of an element. `ptr' specifies the pointer to the region of the new content. `size' specifies the size of the new content. If it is negative, the size is assigned with `strlen(ptr)'. If `index' is equal to or more than the number of elements, this function has no effect.</dd>
+</dl>
+
+<p>The function `cblistsort' is used in order to sort elements of a list in lexical order.</p>
+
+<dl>
+<dt><kbd>void cblistsort(CBLIST *<var>list</var>);</kbd></dt>
+<dd>`list' specifies a list handle. Quick sort is used for sorting.</dd>
+</dl>
+
+<p>The function `cblistlsearch' is used in order to search a list for an element using liner search.</p>
+
+<dl>
+<dt><kbd>int cblistlsearch(const CBLIST *<var>list</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`list' specifies a list handle. `ptr' specifies the pointer to the region of a key. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. The return value is the index of a corresponding element or -1 if there is no corresponding element. If two or more elements corresponds, the former returns.</dd>
+</dl>
+
+<p>The function `cblistbsearch' is used in order to search a list for an element using binary search.</p>
+
+<dl>
+<dt><kbd>int cblistbsearch(const CBLIST *<var>list</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`list' specifies a list handle. It should be sorted in lexical order. `ptr' specifies the pointer to the region of a key. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. The return value is the index of a corresponding element or -1 if there is no corresponding element. If two or more elements corresponds, which returns is not defined.</dd>
+</dl>
+
+<p>The function `cblistdump' is used in order to serialize a list into a byte array.</p>
+
+<dl>
+<dt><kbd>char *cblistdump(const CBLIST *<var>list</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`list' specifies a list handle. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. The return value is the pointer to the region of the result serial region. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cblistload' is used in order to redintegrate a serialized list.</p>
+
+<dl>
+<dt><kbd>CBLIST *cblistload(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a byte array. `size' specifies the size of the region. The return value is a new list handle.</dd>
+</dl>
+
+<p>The function `cbmapopen' is used in order to get a map handle.</p>
+
+<dl>
+<dt><kbd>CBMAP *cbmapopen(void);</kbd></dt>
+<dd>The return value is a map handle.</dd>
+</dl>
+
+<p>The function `cbmapdup' is used in order to copy a map.</p>
+
+<dl>
+<dt><kbd>CBMAP *cbmapdup(CBMAP *<var>map</var>);</kbd></dt>
+<dd>`map' specifies a map handle. The return value is a new map handle. The iterator of the source map is initialized.</dd>
+</dl>
+
+<p>The function `cbmapclose' is used in order to close a map handle.</p>
+
+<dl>
+<dt><kbd>void cbmapclose(CBMAP *<var>map</var>);</kbd></dt>
+<dd>`map' specifies a map handle. Because the region of a closed handle is released, it becomes impossible to use the handle.</dd>
+</dl>
+
+<p>The function `cbmapput' is used in order to store a record into a map.</p>
+
+<dl>
+<dt><kbd>int cbmapput(CBMAP *<var>map</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const char *<var>vbuf</var>, int <var>vsiz</var>, int <var>over</var>);</kbd></dt>
+<dd>`map' specifies a map handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `vbuf' specifies the pointer to the region of a value. `vsiz' specifies the size of the region of the value. If it is negative, the size is assigned with `strlen(vbuf)'. `over' specifies whether the value of the duplicated record is overwritten or not. If `over' is false and the key duplicated, the return value is false, else, it is true.</dd>
+</dl>
+
+<p>The function `cbmapputcat' is used in order to concatenate a value at the end of the value of the existing record.</p>
+
+<dl>
+<dt><kbd>void cbmapputcat(CBMAP *<var>map</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const char *<var>vbuf</var>, int <var>vsiz</var>);</kbd></dt>
+<dd>`map' specifies a map handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `vbuf' specifies the pointer to the region of a value. `vsiz' specifies the size of the region of the value. If it is negative, the size is assigned with `strlen(vbuf)'. If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `cbmapout' is used in order to delete a record of a map.</p>
+
+<dl>
+<dt><kbd>int cbmapout(CBMAP *<var>map</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`map' specifies a map handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. If successful, the return value is true. False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `cbmapget' is used in order to retrieve a record of a map.</p>
+
+<dl>
+<dt><kbd>const char *cbmapget(const CBMAP *<var>map</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`map' specifies a map handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the value of the corresponding record. `NULL' is returned when no record corresponds. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.</dd>
+</dl>
+
+<p>The function `cbmapmove' is used in order to move a record to the edge of a map.</p>
+
+<dl>
+<dt><kbd>int cbmapmove(CBMAP *<var>map</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>head</var>);</kbd></dt>
+<dd>`map' specifies a map handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `head' specifies the destination which is head if it is true or tail if else. If successful, the return value is true. False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `cbmapiterinit' is used in order to initialize the iterator of a map.</p>
+
+<dl>
+<dt><kbd>void cbmapiterinit(CBMAP *<var>map</var>);</kbd></dt>
+<dd>`map' specifies a map handle. The iterator is used in order to access the key of every record stored in a map.</dd>
+</dl>
+
+<p>The function `cbmapiternext' is used in order to get the next key of the iterator of a map.</p>
+
+<dl>
+<dt><kbd>const char *cbmapiternext(CBMAP *<var>map</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`map' specifies a map handle. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the next key, else, it is `NULL'. `NULL' is returned when no record is to be get out of the iterator. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. The order of iteration is assured to be the same of the one of storing.</dd>
+</dl>
+
+<p>The function `cbmapiterval' is used in order to get the value binded to the key fetched from the iterator of a map.</p>
+
+<dl>
+<dt><kbd>const char *cbmapiterval(const char *<var>kbuf</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`kbuf' specifies the pointer to the region of a iteration key. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. The return value is the pointer to the region of the value of the corresponding record. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.</dd>
+</dl>
+
+<p>The function `cbmaprnum' is used in order to get the number of the records stored in a map.</p>
+
+<dl>
+<dt><kbd>int cbmaprnum(const CBMAP *<var>map</var>);</kbd></dt>
+<dd>`map' specifies a map handle. The return value is the number of the records stored in the map.</dd>
+</dl>
+
+<p>The function `cbmapkeys' is used in order to get the list handle contains all keys in a map.</p>
+
+<dl>
+<dt><kbd>CBLIST *cbmapkeys(CBMAP *<var>map</var>);</kbd></dt>
+<dd>`map' specifies a map handle. The return value is the list handle contains all keys in the map. Because the handle of the return value is opened with the function `cblistopen', it should be closed with the function `cblistclose' if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbmapvals' is used in order to get the list handle contains all values in a map.</p>
+
+<dl>
+<dt><kbd>CBLIST *cbmapvals(CBMAP *<var>map</var>);</kbd></dt>
+<dd>`map' specifies a map handle. The return value is the list handle contains all values in the map. Because the handle of the return value is opened with the function `cblistopen', it should be closed with the function `cblistclose' if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbmapdump' is used in order to serialize a map into a byte array.</p>
+
+<dl>
+<dt><kbd>char *cbmapdump(const CBMAP *<var>map</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`map' specifies a map handle. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. The return value is the pointer to the region of the result serial region. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbmapload' is used in order to redintegrate a serialized map.</p>
+
+<dl>
+<dt><kbd>CBMAP *cbmapload(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a byte array. `size' specifies the size of the region. The return value is a new map handle.</dd>
+</dl>
+
+<p>The function `cbmaploadone' is used in order to extract a record from a serialized map.</p>
+
+<dl>
+<dt><kbd>char *cbmaploadone(const char *<var>ptr</var>, int <var>size</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a byte array. `size' specifies the size of the region. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the value of the corresponding record. `NULL' is returned when no record corresponds. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.</dd>
+</dl>
+
+
+<p>The function `cbheapopen' is used in order to get a heap handle.</p>
+
+<dl>
+<dt><kbd>CBHEAP *cbheapopen(int <var>size</var>, int <var>max</var>, int(*<var>compar</var>)(const void *, const void *));</kbd></dt>
+<dd>`size' specifies the size of each record. `max' specifies the maximum number of records in the heap. `compar' specifies the pointer to comparing function. The two arguments specify the pointers of records. The comparing function should returns positive if the former is big, negative if the latter is big, 0 if both are equal. The return value is a heap handle.</dd>
+</dl>
+
+<p>The function `cbheapdup' is used in order to copy a heap.</p>
+
+<dl>
+<dt><kbd>CBHEAP *cbheapdup(CBHEAP *<var>heap</var>);</kbd></dt>
+<dd>`heap' specifies a heap handle. The return value is a new heap handle.</dd>
+</dl>
+
+<p>The function `cbheapclose' is used in order to close a heap handle.</p>
+
+<dl>
+<dt><kbd>void cbheapclose(CBHEAP *<var>heap</var>);</kbd></dt>
+<dd>`heap' specifies a heap handle. Because the region of a closed handle is released, it becomes impossible to use the handle.</dd>
+</dl>
+
+<p>The function `cbheapnum' is used in order to get the number of the records stored in a heap.</p>
+
+<dl>
+<dt><kbd>int cbheapnum(CBHEAP *<var>heap</var>);</kbd></dt>
+<dd>`heap' specifies a heap handle. The return value is the number of the records stored in the heap.</dd>
+</dl>
+
+<p>The function `cbheapinsert' is used in order to insert a record into a heap.</p>
+
+<dl>
+<dt><kbd>int cbheapinsert(CBHEAP *<var>heap</var>, const void *<var>ptr</var>);</kbd></dt>
+<dd>`heap' specifies a heap handle. `ptr' specifies the pointer to the region of a record. The return value is true if the record is added, else false. If the new record is bigger than the biggest existing regord, the new record is not added. If the new record is added and the number of records exceeds the maximum number, the biggest existing record is removed.</dd>
+</dl>
+
+<p>The function `cbheapval' is used in order to get the pointer to the region of a record in a heap.</p>
+
+<dl>
+<dt><kbd>void *cbheapval(CBHEAP *<var>heap</var>, int <var>index</var>);</kbd></dt>
+<dd>`heap' specifies a heap handle. `index' specifies the index of a record. The return value is the pointer to the region of the record. If `index' is equal to or more than the number of records, the return value is `NULL'. Note that records are organized by the nagative order the comparing function.</dd>
+</dl>
+
+<p>The function `cbheaptomalloc' is used in order to convert a heap to an allocated region.</p>
+
+<dl>
+<dt><kbd>void *cbheaptomalloc(CBHEAP *<var>heap</var>, int *<var>np</var>);</kbd></dt>
+<dd>`heap' specifies a heap handle. `np' specifies the pointer to a variable to which the number of records of the return value is assigned. If it is `NULL', it is not used. The return value is the pointer to the region of the heap. Records are sorted. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. Because the region of the original heap is released, it should not be released again.</dd>
+</dl>
+
+<p>The function `cbsprintf' is used in order to allocate a formatted string on memory.</p>
+
+<dl>
+<dt><kbd>char *cbsprintf(const char *<var>format</var>, ...);</kbd></dt>
+<dd>`format' specifies a printf-like format string. The conversion character `%' can be used with such flag characters as `d', `o', `u', `x', `X', `e', `E', `f', `g', `G', `c', `s', and `%'. Specifiers of the field length and the precision can be put between the conversion characters and the flag characters. The specifiers consist of decimal characters, `.', `+', `-', and the space character. The other arguments are used according to the format string. The return value is the pointer to the allocated region of the result string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbreplace' is used in order to replace some patterns in a string.</p>
+
+<dl>
+<dt><kbd>char *cbreplace(const char *<var>str</var>, CBMAP *<var>pairs</var>);</kbd></dt>
+<dd>`str' specifies the pointer to a source string. `pairs' specifies the handle of a map composed of pairs of replacement. The key of each pair specifies a pattern before replacement and its value specifies the pattern after replacement. The return value is the pointer to the allocated region of the result string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbsplit' is used in order to make a list by splitting a serial datum.</p>
+
+<dl>
+<dt><kbd>CBLIST *cbsplit(const char *<var>ptr</var>, int <var>size</var>, const char *<var>delim</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to the region of the source content. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. `delim' specifies a string containing delimiting characters. If it is `NULL', zero code is used as a delimiter. The return value is a list handle. If two delimiters are successive, it is assumed that an empty element is between the two. Because the handle of the return value is opened with the function `cblistopen', it should be closed with the function `cblistclose'.</dd>
+</dl>
+
+<p>The function `cbreadfile' is used in order to read whole data of a file.</p>
+
+<dl>
+<dt><kbd>char *cbreadfile(const char *<var>name</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`name' specifies the name of a file. If it is `NULL', the standard input is specified. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. The return value is the pointer to the allocated region of the read data. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbwritefile' is used in order to write a serial datum into a file.</p>
+
+<dl>
+<dt><kbd>int cbwritefile(const char *<var>name</var>, const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`name specifies the name of a file. If it is `NULL', the standard output is specified. `ptr' specifies the pointer to the region of the source content. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. If successful, the return value is true, else, it is false. If the file exists, it is overwritten. Else, a new file is created.</dd>
+</dl>
+
+<p>The function `cbreadlines' is used in order to read every line of a file.</p>
+
+<dl>
+<dt><kbd>CBLIST *cbreadlines(const char *<var>name</var>);</kbd></dt>
+<dd>`name' specifies the name of a file. If it is `NULL', the standard input is specified. The return value is a list handle of the lines if successful, else it is NULL. Line separators are cut out. Because the handle of the return value is opened with the function `cblistopen', it should be closed with the function `cblistclose' if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbdirlist' is used in order to read names of files in a directory.</p>
+
+<dl>
+<dt><kbd>CBLIST *cbdirlist(const char *<var>name</var>);</kbd></dt>
+<dd>`name' specifies the name of a directory. The return value is a list handle of names if successful, else it is NULL. Because the handle of the return value is opened with the function `cblistopen', it should be closed with the function `cblistclose' if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbfilestat' is used in order to get the status of a file or a directory.</p>
+
+<dl>
+<dt><kbd>int cbfilestat(const char *<var>name</var>, int *<var>isdirp</var>, int *<var>sizep</var>, int *<var>mtimep</var>);</kbd></dt>
+<dd>`name' specifies the name of a file or a directory. `dirp' specifies the pointer to a variable to which whether the file is a directory is assigned. If it is `NULL', it is not used. `sizep' specifies the pointer to a variable to which the size of the file is assigned. If it is `NULL', it is not used. `mtimep' specifies the pointer to a variable to which the last modified time of the file is assigned. If it is `NULL', it is not used. If successful, the return value is true, else, false. False is returned when the file does not exist or the permission is denied.</dd>
+</dl>
+
+<p>The function `cbremove' is used in order to remove a file or a directory and its sub ones recursively.</p>
+
+<dl>
+<dt><kbd>int cbremove(const char *<var>name</var>);</kbd></dt>
+<dd>`name' specifies the name of a file or a directory. If successful, the return value is true, else, false. False is returned when the file does not exist or the permission is denied.</dd>
+</dl>
+
+<p>The function `cburlbreak' is used in order to break up a URL into elements.</p>
+
+<dl>
+<dt><kbd>CBMAP *cburlbreak(const char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies the pointer to a string of URL. The return value is a map handle. Each key of the map is the name of an element. The key "self" specifies the URL itself. The key "scheme" specifies the scheme. The key "host" specifies the host of the server. The key "port" specifies the port number of the server. The key "authority" specifies the authority information. The key "path" specifies the path of the resource. The key "file" specifies the file name without the directory section. The key "query" specifies the query string. The key "fragment" specifies the fragment string. Supported schema are HTTP, HTTPS, FTP, and FILE. Absolute URL and relative URL are supported. Because the handle of the return value is opened with the function `cbmapopen', it should be closed with the function `cbmapclose' if it is no longer in use.</dd>
+</dl>
+
+<p>The runction `cburlresolve' is used in order to resolve a relative URL with another absolute URL.</p>
+
+<dl>
+<dt><kbd>char *cburlresolve(const char *<var>base</var>, const char *<var>target</var>);</kbd></dt>
+<dd>`base' specifies an absolute URL of a base location. `target' specifies a URL to be resolved. The return value is a resolved URL. If the target URL is relative, a new URL of relative location from the base location is returned. Else, a copy of the target URL is returned. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cburlencode' is used in order to encode a serial object with URL encoding.</p>
+
+<dl>
+<dt><kbd>char *cburlencode(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. The return value is the pointer to the result string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cburldecode' is used in order to decode a string encoded with URL encoding.</p>
+
+<dl>
+<dt><kbd>char *cburldecode(const char *<var>str</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`str' specifies the pointer to a source string. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. The return value is the pointer to the region of the result. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbbaseencode' is used in order to encode a serial object with Base64 encoding.</p>
+
+<dl>
+<dt><kbd>char *cbbaseencode(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. The return value is the pointer to the result string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbbasedecode' is used in order to decode a string encoded with Base64 encoding.</p>
+
+<dl>
+<dt><kbd>char *cbbasedecode(const char *<var>str</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`str' specifies the pointer to a source string. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. The return value is the pointer to the region of the result. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbquoteencode' is used in order to encode a serial object with quoted-printable encoding.</p>
+
+<dl>
+<dt><kbd>char *cbquoteencode(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. The return value is the pointer to the result string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbquotedecode' is used in order to decode a string encoded with quoted-printable encoding.</p>
+
+<dl>
+<dt><kbd>char *cbquotedecode(const char *<var>str</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`str' specifies the pointer to a source string. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. The return value is the pointer to the region of the result. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+
+<p>The function `cbmimebreak' is used in order to split a string of MIME into headers and the body.</p>
+
+<dl>
+<dt><kbd>char *cbmimebreak(const char *<var>ptr</var>, int <var>size</var>, CBMAP *<var>attrs</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to the region of MIME data. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. `attrs' specifies a map handle to store attributes. If it is `NULL', it is not used. Each key of the map is an attribute name uncapitalized. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. The return value is the pointer of the body data. If the content type is defined, the attribute map has the key "TYPE" specifying the type. If the character encoding is defined, the key "CHARSET" specifies the encoding name. If the boundary string of multipart is defined, the key "BOUNDARY" specifies the string. If the content disposition is defined, the key "DISPOSITION" specifies the direction. If the file name is defined, the key "FILENAME" specifies the name. If the attribute name is defined, the key "NAME" specifies the name. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbmimeparts' is used in order to split multipart data of MIME into its parts.</p>
+
+<dl>
+<dt><kbd>CBLIST *cbmimeparts(const char *<var>ptr</var>, int <var>size</var>, const char *<var>boundary</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to the region of multipart data of MIME. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. `boundary' specifies the pointer to the region of the boundary string. The return value is a list handle. Each element of the list is the string of a part. Because the handle of the return value is opened with the function `cblistopen', it should be closed with the function `cblistclose' if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbmimeencode' is used in order to encode a string with MIME encoding.</p>
+
+<dl>
+<dt><kbd>char *cbmimeencode(const char *<var>str</var>, const char *<var>encname</var>, int <var>base</var>);</kbd></dt>
+<dd>`str' specifies the pointer to a string. `encname' specifies a string of the name of the character encoding. The return value is the pointer to the result string. `base' specifies whether to use Base64 encoding. If it is false, quoted-printable is used. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbmimedecode' is used in order to decode a string encoded with MIME encoding.</p>
+
+<dl>
+<dt><kbd>char *cbmimedecode(const char *<var>str</var>, char *<var>enp</var>);</kbd></dt>
+<dd>`str' specifies the pointer to an encoded string. `enp' specifies the pointer to a region into which the name of encoding is written. If it is `NULL', it is not used. The size of the buffer should be equal to or more than 32 bytes. The return value is the pointer to the result string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbcsvrows' is used in order to split a string of CSV into rows.</p>
+
+<dl>
+<dt><kbd>CBLIST *cbcsvrows(const char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies the pointer to the region of an CSV string. The return value is a list handle. Each element of the list is a string of a row. Because the handle of the return value is opened with the function `cblistopen', it should be closed with the function `cblistclose' if it is no longer in use. The character encoding of the input string should be US-ASCII, UTF-8, ISO-8859-*, EUC-*, or Shift_JIS. Being compatible with MS-Excel, these functions for CSV can handle cells including such meta characters as comma, between double quotation marks.</dd>
+</dl>
+
+<p>The function `cbcsvcells' is used in order to split the string of a row of CSV into cells.</p>
+
+<dl>
+<dt><kbd>CBLIST *cbcsvcells(const char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies the pointer to the region of a row of CSV. The return value is a list handle. Each element of the list is the unescaped string of a cell of the row. Because the handle of the return value is opened with the function `cblistopen', it should be closed with the function `cblistclose' if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbcsvescape' is used in order to escape a string with the meta characters of CSV.</p>
+
+<dl>
+<dt><kbd>char *cbcsvescape(const char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies the pointer to the region of a string. The return value is the pointer to the escaped string sanitized of meta characters. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbcsvunescape' is used in order to unescape a string with the escaped meta characters of CSV.</p>
+
+<dl>
+<dt><kbd>char *cbcsvunescape(const char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies the pointer to the region of a string with meta characters. The return value is the pointer to the unescaped string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbxmlbreak' is used in order to split a string of XML into tags and text sections.</p>
+
+<dl>
+<dt><kbd>CBLIST *cbxmlbreak(const char *<var>str</var>, int <var>cr</var>);</kbd></dt>
+<dd>`str' specifies the pointer to the region of an XML string. `cr' specifies whether to remove comments. The return value is a list handle. Each element of the list is the string of a tag or a text section. Because the handle of the return value is opened with the function `cblistopen', it should be closed with the function `cblistclose' if it is no longer in use. The character encoding of the input string should be US-ASCII, UTF-8, ISO-8859-*, EUC-*, or Shift_JIS. Because these functions for XML are not XML parser with validation check, it can handle also HTML and SGML.</dd>
+</dl>
+
+<p>The function `cbxmlattrs' is used in order to get the map of attributes of an XML tag.</p>
+
+<dl>
+<dt><kbd>CBMAP *cbxmlattrs(const char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies the pointer to the region of a tag string. The return value is a map handle. Each key of the map is the name of an attribute. Each value is unescaped. You can get the name of the tag with the key of an empty string. Because the handle of the return value is opened with the function `cbmapopen', it should be closed with the function `cbmapclose' if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbxmlescape' is used in order to escape a string with the meta characters of XML.</p>
+
+<dl>
+<dt><kbd>char *cbxmlescape(const char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies the pointer to the region of a string. The return value is the pointer to the escaped string sanitized of meta characters. This function converts only `&amp;', `&lt;', `&gt;', and `&quot;'. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbxmlunescape' is used in order to unescape a string with the entity references of XML.</p>
+
+<dl>
+<dt><kbd>char *cbxmlunescape(const char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies the pointer to the region of a string. The return value is the pointer to the unescaped string. This function restores only `&amp;amp;', `&amp;lt;', `&amp;gt;', and `&amp;quot;'. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbdeflate' is used in order to compress a serial object with ZLIB.</p>
+
+<dl>
+<dt><kbd>char *cbdeflate(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If successful, the return value is the pointer to the result object, else, it is `NULL'. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. This function is available only if QDBM was built with ZLIB enabled.</dd>
+</dl>
+
+<p>The function `cbinflate' is used in order to decompress a serial object compressed with ZLIB.</p>
+
+<dl>
+<dt><kbd>char *cbinflate(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the result object, else, it is `NULL'. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. This function is available only if QDBM was built with ZLIB enabled.</dd>
+</dl>
+
+<p>The function `cbgzencode' is used in order to compress a serial object with GZIP.</p>
+
+<dl>
+<dt><kbd>char *cbgzencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If successful, the return value is the pointer to the result object, else, it is `NULL'. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. This function is available only if QDBM was built with ZLIB enabled.</dd>
+</dl>
+
+<p>The function `cbgzdecode' is used in order to decompress a serial object compressed with GZIP.</p>
+
+<dl>
+<dt><kbd>char *cbgzdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the result object, else, it is `NULL'. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. This function is available only if QDBM was built with ZLIB enabled.</dd>
+</dl>
+
+<p>The function `cbgetcrc' is used in order to get the CRC32 checksum of a serial object.</p>
+
+<dl>
+<dt><kbd>unsigned int cbgetcrc(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. The return value is the CRC32 checksum of the object. This function is available only if QDBM was built with ZLIB enabled.</dd>
+</dl>
+
+<p>The function `cblzoencode' is used in order to compress a serial object with LZO.</p>
+
+<dl>
+<dt><kbd>char *cblzoencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If successful, the return value is the pointer to the result object, else, it is `NULL'. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. This function is available only if QDBM was built with LZO enabled.</dd>
+</dl>
+
+<p>The function `cblzodecode' is used in order to decompress a serial object compressed with LZO.</p>
+
+<dl>
+<dt><kbd>char *cblzodecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the result object, else, it is `NULL'. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. This function is available only if QDBM was built with LZO enabled.</dd>
+</dl>
+
+<p>The function `cbbzencode' is used in order to compress a serial object with BZIP2.</p>
+
+<dl>
+<dt><kbd>char *cbbzencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If successful, the return value is the pointer to the result object, else, it is `NULL'. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. This function is available only if QDBM was built with BZIP2 enabled.</dd>
+</dl>
+
+<p>The function `cbbzdecode' is used in order to decompress a serial object compressed with BZIP2.</p>
+
+<dl>
+<dt><kbd>char *cbbzdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the result object, else, it is `NULL'. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. This function is available only if QDBM was built with BZIP2 enabled.</dd>
+</dl>
+
+<p>The function `cbiconv' is used in order to convert the character encoding of a string.</p>
+
+<dl>
+<dt><kbd>char *cbiconv(const char *<var>ptr</var>, int <var>size</var>, const char *<var>icode</var>, const char *<var>ocode</var>, int *<var>sp</var>, int *<var>mp</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. `icode' specifies the name of encoding of the input string. `ocode' specifies the name of encoding of the output string. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. `mp' specifies the pointer to a variable to which the number of missing characters by failure of conversion is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the result object, else, it is `NULL'. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. This function is available only if QDBM was built with ICONV enabled.</dd>
+</dl>
+
+<p>The function `cbencname' is used in order to detect the encoding of a string automatically.</p>
+
+<dl>
+<dt><kbd>const char *cbencname(const char *<var>ptr</var>, int <var>size</var>);</kbd></dt>
+<dd>`ptr' specifies the pointer to a region. `size' specifies the size of the region. If it is negative, the size is assigned with `strlen(ptr)'. The return value is the string of the encoding name of the string. As it stands, US-ASCII, ISO-2022-JP, Shift_JIS, CP932, EUC-JP, UTF-8, UTF-16, UTF-16BE, and UTF-16LE are supported. If none of them matches, ISO-8859-1 is selected. This function is available only if QDBM was built with ICONV enabled.</dd>
+</dl>
+
+<p>The function `cbjetlag' is used in order to get the jet lag of the local time in seconds.</p>
+
+<dl>
+<dt><kbd>int cbjetlag(void);</kbd></dt>
+<dd>The return value is the jet lag of the local time in seconds.</dd>
+</dl>
+
+<p>The function `cbcalendar' is used in order to get the Gregorian calendar of a time.</p>
+
+<dl>
+<dt><kbd>void cbcalendar(time_t <var>t</var>, int <var>jl</var>, int *<var>yearp</var>, int *<var>monp</var>, int *<var>dayp</var>, int *<var>hourp</var>, int *<var>minp</var>, int *<var>secp</var>);</kbd></dt>
+<dd>`t' specifies a source time. If it is negative, the current time is specified. `jl' specifies the jet lag of a location in seconds. `yearp' specifies the pointer to a variable to which the year is assigned. If it is `NULL', it is not used. `monp' specifies the pointer to a variable to which the month is assigned. If it is `NULL', it is not used. 1 means January and 12 means December. `dayp' specifies the pointer to a variable to which the day of the month is assigned. If it is `NULL', it is not used. `hourp' specifies the pointer to a variable to which the hours is assigned. If it is `NULL', it is not used. `minp' specifies the pointer to a variable to which the minutes is assigned. If it is `NULL', it is not used. `secp' specifies the pointer to a variable to which the seconds is assigned. If it is `NULL', it is not used.</dd>
+</dl>
+
+<p>The function `cbdayofweek' is used in order to get the day of week of a date.</p>
+
+<dl>
+<dt><kbd>int cbdayofweek(int <var>year</var>, int <var>mon</var>, int <var>day</var>);</kbd></dt>
+<dd>`year' specifies the year of a date. `mon' specifies the month of the date. `day' specifies the day of the date. The return value is the day of week of the date. 0 means Sunday and 6 means Saturday.</dd>
+</dl>
+
+<p>The function `cbdatestrwww' is used in order to get the string for a date in W3CDTF.</p>
+
+<dl>
+<dt><kbd>char *cbdatestrwww(time_t <var>t</var>, int <var>jl</var>);</kbd></dt>
+<dd>`t' specifies a source time. If it is negative, the current time is specified. `jl' specifies the jet lag of a location in seconds. The return value is the string of the date in W3CDTF (YYYY-MM-DDThh:mm:ddTZD). Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbdatestrhttp' is used in order to get the string for a date in RFC 1123 format.</p>
+
+<dl>
+<dt><kbd>char *cbdatestrhttp(time_t <var>t</var>, int <var>jl</var>);</kbd></dt>
+<dd>`t' specifies a source time. If it is negative, the current time is specified. `jl' specifies the jet lag of a location in seconds. The return value is the string of the date in RFC 1123 format (Wdy, DD-Mon-YYYY hh:mm:dd TZD). Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `cbstrmktime' is used in order to get the time value of a date string in decimal, hexadecimal, W3CDTF, or RFC 822 (1123).</p>
+
+<dl>
+<dt><kbd>time_t cbstrmktime(const char *<var>str</var>);</kbd></dt>
+<dd>`str' specifies a date string in decimal, hexadecimal, W3CDTF, or RFC 822 (1123). The return value is the time value of the date or -1 if the format is invalid. Decimal can be trailed by "s" for in seconds, "m" for in minutes, "h" for in hours, and "d" for in days.</dd>
+</dl>
+
+<p>The function `cbproctime' is used in order to get user and system processing times.</p>
+
+<dl>
+<dt><kbd>void cbproctime(double *<var>usrp</var>, double *<var>sysp</var>);</kbd></dt>
+<dd>`usrp' specifies the pointer to a variable to which the user processing time is assigned. If it is `NULL', it is not used. The unit of time is seconds. `sysp' specifies the pointer to a variable to which the system processing time is assigned. If it is `NULL', it is not used. The unit of time is seconds.</dd>
+</dl>
+
+<p>The function `cbstdiobin' is used in order to ensure that the standard I/O is binary mode.</p>
+
+<dl>
+<dt><kbd>void cbstdiobin(void);</kbd></dt>
+<dd>This function is useful for applications on dosish file systems.</dd>
+</dl>
+
+<h3>Examples</h3>
+
+<p>The following example is typical use.</p>
+
+<pre>#include &lt;cabin.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+int main(int argc, char **argv){
+ CBDATUM *datum;
+ CBLIST *list;
+ CBMAP *map;
+ char *buf1, *buf2;
+ int i;
+
+ /* open the datum handle */
+ datum = cbdatumopen("123", -1);
+ /* concatenate the data */
+ cbdatumcat(datum, "abc", -1);
+ /* print the datum */
+ printf("%s\n", cbdatumptr(datum));
+ /* close the datum handle */
+ cbdatumclose(datum);
+
+ /* open the list handle */
+ list = cblistopen();
+ /* add elements into the list */
+ cblistpush(list, "apple", -1);
+ cblistpush(list, "orange", -1);
+ /* print all elements */
+ for(i = 0; i &lt; cblistnum(list); i++){
+ printf("%s\n", cblistval(list, i, NULL));
+ }
+ /* close the list handle */
+ cblistclose(list);
+
+ /* open the map handle */
+ map = cbmapopen();
+ /* add records into the map */
+ cbmapput(map, "dog", -1, "bowwow", -1, 1);
+ cbmapput(map, "cat", -1, "meow", -1, 1);
+ /* search for values and print them */
+ printf("%s\n", cbmapget(map, "dog", -1, NULL));
+ printf("%s\n", cbmapget(map, "cat", -1, NULL));
+ /* close the map */
+ cbmapclose(map);
+
+ /* Base64 encoding */
+ buf1 = cbbaseencode("I miss you.", -1);
+ printf("%s\n", buf1);
+ /* Base64 decoding */
+ buf2 = cbbasedecode(buf1, NULL);
+ printf("%s\n", buf2);
+ /* release the resources */
+ free(buf2);
+ free(buf1);
+
+ /* register a plain pointer to the global garbage collector */
+ buf1 = cbmemdup("Take it easy.", -1);
+ cbglobalgc(buf1, free);
+ /* the pointer is available but you don't have to release it */
+ printf("%s\n", buf1);
+
+ /* register a list to the global garbage collector */
+ list = cblistopen();
+ cbglobalgc(list, (void (*)(void *))cblistclose);
+ /* the handle is available but you don't have to close it */
+ cblistpush(list, "Don't hesitate.", -1);
+ for(i = 0; i &lt; cblistnum(list); i++){
+ printf("%s\n", cblistval(list, i, NULL));
+ }
+
+ return 0;
+}
+</pre>
+
+<h3>Notes</h3>
+
+<p>How to build programs using Cabin is the same as the case of Depot.</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>Functions of Cabin except for `cbglobalgc' are thread-safe as long as a handle is not accessed by threads at the same time, on the assumption that `errno', `malloc', and so on are thread-safe.</p>
+
+<hr />
+
+<h2><a name="cabincli" id="cabincli" class="head">Commands for Cabin</a></h2>
+
+<p>Cabin has the following command line interfaces.</p>
+
+<p>The command `cbtest' is a utility for facility test and performance test. Measure the execution time of the command. This command is used in the following format. `rnum' specifies the number of records.</p>
+
+<dl>
+<dt><kbd>cbtest sort [-d] <var>rnum</var></kbd></dt>
+<dd>Perform test of sorting algorithms.</dd>
+<dt><kbd>cbtest strstr [-d] <var>rnum</var></kbd></dt>
+<dd>Perform test of string locating algorithms.</dd>
+<dt><kbd>cbtest list [-d] <var>rnum</var></kbd></dt>
+<dd>Perform writing test of list.</dd>
+<dt><kbd>cbtest map [-d] <var>rnum</var></kbd></dt>
+<dd>Perform writing test of map.</dd>
+<dt><kbd>cbtest wicked <var>rnum</var></kbd></dt>
+<dd>Perform updating operations of list and map selected at random.</dd>
+<dt><kbd>cbtest misc</kbd></dt>
+<dd>Perform test of miscellaneous routines.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-d</kbd> : read and show data of the result.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `cbcodec' is a tool to use encoding and decoding features provided by Cabin. This command is used in the following format. `file' specifies a input file. If it is omitted, the standard input is read.</p>
+
+<dl>
+<dt><kbd>cbcodec url [-d] [-br] [-rs <var>base</var> <var>target</var>] [-l] [-e <var>expr</var>] [<var>file</var>]</kbd></dt>
+<dd>Perform URL encoding and its decoding.</dd>
+<dt><kbd>cbcodec base [-d] [-l] [-c <var>num</var>] [-e <var>expr</var>] [<var>file</var>]</kbd></dt>
+<dd>Perform Base64 encoding and its decoding.</dd>
+<dt><kbd>cbcodec quote [-d] [-l] [-c <var>num</var>] [-e <var>expr</var>] [<var>file</var>]</kbd></dt>
+<dd>Perform quoted-printable encoding and its decoding.</dd>
+<dt><kbd>cbcodec mime [-d] [-hd] [-bd] [-part <var>num</var>] [-l] [-ec <var>code</var>] [-qp] [-dc] [-e <var>expr</var>] [<var>file</var>]</kbd></dt>
+<dd>Perform MIME encoding and its decoding.</dd>
+<dt><kbd>cbcodec csv [-d] [-t] [-l] [-e <var>expr</var>] [-html] [<var>file</var>]</kbd></dt>
+<dd>Process CSV. By default, escape meta characters.</dd>
+<dt><kbd>cbcodec xml [-d] [-p] [-l] [-e <var>expr</var>] [-tsv] [<var>file</var>]</kbd></dt>
+<dd>Process XML. By default, escape meta characters.</dd>
+<dt><kbd>cbcodec zlib [-d] [-gz] [-crc] [<var>file</var>]</kbd></dt>
+<dd>Perform deflation and inflation with ZLIB. It is available only if QDBM was built with ZLIB enabled.</dd>
+<dt><kbd>cbcodec lzo [-d] [<var>file</var>]</kbd></dt>
+<dd>Perform compression and decompression with LZO. It is available only if QDBM was built with LZO enabled.</dd>
+<dt><kbd>cbcodec bzip [-d] [<var>file</var>]</kbd></dt>
+<dd>Perform compression and decompression with BZIP2. It is available only if QDBM was built with BZIP2 enabled.</dd>
+<dt><kbd>cbcodec iconv [-ic <var>code</var>] [-oc <var>code</var>] [-ol <var>ltype</var>] [-cn] [-um] [-wc] [<var>file</var>]</kbd></dt>
+<dd>Convert character encoding with ICONV. It is available only if QDBM was built with ICONV enabled.</dd>
+<dt><kbd>cbcodec date [-wf] [-rf] [-utc] [<var>str</var>]</kbd></dt>
+<dd>Convert a date string specified `str'. By default, UNIX time is output. If `str' is omitted, the current date is dealt.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-d</kbd> : perform decoding (unescaping), not encoding (escaping).</li>
+<li><kbd>-br</kbd> : break up URL into elements.</li>
+<li><kbd>-rs</kbd> : resolve relative URL.</li>
+<li><kbd>-l</kbd> : output the tailing newline.</li>
+<li><kbd>-e <var>expr</var></kbd> : specify input data directly.</li>
+<li><kbd>-c <var>num</var></kbd> : limit the number of columns of the encoded data.</li>
+<li><kbd>-hd</kbd> : parse MIME and extract headers in TSV format.</li>
+<li><kbd>-bd</kbd> : parse MIME and extract the body.</li>
+<li><kbd>-part <var>num</var></kbd> : parse MIME and extract a part.</li>
+<li><kbd>-ec <var>code</var></kbd> : specify the input encoding, which is UTF-8 by default.</li>
+<li><kbd>-qp</kbd> : use quoted-printable encoding, which is Base64 by default.</li>
+<li><kbd>-dc</kbd> : output the encoding name instead of the result string when decoding.</li>
+<li><kbd>-t</kbd> : parse CSV. Convert the data into TSV. Tab and new-line in a cell are deleted.</li>
+<li><kbd>-html</kbd> : parse CSV. Convert the data into HTML.</li>
+<li><kbd>-p</kbd> : parse XML. Show tags and text sections with dividing headers.</li>
+<li><kbd>-tsv</kbd> : parse XML. Show the result in TSV format. Characters of tabs and new-lines are URL-encoded.</li>
+<li><kbd>-gz</kbd> : use GZIP format.</li>
+<li><kbd>-crc</kbd> : output the CRC32 checksum as hexadecimal and big endian.</li>
+<li><kbd>-ic <var>code</var></kbd> : specify the input encoding, which is detected automatically by default.</li>
+<li><kbd>-oc <var>code</var></kbd> : specify the output encoding, which is UTF-8 by default.</li>
+<li><kbd>-ol <var>ltype</var></kbd> : convert line feed characters, with `unix'(LF), `dos'(CRLF), and `mac'(CR).</li>
+<li><kbd>-cn</kbd> : detect the input encoding and show its name.</li>
+<li><kbd>-wc</kbd> : count the number of characters of the input string of UTF-8.</li>
+<li><kbd>-um</kbd> : output mappings of UCS-2 characters and C strings of UTF-16BE and UTF-8.</li>
+<li><kbd>-wf</kbd> : output in W3CDTF format.</li>
+<li><kbd>-rf</kbd> : output in RFC 1123 format.</li>
+<li><kbd>-utc</kbd> : output the coordinate universal time.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<hr />
+
+<h2><a name="villaapi" id="villaapi" class="head">Villa: Advanced API</a></h2>
+
+<h3>Overview</h3>
+
+<p>Villa is the advanced API of QDBM. It provides routines for managing a database file of B+ tree. Each record is stored being sorted in order defined by a user. As for hash databases, retrieving method is provided only as complete accord. However, with Villa, it is possible to retrieve records specified by range. Cursor is used in order to access each record in order. It is possible to store records duplicating keys in a database. Moreover, according to the transaction mechanism, you can commit or abort operations of a database in a lump.</p>
+
+<p>Villa is implemented, based on Depot and Cabin. A database file of Villa is actual one of Depot. Although processing speed of retrieving and storing is slower than Depot, the size of a database is smaller.</p>
+
+<p>In order to use Villa, you should include `depot.h', `cabin.h', `villa.h' and `stdlib.h' in the source files. Usually, the following description will be near the beginning of a source file.</p>
+
+<dl>
+<dt><kbd>#include &lt;depot.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;cabin.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;villa.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+</dl>
+
+<p>A pointer to `VILLA' is used as a database handle. It is like that some file I/O routines of `stdio.h' use a pointer to `FILE'. A database handle is opened with the function `vlopen' and closed with `vlclose'. You should not refer directly to any member of the handle. If a fatal error occurs in a database, any access method via the handle except `vlclose' will not work and return error status. Although a process is allowed to use multiple database handles at the same time, handles of the same database file should not be used. Before the cursor is used, it should be initialized by one of `vlcurfirst', `vlcurlast' or `vlcurjump'. Also after storing or deleting a record with functions except for `vlcurput' and `vlcurout', the cursor should be initialized.</p>
+
+<p>Villa also assign the external variable `dpecode' with the error code. The function `dperrmsg' is used in order to get the message of the error code.</p>
+
+<h3>API</h3>
+
+<p>You can define a comparing function to specify the order of records. The function should be the following type.</p>
+
+<dl>
+<dt><kbd>typedef int(*VLCFUNC)(const char *<var>aptr</var>, int <var>asiz</var>, const char *<var>bptr</var>, int <var>bsiz</var>);</kbd></dt>
+<dd>`aptr' specifies the pointer to the region of one key. `asiz' specifies the size of the region of one key. `bptr' specifies the pointer to the region of the other key. `bsiz' specifies the size of the region of the other key. The return value is positive if the former is big, negative if the latter is big, 0 if both are equivalent.</dd>
+</dl>
+
+<p>The function `vlopen' is used in order to get a database handle.</p>
+
+<dl>
+<dt><kbd>VILLA *vlopen(const char *<var>name</var>, int <var>omode</var>, VLCFUNC <var>cmp</var>);</kbd></dt>
+<dd>`name' specifies the name of a database file. `omode' specifies the connection mode: `VL_OWRITER' as a writer, `VL_OREADER' as a reader. If the mode is `VL_OWRITER', the following may be added by bitwise or: `VL_OCREAT', which means it creates a new database if not exist, `VL_OTRUNC', which means it creates a new database regardless if one exists, `VL_OZCOMP', which means leaves in the database are compressed with ZLIB, `VL_OYCOMP', which means leaves in the database are compressed with LZO, `VL_OXCOMP', which means leaves in the database are compressed with BZIP2. Both of `VL_OREADER' and `VL_OWRITER' can be added to by bitwise or: `VL_ONOLCK', which means it opens a database file without file locking, or `VL_OLCKNB', which means locking is performed without blocking. `cmp' specifies the comparing function: `VL_CMPLEX' comparing keys in lexical order, `VL_CMPINT' comparing keys as objects of `int' in native byte order, `VL_CMPNUM' comparing keys as numbers of big endian, `VL_CMPDEC' comparing keys as decimal strings. Any function based on the declaration of the type `VLCFUNC' can be assigned to the comparing function. The comparing function should be kept same in the life of a database. The return value is the database handle or `NULL' if it is not successful. While connecting as a writer, an exclusive lock is invoked to the database file. While connecting as a reader, a shared lock is invoked to the database file. The thread blocks until the lock is achieved. `VL_OZCOMP', `VL_OYCOMP', and `VL_OXCOMP' are available only if QDBM was built each with ZLIB, LZO, and BZIP2 enabled. If `VL_ONOLCK' is used, the application is responsible for exclusion control.</dd>
+</dl>
+
+<p>The function `vlclose' is used in order to close a database handle.</p>
+
+<dl>
+<dt><kbd>int vlclose(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. If successful, the return value is true, else, it is false. Because the region of a closed handle is released, it becomes impossible to use the handle. Updating a database is assured to be written when the handle is closed. If a writer opens a database but does not close it appropriately, the database will be broken. If the transaction is activated and not committed, it is aborted.</dd>
+</dl>
+
+<p>The function `vlput' is used in order to store a record.</p>
+
+<dl>
+<dt><kbd>int vlput(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const char *<var>vbuf</var>, int <var>vsiz</var>, int <var>dmode</var>);</kbd></dt>
+<dd>`villa' specifies a database handle connected as a writer. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `vbuf' specifies the pointer to the region of a value. `vsiz' specifies the size of the region of the value. If it is negative, the size is assigned with `strlen(vbuf)'. `dmode' specifies behavior when the key overlaps, by the following values: `VL_DOVER', which means the specified value overwrites the existing one, `VL_DKEEP', which means the existing value is kept, `VL_DCAT', which means the specified value is concatenated at the end of the existing value, `VL_DDUP', which means duplication of keys is allowed and the specified value is added as the last one, `VL_DDUPR', which means duplication of keys is allowed and the specified value is added as the first one. If successful, the return value is true, else, it is false. The cursor becomes unavailable due to updating database.</dd>
+</dl>
+
+<p>The function `vlout' is used in order to delete a record.</p>
+
+<dl>
+<dt><kbd>int vlout(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`villa' specifies a database handle connected as a writer. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. If successful, the return value is true, else, it is false. False is returned when no record corresponds to the specified key. When the key of duplicated records is specified, the first record of the same key is deleted. The cursor becomes unavailable due to updating database.</dd>
+</dl>
+
+<p>The function `vlget' is used in order to retrieve a record.</p>
+
+<dl>
+<dt><kbd>char *vlget(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the value of the corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to the specified key. When the key of duplicated records is specified, the value of the first record of the same key is selected. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `vlvsiz' is used in order to get the size of the value of a record.</p>
+
+<dl>
+<dt><kbd>int vlvsiz(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. If successful, the return value is the size of the value of the corresponding record, else, it is -1. If multiple records correspond, the size of the first is returned.</dd>
+</dl>
+
+<p>The function `vlvnum' is used in order to get the number of records corresponding a key.</p>
+
+<dl>
+<dt><kbd>int vlvnum(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. The return value is the number of corresponding records. If no record corresponds, 0 is returned.</dd>
+</dl>
+
+<p>The function `vlputlist' is used in order to store plural records corresponding a key.</p>
+
+<dl>
+<dt><kbd>int vlputlist(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, const CBLIST *<var>vals</var>);</kbd></dt>
+<dd>`villa' specifies a database handle connected as a writer. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `vals' specifies a list handle of values. The list should not be empty. If successful, the return value is true, else, it is false. The cursor becomes unavailable due to updating database.</dd>
+</dl>
+
+<p>The function `vloutlist' is used in order to delete all records corresponding a key.</p>
+
+<dl>
+<dt><kbd>int vloutlist(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`villa' specifies a database handle connected as a writer. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. If successful, the return value is true, else, it is false. False is returned when no record corresponds to the specified key. The cursor becomes unavailable due to updating database.</dd>
+</dl>
+
+<p>The function `vlgetlist' is used in order to retrieve values of all records corresponding a key.</p>
+
+<dl>
+<dt><kbd>CBLIST *vlgetlist(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. If successful, the return value is a list handle of the values of the corresponding records, else, it is `NULL'. `NULL' is returned when no record corresponds to the specified key. Because the handle of the return value is opened with the function `cblistopen', it should be closed with the function `cblistclose' if it is no longer in use.</dd>
+</dl>
+
+<p>The function `vlgetcat' is used in order to retrieve concatenated values of all records corresponding a key.</p>
+
+<dl>
+<dt><kbd>char *vlgetcat(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the concatenated values of the corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to the specified key. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `vlcurfirst' is used in order to move the cursor to the first record.</p>
+
+<dl>
+<dt><kbd>int vlcurfirst(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. If successful, the return value is true, else, it is false. False is returned if there is no record in the database.</dd>
+</dl>
+
+<p>The function `vlcurlast' is used in order to move the cursor to the last record.</p>
+
+<dl>
+<dt><kbd>int vlcurlast(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. If successful, the return value is true, else, it is false. False is returned if there is no record in the database.</dd>
+</dl>
+
+<p>The function `vlcurprev' is used in order to move the cursor to the previous record.</p>
+
+<dl>
+<dt><kbd>int vlcurprev(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. If successful, the return value is true, else, it is false. False is returned if there is no previous record.</dd>
+</dl>
+
+<p>The function `vlcurnext' is used in order to move the cursor to the next record.</p>
+
+<dl>
+<dt><kbd>int vlcurnext(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. If successful, the return value is true, else, it is false. False is returned if there is no next record.</dd>
+</dl>
+
+<p>The function `vlcurjump' is used in order to move the cursor to a position around a record.</p>
+
+<dl>
+<dt><kbd>int vlcurjump(VILLA *<var>villa</var>, const char *<var>kbuf</var>, int <var>ksiz</var>, int <var>jmode</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. `kbuf' specifies the pointer to the region of a key. `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned with `strlen(kbuf)'. `jmode' specifies detail adjustment: `VL_JFORWARD', which means that the cursor is set to the first record of the same key and that the cursor is set to the next substitute if completely matching record does not exist, `VL_JBACKWARD', which means that the cursor is set to the last record of the same key and that the cursor is set to the previous substitute if completely matching record does not exist. If successful, the return value is true, else, it is false. False is returned if there is no record corresponding the condition.</dd>
+</dl>
+
+<p>The function `vlcurkey' is used in order to get the key of the record where the cursor is.</p>
+
+<dl>
+<dt><kbd>char *vlcurkey(VILLA *<var>villa</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the key of the corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to the cursor. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `vlcurval' is used in order to get the value of the record where the cursor is.</p>
+
+<dl>
+<dt><kbd>char *vlcurval(VILLA *<var>villa</var>, int *<var>sp</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. `sp' specifies the pointer to a variable to which the size of the region of the return value is assigned. If it is `NULL', it is not used. If successful, the return value is the pointer to the region of the value of the corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to the cursor. Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `vlcurput' is used in order to insert a record around the cursor.</p>
+
+<dl>
+<dt><kbd>int vlcurput(VILLA *<var>villa</var>, const char *<var>vbuf</var>, int <var>vsiz</var>, int <var>cpmode</var>);</kbd></dt>
+<dd>`villa' specifies a database handle connected as a writer. `vbuf' specifies the pointer to the region of a value. `vsiz' specifies the size of the region of the value. If it is negative, the size is assigned with `strlen(vbuf)'. `cpmode' specifies detail adjustment: `VL_CPCURRENT', which means that the value of the current record is overwritten, `VL_CPBEFORE', which means that a new record is inserted before the current record, `VL_CPAFTER', which means that a new record is inserted after the current record. If successful, the return value is true, else, it is false. False is returned when no record corresponds to the cursor. After insertion, the cursor is moved to the inserted record.</dd>
+</dl>
+
+<p>The function `vlcurout' is used in order to delete the record where the cursor is.</p>
+
+<dl>
+<dt><kbd>int vlcurout(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle connected as a writer. If successful, the return value is true, else, it is false. False is returned when no record corresponds to the cursor. After deletion, the cursor is moved to the next record if possible.</dd>
+</dl>
+
+<p>The function `vlsettuning' is used in order to set the tuning parameters for performance.</p>
+
+<dl>
+<dt><kbd>void vlsettuning(VILLA *<var>villa</var>, int <var>lrecmax</var>, int <var>nidxmax</var>, int <var>lcnum</var>, int <var>ncnum</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. `lrecmax' specifies the max number of records in a leaf node of B+ tree. If it is not more than 0, the default value is specified. `nidxmax' specifies the max number of indexes in a non-leaf node of B+ tree. If it is not more than 0, the default value is specified. `lcnum' specifies the max number of caching leaf nodes. If it is not more than 0, the default value is specified. `ncnum' specifies the max number of caching non-leaf nodes. If it is not more than 0, the default value is specified. The default setting is equivalent to `vlsettuning(49, 192, 1024, 512)'. Because tuning parameters are not saved in a database, you should specify them every opening a database.</dd>
+</dl>
+
+<p>The function `vlsetfbpsiz' is used in order to set the size of the free block pool of a database handle.</p>
+
+<dl>
+<dt><kbd>int vlsetfbpsiz(VILLA *<var>villa</var>, int <var>size</var>);</kbd></dt>
+<dd>`villa' specifies a database handle connected as a writer. `size' specifies the size of the free block pool of a database. If successful, the return value is true, else, it is false. The default size of the free block pool is 256. If the size is greater, the space efficiency of overwriting values is improved with the time efficiency sacrificed.</dd>
+</dl>
+
+<p>The function `vlsync' is used in order to synchronize updating contents with the file and the device.</p>
+
+<dl>
+<dt><kbd>int vlsync(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle connected as a writer. If successful, the return value is true, else, it is false. This function is useful when another process uses the connected database file. This function should not be used while the transaction is activated.</dd>
+</dl>
+
+<p>The function `vloptimize' is used in order to optimize a database.</p>
+
+<dl>
+<dt><kbd>int vloptimize(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle connected as a writer. If successful, the return value is true, else, it is false. In an alternating succession of deleting and storing with overwrite or concatenate, dispensable regions accumulate. This function is useful to do away with them. This function should not be used while the transaction is activated.</dd>
+</dl>
+
+<p>The function `vlname' is used in order to get the name of a database.</p>
+
+<dl>
+<dt><kbd>char *vlname(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. If successful, the return value is the pointer to the region of the name of the database, else, it is `NULL'. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `vlfsiz' is used in order to get the size of a database file.</p>
+
+<dl>
+<dt><kbd>int vlfsiz(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. If successful, the return value is the size of the database file, else, it is -1. Because of the I/O buffer, the return value may be less than the hard size.</dd>
+</dl>
+
+<p>The function `vllnum' is used in order to get the number of the leaf nodes of B+ tree.</p>
+
+<dl>
+<dt><kbd>int vllnum(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. If successful, the return value is the number of the leaf nodes, else, it is -1.</dd>
+</dl>
+
+<p>The function `vlnnum' is used in order to get the number of the non-leaf nodes of B+ tree.</p>
+
+<dl>
+<dt><kbd>int vlnnum(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. If successful, the return value is the number of the non-leaf nodes, else, it is -1.</dd>
+</dl>
+
+<p>The function `vlrnum' is used in order to get the number of the records stored in a database.</p>
+
+<dl>
+<dt><kbd>int vlrnum(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. If successful, the return value is the number of the records stored in the database, else, it is -1.</dd>
+</dl>
+
+<p>The function `vlwritable' is used in order to check whether a database handle is a writer or not.</p>
+
+<dl>
+<dt><kbd>int vlwritable(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. The return value is true if the handle is a writer, false if not.</dd>
+</dl>
+
+<p>The function `vlfatalerror' is used in order to check whether a database has a fatal error or not.</p>
+
+<dl>
+<dt><kbd>int vlfatalerror(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. The return value is true if the database has a fatal error, false if not.</dd>
+</dl>
+
+<p>The function `vlinode' is used in order to get the inode number of a database file.</p>
+
+<dl>
+<dt><kbd>int vlinode(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. The return value is the inode number of the database file.</dd>
+</dl>
+
+<p>The function `vlmtime' is used in order to get the last modified time of a database.</p>
+
+<dl>
+<dt><kbd>time_t vlmtime(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. The return value is the last modified time of the database.</dd>
+</dl>
+
+<p>The function `vltranbegin' is used in order to begin the transaction.</p>
+
+<dl>
+<dt><kbd>int vltranbegin(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle connected as a writer. If successful, the return value is true, else, it is false. Because this function does not perform mutual exclusion control in multi-thread, the application is responsible for it. Only one transaction can be activated with a database handle at the same time.</dd>
+</dl>
+
+<p>The function `vltrancommit' is used in order to commit the transaction.</p>
+
+<dl>
+<dt><kbd>int vltrancommit(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle connected as a writer. If successful, the return value is true, else, it is false. Updating a database in the transaction is fixed when it is committed successfully.</dd>
+</dl>
+
+<p>The function `vltranabort' is used in order to abort the transaction.</p>
+
+<dl>
+<dt><kbd>int vltranabort(VILLA *<var>villa</var>);</kbd></dt>
+<dd>`villa' specifies a database handle connected as a writer. If successful, the return value is true, else, it is false. Updating a database in the transaction is discarded when it is aborted. The state of the database is rollbacked to before transaction.</dd>
+</dl>
+
+<p>The function `vlremove' is used in order to remove a database file.</p>
+
+<dl>
+<dt><kbd>int vlremove(const char *<var>name</var>);</kbd></dt>
+<dd>`name' specifies the name of a database file. If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `vlrepair' is used in order to repair a broken database file.</p>
+
+<dl>
+<dt><kbd>int vlrepair(const char *<var>name</var>, VLCFUNC <var>cmp</var>);</kbd></dt>
+<dd>`name' specifies the name of a database file. `cmp' specifies the comparing function of the database file. If successful, the return value is true, else, it is false. There is no guarantee that all records in a repaired database file correspond to the original or expected state.</dd>
+</dl>
+
+<p>The function `vlexportdb' is used in order to dump all records as endian independent data.</p>
+
+<dl>
+<dt><kbd>int vlexportdb(VILLA *<var>villa</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`villa' specifies a database handle. `name' specifies the name of an output file. If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `vlimportdb' is used in order to load all records from endian independent data.</p>
+
+<dl>
+<dt><kbd>int vlimportdb(VILLA *<var>villa</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`villa' specifies a database handle connected as a writer. The database of the handle must be empty. `name' specifies the name of an input file. If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<h3>Examples</h3>
+
+<p>The following example stores and retrieves a phone number, using the name as the key.</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;cabin.h&gt;
+#include &lt;villa.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+#define NAME "mikio"
+#define NUMBER "000-1234-5678"
+#define DBNAME "book"
+
+int main(int argc, char **argv){
+ VILLA *villa;
+ char *val;
+
+ /* open the database */
+ if(!(villa = vlopen(DBNAME, VL_OWRITER | VL_OCREAT, VL_CMPLEX))){
+ fprintf(stderr, "vlopen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* store the record */
+ if(!vlput(villa, NAME, -1, NUMBER, -1, VL_DOVER)){
+ fprintf(stderr, "vlput: %s\n", dperrmsg(dpecode));
+ }
+
+ /* retrieve the record */
+ if(!(val = vlget(villa, NAME, -1, NULL))){
+ fprintf(stderr, "vlget: %s\n", dperrmsg(dpecode));
+ } else {
+ printf("Name: %s\n", NAME);
+ printf("Number: %s\n", val);
+ free(val);
+ }
+
+ /* close the database */
+ if(!vlclose(villa)){
+ fprintf(stderr, "vlclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<p>The following example performs forward matching search for strings.</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;cabin.h&gt;
+#include &lt;villa.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+#include &lt;string.h&gt;
+
+#define DBNAME "words"
+#define PREFIX "apple"
+
+int main(int argc, char **argv){
+ VILLA *villa;
+ char *key, *val;
+
+ /* open the database */
+ if(!(villa = vlopen(DBNAME, VL_OWRITER | VL_OCREAT, VL_CMPLEX))){
+ fprintf(stderr, "vlopen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* store records */
+ if(!vlput(villa, "applet", -1, "little application", -1, VL_DDUP) ||
+ !vlput(villa, "aurora", -1, "polar wonderwork", -1, VL_DDUP) ||
+ !vlput(villa, "apple", -1, "delicious fruit", -1, VL_DDUP) ||
+ !vlput(villa, "amigo", -1, "good friend", -1, VL_DDUP) ||
+ !vlput(villa, "apple", -1, "big city", -1, VL_DDUP)){
+ fprintf(stderr, "vlput: %s\n", dperrmsg(dpecode));
+ }
+
+ /* set the cursor at the top of candidates */
+ vlcurjump(villa, PREFIX, -1, VL_JFORWARD);
+
+ /* scan with the cursor */
+ while((key = vlcurkey(villa, NULL)) != NULL){
+ if(strstr(key, PREFIX) != key){
+ free(key);
+ break;
+ }
+ if(!(val = vlcurval(villa, NULL))){
+ fprintf(stderr, "vlcurval: %s\n", dperrmsg(dpecode));
+ free(key);
+ break;
+ }
+ printf("%s: %s\n", key, val);
+ free(val);
+ free(key);
+ vlcurnext(villa);
+ }
+
+ /* close the database */
+ if(!vlclose(villa)){
+ fprintf(stderr, "vlclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<h3>Notes</h3>
+
+<p>How to build programs using Villa is the same as the case of Depot.</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>If QDBM was built with POSIX thread enabled, the global variable `dpecode' is treated as thread specific data, and functions of Villa are reentrant. In that case, they are thread-safe as long as a handle is not accessed by threads at the same time, on the assumption that `errno', `malloc', and so on are thread-safe.</p>
+
+<h3>Vista: Extended Advanced API</h3>
+
+<p>Vista is the extended API of Villa. To compensate for the defect that Villa can not handle a file whose size is more than 2GB, Vista does not use Depot but Curia for handling its internal database. While Vista provides data structure and operations of B+ tree as with Villa, its database is realized as a directory.</p>
+
+<p>In order to use Vista, you should include `vista.h' instead of `villa.h'. Because Vista is implemented by overriding symbols of Villa, it can be used as with Villa. That is, Signatures of Villa and Vista is all the same. However, as its adverse effect, modules (compilation unit) using Vista can not use Villa (do not include `villa.h').</p>
+
+<hr />
+
+<h2><a name="villacli" id="villacli" class="head">Commands for Villa</a></h2>
+
+<p>Villa has the following command line interfaces.</p>
+
+<p>The command `vlmgr' is a utility for debugging Villa and its applications. It features editing and checking of a database. It can be used for database applications with shell scripts. This command is used in the following format. `name' specifies a database name. `key' specifies the key of a record. `val' specifies the value of a record.</p>
+
+<dl>
+<dt><kbd>vlmgr create [-cz|-cy|-cx] <var>name</var></kbd></dt>
+<dd>Create a database file.</dd>
+<dt><kbd>vlmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat|-dup] <var>name</var> <var>key</var> <var>val</var></kbd></dt>
+<dd>Store a record with a key and a value.</dd>
+<dt><kbd>vlmgr out [-l] [-kx|-ki] <var>name</var> <var>key</var></kbd></dt>
+<dd>Delete a record with a key.</dd>
+<dt><kbd>vlmgr get [-nl] [-l] [-kx|-ki] [-ox] [-n] <var>name</var> <var>key</var></kbd></dt>
+<dd>Retrieve a record with a key and output it to the standard output.</dd>
+<dt><kbd>vlmgr list [-nl] [-k|-v] [-kx|-ki] [-ox] [-top <var>key</var>] [-bot <var>key</var>] [-gt] [-lt] [-max <var>num</var>] [-desc] <var>name</var></kbd></dt>
+<dd>List all keys and values delimited with tab and line-feed to the standard output.</dd>
+<dt><kbd>vlmgr optimize <var>name</var></kbd></dt>
+<dd>Optimize a database.</dd>
+<dt><kbd>vlmgr inform [-nl] <var>name</var></kbd></dt>
+<dd>Output miscellaneous information to the standard output.</dd>
+<dt><kbd>vlmgr remove <var>name</var></kbd></dt>
+<dd>Remove a database file.</dd>
+<dt><kbd>vlmgr repair [-ki] <var>name</var></kbd></dt>
+<dd>Repair a broken database file.</dd>
+<dt><kbd>vlmgr exportdb [-ki] <var>name</var> <var>file</var></kbd></dt>
+<dd>Dump all records as endian independent data.</dd>
+<dt><kbd>vlmgr importdb [-ki] <var>name</var> <var>file</var></kbd></dt>
+<dd>Load all records from endian independent data.</dd>
+<dt><kbd>vlmgr version</kbd></dt>
+<dd>Output version information of QDBM to the standard output.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-cz</kbd> : compress leaves in the database with ZLIB.</li>
+<li><kbd>-cy</kbd> : compress leaves in the database with LZO.</li>
+<li><kbd>-cx</kbd> : compress leaves in the database with BZIP2.</li>
+<li><kbd>-l</kbd> : all records corresponding the key are dealt.</li>
+<li><kbd>-kx</kbd> : treat `key' as a binary expression of hexadecimal notation.</li>
+<li><kbd>-ki</kbd> : treat `key' as an integer expression of decimal notation.</li>
+<li><kbd>-vx</kbd> : treat `val' as a binary expression of hexadecimal notation.</li>
+<li><kbd>-vi</kbd> : treat `val' as an integer expression of decimal notation.</li>
+<li><kbd>-vf</kbd> : read the value from a file specified with `val'.</li>
+<li><kbd>-keep</kbd> : specify the storing mode for `VL_DKEEP'.</li>
+<li><kbd>-cat</kbd> : specify the storing mode for `VL_DCAT'.</li>
+<li><kbd>-dup</kbd> : specify the storing mode for `VL_DDUP'.</li>
+<li><kbd>-nl</kbd> : open the database without file locking.</li>
+<li><kbd>-top <var>key</var></kbd> : specify the top key of listing.</li>
+<li><kbd>-bot <var>key</var></kbd> : specify the bottom key of listing.</li>
+<li><kbd>-gt</kbd> : do not include the top key of listing.</li>
+<li><kbd>-lt</kbd> : do not include the bottom key of listing.</li>
+<li><kbd>-max <var>num</var></kbd> : specify the max number of listing.</li>
+<li><kbd>-desc</kbd> : list in descending order.</li>
+<li><kbd>-ox</kbd> : treat the output as a binary expression of hexadecimal notation.</li>
+<li><kbd>-n</kbd> : do not output the tailing newline.</li>
+<li><kbd>-k</kbd> : output keys only.</li>
+<li><kbd>-v</kbd> : output values only.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure. The environment variable `QDBMDBGFD' specifies the file descriptor to output the history of updating the variable `dpecode'.</p>
+
+<p>The command `vltest' is a utility for facility test and performance test. Check a database generated by the command or measure the execution time of the command. This command is used in the following format. `name' specifies a database name. `rnum' specifies the number of the records.</p>
+
+<dl>
+<dt><kbd>vltest write [-int] [-cz|-cy|-cx] [-tune <var>lrecmax</var> <var>nidxmax</var> <var>lcnum</var> <var>ncnum</var>] [-fbp <var>num</var>] <var>name</var> <var>rnum</var></kbd></dt>
+<dd>Store records with keys of 8 bytes. They change as `00000001', `00000002'...</dd>
+<dt><kbd>vltest read [-int] [-vc] <var>name</var></kbd></dt>
+<dd>Retrieve all records of the database above.</dd>
+<dt><kbd>vltest rdup [-int] [-cz|-cy|-cx] [-cc] [-tune <var>lrecmax</var> <var>nidxmax</var> <var>lcnum</var> <var>ncnum</var>] [-fbp <var>num</var>] <var>name</var> <var>rnum</var> <var>pnum</var></kbd></dt>
+<dd>Store records with partway duplicated keys using duplicate mode.</dd>
+<dt><kbd>vltest combo [-cz|-cy|-cx] <var>name</var></kbd></dt>
+<dd>Perform combination test of various operations.</dd>
+<dt><kbd>vltest wicked [-cz|-cy|-cx] <var>name</var> <var>rnum</var></kbd></dt>
+<dd>Perform updating operations selected at random.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-int</kbd> : treat keys and values as objects of `int', and use comparing function `VL_CMPINT'.</li>
+<li><kbd>-cz</kbd> : compress leaves in the database with ZLIB.</li>
+<li><kbd>-cy</kbd> : compress leaves in the database with LZO.</li>
+<li><kbd>-cx</kbd> : compress leaves in the database with BZIP2.</li>
+<li><kbd>-vc</kbd> : refer to volatile cache.</li>
+<li><kbd>-cc</kbd> : select `VL_DCAT' or `VL_DDUP' at random.</li>
+<li><kbd>-tune <var>lrecmax</var> <var>nidxmax</var> <var>lcnum</var> <var>ncnum</var></kbd> : set tuning parameters.</li>
+<li><kbd>-fbp <var>num</var></kbd> : set the size of the free block pool.</li>
+<li><kbd>-c</kbd> : perform comparison test with map of Cabin.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure. The environment variable `QDBMDBGFD' specifies the file descriptor to output the history of updating the variable `dpecode'.</p>
+
+<p>The command `vltsv' features mutual conversion between a database of Villa and a TSV text. This command is useful when data exchange with another version of QDBM or another DBM, or when data exchange between systems which have different byte orders. This command is used in the following format. `name' specifies a database name. The subcommand `export' reads TSV data from the standard input. The subcommand `import' writes TSV data to the standard output.</p>
+
+<dl>
+<dt><kbd>vltsv import [-bin] <var>name</var></kbd></dt>
+<dd>Create a database from TSV.</dd>
+<dt><kbd>vltsv export [-bin] <var>name</var></kbd></dt>
+<dd>Write TSV data of a database.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-bin</kbd> : treat records as Base64 format.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>Commands of Villa realize a simple database system. For example, to make a database to search `/etc/password' by a user name, perform the following command.</p>
+
+<pre>cat /etc/passwd | tr ':' '\t' | vltsv import casket
+</pre>
+
+<p>Thus, to retrieve the information of a user `mikio', perform the following command.</p>
+
+<pre>vlmgr get casket mikio
+</pre>
+
+<p>It is easy to implement functions upsides with these commands, using the API of Villa.</p>
+
+<p>The command `qmttest' checks multi-thread safety of Depot, Curia, and Villa. This command works with multi threads only if QDBM was built with POSIX thread. This command is used in the following format. `name' specifies the prefix of each database. `rnum' specifies the number of records to be stored in each database. `tnum' specifies the number of threads.</p>
+
+<dl>
+<dt><kbd>qmttest <var>name</var> <var>rnum</var> <var>tnum</var></kbd></dt>
+<dd>Check multi-thread safety.</dd>
+</dl>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<hr />
+
+<h2><a name="odeumapi" id="odeumapi" class="head">Odeum: Inverted API</a></h2>
+
+<h3>Overview</h3>
+
+<p>Odeum is the API which handles an inverted index. An inverted index is a data structure to retrieve a list of some documents that include one of words which were extracted from a population of documents. It is easy to realize a full-text search system with an inverted index. Odeum provides an abstract data structure which consists of words and attributes of a document. It is used when an application stores a document into a database and when an application retrieves some documents from a database.</p>
+
+<p>Odeum does not provide methods to extract the text from the original data of a document. It should be implemented by applications. Although Odeum provides utilities to extract words from a text, it is oriented to such languages whose words are separated with space characters as English. If an application handles such languages which need morphological analysis or N-gram analysis as Japanese, or if an application perform more such rarefied analysis of natural languages as stemming, its own analyzing method can be adopted. Result of search is expressed as an array contains elements which are structures composed of the ID number of documents and its score. In order to search with two or more words, Odeum provides utilities of set operations.</p>
+
+<p>Odeum is implemented, based on Curia, Cabin, and Villa. Odeum creates a database with a directory name. Some databases of Curia and Villa are placed in the specified directory. For example, `casket/docs', `casket/index', and `casket/rdocs' are created in the case that a database directory named as `casket'. `docs' is a database directory of Curia. The key of each record is the ID number of a document, and the value is such attributes as URI. `index' is a database directory of Curia. The key of each record is the normalized form of a word, and the value is an array whose element is a pair of the ID number of a document including the word and its score. `rdocs' is a database file of Villa. The key of each record is the URI of a document, and the value is its ID number.</p>
+
+<p>In order to use Odeum, you should include `depot.h', `cabin.h', `odeum.h' and `stdlib.h' in the source files. Usually, the following description will be near the beginning of a source file.</p>
+
+<dl>
+<dt><kbd>#include &lt;depot.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;cabin.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;odeum.h&gt;</kbd></dt>
+<dt><kbd>#include &lt;stdlib.h&gt;</kbd></dt>
+</dl>
+
+<p>A pointer to `ODEUM' is used as a database handle. A database handle is opened with the function `odopen' and closed with `odclose'. You should not refer directly to any member of the handle. If a fatal error occurs in a database, any access method via the handle except `odclose' will not work and return error status. Although a process is allowed to use multiple database handles at the same time, handles of the same database file should not be used.</p>
+
+<p>A pointer to `ODDOC' is used as a document handle. A document handle is opened with the function `oddocopen' and closed with `oddocclose'. You should not refer directly to any member of the handle. A document consists of attributes and words. Each word is expressed as a pair of a normalized form and a appearance form.</p>
+
+<p>Odeum also assign the external variable `dpecode' with the error code. The function `dperrmsg' is used in order to get the message of the error code.</p>
+
+<h3>API</h3>
+
+<p>Structures of `ODPAIR' type is used in order to handle results of search.</p>
+
+<dl>
+<dt><kbd>typedef struct { int id; int score; } ODPAIR;</kbd></dt>
+<dd>`id' specifies the ID number of a document. `score' specifies the score calculated from the number of searching words in the document.</dd>
+</dl>
+
+<p>The function `odopen' is used in order to get a database handle.</p>
+
+<dl>
+<dt><kbd>ODEUM *odopen(const char *<var>name</var>, int <var>omode</var>);</kbd></dt>
+<dd>`name' specifies the name of a database directory. `omode' specifies the connection mode: `OD_OWRITER' as a writer, `OD_OREADER' as a reader. If the mode is `OD_OWRITER', the following may be added by bitwise or: `OD_OCREAT', which means it creates a new database if not exist, `OD_OTRUNC', which means it creates a new database regardless if one exists. Both of `OD_OREADER' and `OD_OWRITER' can be added to by bitwise or: `OD_ONOLCK', which means it opens a database directory without file locking, or `OD_OLCKNB', which means locking is performed without blocking. The return value is the database handle or `NULL' if it is not successful. While connecting as a writer, an exclusive lock is invoked to the database directory. While connecting as a reader, a shared lock is invoked to the database directory. The thread blocks until the lock is achieved. If `OD_ONOLCK' is used, the application is responsible for exclusion control.</dd>
+</dl>
+
+<p>The function `odclose' is used in order to close a database handle.</p>
+
+<dl>
+<dt><kbd>int odclose(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. If successful, the return value is true, else, it is false. Because the region of a closed handle is released, it becomes impossible to use the handle. Updating a database is assured to be written when the handle is closed. If a writer opens a database but does not close it appropriately, the database will be broken.</dd>
+</dl>
+
+<p>The function `odput' is used in order to store a document.</p>
+
+<dl>
+<dt><kbd>int odput(ODEUM *<var>odeum</var>, const ODDOC *<var>doc</var>, int <var>wmax</var>, int <var>over</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle connected as a writer. `doc' specifies a document handle. `wmax' specifies the max number of words to be stored in the document database. If it is negative, the number is unlimited. `over' specifies whether the data of the duplicated document is overwritten or not. If it is false and the URI of the document is duplicated, the function returns as an error. If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `odout' is used in order to delete a document specified by a URI.</p>
+
+<dl>
+<dt><kbd>int odout(ODEUM *<var>odeum</var>, const char *<var>uri</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle connected as a writer. `uri' specifies the string of the URI of a document. If successful, the return value is true, else, it is false. False is returned when no document corresponds to the specified URI.</dd>
+</dl>
+
+<p>The function `odoutbyid' is used in order to delete a document specified by an ID number.</p>
+
+<dl>
+<dt><kbd>int odoutbyid(ODEUM *<var>odeum</var>, int <var>id</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle connected as a writer. `id' specifies the ID number of a document. If successful, the return value is true, else, it is false. False is returned when no document corresponds to the specified ID number.</dd>
+</dl>
+
+<p>The function `odget' is used in order to retrieve a document specified by a URI.</p>
+
+<dl>
+<dt><kbd>ODDOC *odget(ODEUM *<var>odeum</var>, const char *<var>uri</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. `uri' specifies the string of the URI of a document. If successful, the return value is the handle of the corresponding document, else, it is `NULL'. `NULL' is returned when no document corresponds to the specified URI. Because the handle of the return value is opened with the function `oddocopen', it should be closed with the function `oddocclose'.</dd>
+</dl>
+
+<p>The function `odgetbyid' is used in order to retrieve a document by an ID number.</p>
+
+<dl>
+<dt><kbd>ODDOC *odgetbyid(ODEUM *<var>odeum</var>, int <var>id</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. `id' specifies the ID number of a document. If successful, the return value is the handle of the corresponding document, else, it is `NULL'. `NULL' is returned when no document corresponds to the specified ID number. Because the handle of the return value is opened with the function `oddocopen', it should be closed with the function `oddocclose'.</dd>
+</dl>
+
+<p>The function `odgetidbyuri' is used in order to retrieve the ID of the document specified by a URI.</p>
+
+<dl>
+<dt><kbd>int odgetidbyuri(ODEUM *<var>odeum</var>, const char *<var>uri</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. `uri' specifies the string the URI of a document. If successful, the return value is the ID number of the document, else, it is -1. -1 is returned when no document corresponds to the specified URI.</dd>
+</dl>
+
+<p>The function `odcheck' is used in order to check whether the document specified by an ID number exists.</p>
+
+<dl>
+<dt><kbd>int odcheck(ODEUM *<var>odeum</var>, int <var>id</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. `id' specifies the ID number of a document. The return value is true if the document exists, else, it is false.</dd>
+</dl>
+
+<p>The function `odsearch' is used in order to search the inverted index for documents including a particular word.</p>
+
+<dl>
+<dt><kbd>ODPAIR *odsearch(ODEUM *<var>odeum</var>, const char *<var>word</var>, int <var>max</var>, int *<var>np</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. `word' specifies a searching word. `max' specifies the max number of documents to be retrieve. `np' specifies the pointer to a variable to which the number of the elements of the return value is assigned. If successful, the return value is the pointer to an array, else, it is `NULL'. Each element of the array is a pair of the ID number and the score of a document, and sorted in descending order of their scores. Even if no document corresponds to the specified word, it is not error but returns an dummy array. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. Note that each element of the array of the return value can be data of a deleted document.</dd>
+</dl>
+
+<p>The function `odsearchnum' is used in order to get the number of documents including a word.</p>
+
+<dl>
+<dt><kbd>int odsearchdnum(ODEUM *<var>odeum</var>, const char *<var>word</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. `word' specifies a searching word. If successful, the return value is the number of documents including the word, else, it is -1. Because this function does not read the entity of the inverted index, it is faster than `odsearch'.</dd>
+</dl>
+
+<p>The function `oditerinit' is used in order to initialize the iterator of a database handle.</p>
+
+<dl>
+<dt><kbd>int oditerinit(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. If successful, the return value is true, else, it is false. The iterator is used in order to access every document stored in a database.</dd>
+</dl>
+
+<p>The function `oditernext' is used in order to get the next key of the iterator.</p>
+
+<dl>
+<dt><kbd>ODDOC *oditernext(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. If successful, the return value is the handle of the next document, else, it is `NULL'. `NULL' is returned when no document is to be get out of the iterator. It is possible to access every document by iteration of calling this function. However, it is not assured if updating the database is occurred while the iteration. Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of string matches the one of the traversal access. Because the handle of the return value is opened with the function `oddocopen', it should be closed with the function `oddocclose'.</dd>
+</dl>
+
+<p>The function `odsync' is used in order to synchronize updating contents with the files and the devices.</p>
+
+<dl>
+<dt><kbd>int odsync(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle connected as a writer. If successful, the return value is true, else, it is false. This function is useful when another process uses the connected database directory.</dd>
+</dl>
+
+<p>The function `odoptimize' is used in order to optimize a database.</p>
+
+<dl>
+<dt><kbd>int odoptimize(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle connected as a writer. If successful, the return value is true, else, it is false. Elements of the deleted documents in the inverted index are purged.</dd>
+</dl>
+
+<p>The function `odname' is used in order to get the name of a database.</p>
+
+<dl>
+<dt><kbd>char *odname(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. If successful, the return value is the pointer to the region of the name of the database, else, it is `NULL'. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `odfsiz' is used in order to get the total size of database files.</p>
+
+<dl>
+<dt><kbd>double odfsiz(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. If successful, the return value is the total size of the database files, else, it is -1.0.</dd>
+</dl>
+
+<p>The function `odbnum' is used in order to get the total number of the elements of the bucket arrays in the inverted index.</p>
+
+<dl>
+<dt><kbd>int odbnum(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. If successful, the return value is the total number of the elements of the bucket arrays, else, it is -1.</dd>
+</dl>
+
+<p>The function `odbusenum' is used in order to get the total number of the used elements of the bucket arrays in the inverted index.</p>
+
+<dl>
+<dt><kbd>int odbusenum(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. If successful, the return value is the total number of the used elements of the bucket arrays, else, it is -1.</dd>
+</dl>
+
+<p>The function `oddnum' is used in order to get the number of the documents stored in a database.</p>
+
+<dl>
+<dt><kbd>int oddnum(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. If successful, the return value is the number of the documents stored in the database, else, it is -1.</dd>
+</dl>
+
+<p>The function `odwnum' is used in order to get the number of the words stored in a database.</p>
+
+<dl>
+<dt><kbd>int odwnum(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. If successful, the return value is the number of the words stored in the database, else, it is -1. Because of the I/O buffer, the return value may be less than the hard number.</dd>
+</dl>
+
+<p>The function `odwritable' is used in order to check whether a database handle is a writer or not.</p>
+
+<dl>
+<dt><kbd>int odwritable(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. The return value is true if the handle is a writer, false if not.</dd>
+</dl>
+
+<p>The function `odfatalerror' is used in order to check whether a database has a fatal error or not.</p>
+
+<dl>
+<dt><kbd>int odfatalerror(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. The return value is true if the database has a fatal error, false if not.</dd>
+</dl>
+
+<p>The function `odinode' is used in order to get the inode number of a database directory.</p>
+
+<dl>
+<dt><kbd>int odinode(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. The return value is the inode number of the database directory.</dd>
+</dl>
+
+<p>The function `odmtime' is used in order to get the last modified time of a database.</p>
+
+<dl>
+<dt><kbd>time_t odmtime(ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. The return value is the last modified time of the database.</dd>
+</dl>
+
+<p>The function `odmerge' is used in order to merge plural database directories.</p>
+
+<dl>
+<dt><kbd>int odmerge(const char *<var>name</var>, const CBLIST *<var>elemnames</var>);</kbd></dt>
+<dd>`name' specifies the name of a database directory to create. `elemnames' specifies a list of names of element databases. If successful, the return value is true, else, it is false. If two or more documents which have the same URL come in, the first one is adopted and the others are ignored.</dd>
+</dl>
+
+<p>The function `odremove' is used in order to remove a database directory.</p>
+
+<dl>
+<dt><kbd>int odremove(const char *<var>name</var>);</kbd></dt>
+<dd>`name' specifies the name of a database directory. If successful, the return value is true, else, it is false. A database directory can contain databases of other APIs of QDBM, they are also removed by this function.</dd>
+</dl>
+
+<p>The function `oddocopen' is used in order to get a document handle.</p>
+
+<dl>
+<dt><kbd>ODDOC *oddocopen(const char *<var>uri</var>);</kbd></dt>
+<dd>`uri' specifies the URI of a document. The return value is a document handle. The ID number of a new document is not defined. It is defined when the document is stored in a database.</dd>
+</dl>
+
+<p>The function `oddocclose' is used in order to close a document handle.</p>
+
+<dl>
+<dt><kbd>void oddocclose(ODDOC *<var>doc</var>);</kbd></dt>
+<dd>`doc' specifies a document handle. Because the region of a closed handle is released, it becomes impossible to use the handle.</dd>
+</dl>
+
+<p>The function `oddocaddattr' is used in order to add an attribute to a document.</p>
+
+<dl>
+<dt><kbd>void oddocaddattr(ODDOC *<var>doc</var>, const char *<var>name</var>, const char *<var>value</var>);</kbd></dt>
+<dd>`doc' specifies a document handle. `name' specifies the string of the name of an attribute. `value' specifies the string of the value of the attribute.</dd>
+</dl>
+
+<p>The function `oddocaddword' is used in order to add a word to a document.</p>
+
+<dl>
+<dt><kbd>void oddocaddword(ODDOC *<var>doc</var>, const char *<var>normal</var>, const char *<var>asis</var>);</kbd></dt>
+<dd>`doc' specifies a document handle. `normal' specifies the string of the normalized form of a word. Normalized forms are treated as keys of the inverted index. If the normalized form of a word is an empty string, the word is not reflected in the inverted index. `asis' specifies the string of the appearance form of the word. Appearance forms are used after the document is retrieved by an application.</dd>
+</dl>
+
+<p>The function `oddocid' is used in order to get the ID number of a document.</p>
+
+<dl>
+<dt><kbd>int oddocid(const ODDOC *<var>doc</var>);</kbd></dt>
+<dd>`doc' specifies a document handle. The return value is the ID number of a document.</dd>
+</dl>
+
+<p>The function `oddocuri' is used in order to get the URI of a document.</p>
+
+<dl>
+<dt><kbd>const char *oddocuri(const ODDOC *<var>doc</var>);</kbd></dt>
+<dd>`doc' specifies a document handle. The return value is the string of the URI of a document.</dd>
+</dl>
+
+<p>The function `oddocgetattr' is used in order to get the value of an attribute of a document.</p>
+
+<dl>
+<dt><kbd>const char *oddocgetattr(const ODDOC *<var>doc</var>, const char *<var>name</var>);</kbd></dt>
+<dd>`doc' specifies a document handle. `name' specifies the string of the name of an attribute. The return value is the string of the value of the attribute, or `NULL' if no attribute corresponds.</dd>
+</dl>
+
+<p>The function `oddocnwords' is used in order to get the list handle contains words in normalized form of a document.</p>
+
+<dl>
+<dt><kbd>const CBLIST *oddocnwords(const ODDOC *<var>doc</var>);</kbd></dt>
+<dd>`doc' specifies a document handle. The return value is the list handle contains words in normalized form.</dd>
+</dl>
+
+<p>The function `oddocawords' is used in order to get the list handle contains words in appearance form of a document.</p>
+
+<dl>
+<dt><kbd>const CBLIST *oddocawords(const ODDOC *<var>doc</var>);</kbd></dt>
+<dd>`doc' specifies a document handle. The return value is the list handle contains words in appearance form.</dd>
+</dl>
+
+<p>The function `oddocscores' is used in order to get the map handle contains keywords in normalized form and their scores.</p>
+
+<dl>
+<dt><kbd>CBMAP *oddocscores(const ODDOC *<var>doc</var>, int <var>max</var>, ODEUM *<var>odeum</var>);</kbd></dt>
+<dd>`doc' specifies a document handle. `max' specifies the max number of keywords to get. `odeum' specifies a database handle with which the IDF for weighting is calculate. If it is `NULL', it is not used. The return value is the map handle contains keywords and their scores. Scores are expressed as decimal strings. Because the handle of the return value is opened with the function `cbmapopen', it should be closed with the function `cbmapclose' if it is no longer in use.</dd>
+</dl>
+
+<p>The function `odbreaktext' is used in order to break a text into words in appearance form.</p>
+
+<dl>
+<dt><kbd>CBLIST *odbreaktext(const char *<var>text</var>);</kbd></dt>
+<dd>`text' specifies the string of a text. The return value is the list handle contains words in appearance form. Words are separated with space characters and such delimiters as period, comma and so on. Because the handle of the return value is opened with the function `cblistopen', it should be closed with the function `cblistclose' if it is no longer in use.</dd>
+</dl>
+
+<p>The function `odnormalizeword' is used in order to make the normalized form of a word.</p>
+
+<dl>
+<dt><kbd>char *odnormalizeword(const char *<var>asis</var>);</kbd></dt>
+<dd>`asis' specifies the string of the appearance form of a word. The return value is is the string of the normalized form of the word. Alphabets of the ASCII code are unified into lower cases. Words composed of only delimiters are treated as empty strings. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `odpairsand' is used in order to get the common elements of two sets of documents.</p>
+
+<dl>
+<dt><kbd>ODPAIR *odpairsand(ODPAIR *<var>apairs</var>, int <var>anum</var>, ODPAIR *<var>bpairs</var>, int <var>bnum</var>, int *<var>np</var>);</kbd></dt>
+<dd>`apairs' specifies the pointer to the former document array. `anum' specifies the number of the elements of the former document array. `bpairs' specifies the pointer to the latter document array. `bnum' specifies the number of the elements of the latter document array. `np' specifies the pointer to a variable to which the number of the elements of the return value is assigned. The return value is the pointer to a new document array whose elements commonly belong to the specified two sets. Elements of the array are sorted in descending order of their scores. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `odpairsor' is used in order to get the sum of elements of two sets of documents.</p>
+
+<dl>
+<dt><kbd>ODPAIR *odpairsor(ODPAIR *<var>apairs</var>, int <var>anum</var>, ODPAIR *<var>bpairs</var>, int <var>bnum</var>, int *<var>np</var>);</kbd></dt>
+<dd>`apairs' specifies the pointer to the former document array. `anum' specifies the number of the elements of the former document array. `bpairs' specifies the pointer to the latter document array. `bnum' specifies the number of the elements of the latter document array. `np' specifies the pointer to a variable to which the number of the elements of the return value is assigned. The return value is the pointer to a new document array whose elements belong to both or either of the specified two sets. Elements of the array are sorted in descending order of their scores. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `odpairsnotand' is used in order to get the difference set of documents.</p>
+
+<dl>
+<dt><kbd>ODPAIR *odpairsnotand(ODPAIR *<var>apairs</var>, int <var>anum</var>, ODPAIR *<var>bpairs</var>, int <var>bnum</var>, int *<var>np</var>);</kbd></dt>
+<dd>`apairs' specifies the pointer to the former document array. `anum' specifies the number of the elements of the former document array. `bpairs' specifies the pointer to the latter document array of the sum of elements. `bnum' specifies the number of the elements of the latter document array. `np' specifies the pointer to a variable to which the number of the elements of the return value is assigned. The return value is the pointer to a new document array whose elements belong to the former set but not to the latter set. Elements of the array are sorted in descending order of their scores. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use.</dd>
+</dl>
+
+<p>The function `odpairssort' is used in order to sort a set of documents in descending order of scores.</p>
+
+<dl>
+<dt><kbd>void odpairssort(ODPAIR *<var>pairs</var>, int <var>pnum</var>);</kbd></dt>
+<dd>`pairs' specifies the pointer to a document array. `pnum' specifies the number of the elements of the document array.</dd>
+</dl>
+
+<p>The function `odlogarithm' is used in order to get the natural logarithm of a number.</p>
+
+<dl>
+<dt><kbd>double odlogarithm(double <var>x</var>);</kbd></dt>
+<dd>`x' specifies a number. The return value is the natural logarithm of the number. If the number is equal to or less than 1.0, the return value is 0.0. This function is useful when an application calculates the IDF of search results.</dd>
+</dl>
+
+<p>The function `odvectorcosine' is used in order to get the cosine of the angle of two vectors.</p>
+
+<dl>
+<dt><kbd>double odvectorcosine(const int *<var>avec</var>, const int *<var>bvec</var>, int <var>vnum</var>);</kbd></dt>
+<dd>`avec' specifies the pointer to one array of numbers. `bvec' specifies the pointer to the other array of numbers. `vnum' specifies the number of elements of each array. The return value is the cosine of the angle of two vectors. This function is useful when an application calculates similarity of documents.</dd>
+</dl>
+
+<p>The function `odsettuning' is used in order to set the global tuning parameters.</p>
+
+<dl>
+<dt><kbd>void odsettuning(int <var>ibnum</var>, int <var>idnum</var>, int <var>cbnum</var>, int <var>csiz</var>);</kbd></dt>
+<dd>`ibnum' specifies the number of buckets for inverted indexes. `idnum' specifies the division number of inverted index. `cbnum' specifies the number of buckets for dirty buffers. `csiz' specifies the maximum bytes to use memory for dirty buffers. The default setting is equivalent to `odsettuning(32749, 7, 262139, 8388608)'. This function should be called before opening a handle.</dd>
+</dl>
+
+<p>The function `odanalyzetext' is used in order to break a text into words and store appearance forms and normalized form into lists.</p>
+
+<dl>
+<dt><kbd>void odanalyzetext(ODEUM *<var>odeum</var>, const char *<var>text</var>, CBLIST *<var>awords</var>, CBLIST *<var>nwords</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. `text' specifies the string of a text. `awords' specifies a list handle into which appearance form is store. `nwords' specifies a list handle into which normalized form is store. If it is `NULL', it is ignored. Words are separated with space characters and such delimiters as period, comma and so on.</dd>
+</dl>
+
+<p>The function `odsetcharclass' is used in order to set the classes of characters used by `odanalyzetext'.</p>
+
+<dl>
+<dt><kbd>void odsetcharclass(ODEUM *<var>odeum</var>, const char *<var>spacechars</var>, const char *<var>delimchars</var>, const char *<var>gluechars</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. `spacechars' spacifies a string contains space characters. `delimchars' spacifies a string contains delimiter characters. `gluechars' spacifies a string contains glue characters.</dd>
+</dl>
+
+<p>The function `odquery' is used in order to query a database using a small boolean query language.</p>
+
+<dl>
+<dt><kbd>ODPAIR *odquery(ODEUM *<var>odeum</var>, const char *<var>query</var>, int *<var>np</var>, CBLIST *<var>errors</var>);</kbd></dt>
+<dd>`odeum' specifies a database handle. 'query' specifies the text of the query. `np' specifies the pointer to a variable to which the number of the elements of the return value is assigned. `errors' specifies a list handle into which error messages are stored. If it is `NULL', it is ignored. If successful, the return value is the pointer to an array, else, it is `NULL'. Each element of the array is a pair of the ID number and the score of a document, and sorted in descending order of their scores. Even if no document corresponds to the specified condition, it is not error but returns an dummy array. Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if it is no longer in use. Note that each element of the array of the return value can be data of a deleted document.</dd>
+</dl>
+
+<h3>Examples</h3>
+
+<p>The following example stores a document into the database.</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;cabin.h&gt;
+#include &lt;odeum.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+#include &lt;string.h&gt;
+
+#define DBNAME "index"
+
+int main(int argc, char **argv){
+ ODEUM *odeum;
+ ODDOC *doc;
+ CBLIST *awords;
+ const char *asis;
+ char *normal;
+ int i;
+
+ /* open the database */
+ if(!(odeum = odopen(DBNAME, OD_OWRITER | OD_OCREAT))){
+ fprintf(stderr, "odopen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* get the document handle */
+ doc = oddocopen("http://www.foo.bar/baz.txt");
+
+ /* set attributes of the document */
+ oddocaddattr(doc, "title", "Balcony Scene");
+ oddocaddattr(doc, "author", "Shakespeare");
+
+ /* break the text and get the word list */
+ awords = odbreaktext("Parting is such sweet sorrow.");
+
+ /* set each word into the document handle */
+ for(i = 0; i &lt; cblistnum(awords); i++){
+ /* get one word of the list */
+ asis = cblistval(awords, i, NULL);
+ /* get the normalized form from the appearance form */
+ normal = odnormalizeword(asis);
+ /* add the word into the document handle */
+ oddocaddword(doc, normal, asis);
+ /* release the region of the normalized form */
+ free(normal);
+ }
+
+ /* store the document into the database */
+ if(!odput(odeum, doc, -1, 1)){
+ fprintf(stderr, "odput: %s\n", dperrmsg(dpecode));
+ }
+
+ /* release the word list */
+ cblistclose(awords);
+
+ /* release the document handle */
+ oddocclose(doc);
+
+ /* close the database */
+ if(!odclose(odeum)){
+ fprintf(stderr, "odclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<p>The following example retrieves documents.</p>
+
+<pre>#include &lt;depot.h&gt;
+#include &lt;cabin.h&gt;
+#include &lt;odeum.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+#include &lt;string.h&gt;
+
+#define DBNAME "index"
+
+int main(int argc, char **argv){
+ ODEUM *odeum;
+ ODPAIR *pairs;
+ ODDOC *doc;
+ const CBLIST *words;
+ const char *title, *author, *asis;
+ int i, j, pnum;
+
+ /* open the database */
+ if(!(odeum = odopen(DBNAME, OD_OREADER))){
+ fprintf(stderr, "odopen: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ /* retrieve documents */
+ if((pairs = odsearch(odeum, "sorrow", -1, &amp;pnum)) != NULL){
+
+ /* scan each element of the document array */
+ for(i = 0; i &lt; pnum; i++){
+ /* get the document handle */
+ if(!(doc = odgetbyid(odeum, pairs[i].id))) continue;
+ /* show the attributes */
+ printf("URI: %s\n", oddocuri(doc));
+ title = oddocgetattr(doc, "title");
+ if(title) printf("TITLE: %s\n", title);
+ author = oddocgetattr(doc, "author");
+ if(author) printf("AUTHOR: %s\n", author);
+ /* show words in appearance form */
+ printf("WORDS:");
+ words = oddocawords(doc);
+ for(j = 0; j &lt; cblistnum(words); j++){
+ asis = cblistval(words, j, NULL);
+ printf(" %s", asis);
+ }
+ putchar('\n');
+ /* release the document handle */
+ oddocclose(doc);
+ }
+
+ /* release the document array */
+ free(pairs);
+
+ } else {
+ fprintf(stderr, "odsearch: %s\n", dperrmsg(dpecode));
+ }
+
+ /* close the database */
+ if(!odclose(odeum)){
+ fprintf(stderr, "odclose: %s\n", dperrmsg(dpecode));
+ return 1;
+ }
+
+ return 0;
+}
+</pre>
+
+<h3>Notes</h3>
+
+<p>How to build programs using Odeum is the same as the case of Depot.</p>
+
+<pre>gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
+</pre>
+
+<p>If QDBM was built with POSIX thread enabled, the global variable `dpecode' is treated as thread specific data, and functions of Odeum are reentrant. In that case, they are thread-safe as long as a handle is not accessed by threads at the same time, on the assumption that `errno', `malloc', and so on are thread-safe.</p>
+
+<p>If QDBM was built with ZLIB enabled, records in the database for document attributes are compressed. In that case, the size of the database is reduced to 30% or less. Thus, you should enable ZLIB if you use Odeum. A database of Odeum created without ZLIB enabled is not available on environment with ZLIB enabled, and vice versa. If ZLIB was not enabled but LZO, LZO is used instead.</p>
+
+<h3>Query Language</h3>
+
+<p>The query language of the function `odquery' is a basic language following this grammar:</p>
+
+<pre>expr ::= subexpr ( op subexpr )*
+subexpr ::= WORD
+subexpr ::= LPAREN expr RPAREN
+</pre>
+
+<p>Operators are "&amp;" (AND), "|" (OR), and "!" (NOTAND). You can use parenthesis to group sub-expressions together in order to change order of operations. The given query is broken up using the function `odanalyzetext', so if you want to specify different text breaking rules, then make sure that you at least set "&amp;", "|", "!", "(", and ")" to be delimiter characters. Consecutive words are treated as having an implicit "&amp;" operator between them, so "zed shaw" is actually "zed &amp; shaw".</p>
+
+<p>The encoding of the query text should be the same with the encoding of target documents. Moreover, each of space characters, delimiter characters, and glue characters should be single byte.</p>
+
+<hr />
+
+<h2><a name="odeumcli" id="odeumcli" class="head">Commands for Odeum</a></h2>
+
+<p>Odeum has the following command line interfaces.</p>
+
+<p>The command `odmgr' is a utility for debugging Odeum and its applications. It features editing and checking of a database. It can be used for full-text search systems with shell scripts. This command is used in the following format. `name' specifies a database name. `file' specifies a file name, `expr' specifies the URI or the ID number of a document, `words' specifies searching words. `elems' specifies element databases.</p>
+
+<dl>
+<dt><kbd>odmgr create <var>name</var></kbd></dt>
+<dd>Create a database file.</dd>
+<dt><kbd>odmgr put [-uri <var>str</var>] [-title <var>str</var>] [-author <var>str</var>] [-date <var>str</var>] [-wmax <var>num</var>] [-keep] <var>name</var> [<var>file</var>]</kbd></dt>
+<dd>Add a document by reading a file. If `file' is omitted, the standard input is read and URI is needed.</dd>
+<dt><kbd>odmgr out [-id] <var>name</var> <var>expr</var></kbd></dt>
+<dd>Delete a document specified by a URI.</dd>
+<dt><kbd>odmgr get [-id] [-t|-h] <var>name</var> <var>expr</var></kbd></dt>
+<dd>Show a document specified by a URI. The output is the ID number and the URI of a document, in tab separated format.</dd>
+<dt><kbd>odmgr search [-max <var>num</var>] [-or] [-idf] [-t|-h|-n] <var>name</var> <var>words</var>...</kbd></dt>
+<dd>Retrieve documents including specified words. The first line of the output is the total number of hits and each word with its number of hits, in tab separated format. The second line and below are the ID numbers and the scores of documents, in tab separated format.</dd>
+<dt><kbd>odmgr list [-t|-h] <var>name</var></kbd></dt>
+<dd>Show all documents in a database. Each line of the output is the ID number and the score of a document, in tab separated format.</dd>
+<dt><kbd>odmgr optimize <var>name</var></kbd></dt>
+<dd>Optimize a database.</dd>
+<dt><kbd>odmgr inform <var>name</var></kbd></dt>
+<dd>Output miscellaneous information.</dd>
+<dt><kbd>odmgr merge <var>name</var> <var>elems</var>...</kbd></dt>
+<dd>Merge plural databases.</dd>
+<dt><kbd>odmgr remove <var>name</var></kbd></dt>
+<dd>Remove a database directory.</dd>
+<dt><kbd>odmgr break [-h|-k|-s] [<var>file</var>]</kbd></dt>
+<dd>Read a file and output words in the text. Each line of the output is the appearance form and the normalized form of a word, in tab separated format.</dd>
+<dt><kbd>odmgr version</kbd></dt>
+<dd>Output version information of QDBM.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-uri <var>str</var></kbd> : specify the URI of the document explicitly.</li>
+<li><kbd>-title <var>str</var></kbd> : specify the title of the document.</li>
+<li><kbd>-author <var>str</var></kbd> : specify the author of the document.</li>
+<li><kbd>-date <var>str</var></kbd> : specify the modified date of the document.</li>
+<li><kbd>-wmax <var>num</var></kbd> : specify the max number of words to be stored.</li>
+<li><kbd>-keep</kbd> : the storing mode is not to be overwrite.</li>
+<li><kbd>-id</kbd> : specify a document not by a URI but by an ID number.</li>
+<li><kbd>-t</kbd> : output the details of a document in tab separated format.</li>
+<li><kbd>-h</kbd> : output the details of a document in human-readable format.</li>
+<li><kbd>-k</kbd> : output keywords of a document.</li>
+<li><kbd>-s</kbd> : output summary of a document.</li>
+<li><kbd>-max <var>num</var></kbd> : specify the max number of documents of the output.</li>
+<li><kbd>-or</kbd> : perform OR search, nut AND search.</li>
+<li><kbd>-idf</kbd> : tune scores with IDF.</li>
+<li><kbd>-n</kbd> : show ID numbers and scores only.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure. The environment variable `QDBMDBGFD' specifies the file descriptor to output the history of updating the variable `dpecode'.</p>
+
+<p>The command `odtest' is a utility for facility test and performance test. Check a database generated by the command or measure the execution time of the command. This command is used in the following format. `name' specifies a database name. `dnum' specifies the number of the documents. `wnum' specifies the number of words per document. `pnum' specifies the number of patterns of words.</p>
+
+<dl>
+<dt><kbd>odtest write [-tune <var>ibnum</var> <var>idnum</var> <var>cbnum</var> <var>csiz</var>] <var>name</var> <var>dnum</var> <var>wnum</var> <var>pnum</var></kbd></dt>
+<dd>Store documents with random attributes and random words.</dd>
+<dt><kbd>odtest read <var>name</var></kbd></dt>
+<dd>Retrieve all documents of the database above.</dd>
+<dt><kbd>odtest combo <var>name</var></kbd></dt>
+<dd>Perform combination test of various operations.</dd>
+<dt><kbd>odtest wicked <var>name</var> <var>dnum</var></kbd></dt>
+<dd>Perform updating operations selected at random.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-tune <var>ibnum</var> <var>idnum</var> <var>cbnum</var> <var>csiz</var></kbd> : set tuning parameters.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure. The environment variable `QDBMDBGFD' specifies the file descriptor to output the history of updating the variable `dpecode'.</p>
+
+<p>The command `odidx' is a utility for indexing files on the local file system. This command is useful for a full-text search system of a Web site. Supported file format are plain text and HTML. Supported character encoding are US-ASCII and ISO-8859-1. The URI of each document is specified with the path of its file. Attributes named as `title' and `date' are given to each documents. When a document is already existing in the database, it is registered if its last modification time is newer, or it is ignored. Modification times are stored in the sub database `_mtime' in the main database directory. Score information are stored in the sub database `_score' in the main database directory. This command is used in the following format. `name' specifies a database name. `dir' specifies a directory name.</p>
+
+<dl>
+<dt><kbd>odidx register [-l <var>file</var>] [-wmax <var>num</var>] [-tsuf <var>sufs</var>] [-hsuf <var>sufs</var>] <var>name</var> [<var>dir</var>]</kbd></dt>
+<dd>Register files in the specified directory. If `dir' is omitted, the current directory is specified.</dd>
+<dt><kbd>odidx relate <var>name</var></kbd></dt>
+<dd>Add score information for relational document search to each documents in the database.</dd>
+<dt><kbd>odidx purge <var>name</var></kbd></dt>
+<dd>Purge documents which are not existing on the local files system.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="lines">
+<li><kbd>-l <var>file</var></kbd> : read a file and get list of paths of files to register. If `-' is specified, the standard input is read.</li>
+<li><kbd>-wmax <var>num</var></kbd> : specify the max number of words to be stored in the document database.</li>
+<li><kbd>-tsuf <var>sufs</var></kbd> : specify suffixes of plain text files in comma separated format. The default is `-tsuf .txt,.text'.</li>
+<li><kbd>-hsuf <var>sufs</var></kbd> : specify suffixes of HTML files in comma separated format. The default is `-hsuf .html,.htm'.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>Commands of Odeum make it easy to realize a full-text search system. For example, to register files which are under `/home/mikio' and whose suffix are `.txt', `.c', or `.h', perform the following command.</p>
+
+<pre>odidx register -tsuf ".txt,.c,.h" -hsuf "" casket /home/mikio
+</pre>
+
+<p>Thus, to retrieve documents which include `unix' and `posix' and show the top 8 terms, perform the following command.</p>
+
+<pre>odmgr search -max 8 -h casket "unix posix"
+</pre>
+
+<p>A database generated by `odidx' is available with the CGI script which is included in QDBM for full-text search.</p>
+
+<hr />
+
+<h2><a name="fileformat" id="fileformat" class="head">File Format</a></h2>
+
+<h3>File Format of Depot</h3>
+
+<p>The contents of a database file managed by Depot is divided roughly into the following three sections: the header section, the bucket section and the record section.</p>
+
+<p>The header section places at the beginning of the file and its length is constant 48 bytes. The following information are stored in the header section.</p>
+
+<ol>
+<li>magic number: from offset 0, contains "[DEPOT]\n\f" for big endian or "[depot]\n\f" for little endian.</li>
+<li>version number: decimal string of the version number of the library.</li>
+<li>flags for wrappers: from offset 16, type of `int'.</li>
+<li>file size: from offset 24, type of `int'.</li>
+<li>number of the bucket: from offset 32, type of `int'.</li>
+<li>number of records: from offset 40, type of `int'.</li>
+</ol>
+
+<p>The bucket section places after the header section and its length is determined according to the number of the bucket. Each element of the bucket stores an offset of the root node of each separate chain.</p>
+
+<p>The record section places after the bucket section and occupies to the end of the file. The element of the record section contains the following information.</p>
+
+<ol>
+<li>flags: type of `int'.</li>
+<li>second hash value: type of `int'.</li>
+<li>size of the key: type of `int'.</li>
+<li>size of the value: type of `int'.</li>
+<li>size of the padding: type of `int'.</li>
+<li>offset of the left child: type of `int'.</li>
+<li>offset of the right child: type of `int'.</li>
+<li>entity of the key: serial bytes with variable length.</li>
+<li>entity of the value: serial bytes with variable length.</li>
+<li>padding data: void serial bytes with variable length.</li>
+</ol>
+
+<h3>File Format of Villa</h3>
+
+<p>Every data handled by Villa is stored in a database of Depot. Storing data is divided into meta data and logical pages. Logical pages can be classified into leaf nodes and non-leaf nodes. Meta data are such managing information as the number of records. Both of its key and its value are type of `int'. Leaf nodes hold records. Non-leaf nodes hold sparse index referring to pages.</p>
+
+<p>Villa uses variable length numeric format (BER compression) to handle small natural number with frugal storage. A variable length numeric object is parsed from the top of the region and parsing ends at the byte of positive value. Each byte are evaluated as absolute value and calculated as little endian number based on the radix 128.</p>
+
+<p>Record is logical unit of user data. Some records overlapping keys are shaped into one physical record. A Physical record is serialized in the following format.</p>
+
+<ol>
+<li>size of the key: type of variable length number</li>
+<li>entity of the key: serial bytes with variable length</li>
+<li>number of values: type of variable length number</li>
+<li>list of values: serial bytes repeating the following expressions<ol>
+<li>size: type of variable length number</li>
+<li>entity of the key: serial bytes with variable length</li>
+</ol></li>
+</ol>
+
+<p>Leaf node is physical unit to store a set of records. The key of a leaf node is its ID whose type is `int'. A leaf node is stored in a database of Depot with the following values. Its records are sorted in ascending order of each key.</p>
+
+<ol>
+<li>ID of the previous leaf: type of variable length number</li>
+<li>ID of the next leaf: type of variable length number</li>
+<li>list of records: concatenation of serialized records</li>
+</ol>
+
+<p>Index is logical unit of a pointer to search for pages. An index is serialized int the following format.</p>
+
+<ol>
+<li>ID of the referring page: type of variable length number</li>
+<li>size of the key: type of variable length number</li>
+<li>entity of the key: serial bytes with variable length</li>
+</ol>
+
+<p>Non-leaf node is physical unit to store a set of indexes. The key of a non-leaf node is its ID whose type is `int'. A non-leaf node is stored in a database of Depot with the following values. Its indexes are sorted in ascending order of each key.</p>
+
+<ol>
+<li>ID of the first child node: type of variable length number</li>
+<li>list of indexes: concatenation of serialized indexes</li>
+</ol>
+
+<h3>Notes</h3>
+
+<p>Because the database file is not sparse, move, copy, unlink, ftp, and so on with the file are possible. Because Depot reads and writes data without normalization of byte order, it is impossible to share the same file between the environment with different byte order.</p>
+
+<p>When you distribute a database file of Depot or Villa via network, the MIME type suggested to be `application/x-qdbm'. Suffix of the file name is suggested to be `.qdb'. When you distribute a database directory of Curia, you may convert the directory tree to an archive of such type as TAR.</p>
+
+<p>For the command `file' to recognize database files, append the following expressions into `magic' file.</p>
+
+<pre>0 string [DEPOT]\n\f QDBM, big endian
+&gt;12 string x \b, version=%s
+&gt;19 byte ^1 \b, Hash
+&gt;19 byte &amp;1 \b, B+ tree
+&gt;19 byte &amp;2 \b (deflated:ZLIB)
+&gt;19 byte &amp;4 \b (deflated:LZO)
+&gt;19 byte &amp;8 \b (deflated:BZIP2)
+&gt;24 belong x \b, filesize=%d
+&gt;32 belong x \b, buckets=%d
+&gt;40 belong x \b, records=%d
+0 string [depot]\n\f QDBM, little endian
+&gt;12 string x \b, version=%s
+&gt;16 byte ^1 \b, Hash
+&gt;16 byte &amp;1 \b, B+ tree
+&gt;16 byte &amp;2 \b (deflated:ZLIB)
+&gt;16 byte &amp;4 \b (deflated:LZO)
+&gt;16 byte &amp;8 \b (deflated:BZIP2)
+&gt;24 lelong x \b, filesize=%d
+&gt;32 lelong x \b, buckets=%d
+&gt;40 lelong x \b, records=%d
+</pre>
+
+<hr />
+
+<h2><a name="porting" id="porting" class="head">Porting</a></h2>
+
+<p>One of the goal of QDBM is to work on all platforms which conform to POSIX. Even if some APIs are not implemented, QDBM should work. Moreover, it should be possible to build QDBM using compilers other than GCC. Porting to various platforms is performed to add a new `Makefile' or modify some parts of source files. As for APIs of C, some of the following files should be modified. Otherwise, you can create new files based on them.</p>
+
+<ul class="lines">
+<li><kbd>Makefile.in</kbd> : base of `Makefile', used by `./configure'.</li>
+<li><kbd>myconf.h</kbd> : configuration of system dependency.</li>
+<li><kbd>depot.h</kbd> : header of the basic API.</li>
+<li><kbd>curia.h</kbd> : header of the extended API.</li>
+<li><kbd>relic.h</kbd> : header of the NDBM-compatible API.</li>
+<li><kbd>hovel.h</kbd> : header of the GDBM-compatible API.</li>
+<li><kbd>cabin.h</kbd> : header of the utility API.</li>
+<li><kbd>villa.h</kbd> : header of the advanced API.</li>
+<li><kbd>vista.h</kbd> : header of the extended advanced API.</li>
+<li><kbd>odeum.h</kbd> : header of the inverted API.</li>
+<li><kbd>myconf.c</kbd> : implementation of system dependency.</li>
+<li><kbd>depot.c</kbd> : implementation of the basic API.</li>
+<li><kbd>curia.c</kbd> : implementation of the extended API.</li>
+<li><kbd>relic.c</kbd> : implementation of the NDBM-compatible API.</li>
+<li><kbd>hovel.c</kbd> : implementation of the GDBM-compatible API.</li>
+<li><kbd>cabin.c</kbd> : implementation of the utility API.</li>
+<li><kbd>villa.c</kbd> : implementation of the advanced API.</li>
+<li><kbd>vista.c</kbd> : implementation of the extended advanced API.</li>
+<li><kbd>odeum.c</kbd> : implementation of the inverted API.</li>
+</ul>
+
+<p>On platforms which do not support file locking with `fcntl' call, you should append `-DMYNOLOCK' to the macro `CFLAGS' defined in `Makefile'. In that case, you should consider another exclusion control. As with it, on platforms without `mmap' call, you should append `-DMYNOMMAP' to `CFLAGS'. As for `mmap', its emulation using `malloc' and so on is provided. If other system calls are not implemented, you should define emulation by modification of `myconf.h' and `myconf.c'.</p>
+
+<p>Because POSIX thread is used in C++ API, it is impossible to port C++ API to platforms without the package. Because JNI is used in Java API, you should pay attention to location of the headers and libraries. Moreover, you should consider such type definitions as `long long' or `int64'. Because APIs of Perl and Ruby use building commands provided with each language system, you should be knowledgeable about their specifications.</p>
+
+<hr />
+
+<h2><a name="bugs" id="bugs" class="head">Bugs</a></h2>
+
+<p>Each document of QDBM should be calibrated by native English speakers.</p>
+
+<p>There is no such bug which are found but not fixed, as crash by segmentation fault, unexpected data vanishing, memory leak and so on.</p>
+
+<p>If you find any bug, report it to the author, with the information of the version of QDBM, the operating system and the compiler.</p>
+
+<p>Databases created with QDBM version 1.7.13 or earlier are not compatible to ones of the later versions.</p>
+
+<hr />
+
+<h2><a name="faq" id="faq" class="head">Frequently Asked Questions</a></h2>
+
+<dl>
+<dt>Q. : Does QDBM support SQL?</dt>
+<dd>A. : No, it does not. QDBM is not a RDBMS (Relational Database Management System). If you want an embedded RDBMS, use SQLite and so on.</dd>
+<dt>Q. : After all, how different from GDBM (NDBM, SDBM, Berkeley DB)?</dt>
+<dd>A. : Processing speed is higher, a database file is smaller, API is simpler. A highly important thing is that efficiency in time and space is very good when records are frequently overwritten, so, scalability in practical use is high. Moreover, even when constructing such a large database that the number of storing record is more than one million, processing speed does not slowdown deathly, filesize does not grow extremely. However, because other DBM or DBMS may be more suitable in some cases, comparing performance and functionality by yourself is suggested.</dd>
+<dt>Q. : Which API should I use?</dt>
+<dd>A. : If you search for records as complete accord, try Depot. If the scale is large, try Curia. If you access records in some order, try Villa. If the scale is large, try Vista. If you pursue the greatest number of records, build QDBM with ZLIB or LZO enabled and use Vista.</dd>
+<dt>Q. : What is bibliography?</dt>
+<dd>A. : Algorithms of QDBM are mainly based on the descriptions in `Data Structures and Algorithms' by Aho et al and `Algorithms in C' by Sedgewick.</dd>
+<dt>Q. : Are there good sample codes for applications?</dt>
+<dd>A. : Refer to the source code of commands of each API. `dptsv.c', `crtsv.c' and `vltsv.c' are simplest.</dd>
+<dt>Q. : My database file has been broken. Why?</dt>
+<dd>A. : In most cases, the reason is that your application did not close the database on exit. No matter whether it is a demon process or a CGI script, any application should close handling databases when it exits. Moreover, we should remember that a process of CGI may be killed by SIGPIPE or SIGTERM.</dd>
+<dt>Q. : How robust are databases of QDBM?</dt>
+<dd>A. : QDBM does not assure absolute robustness. A database may be broken if your operating system crashes. Although transaction of Villa can save a database from crashes of applications, it is inadequate to crashes of operating systems. So, you should consider multiplexing of a database or backup system if you use QDBM for mission critical applications.</dd>
+<dt>Q: How should I use alignment of Depot and Curia?</dt>
+<dd>A: If your application repeats writing with overwrite or concatenate mode. Alignment saves the rapid growth of the size of the database file. Because the best suited size of alignment of each application is different, you should learn it by experiment. For the meantime, about 32 is suitable.</dd>
+<dt>Q. : How should I tune performance parameters of Villa?</dt>
+<dd>A. : If you perform mainly ordering access, `lrecmax' and `nidxmax' should be larger. If you perform mainly random access, they should be less. If RAM of your system is abundant, `lcnum' and `ncnum' should be increased in order to improve performance. If ZLIB, LZO, or BZIP2 is enabled, increase `lrecmax' and compression efficiency is improved.</dd>
+<dt>Q. : Which is the most preferable of ZLIB, LZO or BZIP2 for Villa?</dt>
+<dd>A. : BZIP2 has the best compression retio. LZO has the best compression speed. ZLIB takes a mean position of them. If you don't have any special reason, using ZLIB is suggested. However, if updating of the database is frequent, LZO is more preferable. If updating of the database is very infrequently, BZIP2 is more preferable. Note that the license of LZO is the GNU LGPL.</dd>
+<dt>Q. : What is `sparse file'?</dt>
+<dd>A. : It is a file where some holes are. `Hole' means a block where any data has never written in. If a file system supports sparse file, holes are not allocated into any physical storage. As for QDBM, if a database is created with such flags as DP_OSPARSE, the bucket array is not initialized and its blocks become holes. According to that mechanism, you can use greatly huge hash tables. However, its performance is strongly depends on the setting of the file system.</dd>
+<dt>Q. : Why Depot and Curia do not feature transaction?</dt>
+<dd>A. : If an application implements its own transaction, inner transaction of database is superfluous. You can implement transaction for application easily with hash map provided by Cabin.</dd>
+<dt>Q. : How should I tune the system for performance?</dt>
+<dd>A. : Install more RAM on your machine than the size of a database. Then, enlarge I/O buffer and cut down on flushing dirty buffers. File system is also important. On Linux, although EXT2 is usually fastest, EXT3 is faster in some cases. ReiserFS is okey. The other modes of EXT3 are very slow. About other file systems, you should learn them by experiment.</dd>
+<dt>Q. : Can I build QDBM using `cc' instead of `gcc'?</dt>
+<dd>A. : Yes. Try to build QDBM with `LTmakefile'.</dd>
+<dt>Q. : Can I build QDBM using Visual C++?</dt>
+<dd>A. : Yes. Use `VCmakefile' instead of `Makefile'.</dd>
+<dt>Q. : Can I use QDBM in other languages?</dt>
+<dd>A. : As for PHP, Scheme (Gauche), and OCaml, interfaces of QDBM have been released. If you need it for another language, try to turn it out.</dd>
+<dt>Q. : What does `QDBM' mean?</dt>
+<dd>A. : `QDBM' stands for `Quick Database Manager'. It means that processing speed is high, and that you can write applications quickly.</dd>
+</dl>
+
+<hr />
+
+<h2><a name="copying" id="copying" class="head">Copying</a></h2>
+
+<p>QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License or any later version.</p>
+
+<p>QDBM 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 Lesser General Public License for more details.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public License along with QDBM (See the file `COPYING'); if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.</p>
+
+<p>QDBM was written by Mikio Hirabayashi. You can contact the author by e-mail to `mikio@users.sourceforge.net'. However, as for topics which can be shared among other users, please send it to the mailing list. To join the mailing list, refer to `http://lists.sourceforge.net/lists/listinfo/qdbm-users'.</p>
+
+<hr />
+
+</body>
+
+</html>
+
+<!-- END OF FILE -->
diff --git a/qdbm/villa.c b/qdbm/villa.c
new file mode 100644
index 00000000..0783ac5d
--- /dev/null
+++ b/qdbm/villa.c
@@ -0,0 +1,2666 @@
+/*************************************************************************************************
+ * Implementation of Villa
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#define QDBM_INTERNAL 1
+
+#include "villa.h"
+#include "myconf.h"
+
+#define VL_LEAFIDMIN 1 /* minimum number of leaf ID */
+#define VL_NODEIDMIN 100000000 /* minimum number of node ID */
+#define VL_VNUMBUFSIZ 8 /* size of a buffer for variable length number */
+#define VL_NUMBUFSIZ 32 /* size of a buffer for a number */
+#define VL_PAGEBUFSIZ 32768 /* size of a buffer to read each page */
+#define VL_MAXLEAFSIZ 49152 /* maximum size of each leaf */
+#define VL_DEFLRECMAX 49 /* default number of records in each leaf */
+#define VL_DEFNIDXMAX 192 /* default number of indexes in each node */
+#define VL_DEFLCNUM 1024 /* default number of leaf cache */
+#define VL_DEFNCNUM 512 /* default number of node cache */
+#define VL_CACHEOUT 8 /* number of pages in a process of cacheout */
+#define VL_INITBNUM 32749 /* initial bucket number */
+#define VL_PAGEALIGN -3 /* alignment for pages */
+#define VL_FBPOOLSIZ 128 /* size of free block pool */
+#define VL_PATHBUFSIZ 1024 /* size of a path buffer */
+#define VL_TMPFSUF MYEXTSTR "vltmp" /* suffix of a temporary file */
+#define VL_ROOTKEY -1 /* key of the root key */
+#define VL_LASTKEY -2 /* key of the last key */
+#define VL_LNUMKEY -3 /* key of the number of leaves */
+#define VL_NNUMKEY -4 /* key of the number of nodes */
+#define VL_RNUMKEY -5 /* key of the number of records */
+#define VL_CRDNUM 7 /* default division number for Vista */
+
+/* set a buffer for a variable length number */
+#define VL_SETVNUMBUF(VL_len, VL_buf, VL_num) \
+ do { \
+ int _VL_num; \
+ _VL_num = VL_num; \
+ if(_VL_num == 0){ \
+ ((signed char *)(VL_buf))[0] = 0; \
+ (VL_len) = 1; \
+ } else { \
+ (VL_len) = 0; \
+ while(_VL_num > 0){ \
+ int _VL_rem = _VL_num & 0x7f; \
+ _VL_num >>= 7; \
+ if(_VL_num > 0){ \
+ ((signed char *)(VL_buf))[(VL_len)] = -_VL_rem - 1; \
+ } else { \
+ ((signed char *)(VL_buf))[(VL_len)] = _VL_rem; \
+ } \
+ (VL_len)++; \
+ } \
+ } \
+ } while(FALSE)
+
+/* read a variable length buffer */
+#define VL_READVNUMBUF(VL_buf, VL_size, VL_num, VL_step) \
+ do { \
+ int _VL_i, _VL_base; \
+ (VL_num) = 0; \
+ _VL_base = 1; \
+ if((VL_size) < 2){ \
+ (VL_num) = ((signed char *)(VL_buf))[0]; \
+ (VL_step) = 1; \
+ } else { \
+ for(_VL_i = 0; _VL_i < (VL_size); _VL_i++){ \
+ if(((signed char *)(VL_buf))[_VL_i] >= 0){ \
+ (VL_num) += ((signed char *)(VL_buf))[_VL_i] * _VL_base; \
+ break; \
+ } \
+ (VL_num) += _VL_base * (((signed char *)(VL_buf))[_VL_i] + 1) * -1; \
+ _VL_base *= 128; \
+ } \
+ (VL_step) = _VL_i + 1; \
+ } \
+ } while(FALSE)
+
+enum { /* enumeration for flags */
+ VL_FLISVILLA = 1 << 0, /* whether for Villa */
+ VL_FLISZLIB = 1 << 1, /* whether with ZLIB */
+ VL_FLISLZO = 1 << 2, /* whether with LZO */
+ VL_FLISBZIP = 1 << 3 /* whether with BZIP2 */
+};
+
+
+/* private function prototypes */
+static int vllexcompare(const char *aptr, int asiz, const char *bptr, int bsiz);
+static int vlintcompare(const char *aptr, int asiz, const char *bptr, int bsiz);
+static int vlnumcompare(const char *aptr, int asiz, const char *bptr, int bsiz);
+static int vldeccompare(const char *aptr, int asiz, const char *bptr, int bsiz);
+static int vldpputnum(DEPOT *depot, int knum, int vnum);
+static int vldpgetnum(DEPOT *depot, int knum, int *vnp);
+static VLLEAF *vlleafnew(VILLA *villa, int prev, int next);
+static int vlleafcacheout(VILLA *villa, int id);
+static int vlleafsave(VILLA *villa, VLLEAF *leaf);
+static VLLEAF *vlleafload(VILLA *villa, int id);
+static VLLEAF *vlgethistleaf(VILLA *villa, const char *kbuf, int ksiz);
+static int vlleafaddrec(VILLA *villa, VLLEAF *leaf, int dmode,
+ const char *kbuf, int ksiz, const char *vbuf, int vsiz);
+static int vlleafdatasize(VLLEAF *leaf);
+static VLLEAF *vlleafdivide(VILLA *villa, VLLEAF *leaf);
+static VLNODE *vlnodenew(VILLA *villa, int heir);
+static int vlnodecacheout(VILLA *villa, int id);
+static int vlnodesave(VILLA *villa, VLNODE *node);
+static VLNODE *vlnodeload(VILLA *villa, int id);
+static void vlnodeaddidx(VILLA *villa, VLNODE *node, int order,
+ int pid, const char *kbuf, int ksiz);
+static int vlsearchleaf(VILLA *villa, const char *kbuf, int ksiz);
+static int vlcacheadjust(VILLA *villa);
+static VLREC *vlrecsearch(VILLA *villa, VLLEAF *leaf, const char *kbuf, int ksiz, int *ip);
+
+
+
+/*************************************************************************************************
+ * public objects
+ *************************************************************************************************/
+
+
+/* Comparing functions. */
+VLCFUNC VL_CMPLEX = vllexcompare;
+VLCFUNC VL_CMPINT = vlintcompare;
+VLCFUNC VL_CMPNUM = vlnumcompare;
+VLCFUNC VL_CMPDEC = vldeccompare;
+
+
+/* Get a database handle. */
+VILLA *vlopen(const char *name, int omode, VLCFUNC cmp){
+ DEPOT *depot;
+ int dpomode, flags, cmode, root, last, lnum, nnum, rnum;
+ VILLA *villa;
+ VLLEAF *leaf;
+ assert(name && cmp);
+ dpomode = DP_OREADER;
+ if(omode & VL_OWRITER){
+ dpomode = DP_OWRITER;
+ if(omode & VL_OCREAT) dpomode |= DP_OCREAT;
+ if(omode & VL_OTRUNC) dpomode |= DP_OTRUNC;
+ }
+ if(omode & VL_ONOLCK) dpomode |= DP_ONOLCK;
+ if(omode & VL_OLCKNB) dpomode |= DP_OLCKNB;
+ if(!(depot = dpopen(name, dpomode, VL_INITBNUM))) return NULL;
+ flags = dpgetflags(depot);
+ cmode = 0;
+ root = -1;
+ last = -1;
+ lnum = 0;
+ nnum = 0;
+ rnum = 0;
+ if(dprnum(depot) > 0){
+ if(!(flags & VL_FLISVILLA) ||
+ !vldpgetnum(depot, VL_ROOTKEY, &root) || !vldpgetnum(depot, VL_LASTKEY, &last) ||
+ !vldpgetnum(depot, VL_LNUMKEY, &lnum) || !vldpgetnum(depot, VL_NNUMKEY, &nnum) ||
+ !vldpgetnum(depot, VL_RNUMKEY, &rnum) || root < VL_LEAFIDMIN || last < VL_LEAFIDMIN ||
+ lnum < 0 || nnum < 0 || rnum < 0){
+ dpclose(depot);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(flags & VL_FLISZLIB){
+ cmode = VL_OZCOMP;
+ } else if(flags & VL_FLISLZO){
+ cmode = VL_OYCOMP;
+ } else if(flags & VL_FLISBZIP){
+ cmode = VL_OXCOMP;
+ }
+ } else if(omode & VL_OWRITER){
+ if(omode & VL_OZCOMP){
+ cmode = VL_OZCOMP;
+ } else if(omode & VL_OYCOMP){
+ cmode = VL_OYCOMP;
+ } else if(omode & VL_OXCOMP){
+ cmode = VL_OXCOMP;
+ }
+ }
+ if(omode & VL_OWRITER){
+ flags |= VL_FLISVILLA;
+ if(_qdbm_deflate && cmode == VL_OZCOMP){
+ flags |= VL_FLISZLIB;
+ } else if(_qdbm_lzoencode && cmode == VL_OYCOMP){
+ flags |= VL_FLISLZO;
+ } else if(_qdbm_bzencode && cmode == VL_OXCOMP){
+ flags |= VL_FLISBZIP;
+ }
+ if(!dpsetflags(depot, flags) || !dpsetalign(depot, VL_PAGEALIGN) ||
+ !dpsetfbpsiz(depot, VL_FBPOOLSIZ)){
+ dpclose(depot);
+ return NULL;
+ }
+ }
+ CB_MALLOC(villa, sizeof(VILLA));
+ villa->depot = depot;
+ villa->cmp = cmp;
+ villa->wmode = (omode & VL_OWRITER);
+ villa->cmode = cmode;
+ villa->root = root;
+ villa->last = last;
+ villa->lnum = lnum;
+ villa->nnum = nnum;
+ villa->rnum = rnum;
+ villa->leafc = cbmapopen();
+ villa->nodec = cbmapopen();
+ villa->hnum = 0;
+ villa->hleaf = -1;
+ villa->lleaf = -1;
+ villa->curleaf = -1;
+ villa->curknum = -1;
+ villa->curvnum = -1;
+ villa->leafrecmax = VL_DEFLRECMAX;
+ villa->nodeidxmax = VL_DEFNIDXMAX;
+ villa->leafcnum = VL_DEFLCNUM;
+ villa->nodecnum = VL_DEFNCNUM;
+ villa->tran = FALSE;
+ villa->rbroot = -1;
+ villa->rblast = -1;
+ villa->rblnum = -1;
+ villa->rbnnum = -1;
+ villa->rbrnum = -1;
+ if(root == -1){
+ leaf = vlleafnew(villa, -1, -1);
+ villa->root = leaf->id;
+ villa->last = leaf->id;
+ if(!vltranbegin(villa) || !vltranabort(villa)){
+ vlclose(villa);
+ return NULL;
+ }
+ }
+ return villa;
+}
+
+
+/* Close a database handle. */
+int vlclose(VILLA *villa){
+ int err, pid;
+ const char *tmp;
+ assert(villa);
+ err = FALSE;
+ if(villa->tran){
+ if(!vltranabort(villa)) err = TRUE;
+ }
+ cbmapiterinit(villa->leafc);
+ while((tmp = cbmapiternext(villa->leafc, NULL)) != NULL){
+ pid = *(int *)tmp;
+ if(!vlleafcacheout(villa, pid)) err = TRUE;
+ }
+ cbmapiterinit(villa->nodec);
+ while((tmp = cbmapiternext(villa->nodec, NULL)) != NULL){
+ pid = *(int *)tmp;
+ if(!vlnodecacheout(villa, pid)) err = TRUE;
+ }
+ if(villa->wmode){
+ if(!dpsetalign(villa->depot, 0)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_ROOTKEY, villa->root)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_LASTKEY, villa->last)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_LNUMKEY, villa->lnum)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_NNUMKEY, villa->nnum)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_RNUMKEY, villa->rnum)) err = TRUE;
+ }
+ cbmapclose(villa->leafc);
+ cbmapclose(villa->nodec);
+ if(!dpclose(villa->depot)) err = TRUE;
+ free(villa);
+ return err ? FALSE : TRUE;
+}
+
+
+/* Store a record. */
+int vlput(VILLA *villa, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode){
+ VLLEAF *leaf, *newleaf;
+ VLNODE *node, *newnode;
+ VLIDX *idxp;
+ CBDATUM *key;
+ int i, pid, todiv, heir, parent, mid;
+ assert(villa && kbuf && vbuf);
+ villa->curleaf = -1;
+ villa->curknum = -1;
+ villa->curvnum = -1;
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(vsiz < 0) vsiz = strlen(vbuf);
+ if(villa->hleaf < VL_LEAFIDMIN || !(leaf = vlgethistleaf(villa, kbuf, ksiz))){
+ if((pid = vlsearchleaf(villa, kbuf, ksiz)) == -1) return FALSE;
+ if(!(leaf = vlleafload(villa, pid))) return FALSE;
+ }
+ if(!vlleafaddrec(villa, leaf, dmode, kbuf, ksiz, vbuf, vsiz)){
+ dpecodeset(DP_EKEEP, __FILE__, __LINE__);
+ return FALSE;
+ }
+ todiv = FALSE;
+ switch(CB_LISTNUM(leaf->recs) % 4){
+ case 0:
+ if(CB_LISTNUM(leaf->recs) >= 4 &&
+ vlleafdatasize(leaf) > VL_MAXLEAFSIZ * (villa->cmode > 0 ? 2 : 1)){
+ todiv = TRUE;
+ break;
+ }
+ case 2:
+ if(CB_LISTNUM(leaf->recs) > villa->leafrecmax) todiv = TRUE;
+ break;
+ }
+ if(todiv){
+ if(!(newleaf = vlleafdivide(villa, leaf))) return FALSE;
+ if(leaf->id == villa->last) villa->last = newleaf->id;
+ heir = leaf->id;
+ pid = newleaf->id;
+ key = ((VLREC *)CB_LISTVAL(newleaf->recs, 0))->key;
+ key = cbdatumdup(key);
+ while(TRUE){
+ if(villa->hnum < 1){
+ node = vlnodenew(villa, heir);
+ vlnodeaddidx(villa, node, TRUE, pid, CB_DATUMPTR(key), CB_DATUMSIZE(key));
+ villa->root = node->id;
+ CB_DATUMCLOSE(key);
+ break;
+ }
+ parent = villa->hist[--villa->hnum];
+ if(!(node = vlnodeload(villa, parent))){
+ CB_DATUMCLOSE(key);
+ return FALSE;
+ }
+ vlnodeaddidx(villa, node, FALSE, pid, CB_DATUMPTR(key), CB_DATUMSIZE(key));
+ CB_DATUMCLOSE(key);
+ if(CB_LISTNUM(node->idxs) <= villa->nodeidxmax) break;
+ mid = CB_LISTNUM(node->idxs) / 2;
+ idxp = (VLIDX *)CB_LISTVAL(node->idxs, mid);
+ newnode = vlnodenew(villa, idxp->pid);
+ heir = node->id;
+ pid = newnode->id;
+ CB_DATUMOPEN2(key, CB_DATUMPTR(idxp->key), CB_DATUMSIZE(idxp->key));
+ for(i = mid + 1; i < CB_LISTNUM(node->idxs); i++){
+ idxp = (VLIDX *)CB_LISTVAL(node->idxs, i);
+ vlnodeaddidx(villa, newnode, TRUE, idxp->pid,
+ CB_DATUMPTR(idxp->key), CB_DATUMSIZE(idxp->key));
+ }
+ for(i = 0; i < CB_LISTNUM(newnode->idxs); i++){
+ idxp = (VLIDX *)cblistpop(node->idxs, NULL);
+ CB_DATUMCLOSE(idxp->key);
+ free(idxp);
+ }
+ node->dirty = TRUE;
+ }
+ }
+ if(!villa->tran && !vlcacheadjust(villa)) return FALSE;
+ return TRUE;
+}
+
+
+/* Delete a record. */
+int vlout(VILLA *villa, const char *kbuf, int ksiz){
+ VLLEAF *leaf;
+ VLREC *recp;
+ int pid, ri, vsiz;
+ char *vbuf;
+ assert(villa && kbuf);
+ villa->curleaf = -1;
+ villa->curknum = -1;
+ villa->curvnum = -1;
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(villa->hleaf < VL_LEAFIDMIN || !(leaf = vlgethistleaf(villa, kbuf, ksiz))){
+ if((pid = vlsearchleaf(villa, kbuf, ksiz)) == -1) return FALSE;
+ if(!(leaf = vlleafload(villa, pid))) return FALSE;
+ }
+ if(!(recp = vlrecsearch(villa, leaf, kbuf, ksiz, &ri))){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(recp->rest){
+ CB_DATUMCLOSE(recp->first);
+ vbuf = cblistshift(recp->rest, &vsiz);
+ CB_DATUMOPEN2(recp->first, vbuf, vsiz);
+ free(vbuf);
+ if(CB_LISTNUM(recp->rest) < 1){
+ CB_LISTCLOSE(recp->rest);
+ recp->rest = NULL;
+ }
+ } else {
+ CB_DATUMCLOSE(recp->key);
+ CB_DATUMCLOSE(recp->first);
+ free(cblistremove(leaf->recs, ri, NULL));
+ }
+ leaf->dirty = TRUE;
+ villa->rnum--;
+ if(!villa->tran && !vlcacheadjust(villa)) return FALSE;
+ return TRUE;
+}
+
+
+/* Retrieve a record. */
+char *vlget(VILLA *villa, const char *kbuf, int ksiz, int *sp){
+ VLLEAF *leaf;
+ VLREC *recp;
+ char *rv;
+ int pid;
+ assert(villa && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(villa->hleaf < VL_LEAFIDMIN || !(leaf = vlgethistleaf(villa, kbuf, ksiz))){
+ if((pid = vlsearchleaf(villa, kbuf, ksiz)) == -1) return NULL;
+ if(!(leaf = vlleafload(villa, pid))) return NULL;
+ }
+ if(!(recp = vlrecsearch(villa, leaf, kbuf, ksiz, NULL))){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(!villa->tran && !vlcacheadjust(villa)) return NULL;
+ if(sp) *sp = CB_DATUMSIZE(recp->first);
+ CB_MEMDUP(rv, CB_DATUMPTR(recp->first), CB_DATUMSIZE(recp->first));
+ return rv;
+}
+
+
+/* Get the size of the value of a record. */
+int vlvsiz(VILLA *villa, const char *kbuf, int ksiz){
+ VLLEAF *leaf;
+ VLREC *recp;
+ int pid;
+ assert(villa && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(villa->hleaf < VL_LEAFIDMIN || !(leaf = vlgethistleaf(villa, kbuf, ksiz))){
+ if((pid = vlsearchleaf(villa, kbuf, ksiz)) == -1) return -1;
+ if(!(leaf = vlleafload(villa, pid))) return -1;
+ }
+ if(!(recp = vlrecsearch(villa, leaf, kbuf, ksiz, NULL))){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return -1;
+ }
+ if(!villa->tran && !vlcacheadjust(villa)) return -1;
+ return CB_DATUMSIZE(recp->first);
+}
+
+
+/* Get the number of records corresponding a key. */
+int vlvnum(VILLA *villa, const char *kbuf, int ksiz){
+ VLLEAF *leaf;
+ VLREC *recp;
+ int pid;
+ assert(villa && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(villa->hleaf < VL_LEAFIDMIN || !(leaf = vlgethistleaf(villa, kbuf, ksiz))){
+ if((pid = vlsearchleaf(villa, kbuf, ksiz)) == -1) return 0;
+ if(!(leaf = vlleafload(villa, pid))) return 0;
+ }
+ if(!(recp = vlrecsearch(villa, leaf, kbuf, ksiz, NULL))){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return 0;
+ }
+ if(!villa->tran && !vlcacheadjust(villa)) return 0;
+ return 1 + (recp->rest ? CB_LISTNUM(recp->rest) : 0);
+}
+
+
+/* Store plural records corresponding a key. */
+int vlputlist(VILLA *villa, const char *kbuf, int ksiz, const CBLIST *vals){
+ int i, vsiz;
+ const char *vbuf;
+ assert(villa && kbuf && vals);
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(CB_LISTNUM(vals) < 1){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ for(i = 0; i < CB_LISTNUM(vals); i++){
+ vbuf = CB_LISTVAL2(vals, i, vsiz);
+ if(!vlput(villa, kbuf, ksiz, vbuf, vsiz, VL_DDUP)) return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Delete all records corresponding a key. */
+int vloutlist(VILLA *villa, const char *kbuf, int ksiz){
+ int i, vnum;
+ assert(villa && kbuf);
+ if(!villa->wmode){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if((vnum = vlvnum(villa, kbuf, ksiz)) < 1) return FALSE;
+ for(i = 0; i < vnum; i++){
+ if(!vlout(villa, kbuf, ksiz)) return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Retrieve values of all records corresponding a key. */
+CBLIST *vlgetlist(VILLA *villa, const char *kbuf, int ksiz){
+ VLLEAF *leaf;
+ VLREC *recp;
+ int pid, i, vsiz;
+ CBLIST *vals;
+ const char *vbuf;
+ assert(villa && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(villa->hleaf < VL_LEAFIDMIN || !(leaf = vlgethistleaf(villa, kbuf, ksiz))){
+ if((pid = vlsearchleaf(villa, kbuf, ksiz)) == -1) return NULL;
+ if(!(leaf = vlleafload(villa, pid))) return NULL;
+ }
+ if(!(recp = vlrecsearch(villa, leaf, kbuf, ksiz, NULL))){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return NULL;
+ }
+ CB_LISTOPEN(vals);
+ CB_LISTPUSH(vals, CB_DATUMPTR(recp->first), CB_DATUMSIZE(recp->first));
+ if(recp->rest){
+ for(i = 0; i < CB_LISTNUM(recp->rest); i++){
+ vbuf = CB_LISTVAL2(recp->rest, i, vsiz);
+ CB_LISTPUSH(vals, vbuf, vsiz);
+ }
+ }
+ if(!villa->tran && !vlcacheadjust(villa)){
+ CB_LISTCLOSE(vals);
+ return NULL;
+ }
+ return vals;
+}
+
+
+/* Retrieve concatenated values of all records corresponding a key. */
+char *vlgetcat(VILLA *villa, const char *kbuf, int ksiz, int *sp){
+ VLLEAF *leaf;
+ VLREC *recp;
+ int pid, i, vsiz, rsiz;
+ char *rbuf;
+ const char *vbuf;
+ assert(villa && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(villa->hleaf < VL_LEAFIDMIN || !(leaf = vlgethistleaf(villa, kbuf, ksiz))){
+ if((pid = vlsearchleaf(villa, kbuf, ksiz)) == -1) return NULL;
+ if(!(leaf = vlleafload(villa, pid))) return NULL;
+ }
+ if(!(recp = vlrecsearch(villa, leaf, kbuf, ksiz, NULL))){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return NULL;
+ }
+ rsiz = CB_DATUMSIZE(recp->first);
+ CB_MALLOC(rbuf, rsiz + 1);
+ memcpy(rbuf, CB_DATUMPTR(recp->first), rsiz);
+ if(recp->rest){
+ for(i = 0; i < CB_LISTNUM(recp->rest); i++){
+ vbuf = CB_LISTVAL2(recp->rest, i, vsiz);
+ CB_REALLOC(rbuf, rsiz + vsiz + 1);
+ memcpy(rbuf + rsiz, vbuf, vsiz);
+ rsiz += vsiz;
+ }
+ }
+ rbuf[rsiz] = '\0';
+ if(!villa->tran && !vlcacheadjust(villa)){
+ free(rbuf);
+ return NULL;
+ }
+ if(sp) *sp = rsiz;
+ return rbuf;
+}
+
+
+/* Move the cursor to the first record. */
+int vlcurfirst(VILLA *villa){
+ VLLEAF *leaf;
+ assert(villa);
+ villa->curleaf = VL_LEAFIDMIN;
+ villa->curknum = 0;
+ villa->curvnum = 0;
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ while(CB_LISTNUM(leaf->recs) < 1){
+ villa->curleaf = leaf->next;
+ villa->curknum = 0;
+ villa->curvnum = 0;
+ if(villa->curleaf == -1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+
+/* Move the cursor to the last record. */
+int vlcurlast(VILLA *villa){
+ VLLEAF *leaf;
+ VLREC *recp;
+ assert(villa);
+ villa->curleaf = villa->last;
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ while(CB_LISTNUM(leaf->recs) < 1){
+ villa->curleaf = leaf->prev;
+ if(villa->curleaf == -1){
+ villa->curleaf = -1;
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ }
+ villa->curknum = CB_LISTNUM(leaf->recs) - 1;
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, villa->curknum);
+ villa->curvnum = recp->rest ? CB_LISTNUM(recp->rest) : 0;
+ return TRUE;
+}
+
+
+/* Move the cursor to the previous record. */
+int vlcurprev(VILLA *villa){
+ VLLEAF *leaf;
+ VLREC *recp;
+ assert(villa);
+ if(villa->curleaf == -1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf)) || CB_LISTNUM(leaf->recs) < 1){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, villa->curknum);
+ villa->curvnum--;
+ if(villa->curvnum < 0){
+ villa->curknum--;
+ if(villa->curknum < 0){
+ villa->curleaf = leaf->prev;
+ if(villa->curleaf == -1){
+ villa->curleaf = -1;
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ while(CB_LISTNUM(leaf->recs) < 1){
+ villa->curleaf = leaf->prev;
+ if(villa->curleaf == -1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ }
+ villa->curknum = CB_LISTNUM(leaf->recs) - 1;
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, villa->curknum);
+ villa->curvnum = recp->rest ? CB_LISTNUM(recp->rest) : 0;
+ }
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, villa->curknum);
+ villa->curvnum = recp->rest ? CB_LISTNUM(recp->rest) : 0;
+ }
+ if(!villa->tran && !vlcacheadjust(villa)) return FALSE;
+ return TRUE;
+}
+
+
+/* Move the cursor to the next record. */
+int vlcurnext(VILLA *villa){
+ VLLEAF *leaf;
+ VLREC *recp;
+ assert(villa);
+ if(villa->curleaf == -1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf)) || CB_LISTNUM(leaf->recs) < 1){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, villa->curknum);
+ villa->curvnum++;
+ if(villa->curvnum > (recp->rest ? CB_LISTNUM(recp->rest) : 0)){
+ villa->curknum++;
+ villa->curvnum = 0;
+ }
+ if(villa->curknum >= CB_LISTNUM(leaf->recs)){
+ villa->curleaf = leaf->next;
+ villa->curknum = 0;
+ villa->curvnum = 0;
+ if(villa->curleaf == -1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ while(CB_LISTNUM(leaf->recs) < 1){
+ villa->curleaf = leaf->next;
+ villa->curknum = 0;
+ villa->curvnum = 0;
+ if(villa->curleaf == -1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ }
+ }
+ if(!villa->tran && !vlcacheadjust(villa)) return FALSE;
+ return TRUE;
+}
+
+
+/* Move the cursor to a position around a record. */
+int vlcurjump(VILLA *villa, const char *kbuf, int ksiz, int jmode){
+ VLLEAF *leaf;
+ VLREC *recp;
+ int pid, index;
+ assert(villa && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if((pid = vlsearchleaf(villa, kbuf, ksiz)) == -1){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, pid))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ while(CB_LISTNUM(leaf->recs) < 1){
+ villa->curleaf = (jmode == VL_JFORWARD) ? leaf->next : leaf->prev;
+ if(villa->curleaf == -1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ }
+ if(!(recp = vlrecsearch(villa, leaf, kbuf, ksiz, &index))){
+ if(jmode == VL_JFORWARD){
+ villa->curleaf = leaf->id;
+ if(index >= CB_LISTNUM(leaf->recs)) index--;
+ villa->curknum = index;
+ villa->curvnum = 0;
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, index);
+ if(villa->cmp(kbuf, ksiz, CB_DATUMPTR(recp->key), CB_DATUMSIZE(recp->key)) < 0) return TRUE;
+ villa->curvnum = (recp->rest ? CB_LISTNUM(recp->rest) : 0);
+ return vlcurnext(villa);
+ } else {
+ villa->curleaf = leaf->id;
+ if(index >= CB_LISTNUM(leaf->recs)) index--;
+ villa->curknum = index;
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, index);
+ villa->curvnum = (recp->rest ? CB_LISTNUM(recp->rest) : 0);
+ if(villa->cmp(kbuf, ksiz, CB_DATUMPTR(recp->key), CB_DATUMSIZE(recp->key)) > 0) return TRUE;
+ villa->curvnum = 0;
+ return vlcurprev(villa);
+ }
+ }
+ if(jmode == VL_JFORWARD){
+ villa->curleaf = pid;
+ villa->curknum = index;
+ villa->curvnum = 0;
+ } else {
+ villa->curleaf = pid;
+ villa->curknum = index;
+ villa->curvnum = (recp->rest ? CB_LISTNUM(recp->rest) : 0);
+ }
+ return TRUE;
+}
+
+
+/* Get the key of the record where the cursor is. */
+char *vlcurkey(VILLA *villa, int *sp){
+ VLLEAF *leaf;
+ VLREC *recp;
+ const char *kbuf;
+ char *rv;
+ int ksiz;
+ assert(villa);
+ if(villa->curleaf == -1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, villa->curknum);
+ kbuf = CB_DATUMPTR(recp->key);
+ ksiz = CB_DATUMSIZE(recp->key);
+ if(sp) *sp = ksiz;
+ CB_MEMDUP(rv, kbuf, ksiz);
+ return rv;
+}
+
+
+/* Get the value of the record where the cursor is. */
+char *vlcurval(VILLA *villa, int *sp){
+ VLLEAF *leaf;
+ VLREC *recp;
+ const char *vbuf;
+ char *rv;
+ int vsiz;
+ assert(villa);
+ if(villa->curleaf == -1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, villa->curknum);
+ if(villa->curvnum < 1){
+ vbuf = CB_DATUMPTR(recp->first);
+ vsiz = CB_DATUMSIZE(recp->first);
+ } else {
+ vbuf = CB_LISTVAL2(recp->rest, villa->curvnum - 1, vsiz);
+ }
+ if(sp) *sp = vsiz;
+ CB_MEMDUP(rv, vbuf, vsiz);
+ return rv;
+}
+
+
+/* Insert a record around the cursor. */
+int vlcurput(VILLA *villa, const char *vbuf, int vsiz, int cpmode){
+ VLLEAF *leaf;
+ VLREC *recp;
+ char *tbuf;
+ int tsiz;
+ assert(villa && vbuf);
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(vsiz < 0) vsiz = strlen(vbuf);
+ if(villa->curleaf == -1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, villa->curknum);
+ switch(cpmode){
+ case VL_CPBEFORE:
+ if(villa->curvnum < 1){
+ if(!recp->rest){
+ CB_DATUMTOMALLOC(recp->first, tbuf, tsiz);
+ CB_DATUMOPEN2(recp->first, vbuf, vsiz);
+ CB_LISTOPEN(recp->rest);
+ CB_LISTPUSHBUF(recp->rest, tbuf, tsiz);
+ } else {
+ cblistunshift(recp->rest, CB_DATUMPTR(recp->first), CB_DATUMSIZE(recp->first));
+ CB_DATUMSETSIZE(recp->first, 0);
+ CB_DATUMCAT(recp->first, vbuf, vsiz);
+ }
+ } else {
+ CB_LISTINSERT(recp->rest, villa->curvnum - 1, vbuf, vsiz);
+ }
+ villa->rnum++;
+ break;
+ case VL_CPAFTER:
+ if(!recp->rest) CB_LISTOPEN(recp->rest);
+ CB_LISTINSERT(recp->rest, villa->curvnum, vbuf, vsiz);
+ villa->curvnum++;
+ villa->rnum++;
+ break;
+ default:
+ if(villa->curvnum < 1){
+ CB_DATUMSETSIZE(recp->first, 0);
+ CB_DATUMCAT(recp->first, vbuf, vsiz);
+ } else {
+ cblistover(recp->rest, villa->curvnum - 1, vbuf, vsiz);
+ }
+ break;
+ }
+ leaf->dirty = TRUE;
+ return TRUE;
+}
+
+
+/* Delete the record where the cursor is. */
+int vlcurout(VILLA *villa){
+ VLLEAF *leaf;
+ VLREC *recp;
+ char *vbuf;
+ int vsiz;
+ assert(villa);
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(villa->curleaf == -1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, villa->curknum);
+ if(villa->curvnum < 1){
+ if(recp->rest){
+ vbuf = cblistshift(recp->rest, &vsiz);
+ CB_DATUMSETSIZE(recp->first, 0);
+ CB_DATUMCAT(recp->first, vbuf, vsiz);
+ free(vbuf);
+ if(CB_LISTNUM(recp->rest) < 1){
+ CB_LISTCLOSE(recp->rest);
+ recp->rest = NULL;
+ }
+ } else {
+ CB_DATUMCLOSE(recp->first);
+ CB_DATUMCLOSE(recp->key);
+ free(cblistremove(leaf->recs, villa->curknum, NULL));
+ }
+ } else {
+ free(cblistremove(recp->rest, villa->curvnum - 1, NULL));
+ if(villa->curvnum - 1 >= CB_LISTNUM(recp->rest)){
+ villa->curknum++;
+ villa->curvnum = 0;
+ }
+ if(CB_LISTNUM(recp->rest) < 1){
+ CB_LISTCLOSE(recp->rest);
+ recp->rest = NULL;
+ }
+ }
+ villa->rnum--;
+ leaf->dirty = TRUE;
+ if(villa->curknum >= CB_LISTNUM(leaf->recs)){
+ villa->curleaf = leaf->next;
+ villa->curknum = 0;
+ villa->curvnum = 0;
+ while(villa->curleaf != -1 && (leaf = vlleafload(villa, villa->curleaf)) != NULL &&
+ CB_LISTNUM(leaf->recs) < 1){
+ villa->curleaf = leaf->next;
+ }
+ }
+ return TRUE;
+}
+
+
+/* Set the tuning parameters for performance. */
+void vlsettuning(VILLA *villa, int lrecmax, int nidxmax, int lcnum, int ncnum){
+ assert(villa);
+ if(lrecmax < 1) lrecmax = VL_DEFLRECMAX;
+ if(lrecmax < 3) lrecmax = 3;
+ if(nidxmax < 1) nidxmax = VL_DEFNIDXMAX;
+ if(nidxmax < 4) nidxmax = 4;
+ if(lcnum < 1) lcnum = VL_DEFLCNUM;
+ if(lcnum < VL_CACHEOUT * 2) lcnum = VL_CACHEOUT * 2;
+ if(ncnum < 1) ncnum = VL_DEFNCNUM;
+ if(ncnum < VL_CACHEOUT * 2) ncnum = VL_CACHEOUT * 2;
+ villa->leafrecmax = lrecmax;
+ villa->nodeidxmax = nidxmax;
+ villa->leafcnum = lcnum;
+ villa->nodecnum = ncnum;
+}
+
+
+/* Set the size of the free block pool of a database handle. */
+int vlsetfbpsiz(VILLA *villa, int size){
+ assert(villa && size >= 0);
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return dpsetfbpsiz(villa->depot, size);
+}
+
+
+/* Synchronize updating contents with the file and the device. */
+int vlsync(VILLA *villa){
+ int err;
+ err = FALSE;
+ if(!vlmemsync(villa)) err = TRUE;
+ if(!dpsync(villa->depot)) err = TRUE;
+ return err ? FALSE : TRUE;
+}
+
+
+/* Optimize a database. */
+int vloptimize(VILLA *villa){
+ int err;
+ assert(villa);
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(villa->tran){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ if(!vlsync(villa)) return FALSE;
+ if(!dpoptimize(villa->depot, -1)) err = TRUE;
+ return err ? FALSE : TRUE;
+}
+
+
+/* Get the name of a database. */
+char *vlname(VILLA *villa){
+ assert(villa);
+ return dpname(villa->depot);
+}
+
+
+/* Get the size of a database file. */
+int vlfsiz(VILLA *villa){
+ return dpfsiz(villa->depot);
+}
+
+
+/* Get the number of the leaf nodes of B+ tree. */
+int vllnum(VILLA *villa){
+ assert(villa);
+ return villa->lnum;
+}
+
+
+/* Get the number of the non-leaf nodes of B+ tree. */
+int vlnnum(VILLA *villa){
+ assert(villa);
+ return villa->nnum;
+}
+
+
+/* Get the number of the records stored in a database. */
+int vlrnum(VILLA *villa){
+ assert(villa);
+ return villa->rnum;
+}
+
+
+/* Check whether a database handle is a writer or not. */
+int vlwritable(VILLA *villa){
+ assert(villa);
+ return villa->wmode;
+}
+
+
+/* Check whether a database has a fatal error or not. */
+int vlfatalerror(VILLA *villa){
+ assert(villa);
+ return dpfatalerror(villa->depot);
+}
+
+
+/* Get the inode number of a database file. */
+int vlinode(VILLA *villa){
+ assert(villa);
+ return dpinode(villa->depot);
+}
+
+
+/* Get the last modified time of a database. */
+time_t vlmtime(VILLA *villa){
+ assert(villa);
+ return dpmtime(villa->depot);
+}
+
+
+/* Begin the transaction. */
+int vltranbegin(VILLA *villa){
+ int err, pid;
+ const char *tmp;
+ VLLEAF *leaf;
+ VLNODE *node;
+ assert(villa);
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(villa->tran){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ cbmapiterinit(villa->leafc);
+ while((tmp = cbmapiternext(villa->leafc, NULL)) != NULL){
+ pid = *(int *)tmp;
+ leaf = (VLLEAF *)cbmapget(villa->leafc, (char *)&pid, sizeof(int), NULL);
+ if(leaf->dirty && !vlleafsave(villa, leaf)) err = TRUE;
+ }
+ cbmapiterinit(villa->nodec);
+ while((tmp = cbmapiternext(villa->nodec, NULL)) != NULL){
+ pid = *(int *)tmp;
+ node = (VLNODE *)cbmapget(villa->nodec, (char *)&pid, sizeof(int), NULL);
+ if(node->dirty && !vlnodesave(villa, node)) err = TRUE;
+ }
+ if(!dpsetalign(villa->depot, 0)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_ROOTKEY, villa->root)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_LASTKEY, villa->last)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_LNUMKEY, villa->lnum)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_NNUMKEY, villa->nnum)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_RNUMKEY, villa->rnum)) err = TRUE;
+ if(!dpmemsync(villa->depot)) err = TRUE;
+ if(!dpsetalign(villa->depot, VL_PAGEALIGN)) err = TRUE;
+ villa->tran = TRUE;
+ villa->rbroot = villa->root;
+ villa->rblast = villa->last;
+ villa->rblnum = villa->lnum;
+ villa->rbnnum = villa->nnum;
+ villa->rbrnum = villa->rnum;
+ return err ? FALSE : TRUE;
+}
+
+
+/* Commit the transaction. */
+int vltrancommit(VILLA *villa){
+ int err, pid;
+ const char *tmp;
+ VLLEAF *leaf;
+ VLNODE *node;
+ assert(villa);
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!villa->tran){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ cbmapiterinit(villa->leafc);
+ while((tmp = cbmapiternext(villa->leafc, NULL)) != NULL){
+ pid = *(int *)tmp;
+ leaf = (VLLEAF *)cbmapget(villa->leafc, (char *)&pid, sizeof(int), NULL);
+ if(leaf->dirty && !vlleafsave(villa, leaf)) err = TRUE;
+ }
+ cbmapiterinit(villa->nodec);
+ while((tmp = cbmapiternext(villa->nodec, NULL)) != NULL){
+ pid = *(int *)tmp;
+ node = (VLNODE *)cbmapget(villa->nodec, (char *)&pid, sizeof(int), NULL);
+ if(node->dirty && !vlnodesave(villa, node)) err = TRUE;
+ }
+ if(!dpsetalign(villa->depot, 0)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_ROOTKEY, villa->root)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_LASTKEY, villa->last)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_LNUMKEY, villa->lnum)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_NNUMKEY, villa->nnum)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_RNUMKEY, villa->rnum)) err = TRUE;
+ if(!dpmemsync(villa->depot)) err = TRUE;
+ if(!dpsetalign(villa->depot, VL_PAGEALIGN)) err = TRUE;
+ villa->tran = FALSE;
+ villa->rbroot = -1;
+ villa->rblast = -1;
+ villa->rblnum = -1;
+ villa->rbnnum = -1;
+ villa->rbrnum = -1;
+ while(cbmaprnum(villa->leafc) > villa->leafcnum || cbmaprnum(villa->nodec) > villa->nodecnum){
+ if(!vlcacheadjust(villa)){
+ err = TRUE;
+ break;
+ }
+ }
+ return err ? FALSE : TRUE;
+}
+
+
+/* Abort the transaction. */
+int vltranabort(VILLA *villa){
+ int err, pid;
+ const char *tmp;
+ VLLEAF *leaf;
+ VLNODE *node;
+ assert(villa);
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!villa->tran){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ cbmapiterinit(villa->leafc);
+ while((tmp = cbmapiternext(villa->leafc, NULL)) != NULL){
+ pid = *(int *)tmp;
+ if(!(leaf = (VLLEAF *)cbmapget(villa->leafc, (char *)&pid, sizeof(int), NULL))){
+ err = TRUE;
+ continue;
+ }
+ if(leaf->dirty){
+ leaf->dirty = FALSE;
+ if(!vlleafcacheout(villa, pid)) err = TRUE;
+ }
+ }
+ cbmapiterinit(villa->nodec);
+ while((tmp = cbmapiternext(villa->nodec, NULL)) != NULL){
+ pid = *(int *)tmp;
+ if(!(node = (VLNODE *)cbmapget(villa->nodec, (char *)&pid, sizeof(int), NULL))){
+ err = TRUE;
+ continue;
+ }
+ if(node->dirty){
+ node->dirty = FALSE;
+ if(!vlnodecacheout(villa, pid)) err = TRUE;
+ }
+ }
+ villa->tran = FALSE;
+ villa->root = villa->rbroot;
+ villa->last = villa->rblast;
+ villa->lnum = villa->rblnum;
+ villa->nnum = villa->rbnnum;
+ villa->rnum = villa->rbrnum;
+ while(cbmaprnum(villa->leafc) > villa->leafcnum || cbmaprnum(villa->nodec) > villa->nodecnum){
+ if(!vlcacheadjust(villa)){
+ err = TRUE;
+ break;
+ }
+ }
+ return err ? FALSE : TRUE;
+}
+
+
+/* Remove a database file. */
+int vlremove(const char *name){
+ assert(name);
+ return dpremove(name);
+}
+
+
+/* Repair a broken database file. */
+int vlrepair(const char *name, VLCFUNC cmp){
+ DEPOT *depot;
+ VILLA *tvilla;
+ char path[VL_PATHBUFSIZ], *kbuf, *vbuf, *zbuf, *rp, *tkbuf, *tvbuf;
+ int i, err, flags, omode, ksiz, vsiz, zsiz, size, step, tksiz, tvsiz, vnum;
+ assert(name && cmp);
+ err = FALSE;
+ if(!dprepair(name)) err = TRUE;
+ if(!(depot = dpopen(name, DP_OREADER, -1))) return FALSE;
+ flags = dpgetflags(depot);
+ if(!(flags & VL_FLISVILLA)){
+ dpclose(depot);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ sprintf(path, "%s%s", name, VL_TMPFSUF);
+ omode = VL_OWRITER | VL_OCREAT | VL_OTRUNC;
+ if(flags & VL_FLISZLIB){
+ omode |= VL_OZCOMP;
+ } else if(flags & VL_FLISLZO){
+ omode |= VL_OXCOMP;
+ } else if(flags & VL_FLISBZIP){
+ omode |= VL_OYCOMP;
+ }
+ if(!(tvilla = vlopen(path, omode, cmp))){
+ dpclose(depot);
+ return FALSE;
+ }
+ if(!dpiterinit(depot)) err = TRUE;
+ while((kbuf = dpiternext(depot, &ksiz)) != NULL){
+ if(ksiz == sizeof(int) && *(int *)kbuf < VL_NODEIDMIN && *(int *)kbuf > 0){
+ if((vbuf = dpget(depot, (char *)kbuf, sizeof(int), 0, -1, &vsiz)) != NULL){
+ if(_qdbm_inflate && (flags & VL_FLISZLIB) &&
+ (zbuf = _qdbm_inflate(vbuf, vsiz, &zsiz, _QDBM_ZMRAW)) != NULL){
+ free(vbuf);
+ vbuf = zbuf;
+ vsiz = zsiz;
+ } else if(_qdbm_lzodecode && (flags & VL_FLISLZO) &&
+ (zbuf = _qdbm_lzodecode(vbuf, vsiz, &zsiz)) != NULL){
+ free(vbuf);
+ vbuf = zbuf;
+ vsiz = zsiz;
+ } else if(_qdbm_bzdecode && (flags & VL_FLISBZIP) &&
+ (zbuf = _qdbm_bzdecode(vbuf, vsiz, &zsiz)) != NULL){
+ free(vbuf);
+ vbuf = zbuf;
+ vsiz = zsiz;
+ }
+ rp = vbuf;
+ size = vsiz;
+ if(size >= 1){
+ VL_READVNUMBUF(rp, size, vnum, step);
+ rp += step;
+ size -= step;
+ }
+ if(size >= 1){
+ VL_READVNUMBUF(rp, size, vnum, step);
+ rp += step;
+ size -= step;
+ }
+ while(size >= 1){
+ VL_READVNUMBUF(rp, size, tksiz, step);
+ rp += step;
+ size -= step;
+ if(size < tksiz) break;
+ tkbuf = rp;
+ rp += tksiz;
+ size -= tksiz;
+ if(size < 1) break;
+ VL_READVNUMBUF(rp, size, vnum, step);
+ rp += step;
+ size -= step;
+ if(vnum < 1 || size < 1) break;
+ for(i = 0; i < vnum && size >= 1; i++){
+ VL_READVNUMBUF(rp, size, tvsiz, step);
+ rp += step;
+ size -= step;
+ if(size < tvsiz) break;
+ tvbuf = rp;
+ rp += tvsiz;
+ size -= tvsiz;
+ if(!vlput(tvilla, tkbuf, tksiz, tvbuf, tvsiz, VL_DDUP)) err = TRUE;
+ }
+ }
+ free(vbuf);
+ }
+ }
+ free(kbuf);
+ }
+ if(!vlclose(tvilla)) err = TRUE;
+ if(!dpclose(depot)) err = TRUE;
+ if(!dpremove(name)) err = TRUE;
+ if(rename(path, name) == -1){
+ if(!err) dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ return err ? FALSE : TRUE;
+}
+
+
+/* Dump all records as endian independent data. */
+int vlexportdb(VILLA *villa, const char *name){
+ DEPOT *depot;
+ char path[VL_PATHBUFSIZ], *kbuf, *vbuf, *nkey;
+ int i, err, ksiz, vsiz, ki;
+ assert(villa && name);
+ sprintf(path, "%s%s", name, VL_TMPFSUF);
+ if(!(depot = dpopen(path, DP_OWRITER | DP_OCREAT | DP_OTRUNC, -1))) return FALSE;
+ err = FALSE;
+ vlcurfirst(villa);
+ for(i = 0; !err && (kbuf = vlcurkey(villa, &ksiz)) != NULL; i++){
+ if((vbuf = vlcurval(villa, &vsiz)) != NULL){
+ CB_MALLOC(nkey, ksiz + VL_NUMBUFSIZ);
+ ki = sprintf(nkey, "%X\t", i);
+ memcpy(nkey + ki, kbuf, ksiz);
+ if(!dpput(depot, nkey, ki + ksiz, vbuf, vsiz, DP_DKEEP)) err = TRUE;
+ free(nkey);
+ free(vbuf);
+ } else {
+ err = TRUE;
+ }
+ free(kbuf);
+ vlcurnext(villa);
+ }
+ if(!dpexportdb(depot, name)) err = TRUE;
+ if(!dpclose(depot)) err = TRUE;
+ if(!dpremove(path)) err = TRUE;
+ return !err && !vlfatalerror(villa);
+}
+
+
+/* Load all records from endian independent data. */
+int vlimportdb(VILLA *villa, const char *name){
+ DEPOT *depot;
+ char path[VL_PATHBUFSIZ], *kbuf, *vbuf, *rp;
+ int err, ksiz, vsiz;
+ assert(villa && name);
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(vlrnum(villa) > 0){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ kbuf = dpname(villa->depot);
+ sprintf(path, "%s%s", kbuf, VL_TMPFSUF);
+ free(kbuf);
+ if(!(depot = dpopen(path, DP_OWRITER | DP_OCREAT | DP_OTRUNC, -1))) return FALSE;
+ err = FALSE;
+ if(!dpimportdb(depot, name)) err = TRUE;
+ dpiterinit(depot);
+ while(!err && (kbuf = dpiternext(depot, &ksiz)) != NULL){
+ if((vbuf = dpget(depot, kbuf, ksiz, 0, -1, &vsiz)) != NULL){
+ if((rp = strchr(kbuf, '\t')) != NULL){
+ rp++;
+ if(!vlput(villa, rp, ksiz - (rp - kbuf), vbuf, vsiz, VL_DDUP)) err = TRUE;
+ } else {
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ err = TRUE;
+ }
+ free(vbuf);
+ } else {
+ err = TRUE;
+ }
+ free(kbuf);
+ }
+ if(!dpclose(depot)) err = TRUE;
+ if(!dpremove(path)) err = TRUE;
+ return !err && !vlfatalerror(villa);
+}
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Number of division of the database for Vista. */
+int *vlcrdnumptr(void){
+ static int defvlcrdnum = VL_CRDNUM;
+ void *ptr;
+ if(_qdbm_ptsafe){
+ if(!(ptr = _qdbm_settsd(&defvlcrdnum, sizeof(int), &defvlcrdnum))){
+ defvlcrdnum = DP_EMISC;
+ return &defvlcrdnum;
+ }
+ return (int *)ptr;
+ }
+ return &defvlcrdnum;
+}
+
+
+/* Synchronize updating contents on memory. */
+int vlmemsync(VILLA *villa){
+ int err, pid;
+ const char *tmp;
+ assert(villa);
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(villa->tran){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ cbmapiterinit(villa->leafc);
+ while((tmp = cbmapiternext(villa->leafc, NULL)) != NULL){
+ pid = *(int *)tmp;
+ if(!vlleafcacheout(villa, pid)) err = TRUE;
+ }
+ cbmapiterinit(villa->nodec);
+ while((tmp = cbmapiternext(villa->nodec, NULL)) != NULL){
+ pid = *(int *)tmp;
+ if(!vlnodecacheout(villa, pid)) err = TRUE;
+ }
+ if(!dpsetalign(villa->depot, 0)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_ROOTKEY, villa->root)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_LASTKEY, villa->last)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_LNUMKEY, villa->lnum)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_NNUMKEY, villa->nnum)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_RNUMKEY, villa->rnum)) err = TRUE;
+ if(!dpsetalign(villa->depot, VL_PAGEALIGN)) err = TRUE;
+ if(!dpmemsync(villa->depot)) err = TRUE;
+ return err ? FALSE : TRUE;
+}
+
+
+/* Synchronize updating contents on memory, not physically. */
+int vlmemflush(VILLA *villa){
+ int err, pid;
+ const char *tmp;
+ assert(villa);
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(villa->tran){
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ err = FALSE;
+ cbmapiterinit(villa->leafc);
+ while((tmp = cbmapiternext(villa->leafc, NULL)) != NULL){
+ pid = *(int *)tmp;
+ if(!vlleafcacheout(villa, pid)) err = TRUE;
+ }
+ cbmapiterinit(villa->nodec);
+ while((tmp = cbmapiternext(villa->nodec, NULL)) != NULL){
+ pid = *(int *)tmp;
+ if(!vlnodecacheout(villa, pid)) err = TRUE;
+ }
+ if(!dpsetalign(villa->depot, 0)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_ROOTKEY, villa->root)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_LASTKEY, villa->last)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_LNUMKEY, villa->lnum)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_NNUMKEY, villa->nnum)) err = TRUE;
+ if(!vldpputnum(villa->depot, VL_RNUMKEY, villa->rnum)) err = TRUE;
+ if(!dpsetalign(villa->depot, VL_PAGEALIGN)) err = TRUE;
+ if(!dpmemflush(villa->depot)) err = TRUE;
+ return err ? FALSE : TRUE;
+}
+
+
+/* Refer to a volatile cache of a value of a record. */
+const char *vlgetcache(VILLA *villa, const char *kbuf, int ksiz, int *sp){
+ VLLEAF *leaf;
+ VLREC *recp;
+ int pid;
+ assert(villa && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(villa->hleaf < VL_LEAFIDMIN || !(leaf = vlgethistleaf(villa, kbuf, ksiz))){
+ if((pid = vlsearchleaf(villa, kbuf, ksiz)) == -1) return NULL;
+ if(!(leaf = vlleafload(villa, pid))) return NULL;
+ }
+ if(!(recp = vlrecsearch(villa, leaf, kbuf, ksiz, NULL))){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(!villa->tran && !vlcacheadjust(villa)) return NULL;
+ if(sp) *sp = CB_DATUMSIZE(recp->first);
+ return CB_DATUMPTR(recp->first);
+}
+
+
+/* Refer to volatile cache of the key of the record where the cursor is. */
+const char *vlcurkeycache(VILLA *villa, int *sp){
+ VLLEAF *leaf;
+ VLREC *recp;
+ const char *kbuf;
+ int ksiz;
+ assert(villa);
+ if(villa->curleaf == -1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, villa->curknum);
+ kbuf = CB_DATUMPTR(recp->key);
+ ksiz = CB_DATUMSIZE(recp->key);
+ if(sp) *sp = ksiz;
+ return kbuf;
+}
+
+
+/* Refer to volatile cache of the value of the record where the cursor is. */
+const char *vlcurvalcache(VILLA *villa, int *sp){
+ VLLEAF *leaf;
+ VLREC *recp;
+ const char *vbuf;
+ int vsiz;
+ assert(villa);
+ if(villa->curleaf == -1){
+ dpecodeset(DP_ENOITEM, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!(leaf = vlleafload(villa, villa->curleaf))){
+ villa->curleaf = -1;
+ return FALSE;
+ }
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, villa->curknum);
+ if(villa->curvnum < 1){
+ vbuf = CB_DATUMPTR(recp->first);
+ vsiz = CB_DATUMSIZE(recp->first);
+ } else {
+ vbuf = CB_LISTVAL2(recp->rest, villa->curvnum - 1, vsiz);
+ }
+ if(sp) *sp = vsiz;
+ return vbuf;
+}
+
+
+/* Get a multiple cursor handle. */
+VLMULCUR *vlmulcuropen(VILLA *villa){
+ VLMULCUR *mulcur;
+ assert(villa);
+ if(villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return NULL;
+ }
+ CB_MALLOC(mulcur, sizeof(VLMULCUR));
+ mulcur->villa = villa;
+ mulcur->curleaf = -1;
+ mulcur->curknum = -1;
+ mulcur->curvnum = -1;
+ return mulcur;
+}
+
+
+/* Close a multiple cursor handle. */
+void vlmulcurclose(VLMULCUR *mulcur){
+ assert(mulcur);
+ free(mulcur);
+}
+
+
+/* Move a multiple cursor to the first record. */
+int vlmulcurfirst(VLMULCUR *mulcur){
+ VLMULCUR swap;
+ int rv;
+ assert(mulcur);
+ swap.curleaf = mulcur->villa->curleaf;
+ swap.curknum = mulcur->villa->curknum;
+ swap.curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = mulcur->curleaf;
+ mulcur->villa->curknum = mulcur->curknum;
+ mulcur->villa->curvnum = mulcur->curvnum;
+ rv = vlcurfirst(mulcur->villa);
+ mulcur->curleaf = mulcur->villa->curleaf;
+ mulcur->curknum = mulcur->villa->curknum;
+ mulcur->curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = swap.curleaf;
+ mulcur->villa->curknum = swap.curknum;
+ mulcur->villa->curvnum = swap.curvnum;
+ return rv;
+}
+
+
+/* Move a multiple cursor to the last record. */
+int vlmulcurlast(VLMULCUR *mulcur){
+ VLMULCUR swap;
+ int rv;
+ assert(mulcur);
+ swap.curleaf = mulcur->villa->curleaf;
+ swap.curknum = mulcur->villa->curknum;
+ swap.curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = mulcur->curleaf;
+ mulcur->villa->curknum = mulcur->curknum;
+ mulcur->villa->curvnum = mulcur->curvnum;
+ rv = vlcurlast(mulcur->villa);
+ mulcur->curleaf = mulcur->villa->curleaf;
+ mulcur->curknum = mulcur->villa->curknum;
+ mulcur->curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = swap.curleaf;
+ mulcur->villa->curknum = swap.curknum;
+ mulcur->villa->curvnum = swap.curvnum;
+ return rv;
+}
+
+
+/* Move a multiple cursor to the previous record. */
+int vlmulcurprev(VLMULCUR *mulcur){
+ VLMULCUR swap;
+ int rv;
+ assert(mulcur);
+ swap.curleaf = mulcur->villa->curleaf;
+ swap.curknum = mulcur->villa->curknum;
+ swap.curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = mulcur->curleaf;
+ mulcur->villa->curknum = mulcur->curknum;
+ mulcur->villa->curvnum = mulcur->curvnum;
+ rv = vlcurprev(mulcur->villa);
+ mulcur->curleaf = mulcur->villa->curleaf;
+ mulcur->curknum = mulcur->villa->curknum;
+ mulcur->curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = swap.curleaf;
+ mulcur->villa->curknum = swap.curknum;
+ mulcur->villa->curvnum = swap.curvnum;
+ return rv;
+}
+
+
+/* Move a multiple cursor to the next record. */
+int vlmulcurnext(VLMULCUR *mulcur){
+ VLMULCUR swap;
+ int rv;
+ assert(mulcur);
+ swap.curleaf = mulcur->villa->curleaf;
+ swap.curknum = mulcur->villa->curknum;
+ swap.curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = mulcur->curleaf;
+ mulcur->villa->curknum = mulcur->curknum;
+ mulcur->villa->curvnum = mulcur->curvnum;
+ rv = vlcurnext(mulcur->villa);
+ mulcur->curleaf = mulcur->villa->curleaf;
+ mulcur->curknum = mulcur->villa->curknum;
+ mulcur->curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = swap.curleaf;
+ mulcur->villa->curknum = swap.curknum;
+ mulcur->villa->curvnum = swap.curvnum;
+ return rv;
+}
+
+
+/* Move a multiple cursor to a position around a record. */
+int vlmulcurjump(VLMULCUR *mulcur, const char *kbuf, int ksiz, int jmode){
+ VLMULCUR swap;
+ int rv;
+ assert(mulcur);
+ swap.curleaf = mulcur->villa->curleaf;
+ swap.curknum = mulcur->villa->curknum;
+ swap.curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = mulcur->curleaf;
+ mulcur->villa->curknum = mulcur->curknum;
+ mulcur->villa->curvnum = mulcur->curvnum;
+ rv = vlcurjump(mulcur->villa, kbuf, ksiz, jmode);
+ mulcur->curleaf = mulcur->villa->curleaf;
+ mulcur->curknum = mulcur->villa->curknum;
+ mulcur->curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = swap.curleaf;
+ mulcur->villa->curknum = swap.curknum;
+ mulcur->villa->curvnum = swap.curvnum;
+ return rv;
+}
+
+
+/* Get the key of the record where a multiple cursor is. */
+char *vlmulcurkey(VLMULCUR *mulcur, int *sp){
+ VLMULCUR swap;
+ char *rv;
+ assert(mulcur);
+ swap.curleaf = mulcur->villa->curleaf;
+ swap.curknum = mulcur->villa->curknum;
+ swap.curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = mulcur->curleaf;
+ mulcur->villa->curknum = mulcur->curknum;
+ mulcur->villa->curvnum = mulcur->curvnum;
+ rv = vlcurkey(mulcur->villa, sp);
+ mulcur->curleaf = mulcur->villa->curleaf;
+ mulcur->curknum = mulcur->villa->curknum;
+ mulcur->curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = swap.curleaf;
+ mulcur->villa->curknum = swap.curknum;
+ mulcur->villa->curvnum = swap.curvnum;
+ return rv;
+}
+
+
+/* Get the value of the record where a multiple cursor is. */
+char *vlmulcurval(VLMULCUR *mulcur, int *sp){
+ VLMULCUR swap;
+ char *rv;
+ assert(mulcur);
+ swap.curleaf = mulcur->villa->curleaf;
+ swap.curknum = mulcur->villa->curknum;
+ swap.curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = mulcur->curleaf;
+ mulcur->villa->curknum = mulcur->curknum;
+ mulcur->villa->curvnum = mulcur->curvnum;
+ rv = vlcurval(mulcur->villa, sp);
+ mulcur->curleaf = mulcur->villa->curleaf;
+ mulcur->curknum = mulcur->villa->curknum;
+ mulcur->curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = swap.curleaf;
+ mulcur->villa->curknum = swap.curknum;
+ mulcur->villa->curvnum = swap.curvnum;
+ return rv;
+}
+
+
+/* Refer to volatile cache of the key of the record where a multiple cursor is. */
+const char *vlmulcurkeycache(VLMULCUR *mulcur, int *sp){
+ VLMULCUR swap;
+ const char *rv;
+ assert(mulcur);
+ swap.curleaf = mulcur->villa->curleaf;
+ swap.curknum = mulcur->villa->curknum;
+ swap.curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = mulcur->curleaf;
+ mulcur->villa->curknum = mulcur->curknum;
+ mulcur->villa->curvnum = mulcur->curvnum;
+ rv = vlcurkeycache(mulcur->villa, sp);
+ mulcur->curleaf = mulcur->villa->curleaf;
+ mulcur->curknum = mulcur->villa->curknum;
+ mulcur->curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = swap.curleaf;
+ mulcur->villa->curknum = swap.curknum;
+ mulcur->villa->curvnum = swap.curvnum;
+ return rv;
+}
+
+
+/* Refer to volatile cache of the value of the record where a multiple cursor is. */
+const char *vlmulcurvalcache(VLMULCUR *mulcur, int *sp){
+ VLMULCUR swap;
+ const char *rv;
+ assert(mulcur);
+ swap.curleaf = mulcur->villa->curleaf;
+ swap.curknum = mulcur->villa->curknum;
+ swap.curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = mulcur->curleaf;
+ mulcur->villa->curknum = mulcur->curknum;
+ mulcur->villa->curvnum = mulcur->curvnum;
+ rv = vlcurvalcache(mulcur->villa, sp);
+ mulcur->curleaf = mulcur->villa->curleaf;
+ mulcur->curknum = mulcur->villa->curknum;
+ mulcur->curvnum = mulcur->villa->curvnum;
+ mulcur->villa->curleaf = swap.curleaf;
+ mulcur->villa->curknum = swap.curknum;
+ mulcur->villa->curvnum = swap.curvnum;
+ return rv;
+}
+
+
+
+/*************************************************************************************************
+ * private objects
+ *************************************************************************************************/
+
+
+/* Compare keys of two records by lexical order.
+ `aptr' specifies the pointer to the region of one key.
+ `asiz' specifies the size of the region of one key.
+ `bptr' specifies the pointer to the region of the other key.
+ `bsiz' specifies the size of the region of the other key.
+ The return value is positive if the former is big, negative if the latter is big, 0 if both
+ are equivalent. */
+static int vllexcompare(const char *aptr, int asiz, const char *bptr, int bsiz){
+ int i, min;
+ assert(aptr && asiz >= 0 && bptr && bsiz >= 0);
+ min = asiz < bsiz ? asiz : bsiz;
+ for(i = 0; i < min; i++){
+ if(((unsigned char *)aptr)[i] != ((unsigned char *)bptr)[i])
+ return ((unsigned char *)aptr)[i] - ((unsigned char *)bptr)[i];
+ }
+ if(asiz == bsiz) return 0;
+ return asiz - bsiz;
+}
+
+
+/* Compare keys of two records as native integers.
+ `aptr' specifies the pointer to the region of one key.
+ `asiz' specifies the size of the region of one key.
+ `bptr' specifies the pointer to the region of the other key.
+ `bsiz' specifies the size of the region of the other key.
+ The return value is positive if the former is big, negative if the latter is big, 0 if both
+ are equivalent. */
+static int vlintcompare(const char *aptr, int asiz, const char *bptr, int bsiz){
+ int anum, bnum;
+ assert(aptr && asiz >= 0 && bptr && bsiz >= 0);
+ if(asiz != bsiz) return asiz - bsiz;
+ anum = (asiz == sizeof(int) ? *(int *)aptr : INT_MIN);
+ bnum = (bsiz == sizeof(int) ? *(int *)bptr : INT_MIN);
+ return anum - bnum;
+}
+
+
+/* Compare keys of two records as numbers of big endian.
+ `aptr' specifies the pointer to the region of one key.
+ `asiz' specifies the size of the region of one key.
+ `bptr' specifies the pointer to the region of the other key.
+ `bsiz' specifies the size of the region of the other key.
+ The return value is positive if the former is big, negative if the latter is big, 0 if both
+ are equivalent. */
+static int vlnumcompare(const char *aptr, int asiz, const char *bptr, int bsiz){
+ int i;
+ assert(aptr && asiz >= 0 && bptr && bsiz >= 0);
+ if(asiz != bsiz) return asiz - bsiz;
+ for(i = 0; i < asiz; i++){
+ if(aptr[i] != bptr[i]) return aptr[i] - bptr[i];
+ }
+ return 0;
+}
+
+
+/* Compare keys of two records as numeric strings of octal, decimal or hexadecimal.
+ `aptr' specifies the pointer to the region of one key.
+ `asiz' specifies the size of the region of one key.
+ `bptr' specifies the pointer to the region of the other key.
+ `bsiz' specifies the size of the region of the other key.
+ The return value is positive if the former is big, negative if the latter is big, 0 if both
+ are equivalent. */
+static int vldeccompare(const char *aptr, int asiz, const char *bptr, int bsiz){
+ assert(aptr && asiz >= 0 && bptr && bsiz >= 0);
+ return (int)(strtod(aptr, NULL) - strtod(bptr, NULL));
+}
+
+
+/* Store a record composed of a pair of integers.
+ `depot' specifies an internal database handle.
+ `knum' specifies an integer of the key.
+ `vnum' specifies an integer of the value.
+ The return value is true if successful, else, it is false. */
+static int vldpputnum(DEPOT *depot, int knum, int vnum){
+ assert(depot);
+ return dpput(depot, (char *)&knum, sizeof(int), (char *)&vnum, sizeof(int), DP_DOVER);
+}
+
+
+/* Retrieve a record composed of a pair of integers.
+ `depot' specifies an internal database handle.
+ `knum' specifies an integer of the key.
+ `vip' specifies the pointer to a variable to assign the result to.
+ The return value is true if successful, else, it is false. */
+static int vldpgetnum(DEPOT *depot, int knum, int *vnp){
+ char *vbuf;
+ int vsiz;
+ assert(depot && vnp);
+ vbuf = dpget(depot, (char *)&knum, sizeof(int), 0, -1, &vsiz);
+ if(!vbuf || vsiz != sizeof(int)){
+ free(vbuf);
+ return FALSE;
+ }
+ *vnp = *(int *)vbuf;
+ free(vbuf);
+ return TRUE;
+}
+
+
+/* Create a new leaf.
+ `villa' specifies a database handle.
+ `prev' specifies the ID number of the previous leaf.
+ `next' specifies the ID number of the previous leaf.
+ The return value is a handle of the leaf. */
+static VLLEAF *vlleafnew(VILLA *villa, int prev, int next){
+ VLLEAF lent;
+ assert(villa);
+ lent.id = villa->lnum + VL_LEAFIDMIN;
+ lent.dirty = TRUE;
+ CB_LISTOPEN(lent.recs);
+ lent.prev = prev;
+ lent.next = next;
+ villa->lnum++;
+ cbmapput(villa->leafc, (char *)&(lent.id), sizeof(int), (char *)&lent, sizeof(VLLEAF), TRUE);
+ return (VLLEAF *)cbmapget(villa->leafc, (char *)&(lent.id), sizeof(int), NULL);
+}
+
+
+/* Remove a leaf from the cache.
+ `villa' specifies a database handle.
+ `id' specifies the ID number of the leaf.
+ The return value is true if successful, else, it is false. */
+static int vlleafcacheout(VILLA *villa, int id){
+ VLLEAF *leaf;
+ VLREC *recp;
+ CBLIST *recs;
+ int i, err, ln;
+ assert(villa && id >= VL_LEAFIDMIN);
+ if(!(leaf = (VLLEAF *)cbmapget(villa->leafc, (char *)&id, sizeof(int), NULL))) return FALSE;
+ err = FALSE;
+ if(leaf->dirty && !vlleafsave(villa, leaf)) err = TRUE;
+ recs = leaf->recs;
+ ln = CB_LISTNUM(recs);
+ for(i = 0; i < ln; i++){
+ recp = (VLREC *)CB_LISTVAL(recs, i);
+ CB_DATUMCLOSE(recp->key);
+ CB_DATUMCLOSE(recp->first);
+ if(recp->rest) CB_LISTCLOSE(recp->rest);
+ }
+ CB_LISTCLOSE(recs);
+ cbmapout(villa->leafc, (char *)&id, sizeof(int));
+ return err ? FALSE : TRUE;
+}
+
+
+/* Save a leaf into the database.
+ `villa' specifies a database handle.
+ `leaf' specifies a leaf handle.
+ The return value is true if successful, else, it is false. */
+static int vlleafsave(VILLA *villa, VLLEAF *leaf){
+ VLREC *recp;
+ CBLIST *recs;
+ CBDATUM *buf;
+ char vnumbuf[VL_VNUMBUFSIZ], *zbuf;
+ const char *vbuf;
+ int i, j, ksiz, vnum, vsiz, prev, next, vnumsiz, ln, zsiz;
+ assert(villa && leaf);
+ CB_DATUMOPEN(buf);
+ prev = leaf->prev;
+ if(prev == -1) prev = VL_NODEIDMIN - 1;
+ VL_SETVNUMBUF(vnumsiz, vnumbuf, prev);
+ CB_DATUMCAT(buf, vnumbuf, vnumsiz);
+ next = leaf->next;
+ if(next == -1) next = VL_NODEIDMIN - 1;
+ VL_SETVNUMBUF(vnumsiz, vnumbuf, next);
+ CB_DATUMCAT(buf, vnumbuf, vnumsiz);
+ recs = leaf->recs;
+ ln = CB_LISTNUM(recs);
+ for(i = 0; i < ln; i++){
+ recp = (VLREC *)CB_LISTVAL(recs, i);
+ ksiz = CB_DATUMSIZE(recp->key);
+ VL_SETVNUMBUF(vnumsiz, vnumbuf, ksiz);
+ CB_DATUMCAT(buf, vnumbuf, vnumsiz);
+ CB_DATUMCAT(buf, CB_DATUMPTR(recp->key), ksiz);
+ vnum = 1 + (recp->rest ? CB_LISTNUM(recp->rest) : 0);
+ VL_SETVNUMBUF(vnumsiz, vnumbuf, vnum);
+ CB_DATUMCAT(buf, vnumbuf, vnumsiz);
+ vsiz = CB_DATUMSIZE(recp->first);
+ VL_SETVNUMBUF(vnumsiz, vnumbuf, vsiz);
+ CB_DATUMCAT(buf, vnumbuf, vnumsiz);
+ CB_DATUMCAT(buf, CB_DATUMPTR(recp->first), vsiz);
+ if(recp->rest){
+ for(j = 0; j < CB_LISTNUM(recp->rest); j++){
+ vbuf = CB_LISTVAL2(recp->rest, j, vsiz);
+ VL_SETVNUMBUF(vnumsiz, vnumbuf, vsiz);
+ CB_DATUMCAT(buf, vnumbuf, vnumsiz);
+ CB_DATUMCAT(buf, vbuf, vsiz);
+ }
+ }
+ }
+ if(_qdbm_deflate && villa->cmode == VL_OZCOMP){
+ if(!(zbuf = _qdbm_deflate(CB_DATUMPTR(buf), CB_DATUMSIZE(buf), &zsiz, _QDBM_ZMRAW))){
+ CB_DATUMCLOSE(buf);
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!dpput(villa->depot, (char *)&(leaf->id), sizeof(int), zbuf, zsiz, DP_DOVER)){
+ CB_DATUMCLOSE(buf);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ free(zbuf);
+ } else if(_qdbm_lzoencode && villa->cmode == VL_OYCOMP){
+ if(!(zbuf = _qdbm_lzoencode(CB_DATUMPTR(buf), CB_DATUMSIZE(buf), &zsiz))){
+ CB_DATUMCLOSE(buf);
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!dpput(villa->depot, (char *)&(leaf->id), sizeof(int), zbuf, zsiz, DP_DOVER)){
+ CB_DATUMCLOSE(buf);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ free(zbuf);
+ } else if(_qdbm_bzencode && villa->cmode == VL_OXCOMP){
+ if(!(zbuf = _qdbm_bzencode(CB_DATUMPTR(buf), CB_DATUMSIZE(buf), &zsiz))){
+ CB_DATUMCLOSE(buf);
+ dpecodeset(DP_EMISC, __FILE__, __LINE__);
+ return FALSE;
+ }
+ if(!dpput(villa->depot, (char *)&(leaf->id), sizeof(int), zbuf, zsiz, DP_DOVER)){
+ CB_DATUMCLOSE(buf);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ free(zbuf);
+ } else {
+ if(!dpput(villa->depot, (char *)&(leaf->id), sizeof(int),
+ CB_DATUMPTR(buf), CB_DATUMSIZE(buf), DP_DOVER)){
+ CB_DATUMCLOSE(buf);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ }
+ CB_DATUMCLOSE(buf);
+ leaf->dirty = FALSE;
+ return TRUE;
+}
+
+
+/* Load a leaf from the database.
+ `villa' specifies a database handle.
+ `id' specifies the ID number of the leaf.
+ If successful, the return value is the pointer to the leaf, else, it is `NULL'. */
+static VLLEAF *vlleafload(VILLA *villa, int id){
+ char wbuf[VL_PAGEBUFSIZ], *buf, *rp, *kbuf, *vbuf, *zbuf;
+ int i, size, step, ksiz, vnum, vsiz, prev, next, zsiz;
+ VLLEAF *leaf, lent;
+ VLREC rec;
+ assert(villa && id >= VL_LEAFIDMIN);
+ if((leaf = (VLLEAF *)cbmapget(villa->leafc, (char *)&id, sizeof(int), NULL)) != NULL){
+ cbmapmove(villa->leafc, (char *)&id, sizeof(int), FALSE);
+ return leaf;
+ }
+ ksiz = -1;
+ prev = -1;
+ next = -1;
+ if((size = dpgetwb(villa->depot, (char *)&id, sizeof(int), 0, VL_PAGEBUFSIZ, wbuf)) > 0 &&
+ size < VL_PAGEBUFSIZ){
+ buf = NULL;
+ } else if(!(buf = dpget(villa->depot, (char *)&id, sizeof(int), 0, -1, &size))){
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ if(_qdbm_inflate && villa->cmode == VL_OZCOMP){
+ if(!(zbuf = _qdbm_inflate(buf ? buf : wbuf, size, &zsiz, _QDBM_ZMRAW))){
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ free(buf);
+ return NULL;
+ }
+ free(buf);
+ buf = zbuf;
+ size = zsiz;
+ } else if(_qdbm_lzodecode && villa->cmode == VL_OYCOMP){
+ if(!(zbuf = _qdbm_lzodecode(buf ? buf : wbuf, size, &zsiz))){
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ free(buf);
+ return NULL;
+ }
+ free(buf);
+ buf = zbuf;
+ size = zsiz;
+ } else if(_qdbm_bzdecode && villa->cmode == VL_OXCOMP){
+ if(!(zbuf = _qdbm_bzdecode(buf ? buf : wbuf, size, &zsiz))){
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ free(buf);
+ return NULL;
+ }
+ free(buf);
+ buf = zbuf;
+ size = zsiz;
+ }
+ rp = buf ? buf : wbuf;
+ if(size >= 1){
+ VL_READVNUMBUF(rp, size, prev, step);
+ rp += step;
+ size -= step;
+ if(prev >= VL_NODEIDMIN - 1) prev = -1;
+ }
+ if(size >= 1){
+ VL_READVNUMBUF(rp, size, next, step);
+ rp += step;
+ size -= step;
+ if(next >= VL_NODEIDMIN - 1) next = -1;
+ }
+ lent.id = id;
+ lent.dirty = FALSE;
+ CB_LISTOPEN(lent.recs);
+ lent.prev = prev;
+ lent.next = next;
+ while(size >= 1){
+ VL_READVNUMBUF(rp, size, ksiz, step);
+ rp += step;
+ size -= step;
+ if(size < ksiz) break;
+ kbuf = rp;
+ rp += ksiz;
+ size -= ksiz;
+ VL_READVNUMBUF(rp, size, vnum, step);
+ rp += step;
+ size -= step;
+ if(vnum < 1 || size < 1) break;
+ for(i = 0; i < vnum && size >= 1; i++){
+ VL_READVNUMBUF(rp, size, vsiz, step);
+ rp += step;
+ size -= step;
+ if(size < vsiz) break;
+ vbuf = rp;
+ rp += vsiz;
+ size -= vsiz;
+ if(i < 1){
+ CB_DATUMOPEN2(rec.key, kbuf, ksiz);
+ CB_DATUMOPEN2(rec.first, vbuf, vsiz);
+ rec.rest = NULL;
+ } else {
+ if(!rec.rest) CB_LISTOPEN(rec.rest);
+ CB_LISTPUSH(rec.rest, vbuf, vsiz);
+ }
+ }
+ if(i > 0) CB_LISTPUSH(lent.recs, (char *)&rec, sizeof(VLREC));
+ }
+ free(buf);
+ cbmapput(villa->leafc, (char *)&(lent.id), sizeof(int), (char *)&lent, sizeof(VLLEAF), TRUE);
+ return (VLLEAF *)cbmapget(villa->leafc, (char *)&(lent.id), sizeof(int), NULL);
+}
+
+
+/* Load the historical leaf from the database.
+ `villa' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key.
+ If successful, the return value is the pointer to the leaf, else, it is `NULL'. */
+static VLLEAF *vlgethistleaf(VILLA *villa, const char *kbuf, int ksiz){
+ VLLEAF *leaf;
+ VLREC *recp;
+ int ln, rv;
+ assert(villa && kbuf && ksiz >= 0);
+ if(!(leaf = vlleafload(villa, villa->hleaf))) return NULL;
+ if((ln = CB_LISTNUM(leaf->recs)) < 2) return NULL;
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, 0);
+ rv = villa->cmp(kbuf, ksiz, CB_DATUMPTR(recp->key), CB_DATUMSIZE(recp->key));
+ if(rv == 0) return leaf;
+ if(rv < 0) return NULL;
+ recp = (VLREC *)CB_LISTVAL(leaf->recs, ln - 1);
+ rv = villa->cmp(kbuf, ksiz, CB_DATUMPTR(recp->key), CB_DATUMSIZE(recp->key));
+ if(rv <= 0 || leaf->next < VL_LEAFIDMIN) return leaf;
+ return NULL;
+}
+
+
+/* Add a record to a leaf.
+ `villa' specifies a database handle.
+ `leaf' specifies a leaf handle.
+ `dmode' specifies behavior when the key overlaps.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key.
+ `vbuf' specifies the pointer to the region of a value.
+ `vsiz' specifies the size of the region of the value.
+ The return value is true if successful, else, it is false. */
+static int vlleafaddrec(VILLA *villa, VLLEAF *leaf, int dmode,
+ const char *kbuf, int ksiz, const char *vbuf, int vsiz){
+ VLREC *recp, rec;
+ CBLIST *recs;
+ int i, rv, left, right, ln, tsiz;
+ char *tbuf;
+ assert(villa && leaf && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
+ left = 0;
+ recs = leaf->recs;
+ ln = CB_LISTNUM(recs);
+ right = ln;
+ i = (left + right) / 2;
+ while(right >= left && i < ln){
+ recp = (VLREC *)CB_LISTVAL(recs, i);
+ rv = villa->cmp(kbuf, ksiz, CB_DATUMPTR(recp->key), CB_DATUMSIZE(recp->key));
+ if(rv == 0){
+ break;
+ } else if(rv <= 0){
+ right = i - 1;
+ } else {
+ left = i + 1;
+ }
+ i = (left + right) / 2;
+ }
+ while(i < ln){
+ recp = (VLREC *)CB_LISTVAL(recs, i);
+ rv = villa->cmp(kbuf, ksiz, CB_DATUMPTR(recp->key), CB_DATUMSIZE(recp->key));
+ if(rv == 0){
+ switch(dmode){
+ case VL_DKEEP:
+ return FALSE;
+ case VL_DCAT:
+ CB_DATUMCAT(recp->first, vbuf, vsiz);
+ break;
+ case VL_DDUP:
+ if(!recp->rest) CB_LISTOPEN(recp->rest);
+ CB_LISTPUSH(recp->rest, vbuf, vsiz);
+ villa->rnum++;
+ break;
+ case VL_DDUPR:
+ if(!recp->rest){
+ CB_DATUMTOMALLOC(recp->first, tbuf, tsiz);
+ CB_DATUMOPEN2(recp->first, vbuf, vsiz);
+ CB_LISTOPEN(recp->rest);
+ CB_LISTPUSHBUF(recp->rest, tbuf, tsiz);
+ } else {
+ cblistunshift(recp->rest, CB_DATUMPTR(recp->first), CB_DATUMSIZE(recp->first));
+ CB_DATUMSETSIZE(recp->first, 0);
+ CB_DATUMCAT(recp->first, vbuf, vsiz);
+ }
+ villa->rnum++;
+ break;
+ default:
+ CB_DATUMSETSIZE(recp->first, 0);
+ CB_DATUMCAT(recp->first, vbuf, vsiz);
+ break;
+ }
+ break;
+ } else if(rv < 0){
+ CB_DATUMOPEN2(rec.key, kbuf, ksiz);
+ CB_DATUMOPEN2(rec.first, vbuf, vsiz);
+ rec.rest = NULL;
+ CB_LISTINSERT(recs, i, (char *)&rec, sizeof(VLREC));
+ villa->rnum++;
+ break;
+ }
+ i++;
+ }
+ if(i >= ln){
+ CB_DATUMOPEN2(rec.key, kbuf, ksiz);
+ CB_DATUMOPEN2(rec.first, vbuf, vsiz);
+ rec.rest = NULL;
+ CB_LISTPUSH(recs, (char *)&rec, sizeof(VLREC));
+ villa->rnum++;
+ }
+ leaf->dirty = TRUE;
+ return TRUE;
+}
+
+
+/* Calculate the size of data of a leaf.
+ `leaf' specifies a leaf handle.
+ The return value is size of data of the leaf. */
+static int vlleafdatasize(VLLEAF *leaf){
+ VLREC *recp;
+ CBLIST *recs, *rest;
+ const char *vbuf;
+ int i, j, sum, rnum, restnum, vsiz;
+ assert(leaf);
+ sum = 0;
+ recs = leaf->recs;
+ rnum = CB_LISTNUM(recs);
+ for(i = 0; i < rnum; i++){
+ recp = (VLREC *)CB_LISTVAL(recs, i);
+ sum += CB_DATUMSIZE(recp->key);
+ sum += CB_DATUMSIZE(recp->first);
+ if(recp->rest){
+ rest = recp->rest;
+ restnum = CB_LISTNUM(rest);
+ for(j = 0; j < restnum; j++){
+ vbuf = CB_LISTVAL2(rest, j, vsiz);
+ sum += vsiz;
+ }
+ }
+ }
+ return sum;
+}
+
+
+/* Divide a leaf into two.
+ `villa' specifies a database handle.
+ `leaf' specifies a leaf handle.
+ The return value is the handle of a new leaf, or `NULL' on failure. */
+static VLLEAF *vlleafdivide(VILLA *villa, VLLEAF *leaf){
+ VLLEAF *newleaf, *nextleaf;
+ VLREC *recp;
+ CBLIST *recs, *newrecs;
+ int i, mid, ln;
+ assert(villa && leaf);
+ villa->hleaf = -1;
+ recs = leaf->recs;
+ mid = CB_LISTNUM(recs) / 2;
+ recp = (VLREC *)CB_LISTVAL(recs, mid);
+ newleaf = vlleafnew(villa, leaf->id, leaf->next);
+ if(newleaf->next != -1){
+ if(!(nextleaf = vlleafload(villa, newleaf->next))) return NULL;
+ nextleaf->prev = newleaf->id;
+ nextleaf->dirty = TRUE;
+ }
+ leaf->next = newleaf->id;
+ leaf->dirty = TRUE;
+ ln = CB_LISTNUM(recs);
+ newrecs = newleaf->recs;
+ for(i = mid; i < ln; i++){
+ recp = (VLREC *)CB_LISTVAL(recs, i);
+ CB_LISTPUSH(newrecs, (char *)recp, sizeof(VLREC));
+ }
+ ln = CB_LISTNUM(newrecs);
+ for(i = 0; i < ln; i++){
+ CB_LISTDROP(recs);
+ }
+ return newleaf;
+}
+
+
+/* Create a new node.
+ `villa' specifies a database handle.
+ `heir' specifies the ID of the child before the first index.
+ The return value is a handle of the node. */
+static VLNODE *vlnodenew(VILLA *villa, int heir){
+ VLNODE nent;
+ assert(villa && heir >= VL_LEAFIDMIN);
+ nent.id = villa->nnum + VL_NODEIDMIN;
+ nent.dirty = TRUE;
+ nent.heir = heir;
+ CB_LISTOPEN(nent.idxs);
+ villa->nnum++;
+ cbmapput(villa->nodec, (char *)&(nent.id), sizeof(int), (char *)&nent, sizeof(VLNODE), TRUE);
+ return (VLNODE *)cbmapget(villa->nodec, (char *)&(nent.id), sizeof(int), NULL);
+}
+
+
+/* Remove a node from the cache.
+ `villa' specifies a database handle.
+ `id' specifies the ID number of the node.
+ The return value is true if successful, else, it is false. */
+static int vlnodecacheout(VILLA *villa, int id){
+ VLNODE *node;
+ VLIDX *idxp;
+ int i, err, ln;
+ assert(villa && id >= VL_NODEIDMIN);
+ if(!(node = (VLNODE *)cbmapget(villa->nodec, (char *)&id, sizeof(int), NULL))) return FALSE;
+ err = FALSE;
+ if(node->dirty && !vlnodesave(villa, node)) err = TRUE;
+ ln = CB_LISTNUM(node->idxs);
+ for(i = 0; i < ln; i++){
+ idxp = (VLIDX *)CB_LISTVAL(node->idxs, i);
+ CB_DATUMCLOSE(idxp->key);
+ }
+ CB_LISTCLOSE(node->idxs);
+ cbmapout(villa->nodec, (char *)&id, sizeof(int));
+ return err ? FALSE : TRUE;
+}
+
+
+/* Save a node into the database.
+ `villa' specifies a database handle.
+ `node' specifies a node handle.
+ The return value is true if successful, else, it is false. */
+static int vlnodesave(VILLA *villa, VLNODE *node){
+ CBDATUM *buf;
+ char vnumbuf[VL_VNUMBUFSIZ];
+ VLIDX *idxp;
+ int i, heir, pid, ksiz, vnumsiz, ln;
+ assert(villa && node);
+ CB_DATUMOPEN(buf);
+ heir = node->heir;
+ VL_SETVNUMBUF(vnumsiz, vnumbuf, heir);
+ CB_DATUMCAT(buf, vnumbuf, vnumsiz);
+ ln = CB_LISTNUM(node->idxs);
+ for(i = 0; i < ln; i++){
+ idxp = (VLIDX *)CB_LISTVAL(node->idxs, i);
+ pid = idxp->pid;
+ VL_SETVNUMBUF(vnumsiz, vnumbuf, pid);
+ CB_DATUMCAT(buf, vnumbuf, vnumsiz);
+ ksiz = CB_DATUMSIZE(idxp->key);
+ VL_SETVNUMBUF(vnumsiz, vnumbuf, ksiz);
+ CB_DATUMCAT(buf, vnumbuf, vnumsiz);
+ CB_DATUMCAT(buf, CB_DATUMPTR(idxp->key), ksiz);
+ }
+ if(!dpput(villa->depot, (char *)&(node->id), sizeof(int),
+ CB_DATUMPTR(buf), CB_DATUMSIZE(buf), DP_DOVER)){
+ CB_DATUMCLOSE(buf);
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return FALSE;
+ }
+ CB_DATUMCLOSE(buf);
+ node->dirty = FALSE;
+ return TRUE;
+}
+
+
+/* Load a node from the database.
+ `villa' specifies a database handle.
+ `id' specifies the ID number of the node.
+ If successful, the return value is the pointer to the node, else, it is `NULL'. */
+static VLNODE *vlnodeload(VILLA *villa, int id){
+ char wbuf[VL_PAGEBUFSIZ], *buf, *rp, *kbuf;
+ int size, step, heir, pid, ksiz;
+ VLNODE *node, nent;
+ VLIDX idx;
+ assert(villa && id >= VL_NODEIDMIN);
+ if((node = (VLNODE *)cbmapget(villa->nodec, (char *)&id, sizeof(int), NULL)) != NULL){
+ cbmapmove(villa->nodec, (char *)&id, sizeof(int), FALSE);
+ return node;
+ }
+ heir = -1;
+ if((size = dpgetwb(villa->depot, (char *)&id, sizeof(int), 0, VL_PAGEBUFSIZ, wbuf)) > 0 &&
+ size < VL_PAGEBUFSIZ){
+ buf = NULL;
+ } else if(!(buf = dpget(villa->depot, (char *)&id, sizeof(int), 0, -1, &size))){
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return NULL;
+ }
+ rp = buf ? buf : wbuf;
+ if(size >= 1){
+ VL_READVNUMBUF(rp, size, heir, step);
+ rp += step;
+ size -= step;
+ }
+ if(heir < 0){
+ free(buf);
+ return NULL;
+ }
+ nent.id = id;
+ nent.dirty = FALSE;
+ nent.heir = heir;
+ CB_LISTOPEN(nent.idxs);
+ while(size >= 1){
+ VL_READVNUMBUF(rp, size, pid, step);
+ rp += step;
+ size -= step;
+ if(size < 1) break;
+ VL_READVNUMBUF(rp, size, ksiz, step);
+ rp += step;
+ size -= step;
+ if(size < ksiz) break;
+ kbuf = rp;
+ rp += ksiz;
+ size -= ksiz;
+ idx.pid = pid;
+ CB_DATUMOPEN2(idx.key, kbuf, ksiz);
+ CB_LISTPUSH(nent.idxs, (char *)&idx, sizeof(VLIDX));
+ }
+ free(buf);
+ cbmapput(villa->nodec, (char *)&(nent.id), sizeof(int), (char *)&nent, sizeof(VLNODE), TRUE);
+ return (VLNODE *)cbmapget(villa->nodec, (char *)&(nent.id), sizeof(int), NULL);
+}
+
+
+/* Add an index to a node.
+ `villa' specifies a database handle.
+ `node' specifies a node handle.
+ `order' specifies whether the calling sequence is orderd or not.
+ `pid' specifies the ID number of referred page.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. */
+static void vlnodeaddidx(VILLA *villa, VLNODE *node, int order,
+ int pid, const char *kbuf, int ksiz){
+ VLIDX idx, *idxp;
+ int i, rv, left, right, ln;
+ assert(villa && node && pid >= VL_LEAFIDMIN && kbuf && ksiz >= 0);
+ idx.pid = pid;
+ CB_DATUMOPEN2(idx.key, kbuf, ksiz);
+ if(order){
+ CB_LISTPUSH(node->idxs, (char *)&idx, sizeof(VLIDX));
+ } else {
+ left = 0;
+ right = CB_LISTNUM(node->idxs);
+ i = (left + right) / 2;
+ ln = CB_LISTNUM(node->idxs);
+ while(right >= left && i < ln){
+ idxp = (VLIDX *)CB_LISTVAL(node->idxs, i);
+ rv = villa->cmp(kbuf, ksiz, CB_DATUMPTR(idxp->key), CB_DATUMSIZE(idxp->key));
+ if(rv == 0){
+ break;
+ } else if(rv <= 0){
+ right = i - 1;
+ } else {
+ left = i + 1;
+ }
+ i = (left + right) / 2;
+ }
+ ln = CB_LISTNUM(node->idxs);
+ while(i < ln){
+ idxp = (VLIDX *)CB_LISTVAL(node->idxs, i);
+ if(villa->cmp(kbuf, ksiz, CB_DATUMPTR(idxp->key), CB_DATUMSIZE(idxp->key)) < 0){
+ CB_LISTINSERT(node->idxs, i, (char *)&idx, sizeof(VLIDX));
+ break;
+ }
+ i++;
+ }
+ if(i >= CB_LISTNUM(node->idxs)) CB_LISTPUSH(node->idxs, (char *)&idx, sizeof(VLIDX));
+ }
+ node->dirty = TRUE;
+}
+
+
+/* Search the leaf corresponding to a key.
+ `villa' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key.
+ The return value is the ID number of the leaf, or -1 on failure. */
+static int vlsearchleaf(VILLA *villa, const char *kbuf, int ksiz){
+ VLNODE *node;
+ VLIDX *idxp;
+ int i, pid, rv, left, right, ln;
+ assert(villa && kbuf && ksiz >= 0);
+ pid = villa->root;
+ idxp = NULL;
+ villa->hnum = 0;
+ villa->hleaf = -1;
+ while(pid >= VL_NODEIDMIN){
+ if(!(node = vlnodeload(villa, pid)) || (ln = CB_LISTNUM(node->idxs)) < 1){
+ dpecodeset(DP_EBROKEN, __FILE__, __LINE__);
+ return -1;
+ }
+ villa->hist[villa->hnum++] = node->id;
+ left = 1;
+ right = ln;
+ i = (left + right) / 2;
+ while(right >= left && i < ln){
+ idxp = (VLIDX *)CB_LISTVAL(node->idxs, i);
+ rv = villa->cmp(kbuf, ksiz, CB_DATUMPTR(idxp->key), CB_DATUMSIZE(idxp->key));
+ if(rv == 0){
+ break;
+ } else if(rv <= 0){
+ right = i - 1;
+ } else {
+ left = i + 1;
+ }
+ i = (left + right) / 2;
+ }
+ if(i > 0) i--;
+ while(i < ln){
+ idxp = (VLIDX *)CB_LISTVAL(node->idxs, i);
+ if(villa->cmp(kbuf, ksiz, CB_DATUMPTR(idxp->key), CB_DATUMSIZE(idxp->key)) < 0){
+ if(i == 0){
+ pid = node->heir;
+ break;
+ }
+ idxp = (VLIDX *)CB_LISTVAL(node->idxs, i - 1);
+ pid = idxp->pid;
+ break;
+ }
+ i++;
+ }
+ if(i >= ln) pid = idxp->pid;
+ }
+ if(villa->lleaf == pid) villa->hleaf = pid;
+ villa->lleaf = pid;
+ return pid;
+}
+
+
+/* Adjust the caches for leaves and nodes.
+ `villa' specifies a database handle.
+ The return value is true if successful, else, it is false. */
+static int vlcacheadjust(VILLA *villa){
+ const char *tmp;
+ int i, pid, err;
+ err = FALSE;
+ if(cbmaprnum(villa->leafc) > villa->leafcnum){
+ cbmapiterinit(villa->leafc);
+ for(i = 0; i < VL_CACHEOUT; i++){
+ tmp = cbmapiternext(villa->leafc, NULL);
+ pid = *(int *)tmp;
+ if(!vlleafcacheout(villa, pid)) err = TRUE;
+ }
+ }
+ if(cbmaprnum(villa->nodec) > villa->nodecnum){
+ cbmapiterinit(villa->nodec);
+ for(i = 0; i < VL_CACHEOUT; i++){
+ tmp = cbmapiternext(villa->nodec, NULL);
+ pid = *(int *)tmp;
+ if(!vlnodecacheout(villa, pid)) err = TRUE;
+ }
+ }
+ return err ? FALSE : TRUE;
+}
+
+
+/* Search a record of a leaf.
+ `villa' specifies a database handle.
+ `leaf' specifies a leaf handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key.
+ `ip' specifies the pointer to a variable to fetch the index of the correspnding record.
+ The return value is the pointer to a corresponding record, or `NULL' on failure. */
+static VLREC *vlrecsearch(VILLA *villa, VLLEAF *leaf, const char *kbuf, int ksiz, int *ip){
+ VLREC *recp;
+ CBLIST *recs;
+ int i, rv, left, right, ln;
+ assert(villa && leaf && kbuf && ksiz >= 0);
+ recs = leaf->recs;
+ ln = CB_LISTNUM(recs);
+ left = 0;
+ right = ln;
+ i = (left + right) / 2;
+ while(right >= left && i < ln){
+ recp = (VLREC *)CB_LISTVAL(recs, i);
+ rv = villa->cmp(kbuf, ksiz, CB_DATUMPTR(recp->key), CB_DATUMSIZE(recp->key));
+ if(rv == 0){
+ if(ip) *ip = i;
+ return recp;
+ } else if(rv <= 0){
+ right = i - 1;
+ } else {
+ left = i + 1;
+ }
+ i = (left + right) / 2;
+ }
+ if(ip) *ip = i;
+ return NULL;
+}
+
+
+/* Get flags of a database. */
+int vlgetflags(VILLA *villa){
+ assert(villa);
+ return dpgetflags(villa->depot);
+}
+
+
+/* Set flags of a database.
+ `villa' specifies a database handle connected as a writer.
+ `flags' specifies flags to set. Lesser ten bits are reserved for internal use.
+ If successful, the return value is true, else, it is false. */
+int vlsetflags(VILLA *villa, int flags){
+ assert(villa);
+ if(!villa->wmode){
+ dpecodeset(DP_EMODE, __FILE__, __LINE__);
+ return FALSE;
+ }
+ return dpsetflags(villa->depot, flags);
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/villa.h b/qdbm/villa.h
new file mode 100644
index 00000000..01f396bf
--- /dev/null
+++ b/qdbm/villa.h
@@ -0,0 +1,758 @@
+/*************************************************************************************************
+ * The advanced API of QDBM
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#ifndef _VILLA_H /* duplication check */
+#define _VILLA_H
+
+#if defined(__cplusplus) /* export for C++ */
+extern "C" {
+#endif
+
+
+#include <depot.h>
+#include <cabin.h>
+#include <stdlib.h>
+#include <time.h>
+
+
+#if defined(_MSC_VER) && !defined(QDBM_INTERNAL) && !defined(QDBM_STATIC)
+#define MYEXTERN extern __declspec(dllimport)
+#else
+#define MYEXTERN extern
+#endif
+
+
+
+/*************************************************************************************************
+ * API
+ *************************************************************************************************/
+
+
+#define VL_LEVELMAX 64 /* max level of B+ tree */
+
+typedef struct { /* type of structure for a record */
+ CBDATUM *key; /* datum of the key */
+ CBDATUM *first; /* datum of the first value */
+ CBLIST *rest; /* list of the rest values */
+} VLREC;
+
+typedef struct { /* type of structure for index of a page */
+ int pid; /* ID number of the referring page */
+ CBDATUM *key; /* threshold key of the page */
+} VLIDX;
+
+typedef struct { /* type of structure for a leaf page */
+ int id; /* ID number of the leaf */
+ int dirty; /* whether to be written back */
+ CBLIST *recs; /* list of records */
+ int prev; /* ID number of the previous leaf */
+ int next; /* ID number of the next leaf */
+} VLLEAF;
+
+typedef struct { /* type of structure for a node page */
+ int id; /* ID number of the node */
+ int dirty; /* whether to be written back */
+ int heir; /* ID of the child before the first index */
+ CBLIST *idxs; /* list of indexes */
+} VLNODE;
+
+/* type of the pointer to a comparing function.
+ `aptr' specifies the pointer to the region of one key.
+ `asiz' specifies the size of the region of one key.
+ `bptr' specifies the pointer to the region of the other key.
+ `bsiz' specifies the size of the region of the other key.
+ The return value is positive if the former is big, negative if the latter is big, 0 if both
+ are equivalent. */
+typedef int (*VLCFUNC)(const char *aptr, int asiz, const char *bptr, int bsiz);
+MYEXTERN VLCFUNC VL_CMPLEX; /* lexical comparing function */
+MYEXTERN VLCFUNC VL_CMPINT; /* native integer comparing function */
+MYEXTERN VLCFUNC VL_CMPNUM; /* big endian number comparing function */
+MYEXTERN VLCFUNC VL_CMPDEC; /* decimal string comparing function */
+
+typedef struct { /* type of structure for a database handle */
+ DEPOT *depot; /* internal database handle */
+ VLCFUNC cmp; /* pointer to the comparing function */
+ int wmode; /* whether to be writable */
+ int cmode; /* compression mode for leaves */
+ int root; /* ID number of the root page */
+ int last; /* ID number of the last leaf */
+ int lnum; /* number of leaves */
+ int nnum; /* number of nodes */
+ int rnum; /* number of records */
+ CBMAP *leafc; /* cache for leaves */
+ CBMAP *nodec; /* cache for nodes */
+ int hist[VL_LEVELMAX]; /* array history of visited nodes */
+ int hnum; /* number of elements of the history */
+ int hleaf; /* ID number of the leaf referred by the history */
+ int lleaf; /* ID number of the last visited leaf */
+ int curleaf; /* ID number of the leaf where the cursor is */
+ int curknum; /* index of the key where the cursor is */
+ int curvnum; /* index of the value where the cursor is */
+ int leafrecmax; /* max number of records in a leaf */
+ int nodeidxmax; /* max number of indexes in a node */
+ int leafcnum; /* max number of caching leaves */
+ int nodecnum; /* max number of caching nodes */
+ int avglsiz; /* average size of each leave */
+ int avgnsiz; /* average size of each node */
+ int tran; /* whether in the transaction */
+ int rbroot; /* root for rollback */
+ int rblast; /* last for rollback */
+ int rblnum; /* lnum for rollback */
+ int rbnnum; /* nnum for rollback */
+ int rbrnum; /* rnum for rollback */
+} VILLA;
+
+typedef struct { /* type of structure for a multiple cursor handle */
+ VILLA *villa; /* database handle */
+ int curleaf; /* ID number of the leaf where the cursor is */
+ int curknum; /* index of the key where the cursor is */
+ int curvnum; /* index of the value where the cursor is */
+} VLMULCUR;
+
+enum { /* enumeration for open modes */
+ VL_OREADER = 1 << 0, /* open as a reader */
+ VL_OWRITER = 1 << 1, /* open as a writer */
+ VL_OCREAT = 1 << 2, /* a writer creating */
+ VL_OTRUNC = 1 << 3, /* a writer truncating */
+ VL_ONOLCK = 1 << 4, /* open without locking */
+ VL_OLCKNB = 1 << 5, /* lock without blocking */
+ VL_OZCOMP = 1 << 6, /* compress leaves with ZLIB */
+ VL_OYCOMP = 1 << 7, /* compress leaves with LZO */
+ VL_OXCOMP = 1 << 8 /* compress leaves with BZIP2 */
+};
+
+enum { /* enumeration for write modes */
+ VL_DOVER, /* overwrite the existing value */
+ VL_DKEEP, /* keep the existing value */
+ VL_DCAT, /* concatenate values */
+ VL_DDUP, /* allow duplication of keys */
+ VL_DDUPR /* allow duplication with reverse order */
+};
+
+enum { /* enumeration for jump modes */
+ VL_JFORWARD, /* step forward */
+ VL_JBACKWARD /* step backward */
+};
+
+enum { /* enumeration for insertion modes */
+ VL_CPCURRENT, /* overwrite the current record */
+ VL_CPBEFORE, /* insert before the current record */
+ VL_CPAFTER /* insert after the current record */
+};
+
+
+/* Get a database handle.
+ `name' specifies the name of a database file.
+ `omode' specifies the connection mode: `VL_OWRITER' as a writer, `VL_OREADER' as a reader.
+ If the mode is `VL_OWRITER', the following may be added by bitwise or: `VL_OCREAT', which
+ means it creates a new database if not exist, `VL_OTRUNC', which means it creates a new
+ database regardless if one exists, `VL_OZCOMP', which means leaves in the database are
+ compressed with ZLIB, `VL_OYCOMP', which means leaves in the database are compressed with LZO,
+ `VL_OXCOMP', which means leaves in the database are compressed with BZIP2. Both of
+ `VL_OREADER' and `VL_OWRITER' can be added to by bitwise or: `VL_ONOLCK', which means it opens
+ a database file without file locking, or `VL_OLCKNB', which means locking is performed without
+ blocking.
+ `cmp' specifies a comparing function: `VL_CMPLEX' comparing keys in lexical order,
+ `VL_CMPINT' comparing keys as objects of `int' in native byte order, `VL_CMPNUM' comparing
+ keys as numbers of big endian, `VL_CMPDEC' comparing keys as decimal strings. Any function
+ based on the declaration of the type `VLCFUNC' can be assigned to the comparing function.
+ The comparing function should be kept same in the life of a database.
+ The return value is the database handle or `NULL' if it is not successful.
+ While connecting as a writer, an exclusive lock is invoked to the database file.
+ While connecting as a reader, a shared lock is invoked to the database file. The thread
+ blocks until the lock is achieved. `VL_OZCOMP', `VL_OYCOMP', and `VL_OXCOMP' are available
+ only if QDBM was built each with ZLIB, LZO, and BZIP2 enabled. If `VL_ONOLCK' is used, the
+ application is responsible for exclusion control. */
+VILLA *vlopen(const char *name, int omode, VLCFUNC cmp);
+
+
+/* Close a database handle.
+ `villa' specifies a database handle.
+ If successful, the return value is true, else, it is false.
+ Because the region of a closed handle is released, it becomes impossible to use the handle.
+ Updating a database is assured to be written when the handle is closed. If a writer opens
+ a database but does not close it appropriately, the database will be broken. If the
+ transaction is activated and not committed, it is aborted. */
+int vlclose(VILLA *villa);
+
+
+/* Store a record.
+ `villa' specifies a database handle connected as a writer.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `vbuf' specifies the pointer to the region of a value.
+ `vsiz' specifies the size of the region of the value. If it is negative, the size is
+ assigned with `strlen(vbuf)'.
+ `dmode' specifies behavior when the key overlaps, by the following values: `VL_DOVER',
+ which means the specified value overwrites the existing one, `VL_DKEEP', which means the
+ existing value is kept, `VL_DCAT', which means the specified value is concatenated at the
+ end of the existing value, `VL_DDUP', which means duplication of keys is allowed and the
+ specified value is added as the last one, `VL_DDUPR', which means duplication of keys is
+ allowed and the specified value is added as the first one.
+ If successful, the return value is true, else, it is false.
+ The cursor becomes unavailable due to updating database. */
+int vlput(VILLA *villa, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
+
+
+/* Delete a record.
+ `villa' specifies a database handle connected as a writer.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ If successful, the return value is true, else, it is false. False is returned when no
+ record corresponds to the specified key.
+ When the key of duplicated records is specified, the first record of the same key is deleted.
+ The cursor becomes unavailable due to updating database. */
+int vlout(VILLA *villa, const char *kbuf, int ksiz);
+
+
+/* Retrieve a record.
+ `villa' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the value of the
+ corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to
+ the specified key.
+ When the key of duplicated records is specified, the value of the first record of the same
+ key is selected. Because an additional zero code is appended at the end of the region of the
+ return value, the return value can be treated as a character string. Because the region of
+ the return value is allocated with the `malloc' call, it should be released with the `free'
+ call if it is no longer in use. */
+char *vlget(VILLA *villa, const char *kbuf, int ksiz, int *sp);
+
+
+/* Get the size of the value of a record.
+ `villa' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ If successful, the return value is the size of the value of the corresponding record, else,
+ it is -1. If multiple records correspond, the size of the first is returned. */
+int vlvsiz(VILLA *villa, const char *kbuf, int ksiz);
+
+
+/* Get the number of records corresponding a key.
+ `villa' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ The return value is the number of corresponding records. If no record corresponds, 0 is
+ returned. */
+int vlvnum(VILLA *villa, const char *kbuf, int ksiz);
+
+
+/* Store plural records corresponding a key.
+ `villa' specifies a database handle connected as a writer.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `vals' specifies a list handle of values. The list should not be empty.
+ If successful, the return value is true, else, it is false.
+ The cursor becomes unavailable due to updating database. */
+int vlputlist(VILLA *villa, const char *kbuf, int ksiz, const CBLIST *vals);
+
+
+/* Delete all records corresponding a key.
+ `villa' specifies a database handle connected as a writer.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ If successful, the return value is true, else, it is false. False is returned when no
+ record corresponds to the specified key.
+ The cursor becomes unavailable due to updating database. */
+int vloutlist(VILLA *villa, const char *kbuf, int ksiz);
+
+
+/* Retrieve values of all records corresponding a key.
+ `villa' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ If successful, the return value is a list handle of the values of the corresponding records,
+ else, it is `NULL'. `NULL' is returned when no record corresponds to the specified key.
+ Because the handle of the return value is opened with the function `cblistopen', it should
+ be closed with the function `cblistclose' if it is no longer in use. */
+CBLIST *vlgetlist(VILLA *villa, const char *kbuf, int ksiz);
+
+
+/* Retrieve concatenated values of all records corresponding a key.
+ `villa' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the concatenated values of
+ the corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds
+ to the specified key. Because an additional zero code is appended at the end of the region of
+ the return value, the return value can be treated as a character string. Because the region
+ of the return value is allocated with the `malloc' call, it should be released with the `free'
+ call if it is no longer in use. */
+char *vlgetcat(VILLA *villa, const char *kbuf, int ksiz, int *sp);
+
+
+/* Move the cursor to the first record.
+ `villa' specifies a database handle.
+ If successful, the return value is true, else, it is false. False is returned if there is
+ no record in the database. */
+int vlcurfirst(VILLA *villa);
+
+
+/* Move the cursor to the last record.
+ `villa' specifies a database handle.
+ If successful, the return value is true, else, it is false. False is returned if there is
+ no record in the database. */
+int vlcurlast(VILLA *villa);
+
+
+/* Move the cursor to the previous record.
+ `villa' specifies a database handle.
+ If successful, the return value is true, else, it is false. False is returned if there is
+ no previous record. */
+int vlcurprev(VILLA *villa);
+
+
+/* Move the cursor to the next record.
+ `villa' specifies a database handle.
+ If successful, the return value is true, else, it is false. False is returned if there is
+ no next record. */
+int vlcurnext(VILLA *villa);
+
+
+/* Move the cursor to a position around a record.
+ `villa' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `jmode' specifies detail adjustment: `VL_JFORWARD', which means that the cursor is set to
+ the first record of the same key and that the cursor is set to the next substitute if
+ completely matching record does not exist, `VL_JBACKWARD', which means that the cursor is
+ set to the last record of the same key and that the cursor is set to the previous substitute
+ if completely matching record does not exist.
+ If successful, the return value is true, else, it is false. False is returned if there is
+ no record corresponding the condition. */
+int vlcurjump(VILLA *villa, const char *kbuf, int ksiz, int jmode);
+
+
+/* Get the key of the record where the cursor is.
+ `villa' specifies a database handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the key of the corresponding
+ record, else, it is `NULL'. `NULL' is returned when no record corresponds to the cursor.
+ Because an additional zero code is appended at the end of the region of the
+ return value, the return value can be treated as a character string. Because the region of
+ the return value is allocated with the `malloc' call, it should be released with the `free'
+ call if it is no longer in use. */
+char *vlcurkey(VILLA *villa, int *sp);
+
+
+/* Get the value of the record where the cursor is.
+ `villa' specifies a database handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the value of the
+ corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to
+ the cursor.
+ Because an additional zero code is appended at the end of the region of the
+ return value, the return value can be treated as a character string. Because the region of
+ the return value is allocated with the `malloc' call, it should be released with the `free'
+ call if it is no longer in use. */
+char *vlcurval(VILLA *villa, int *sp);
+
+
+/* Insert a record around the cursor.
+ `villa' specifies a database handle connected as a writer.
+ `vbuf' specifies the pointer to the region of a value.
+ `vsiz' specifies the size of the region of the value. If it is negative, the size is
+ assigned with `strlen(vbuf)'.
+ `cpmode' specifies detail adjustment: `VL_CPCURRENT', which means that the value of the
+ current record is overwritten, `VL_CPBEFORE', which means that a new record is inserted before
+ the current record, `VL_CPAFTER', which means that a new record is inserted after the current
+ record.
+ If successful, the return value is true, else, it is false. False is returned when no record
+ corresponds to the cursor.
+ After insertion, the cursor is moved to the inserted record. */
+int vlcurput(VILLA *villa, const char *vbuf, int vsiz, int cpmode);
+
+
+/* Delete the record where the cursor is.
+ `villa' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false. False is returned when no record
+ corresponds to the cursor.
+ After deletion, the cursor is moved to the next record if possible. */
+int vlcurout(VILLA *villa);
+
+
+/* Set the tuning parameters for performance.
+ `villa' specifies a database handle.
+ `lrecmax' specifies the max number of records in a leaf node of B+ tree. If it is not more
+ than 0, the default value is specified.
+ `nidxmax' specifies the max number of indexes in a non-leaf node of B+ tree. If it is not
+ more than 0, the default value is specified.
+ `lcnum' specifies the max number of caching leaf nodes. If it is not more than 0, the
+ default value is specified.
+ `ncnum' specifies the max number of caching non-leaf nodes. If it is not more than 0, the
+ default value is specified.
+ The default setting is equivalent to `vlsettuning(49, 192, 1024, 512)'. Because tuning
+ parameters are not saved in a database, you should specify them every opening a database. */
+void vlsettuning(VILLA *villa, int lrecmax, int nidxmax, int lcnum, int ncnum);
+
+
+/* Set the size of the free block pool of a database handle.
+ `villa' specifies a database handle connected as a writer.
+ `size' specifies the size of the free block pool of a database.
+ If successful, the return value is true, else, it is false.
+ The default size of the free block pool is 256. If the size is greater, the space efficiency
+ of overwriting values is improved with the time efficiency sacrificed. */
+int vlsetfbpsiz(VILLA *villa, int size);
+
+
+/* Synchronize updating contents with the file and the device.
+ `villa' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false.
+ This function is useful when another process uses the connected database file. This function
+ should not be used while the transaction is activated. */
+int vlsync(VILLA *villa);
+
+
+/* Optimize a database.
+ `villa' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false.
+ In an alternating succession of deleting and storing with overwrite or concatenate,
+ dispensable regions accumulate. This function is useful to do away with them. This function
+ should not be used while the transaction is activated. */
+int vloptimize(VILLA *villa);
+
+
+/* Get the name of a database.
+ `villa' specifies a database handle.
+ If successful, the return value is the pointer to the region of the name of the database,
+ else, it is `NULL'.
+ Because the region of the return value is allocated with the `malloc' call, it should be
+ released with the `free' call if it is no longer in use. */
+char *vlname(VILLA *villa);
+
+
+/* Get the size of a database file.
+ `villa' specifies a database handle.
+ If successful, the return value is the size of the database file, else, it is -1.
+ Because of the I/O buffer, the return value may be less than the hard size. */
+int vlfsiz(VILLA *villa);
+
+
+/* Get the number of the leaf nodes of B+ tree.
+ `villa' specifies a database handle.
+ If successful, the return value is the number of the leaf nodes, else, it is -1. */
+int vllnum(VILLA *villa);
+
+
+/* Get the number of the non-leaf nodes of B+ tree.
+ `villa' specifies a database handle.
+ If successful, the return value is the number of the non-leaf nodes, else, it is -1. */
+int vlnnum(VILLA *villa);
+
+
+/* Get the number of the records stored in a database.
+ `villa' specifies a database handle.
+ If successful, the return value is the number of the records stored in the database, else,
+ it is -1. */
+int vlrnum(VILLA *villa);
+
+
+/* Check whether a database handle is a writer or not.
+ `villa' specifies a database handle.
+ The return value is true if the handle is a writer, false if not. */
+int vlwritable(VILLA *villa);
+
+
+/* Check whether a database has a fatal error or not.
+ `villa' specifies a database handle.
+ The return value is true if the database has a fatal error, false if not. */
+int vlfatalerror(VILLA *villa);
+
+
+/* Get the inode number of a database file.
+ `villa' specifies a database handle.
+ The return value is the inode number of the database file. */
+int vlinode(VILLA *villa);
+
+
+/* Get the last modified time of a database.
+ `villa' specifies a database handle.
+ The return value is the last modified time of the database. */
+time_t vlmtime(VILLA *villa);
+
+
+/* Begin the transaction.
+ `villa' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false.
+ Because this function does not perform mutual exclusion control in multi-thread, the
+ application is responsible for it. Only one transaction can be activated with a database
+ handle at the same time. */
+int vltranbegin(VILLA *villa);
+
+
+/* Commit the transaction.
+ `villa' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false.
+ Updating a database in the transaction is fixed when it is committed successfully. */
+int vltrancommit(VILLA *villa);
+
+
+/* Abort the transaction.
+ `villa' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false.
+ Updating a database in the transaction is discarded when it is aborted. The state of the
+ database is rollbacked to before transaction. */
+int vltranabort(VILLA *villa);
+
+
+/* Remove a database file.
+ `name' specifies the name of a database file.
+ If successful, the return value is true, else, it is false. */
+int vlremove(const char *name);
+
+
+/* Repair a broken database file.
+ `name' specifies the name of a database file.
+ `cmp' specifies the comparing function of the database file.
+ If successful, the return value is true, else, it is false.
+ There is no guarantee that all records in a repaired database file correspond to the original
+ or expected state. */
+int vlrepair(const char *name, VLCFUNC cmp);
+
+
+/* Dump all records as endian independent data.
+ `villa' specifies a database handle.
+ `name' specifies the name of an output file.
+ If successful, the return value is true, else, it is false. */
+int vlexportdb(VILLA *villa, const char *name);
+
+
+/* Load all records from endian independent data.
+ `villa' specifies a database handle connected as a writer. The database of the handle must
+ be empty.
+ `name' specifies the name of an input file.
+ If successful, the return value is true, else, it is false. */
+int vlimportdb(VILLA *villa, const char *name);
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Number of division of the database for Vista. */
+#define vlcrdnum (*vlcrdnumptr())
+
+
+/* Get the pointer of the variable of the number of division of the database for Vista.
+ The return value is the pointer of the variable. */
+int *vlcrdnumptr(void);
+
+
+/* Synchronize updating contents on memory.
+ `villa' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false. */
+int vlmemsync(VILLA *villa);
+
+
+/* Synchronize updating contents on memory, not physically.
+ `villa' specifies a database handle connected as a writer.
+ If successful, the return value is true, else, it is false. */
+int vlmemflush(VILLA *villa);
+
+
+/* Refer to volatile cache of a value of a record.
+ `villa' specifies a database handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the value of the
+ corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to
+ the specified key.
+ Because the region of the return value is volatile and it may be spoiled by another operation
+ of the database, the data should be copied into another involatile buffer immediately. */
+const char *vlgetcache(VILLA *villa, const char *kbuf, int ksiz, int *sp);
+
+
+/* Refer to volatile cache of the key of the record where the cursor is.
+ `villa' specifies a database handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the key of the corresponding
+ record, else, it is `NULL'. `NULL' is returned when no record corresponds to the cursor.
+ Because the region of the return value is volatile and it may be spoiled by another operation
+ of the database, the data should be copied into another involatile buffer immediately. */
+const char *vlcurkeycache(VILLA *villa, int *sp);
+
+
+/* Refer to volatile cache of the value of the record where the cursor is.
+ `villa' specifies a database handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the value of the
+ corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to
+ the cursor.
+ Because an additional zero code is appended at the end of the region of the
+ return value, the return value can be treated as a character string. Because the region of
+ the return value is allocated with the `malloc' call, it should be released with the `free'
+ call if it is no longer in use. */
+const char *vlcurvalcache(VILLA *villa, int *sp);
+
+
+/* Get a multiple cursor handle.
+ `villa' specifies a database handle connected as a reader.
+ The return value is a multiple cursor handle or `NULL' if it is not successful.
+ The returned object is should be closed before the database handle is closed. Even if plural
+ cursors are fetched out of a database handle, they does not share the locations with each
+ other. Note that this function can be used only if the database handle is connected as a
+ reader. */
+VLMULCUR *vlmulcuropen(VILLA *villa);
+
+
+/* Close a multiple cursor handle.
+ `mulcur' specifies a multiple cursor handle. */
+void vlmulcurclose(VLMULCUR *mulcur);
+
+
+/* Move a multiple cursor to the first record.
+ `mulcur' specifies a multiple cursor handle.
+ If successful, the return value is true, else, it is false. False is returned if there is
+ no record in the database. */
+int vlmulcurfirst(VLMULCUR *mulcur);
+
+
+/* Move a multiple cursor to the last record.
+ `mulcur' specifies a multiple cursor handle.
+ If successful, the return value is true, else, it is false. False is returned if there is
+ no record in the database. */
+int vlmulcurlast(VLMULCUR *mulcur);
+
+
+/* Move a multiple cursor to the previous record.
+ `mulcur' specifies a multiple cursor handle.
+ If successful, the return value is true, else, it is false. False is returned if there is
+ no previous record. */
+int vlmulcurprev(VLMULCUR *mulcur);
+
+
+/* Move a multiple cursor to the next record.
+ `mulcur' specifies a multiple cursor handle.
+ If successful, the return value is true, else, it is false. False is returned if there is
+ no next record. */
+int vlmulcurnext(VLMULCUR *mulcur);
+
+
+/* Move a multiple cursor to a position around a record.
+ `mulcur' specifies a multiple cursor handle.
+ `kbuf' specifies the pointer to the region of a key.
+ `ksiz' specifies the size of the region of the key. If it is negative, the size is assigned
+ with `strlen(kbuf)'.
+ `jmode' specifies detail adjustment: `VL_JFORWARD', which means that the cursor is set to
+ the first record of the same key and that the cursor is set to the next substitute if
+ completely matching record does not exist, `VL_JBACKWARD', which means that the cursor is
+ set to the last record of the same key and that the cursor is set to the previous substitute
+ if completely matching record does not exist.
+ If successful, the return value is true, else, it is false. False is returned if there is
+ no record corresponding the condition. */
+int vlmulcurjump(VLMULCUR *mulcur, const char *kbuf, int ksiz, int jmode);
+
+
+/* Get the key of the record where a multiple cursor is.
+ `mulcur' specifies a multiple cursor handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the key of the corresponding
+ record, else, it is `NULL'. `NULL' is returned when no record corresponds to the cursor.
+ Because an additional zero code is appended at the end of the region of the
+ return value, the return value can be treated as a character string. Because the region of
+ the return value is allocated with the `malloc' call, it should be released with the `free'
+ call if it is no longer in use. */
+char *vlmulcurkey(VLMULCUR *mulcur, int *sp);
+
+
+/* Get the value of the record where a multiple cursor is.
+ `mulcur' specifies a multiple cursor handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the value of the
+ corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to
+ the cursor.
+ Because an additional zero code is appended at the end of the region of the
+ return value, the return value can be treated as a character string. Because the region of
+ the return value is allocated with the `malloc' call, it should be released with the `free'
+ call if it is no longer in use. */
+char *vlmulcurval(VLMULCUR *mulcur, int *sp);
+
+
+/* Refer to volatile cache of the key of the record where a multiple cursor is.
+ `mulcur' specifies a multiple cursor handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the key of the corresponding
+ record, else, it is `NULL'. `NULL' is returned when no record corresponds to the cursor.
+ Because the region of the return value is volatile and it may be spoiled by another operation
+ of the database, the data should be copied into another involatile buffer immediately. */
+const char *vlmulcurkeycache(VLMULCUR *mulcur, int *sp);
+
+
+/* Refer to volatile cache of the value of the record where a multiple cursor is.
+ `mulcur' specifies a multiple cursor handle.
+ `sp' specifies the pointer to a variable to which the size of the region of the return
+ value is assigned. If it is `NULL', it is not used.
+ If successful, the return value is the pointer to the region of the value of the
+ corresponding record, else, it is `NULL'. `NULL' is returned when no record corresponds to
+ the cursor.
+ Because an additional zero code is appended at the end of the region of the
+ return value, the return value can be treated as a character string. Because the region of
+ the return value is allocated with the `malloc' call, it should be released with the `free'
+ call if it is no longer in use. */
+const char *vlmulcurvalcache(VLMULCUR *mulcur, int *sp);
+
+
+/* Get flags of a database.
+ `villa' specifies a database handle.
+ The return value is the flags of a database. */
+int vlgetflags(VILLA *villa);
+
+
+/* Set flags of a database.
+ `villa' specifies a database handle connected as a writer.
+ `flags' specifies flags to set. Least ten bits are reserved for internal use.
+ If successful, the return value is true, else, it is false. */
+int vlsetflags(VILLA *villa, int flags);
+
+
+
+#undef MYEXTERN
+
+#if defined(__cplusplus) /* export for C++ */
+}
+#endif
+
+#endif /* duplication check */
+
+
+/* END OF FILE */
diff --git a/qdbm/vista.c b/qdbm/vista.c
new file mode 100644
index 00000000..011c229a
--- /dev/null
+++ b/qdbm/vista.c
@@ -0,0 +1,171 @@
+/*************************************************************************************************
+ * Implementation of Vista
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#define QDBM_INTERNAL 1
+#define _VISTA_C 1
+
+#include "vista.h"
+#include "myconf.h"
+
+
+
+/*************************************************************************************************
+ * macros to convert Depot to Curia
+ *************************************************************************************************/
+
+
+#define DEPOT CURIA
+
+#define \
+ dpopen(name, omode, bnum) \
+ cropen(name, omode, ((bnum / vlcrdnum) * 2), vlcrdnum)
+
+#define \
+ dpclose(db) \
+ crclose(db)
+
+#define \
+ dpput(db, kbuf, ksiz, vbuf, vsiz, dmode) \
+ crput(db, kbuf, ksiz, vbuf, vsiz, dmode)
+
+#define \
+ dpout(db, kbuf, ksiz) \
+ crout(db, kbuf, ksiz)
+
+#define \
+ dpget(db, kbuf, ksiz, start, max, sp) \
+ crget(db, kbuf, ksiz, start, max, sp)
+
+#define \
+ dpgetwb(db, kbuf, ksiz, start, max, vbuf) \
+ crgetwb(db, kbuf, ksiz, start, max, vbuf)
+
+#define \
+ dpvsiz(db, kbuf, ksiz) \
+ crvsiz(db, kbuf, ksiz)
+
+#define \
+ dpiterinit(db) \
+ criterinit(db)
+
+#define \
+ dpiternext(db, sp) \
+ criternext(db, sp)
+
+#define \
+ dpsetalign(db, align) \
+ crsetalign(db, align)
+
+#define \
+ dpsetfbpsiz(db, size) \
+ crsetfbpsiz(db, size)
+
+#define \
+ dpsync(db) \
+ crsync(db)
+
+#define \
+ dpoptimize(db, bnum) \
+ croptimize(db, bnum)
+
+#define \
+ dpname(db) \
+ crname(db)
+
+#define \
+ dpfsiz(db) \
+ crfsiz(db)
+
+#define \
+ dpbnum(db) \
+ crbnum(db)
+
+#define \
+ dpbusenum(db) \
+ crbusenum(db)
+
+#define \
+ dprnum(db) \
+ crrnum(db)
+
+#define \
+ dpwritable(db) \
+ crwritable(db)
+
+#define \
+ dpfatalerror(db) \
+ crfatalerror(db)
+
+#define \
+ dpinode(db) \
+ crinode(db)
+
+#define \
+ dpmtime(db) \
+ crmtime(db)
+
+#define \
+ dpfdesc(db) \
+ crfdesc(db)
+
+#define \
+ dpremove(db) \
+ crremove(db)
+
+#define \
+ dprepair(db) \
+ crrepair(db)
+
+#define \
+ dpexportdb(db, name) \
+ crexportdb(db, name)
+
+#define \
+ dpimportdb(db, name) \
+ crimportdb(db, name)
+
+#define \
+ dpsnaffle(db, name) \
+ crsnaffle(db, name)
+
+#define \
+ dpmemsync(db) \
+ crmemsync(db)
+
+#define \
+ dpmemflush(db) \
+ crmemflush(db)
+
+#define \
+ dpgetflags(db) \
+ crgetflags(db)
+
+#define \
+ dpsetflags(db, flags) \
+ crsetflags(db, flags)
+
+
+
+/*************************************************************************************************
+ * including real implementation
+ *************************************************************************************************/
+
+
+#include "villa.c"
+
+
+
+/* END OF FILE */
diff --git a/qdbm/vista.h b/qdbm/vista.h
new file mode 100644
index 00000000..bb67fbb1
--- /dev/null
+++ b/qdbm/vista.h
@@ -0,0 +1,138 @@
+/*************************************************************************************************
+ * The extended advanced API of QDBM
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#ifndef _VISTA_H /* duplication check */
+#define _VISTA_H
+
+#if defined(__cplusplus) /* export for C++ */
+extern "C" {
+#endif
+
+
+
+/*************************************************************************************************
+ * macros to borrow symbols from Villa
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <curia.h>
+#include <cabin.h>
+#include <stdlib.h>
+
+#define VLREC VSTREC
+#define VLIDX VSTIDX
+#define VLLEAF VSTLEAF
+#define VLNODE VSTNODE
+#define VLCFUNC VSTCFUNC
+
+#define VL_CMPLEX VST_CMPLEX
+#define VL_CMPINT VST_CMPINT
+#define VL_CMPNUM VST_CMPNUM
+#define VL_CMPDEC VST_CMPDEC
+
+#define VILLA VISTA
+
+#define VL_OREADER VST_OREADER
+#define VL_OWRITER VST_OWRITER
+#define VL_OCREAT VST_OCREAT
+#define VL_OTRUNC VST_OTRUNC
+#define VL_ONOLCK VST_ONOLCK
+#define VL_OLCKNB VST_OLCKNB
+#define VL_OZCOMP VST_OZCOMP
+
+#define VL_DOVER VST_DOVER
+#define VL_DKEEP VST_DKEEP
+#define VL_DCAT VST_DCAT
+#define VL_DDUP VST_DDUP
+
+#define VL_JFORWARD VST_JFORWARD
+#define VL_JBACKWARD VST_JBACKWARD
+
+#define vlopen vstopen
+#define vlclose vstclose
+#define vlput vstput
+#define vlout vstout
+#define vlget vstget
+#define vlvsiz vstvsiz
+#define vlvnum vstvnum
+#define vlputlist vstputlist
+#define vloutlist vstoutlist
+#define vlgetlist vstgetlist
+#define vlgetcat vstgetcat
+#define vlcurfirst vstcurfirst
+#define vlcurlast vstcurlast
+#define vlcurprev vstcurprev
+#define vlcurnext vstcurnext
+#define vlcurjump vstcurjump
+#define vlcurkey vstcurkey
+#define vlcurval vstcurval
+#define vlcurput vstcurput
+#define vlcurout vstcurout
+#define vlsettuning vstsettuning
+#define vlsync vstsync
+#define vloptimize vstoptimize
+#define vlname vstname
+#define vlfsiz vstfsiz
+#define vllnum vstlnum
+#define vlnnum vstnnum
+#define vlrnum vstrnum
+#define vlwritable vstwritable
+#define vlfatalerror vstfatalerror
+#define vlinode vstinode
+#define vlmtime vstmtime
+#define vltranbegin vsttranbegin
+#define vltrancommit vsttrancommit
+#define vltranabort vsttranabort
+#define vlremove vstremove
+#define vlrepair vstrepair
+#define vlexportdb vstexportdb
+#define vlimportdb vstimportdb
+#define vlcrdnumptr vstcrdnumptr
+#define vlmemsync vstmemsync
+#define vlmemflush vstmemflush
+#define vlgetcache vstgetcache
+#define vlcurkeycache vstcurkeycache
+#define vlcurvalcache vstcurvalcache
+#define vlmulcuropen vstmulcuropen
+#define vlmulcurclose vstmulcurclose
+#define vlmulcurfirst vstmulcurfirst
+#define vlmulcurlast vstmulcurlast
+#define vlmulcurprev vstmulcurprev
+#define vlmulcurnext vstmulcurnext
+#define vlmulcurjump vstmulcurjump
+#define vlmulcurkey vstmulcurkey
+#define vlmulcurval vstmulcurval
+#define vlmulcurkeycache vstmulcurkeycache
+#define vlmulcurvalcache vstmulcurvalcache
+#define vlsetfbpsiz vstsetfbpsiz
+#define vlgetflags vstgetflags
+#define vlsetflags vstsetflags
+
+#if !defined(_VISTA_C)
+#include <villa.h>
+#endif
+
+
+
+#if defined(__cplusplus) /* export for C++ */
+}
+#endif
+
+#endif /* duplication check */
+
+
+/* END OF FILE */
diff --git a/qdbm/vlmgr.c b/qdbm/vlmgr.c
new file mode 100644
index 00000000..59aae394
--- /dev/null
+++ b/qdbm/vlmgr.c
@@ -0,0 +1,968 @@
+/*************************************************************************************************
+ * Utility for debugging Villa and its applications
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <cabin.h>
+#include <villa.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+char *hextoobj(const char *str, int *sp);
+char *dectoiobj(const char *str, int *sp);
+int runcreate(int argc, char **argv);
+int runput(int argc, char **argv);
+int runout(int argc, char **argv);
+int runget(int argc, char **argv);
+int runlist(int argc, char **argv);
+int runoptimize(int argc, char **argv);
+int runinform(int argc, char **argv);
+int runremove(int argc, char **argv);
+int runrepair(int argc, char **argv);
+int runexportdb(int argc, char **argv);
+int runimportdb(int argc, char **argv);
+void pdperror(const char *name);
+void printobj(const char *obj, int size);
+void printobjhex(const char *obj, int size);
+int docreate(const char *name, int cmode);
+int doput(const char *name, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+ int dmode, VLCFUNC cmp);
+int doout(const char *name, const char *kbuf, int ksiz, VLCFUNC cmp, int lb);
+int doget(const char *name, int opts, const char *kbuf, int ksiz, VLCFUNC cmp,
+ int lb, int ox, int nb);
+int dolist(const char *name, int opts, const char *tbuf, int tsiz, const char *bbuf, int bsiz,
+ VLCFUNC cmp, int ki, int kb, int vb, int ox, int gt, int lt, int max, int desc);
+int dooptimize(const char *name);
+int doinform(const char *name, int opts);
+int doremove(const char *name);
+int dorepair(const char *name, VLCFUNC cmp);
+int doexportdb(const char *name, const char *file, VLCFUNC cmp);
+int doimportdb(const char *name, const char *file, VLCFUNC cmp);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ char *env;
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ if((env = getenv("QDBMDBGFD")) != NULL) dpdbgfd = atoi(env);
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "create")){
+ rv = runcreate(argc, argv);
+ } else if(!strcmp(argv[1], "put")){
+ rv = runput(argc, argv);
+ } else if(!strcmp(argv[1], "out")){
+ rv = runout(argc, argv);
+ } else if(!strcmp(argv[1], "get")){
+ rv = runget(argc, argv);
+ } else if(!strcmp(argv[1], "list")){
+ rv = runlist(argc, argv);
+ } else if(!strcmp(argv[1], "optimize")){
+ rv = runoptimize(argc, argv);
+ } else if(!strcmp(argv[1], "inform")){
+ rv = runinform(argc, argv);
+ } else if(!strcmp(argv[1], "remove")){
+ rv = runremove(argc, argv);
+ } else if(!strcmp(argv[1], "repair")){
+ rv = runrepair(argc, argv);
+ } else if(!strcmp(argv[1], "exportdb")){
+ rv = runexportdb(argc, argv);
+ } else if(!strcmp(argv[1], "importdb")){
+ rv = runimportdb(argc, argv);
+ } else if(!strcmp(argv[1], "version") || !strcmp(argv[1], "--version")){
+ printf("Powered by QDBM version %s on %s%s\n",
+ dpversion, dpsysname, dpisreentrant ? " (reentrant)" : "");
+ printf("Copyright (c) 2000-2007 Mikio Hirabayashi\n");
+ rv = 0;
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: administration utility for Villa\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s create [-cz|-cy|-cx] name\n", progname);
+ fprintf(stderr, " %s put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat|-dup] name key val\n", progname);
+ fprintf(stderr, " %s out [-l] [-kx|-ki] name key\n", progname);
+ fprintf(stderr, " %s get [-nl] [-l] [-kx|-ki] [-ox] [-n] name key\n", progname);
+ fprintf(stderr, " %s list [-nl] [-k|-v] [-kx|-ki] [-ox] [-top key] [-bot key] [-gt] [-lt]"
+ " [-max num] [-desc] name\n", progname);
+ fprintf(stderr, " %s optimize name\n", progname);
+ fprintf(stderr, " %s inform [-nl] name\n", progname);
+ fprintf(stderr, " %s remove name\n", progname);
+ fprintf(stderr, " %s repair [-ki] name\n", progname);
+ fprintf(stderr, " %s exportdb [-ki] name file\n", progname);
+ fprintf(stderr, " %s importdb [-ki] name file\n", progname);
+ fprintf(stderr, " %s version\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* create a binary object from a hexadecimal string */
+char *hextoobj(const char *str, int *sp){
+ char *buf, mbuf[3];
+ int len, i, j;
+ len = strlen(str);
+ if(!(buf = malloc(len + 1))) return NULL;
+ j = 0;
+ for(i = 0; i < len; i += 2){
+ while(strchr(" \n\r\t\f\v", str[i])){
+ i++;
+ }
+ if((mbuf[0] = str[i]) == '\0') break;
+ if((mbuf[1] = str[i+1]) == '\0') break;
+ mbuf[2] = '\0';
+ buf[j++] = (char)strtol(mbuf, NULL, 16);
+ }
+ buf[j] = '\0';
+ *sp = j;
+ return buf;
+}
+
+
+/* create a integer object from a decimal string */
+char *dectoiobj(const char *str, int *sp){
+ char *buf;
+ int num;
+ num = atoi(str);
+ if(!(buf = malloc(sizeof(int)))) return NULL;
+ *(int *)buf = num;
+ *sp = sizeof(int);
+ return buf;
+}
+
+
+/* parse arguments of create command */
+int runcreate(int argc, char **argv){
+ char *name;
+ int i, cmode, rv;
+ name = NULL;
+ cmode = 0;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-cz")){
+ cmode |= VL_OZCOMP;
+ } else if(!strcmp(argv[i], "-cy")){
+ cmode |= VL_OYCOMP;
+ } else if(!strcmp(argv[i], "-cx")){
+ cmode |= VL_OXCOMP;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = docreate(name, cmode);
+ return rv;
+}
+
+
+/* parse arguments of put command */
+int runput(int argc, char **argv){
+ char *name, *key, *val, *kbuf, *vbuf;
+ int i, kx, ki, vx, vi, vf, ksiz, vsiz, rv;
+ int dmode;
+ name = NULL;
+ kx = FALSE;
+ ki = FALSE;
+ vx = FALSE;
+ vi = FALSE;
+ vf = FALSE;
+ key = NULL;
+ val = NULL;
+ dmode = VL_DOVER;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else if(!strcmp(argv[i], "-vx")){
+ vx = TRUE;
+ } else if(!strcmp(argv[i], "-vi")){
+ vi = TRUE;
+ } else if(!strcmp(argv[i], "-vf")){
+ vf = TRUE;
+ } else if(!strcmp(argv[i], "-keep")){
+ dmode = VL_DKEEP;
+ } else if(!strcmp(argv[i], "-cat")){
+ dmode = VL_DCAT;
+ } else if(!strcmp(argv[i], "-dup")){
+ dmode = VL_DDUP;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else if(!val){
+ val = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key || !val) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else if(ki){
+ kbuf = dectoiobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = -1;
+ }
+ if(vx){
+ vbuf = hextoobj(val, &vsiz);
+ } else if(vi){
+ vbuf = dectoiobj(val, &vsiz);
+ } else if(vf){
+ vbuf = cbreadfile(val, &vsiz);
+ } else {
+ vbuf = cbmemdup(val, -1);
+ vsiz = -1;
+ }
+ if(kbuf && vbuf){
+ rv = doput(name, kbuf, ksiz, vbuf, vsiz, dmode, ki ? VL_CMPINT : VL_CMPLEX);
+ } else {
+ if(vf){
+ fprintf(stderr, "%s: %s: cannot read\n", progname, val);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ }
+ rv = 1;
+ }
+ free(kbuf);
+ free(vbuf);
+ return rv;
+}
+
+
+/* parse arguments of out command */
+int runout(int argc, char **argv){
+ char *name, *key, *kbuf;
+ int i, kx, ki, lb, ksiz, rv;
+ name = NULL;
+ kx = FALSE;
+ ki = FALSE;
+ lb = FALSE;
+ key = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-l")){
+ lb = TRUE;
+ } else if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else if(ki){
+ kbuf = dectoiobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = -1;
+ }
+ if(kbuf){
+ rv = doout(name, kbuf, ksiz, ki ? VL_CMPINT : VL_CMPLEX, lb);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ rv = 1;
+ }
+ free(kbuf);
+ return rv;
+}
+
+
+/* parse arguments of get command */
+int runget(int argc, char **argv){
+ char *name, *key, *kbuf;
+ int i, opts, lb, kx, ki, ox, nb, ksiz, rv;
+ name = NULL;
+ opts = 0;
+ lb = FALSE;
+ kx = FALSE;
+ ki = FALSE;
+ ox = FALSE;
+ nb = FALSE;
+ key = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-nl")){
+ opts |= VL_ONOLCK;
+ } else if(!strcmp(argv[i], "-l")){
+ lb = TRUE;
+ } else if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else if(!strcmp(argv[i], "-ox")){
+ ox = TRUE;
+ } else if(!strcmp(argv[i], "-n")){
+ nb = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!key){
+ key = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !key) usage();
+ if(kx){
+ kbuf = hextoobj(key, &ksiz);
+ } else if(ki){
+ kbuf = dectoiobj(key, &ksiz);
+ } else {
+ kbuf = cbmemdup(key, -1);
+ ksiz = -1;
+ }
+ if(kbuf){
+ rv = doget(name, opts, kbuf, ksiz, ki ? VL_CMPINT : VL_CMPLEX, lb, ox, nb);
+ } else {
+ fprintf(stderr, "%s: out of memory\n", progname);
+ rv = 1;
+ }
+ free(kbuf);
+ return rv;
+}
+
+
+/* parse arguments of list command */
+int runlist(int argc, char **argv){
+ char *name, *top, *bot, *tbuf, *bbuf, *nstr;
+ int i, opts, kb, vb, kx, ki, ox, gt, lt, max, desc, tsiz, bsiz, rv;
+ name = NULL;
+ opts = 0;
+ kb = FALSE;
+ vb = FALSE;
+ kx = FALSE;
+ ki = FALSE;
+ ox = FALSE;
+ gt = FALSE;
+ lt = FALSE;
+ max = -1;
+ desc = FALSE;
+ top = NULL;
+ bot = NULL;
+ nstr = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-nl")){
+ opts |= VL_ONOLCK;
+ } else if(!strcmp(argv[i], "-k")){
+ kb = TRUE;
+ } else if(!strcmp(argv[i], "-v")){
+ vb = TRUE;
+ } else if(!strcmp(argv[i], "-kx")){
+ kx = TRUE;
+ } else if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else if(!strcmp(argv[i], "-ox")){
+ ox = TRUE;
+ } else if(!strcmp(argv[i], "-top")){
+ if(++i >= argc) usage();
+ top = argv[i];
+ } else if(!strcmp(argv[i], "-bot")){
+ if(++i >= argc) usage();
+ bot = argv[i];
+ } else if(!strcmp(argv[i], "-gt")){
+ gt = TRUE;
+ } else if(!strcmp(argv[i], "-lt")){
+ lt = TRUE;
+ } else if(!strcmp(argv[i], "-max")){
+ if(++i >= argc) usage();
+ max = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-desc")){
+ desc = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ tbuf = NULL;
+ bbuf = NULL;
+ if(kx){
+ if(top) tbuf = hextoobj(top, &tsiz);
+ if(bot) bbuf = hextoobj(bot, &bsiz);
+ } else if(ki){
+ if(top) tbuf = dectoiobj(top, &tsiz);
+ if(bot) bbuf = dectoiobj(bot, &bsiz);
+ } else {
+ if(top){
+ tbuf = cbmemdup(top, -1);
+ tsiz = strlen(tbuf);
+ }
+ if(bot){
+ bbuf = cbmemdup(bot, -1);
+ bsiz = strlen(bbuf);
+ }
+ }
+ rv = dolist(name, opts, tbuf, tsiz, bbuf, bsiz, ki ? VL_CMPINT : VL_CMPLEX, ki,
+ kb, vb, ox, gt, lt, max, desc);
+ free(tbuf);
+ free(bbuf);
+ return rv;
+}
+
+
+/* parse arguments of optimize command */
+int runoptimize(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = dooptimize(name);
+ return rv;
+}
+
+
+/* parse arguments of inform command */
+int runinform(int argc, char **argv){
+ char *name;
+ int i, opts, rv;
+ name = NULL;
+ opts = 0;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-nl")){
+ opts |= VL_ONOLCK;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doinform(name, opts);
+ return rv;
+}
+
+
+/* parse arguments of remove command */
+int runremove(int argc, char **argv){
+ char *name;
+ int i, rv;
+ name = NULL;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ usage();
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doremove(name);
+ return rv;
+}
+
+
+/* parse arguments of repair command */
+int runrepair(int argc, char **argv){
+ char *name;
+ int i, ki, rv;
+ name = NULL;
+ ki = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = dorepair(name, ki ? VL_CMPINT : VL_CMPLEX);
+ return rv;
+}
+
+
+/* parse arguments of exportdb command */
+int runexportdb(int argc, char **argv){
+ char *name, *file;
+ int i, ki, rv;
+ name = NULL;
+ file = NULL;
+ ki = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !file) usage();
+ rv = doexportdb(name, file, ki ? VL_CMPINT : VL_CMPLEX);
+ return rv;
+}
+
+
+/* parse arguments of importdb command */
+int runimportdb(int argc, char **argv){
+ char *name, *file;
+ int i, ki, rv;
+ name = NULL;
+ file = NULL;
+ ki = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-ki")){
+ ki = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!file){
+ file = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !file) usage();
+ rv = doimportdb(name, file, ki ? VL_CMPINT : VL_CMPLEX);
+ return rv;
+}
+
+
+/* print an error message */
+void pdperror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, dperrmsg(dpecode));
+}
+
+
+/* print an object */
+void printobj(const char *obj, int size){
+ int i;
+ for(i = 0; i < size; i++){
+ putchar(obj[i]);
+ }
+}
+
+
+/* print an object as a hexadecimal string */
+void printobjhex(const char *obj, int size){
+ int i;
+ for(i = 0; i < size; i++){
+ printf("%s%02X", i > 0 ? " " : "", ((const unsigned char *)obj)[i]);
+ }
+}
+
+
+/* perform create command */
+int docreate(const char *name, int cmode){
+ VILLA *villa;
+ int omode;
+ omode = VL_OWRITER | VL_OCREAT | VL_OTRUNC | cmode;
+ if(!(villa = vlopen(name, omode, VL_CMPLEX))){
+ pdperror(name);
+ return 1;
+ }
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform put command */
+int doput(const char *name, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+ int dmode, VLCFUNC cmp){
+ VILLA *villa;
+ if(!(villa = vlopen(name, VL_OWRITER, cmp))){
+ pdperror(name);
+ return 1;
+ }
+ if(!vlput(villa, kbuf, ksiz, vbuf, vsiz, dmode)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform out command */
+int doout(const char *name, const char *kbuf, int ksiz, VLCFUNC cmp, int lb){
+ VILLA *villa;
+ if(!(villa = vlopen(name, VL_OWRITER, cmp))){
+ pdperror(name);
+ return 1;
+ }
+ if(lb){
+ if(!vloutlist(villa, kbuf, ksiz)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ } else {
+ if(!vlout(villa, kbuf, ksiz)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform get command */
+int doget(const char *name, int opts, const char *kbuf, int ksiz, VLCFUNC cmp,
+ int lb, int ox, int nb){
+ VILLA *villa;
+ CBLIST *vals;
+ char *vbuf;
+ int vsiz;
+ if(!(villa = vlopen(name, VL_OREADER | opts, cmp))){
+ pdperror(name);
+ return 1;
+ }
+ if(lb){
+ if(!(vals = vlgetlist(villa, kbuf, ksiz))){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ while((vbuf = cblistshift(vals, &vsiz)) != NULL){
+ if(ox){
+ printobjhex(vbuf, vsiz);
+ } else {
+ printobj(vbuf, vsiz);
+ }
+ free(vbuf);
+ putchar('\n');
+ }
+ cblistclose(vals);
+ } else {
+ if(!(vbuf = vlget(villa, kbuf, ksiz, &vsiz))){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ if(ox){
+ printobjhex(vbuf, vsiz);
+ } else {
+ printobj(vbuf, vsiz);
+ }
+ free(vbuf);
+ if(!nb) putchar('\n');
+ }
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform list command */
+int dolist(const char *name, int opts, const char *tbuf, int tsiz, const char *bbuf, int bsiz,
+ VLCFUNC cmp, int ki, int kb, int vb, int ox, int gt, int lt, int max, int desc){
+ VILLA *villa;
+ char *kbuf, *vbuf;
+ int ksiz, vsiz, show, rv;
+ if(!(villa = vlopen(name, VL_OREADER | opts, cmp))){
+ pdperror(name);
+ return 1;
+ }
+ if(max < 0) max = vlrnum(villa);
+ if(desc){
+ if(bbuf){
+ vlcurjump(villa, bbuf, bsiz, VL_JBACKWARD);
+ } else {
+ vlcurlast(villa);
+ }
+ show = 0;
+ while(show < max && (kbuf = vlcurkey(villa, &ksiz)) != NULL){
+ if(bbuf && lt){
+ if(cmp(kbuf, ksiz, bbuf, bsiz) == 0){
+ free(kbuf);
+ vlcurnext(villa);
+ continue;
+ }
+ lt = FALSE;
+ }
+ if(tbuf){
+ rv = cmp(kbuf, ksiz, tbuf, tsiz);
+ if(rv < 0 || (gt && rv == 0)){
+ free(kbuf);
+ break;
+ }
+ }
+ if(!(vbuf = vlcurval(villa, &vsiz))){
+ free(kbuf);
+ break;
+ }
+ if(ox){
+ if(!vb) printobjhex(kbuf, ksiz);
+ if(!kb && !vb) putchar('\t');
+ if(!kb) printobjhex(vbuf, vsiz);
+ } else {
+ if(!vb) printobj(kbuf, ksiz);
+ if(!kb && !vb) putchar('\t');
+ if(!kb) printobj(vbuf, vsiz);
+ }
+ putchar('\n');
+ free(kbuf);
+ free(vbuf);
+ show++;
+ vlcurprev(villa);
+ }
+ } else {
+ if(tbuf){
+ vlcurjump(villa, tbuf, tsiz, VL_JFORWARD);
+ } else {
+ vlcurfirst(villa);
+ }
+ show = 0;
+ while(show < max && (kbuf = vlcurkey(villa, &ksiz)) != NULL){
+ if(tbuf && gt){
+ if(cmp(kbuf, ksiz, tbuf, tsiz) == 0){
+ free(kbuf);
+ vlcurnext(villa);
+ continue;
+ }
+ gt = FALSE;
+ }
+ if(bbuf){
+ rv = cmp(kbuf, ksiz, bbuf, bsiz);
+ if(rv > 0 || (lt && rv == 0)){
+ free(kbuf);
+ break;
+ }
+ }
+ if(!(vbuf = vlcurval(villa, &vsiz))){
+ free(kbuf);
+ break;
+ }
+ if(ox){
+ if(!vb) printobjhex(kbuf, ksiz);
+ if(!kb && !vb) putchar('\t');
+ if(!kb) printobjhex(vbuf, vsiz);
+ } else {
+ if(!vb) printobj(kbuf, ksiz);
+ if(!kb && !vb) putchar('\t');
+ if(!kb) printobj(vbuf, vsiz);
+ }
+ putchar('\n');
+ free(kbuf);
+ free(vbuf);
+ show++;
+ vlcurnext(villa);
+ }
+ }
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform optimize command */
+int dooptimize(const char *name){
+ VILLA *villa;
+ if(!(villa = vlopen(name, VL_OWRITER, VL_CMPLEX))){
+ pdperror(name);
+ return 1;
+ }
+ if(!vloptimize(villa)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform inform command */
+int doinform(const char *name, int opts){
+ VILLA *villa;
+ char *tmp;
+ if(!(villa = vlopen(name, VL_OREADER | opts, VL_CMPLEX))){
+ pdperror(name);
+ return 1;
+ }
+ tmp = vlname(villa);
+ printf("name: %s\n", tmp ? tmp : "(null)");
+ free(tmp);
+ printf("file size: %d\n", vlfsiz(villa));
+ printf("leaf nodes: %d\n", vllnum(villa));
+ printf("non-leaf nodes: %d\n", vlnnum(villa));
+ printf("records: %d\n", vlrnum(villa));
+ printf("inode number: %d\n", vlinode(villa));
+ printf("modified time: %.0f\n", (double)vlmtime(villa));
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform remove command */
+int doremove(const char *name){
+ if(!vlremove(name)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform repair command */
+int dorepair(const char *name, VLCFUNC cmp){
+ if(!vlrepair(name, cmp)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform exportdb command */
+int doexportdb(const char *name, const char *file, VLCFUNC cmp){
+ VILLA *villa;
+ if(!(villa = vlopen(name, VL_OREADER, cmp))){
+ pdperror(name);
+ return 1;
+ }
+ if(!vlexportdb(villa, file)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* perform importdb command */
+int doimportdb(const char *name, const char *file, VLCFUNC cmp){
+ VILLA *villa;
+ if(!(villa = vlopen(name, VL_OWRITER | VL_OCREAT | VL_OTRUNC, cmp))){
+ pdperror(name);
+ return 1;
+ }
+ if(!vlimportdb(villa, file)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/vltest.c b/qdbm/vltest.c
new file mode 100644
index 00000000..97f64e48
--- /dev/null
+++ b/qdbm/vltest.c
@@ -0,0 +1,1507 @@
+/*************************************************************************************************
+ * Test cases of Villa
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <cabin.h>
+#include <villa.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <time.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+#define RECBUFSIZ 32 /* buffer for records */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int runrdup(int argc, char **argv);
+int runcombo(int argc, char **argv);
+int runwicked(int argc, char **argv);
+int printfflush(const char *format, ...);
+void pdperror(const char *name);
+int myrand(void);
+int dowrite(const char *name, int rnum, int ii, int cmode,
+ int lrecmax, int nidxmax, int lcnum, int ncnum, int fbp);
+int doread(const char *name, int ii, int vc);
+int dordup(const char *name, int rnum, int pnum, int ii, int cmode, int cc,
+ int lrecmax, int nidxmax, int lcnum, int ncnum, int fbp);
+int docombo(const char *name, int cmode);
+int dowicked(const char *name, int rnum, int cb, int cmode);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ char *env;
+ int rv;
+ cbstdiobin();
+ if((env = getenv("QDBMDBGFD")) != NULL) dpdbgfd = atoi(env);
+ progname = argv[0];
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "write")){
+ rv = runwrite(argc, argv);
+ } else if(!strcmp(argv[1], "read")){
+ rv = runread(argc, argv);
+ } else if(!strcmp(argv[1], "rdup")){
+ rv = runrdup(argc, argv);
+ } else if(!strcmp(argv[1], "combo")){
+ rv = runcombo(argc, argv);
+ } else if(!strcmp(argv[1], "wicked")){
+ rv = runwicked(argc, argv);
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: test cases for Villa\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s write [-int] [-cz|-cy|-cx] [-tune lrecmax nidxmax lcnum ncnum]"
+ " [-fbp num] name rnum\n", progname);
+ fprintf(stderr, " %s read [-int] [-vc] name\n", progname);
+ fprintf(stderr, " %s rdup [-int] [-cz|-cy|-cx] [-cc] [-tune lrecmax nidxmax lcnum ncnum]"
+ " [-fbp num] name rnum pnum\n", progname);
+ fprintf(stderr, " %s combo [-cz|-cy|-cx] name\n", progname);
+ fprintf(stderr, " %s wicked [-c] [-cz|-cy|-cx] name rnum\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+ char *name, *rstr;
+ int i, rnum, ii, cmode, lrecmax, nidxmax, lcnum, ncnum, fbp, rv;
+ name = NULL;
+ rstr = NULL;
+ rnum = 0;
+ ii = FALSE;
+ cmode = 0;
+ lrecmax = -1;
+ nidxmax = -1;
+ lcnum = -1;
+ ncnum = -1;
+ fbp = -1;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-int")){
+ ii = TRUE;
+ } else if(!strcmp(argv[i], "-cz")){
+ cmode |= VL_OZCOMP;
+ } else if(!strcmp(argv[i], "-cy")){
+ cmode |= VL_OYCOMP;
+ } else if(!strcmp(argv[i], "-cx")){
+ cmode |= VL_OXCOMP;
+ } else if(!strcmp(argv[i], "-tune")){
+ if(++i >= argc) usage();
+ lrecmax = atoi(argv[i]);
+ if(++i >= argc) usage();
+ nidxmax = atoi(argv[i]);
+ if(++i >= argc) usage();
+ lcnum = atoi(argv[i]);
+ if(++i >= argc) usage();
+ ncnum = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-fbp")){
+ if(++i >= argc) usage();
+ fbp = atoi(argv[i]);
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!rstr){
+ rstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ rv = dowrite(name, rnum, ii, cmode, lrecmax, nidxmax, lcnum, ncnum, fbp);
+ return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+ char *name;
+ int i, ii, vc, rv;
+ name = NULL;
+ ii = FALSE;
+ vc = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-int")){
+ ii = TRUE;
+ } else if(!strcmp(argv[i], "-vc")){
+ vc = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doread(name, ii, vc);
+ return rv;
+}
+
+
+/* parse arguments of rdup command */
+int runrdup(int argc, char **argv){
+ char *name, *rstr, *pstr;
+ int i, rnum, pnum, ii, cmode, cc, lrecmax, nidxmax, lcnum, ncnum, fbp, rv;
+ name = NULL;
+ rstr = NULL;
+ pstr = NULL;
+ rnum = 0;
+ pnum = 0;
+ ii = FALSE;
+ cmode = 0;
+ cc = FALSE;
+ lrecmax = -1;
+ nidxmax = -1;
+ lcnum = -1;
+ ncnum = -1;
+ fbp = -1;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-int")){
+ ii = TRUE;
+ } else if(!strcmp(argv[i], "-cz")){
+ cmode |= VL_OZCOMP;
+ } else if(!strcmp(argv[i], "-cy")){
+ cmode |= VL_OYCOMP;
+ } else if(!strcmp(argv[i], "-cx")){
+ cmode |= VL_OXCOMP;
+ } else if(!strcmp(argv[i], "-cc")){
+ cc = TRUE;
+ } else if(!strcmp(argv[i], "-tune")){
+ if(++i >= argc) usage();
+ lrecmax = atoi(argv[i]);
+ if(++i >= argc) usage();
+ nidxmax = atoi(argv[i]);
+ if(++i >= argc) usage();
+ lcnum = atoi(argv[i]);
+ if(++i >= argc) usage();
+ ncnum = atoi(argv[i]);
+ } else if(!strcmp(argv[i], "-fbp")){
+ if(++i >= argc) usage();
+ fbp = atoi(argv[i]);
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!rstr){
+ rstr = argv[i];
+ } else if(!pstr){
+ pstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !rstr || !pstr) usage();
+ rnum = atoi(rstr);
+ pnum = atoi(pstr);
+ if(rnum < 1 || pnum < 1) usage();
+ rv = dordup(name, rnum, pnum, ii, cmode, cc, lrecmax, nidxmax, lcnum, ncnum, fbp);
+ return rv;
+}
+
+
+/* parse arguments of combo command */
+int runcombo(int argc, char **argv){
+ char *name;
+ int i, cmode, rv;
+ name = NULL;
+ cmode = 0;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-cz")){
+ cmode |= VL_OZCOMP;
+ } else if(!strcmp(argv[i], "-cy")){
+ cmode |= VL_OYCOMP;
+ } else if(!strcmp(argv[i], "-cx")){
+ cmode |= VL_OXCOMP;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = docombo(name, cmode);
+ return rv;
+}
+
+
+/* parse arguments of wicked command */
+int runwicked(int argc, char **argv){
+ char *name, *rstr;
+ int i, cb, cmode, rnum, rv;
+ name = NULL;
+ rstr = NULL;
+ cb = FALSE;
+ cmode = 0;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-c")){
+ cb = TRUE;
+ } else if(!strcmp(argv[i], "-cz")){
+ cmode |= VL_OZCOMP;
+ } else if(!strcmp(argv[i], "-cy")){
+ cmode |= VL_OYCOMP;
+ } else if(!strcmp(argv[i], "-cx")){
+ cmode |= VL_OXCOMP;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else if(!rstr){
+ rstr = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name || !rstr) usage();
+ rnum = atoi(rstr);
+ if(rnum < 1) usage();
+ rv = dowicked(name, rnum, cb, cmode);
+ return rv;
+}
+
+
+/* print formatted string and flush the buffer */
+int printfflush(const char *format, ...){
+ va_list ap;
+ int rv;
+ va_start(ap, format);
+ rv = vprintf(format, ap);
+ if(fflush(stdout) == EOF) rv = -1;
+ va_end(ap);
+ return rv;
+}
+
+
+/* print an error message */
+void pdperror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, dperrmsg(dpecode));
+}
+
+
+/* pseudo random number generator */
+int myrand(void){
+ static int cnt = 0;
+ if(cnt == 0) srand(time(NULL));
+ return (rand() * rand() + (rand() >> (sizeof(int) * 4)) + (cnt++)) & INT_MAX;
+}
+
+
+/* perform write command */
+int dowrite(const char *name, int rnum, int ii, int cmode,
+ int lrecmax, int nidxmax, int lcnum, int ncnum, int fbp){
+ VILLA *villa;
+ int i, omode, err, len;
+ char buf[RECBUFSIZ];
+ printfflush("<Writing Test>\n name=%s rnum=%d int=%d cmode=%d "
+ "lrecmax=%d nidxmax=%d lcnum=%d ncnum=%d fbp=%d\n\n",
+ name, rnum, ii, cmode, lrecmax, nidxmax, lcnum, ncnum, fbp);
+ /* open a database */
+ omode = VL_OWRITER | VL_OCREAT | VL_OTRUNC | cmode;
+ if(!(villa = vlopen(name, omode, ii ? VL_CMPINT : VL_CMPLEX))){
+ pdperror(name);
+ return 1;
+ }
+ err = FALSE;
+ /* set tuning parameters */
+ if(lrecmax > 0) vlsettuning(villa, lrecmax, nidxmax, lcnum, ncnum);
+ if(fbp >= 0) vlsetfbpsiz(villa, fbp);
+ /* loop for each record */
+ for(i = 1; i <= rnum; i++){
+ /* store a record */
+ if(ii){
+ if(!vlput(villa, (char *)&i, sizeof(int), (char *)&i, sizeof(int), VL_DOVER)){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ } else {
+ len = sprintf(buf, "%08d", i);
+ if(!vlput(villa, buf, len, buf, len, VL_DOVER)){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ }
+ /* print progression */
+ if(rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ /* close the database */
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return 0;
+}
+
+
+/* perform read command */
+int doread(const char *name, int ii, int vc){
+ VILLA *villa;
+ int i, rnum, err, len;
+ const char *cval;
+ char buf[RECBUFSIZ], *val;
+ printfflush("<Reading Test>\n name=%s int=%d\n\n", name, ii);
+ /* open a database */
+ if(!(villa = vlopen(name, VL_OREADER, ii ? VL_CMPINT : VL_CMPLEX))){
+ pdperror(name);
+ return 1;
+ }
+ /* get the number of records */
+ rnum = vlrnum(villa);
+ err = FALSE;
+ /* loop for each record */
+ for(i = 1; i <= rnum; i++){
+ /* retrieve a record */
+ if(ii){
+ if(vc){
+ if(!(cval = vlgetcache(villa, (char *)&i, sizeof(int), NULL))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ } else {
+ if(!(val = vlget(villa, (char *)&i, sizeof(int), NULL))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ free(val);
+ }
+ } else {
+ len = sprintf(buf, "%08d", i);
+ if(vc){
+ if(!(cval = vlgetcache(villa, buf, len, NULL))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ } else {
+ if(!(val = vlget(villa, buf, len, NULL))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ free(val);
+ }
+ }
+ /* print progression */
+ if(rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d)\n", i);
+ }
+ }
+ }
+ /* close the database */
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return 0;
+}
+
+
+/* perform rdup command */
+int dordup(const char *name, int rnum, int pnum, int ii, int cmode, int cc,
+ int lrecmax, int nidxmax, int lcnum, int ncnum, int fbp){
+ VILLA *villa;
+ int i, omode, err, dmode, vi, len;
+ char buf[RECBUFSIZ];
+ printfflush("<Random Writing Test>\n name=%s rnum=%d int=%d cmode=%d "
+ "lrecmax=%d nidxmax=%d lcnum=%d ncnum=%d fbp=%d\n\n",
+ name, rnum, ii, cmode, lrecmax, nidxmax, lcnum, ncnum, fbp);
+ omode = VL_OWRITER | VL_OCREAT | VL_OTRUNC | cmode;
+ if(!(villa = vlopen(name, omode, ii ? VL_CMPINT : VL_CMPLEX))){
+ pdperror(name);
+ return 1;
+ }
+ err = FALSE;
+ if(lrecmax > 0) vlsettuning(villa, lrecmax, nidxmax, lcnum, ncnum);
+ if(fbp >= 0) vlsetfbpsiz(villa, fbp);
+ for(i = 1; i <= rnum; i++){
+ dmode = i % 3 == 0 ? VL_DDUPR : VL_DDUP;
+ if(cc && myrand() % 2 == 0) dmode = VL_DCAT;
+ vi = myrand() % pnum + 1;
+ if(ii){
+ if(!vlput(villa, (char *)&vi, sizeof(int), (char *)&vi, sizeof(int), dmode)){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ } else {
+ len = sprintf(buf, "%08d", vi);
+ if(!vlput(villa, buf, len, buf, len, dmode)){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ }
+ if(rnum > 250 && i % (rnum / 250) == 0){
+ putchar('.');
+ fflush(stdout);
+ if(i == rnum || i % (rnum / 10) == 0){
+ printfflush(" (%08d: fsiz=%d lnum=%d nnum=%d)\n",
+ i, vlfsiz(villa), vllnum(villa), vlnnum(villa));
+ }
+ }
+ }
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return 0;
+}
+
+
+/* perform combo command */
+int docombo(const char *name, int cmode){
+ VILLA *villa;
+ VLMULCUR **mulcurs;
+ char buf[RECBUFSIZ], *vbuf, *kbuf;
+ int i, j, omode, len, vsiz, ksiz, fsiz, lnum, nnum, rnum;
+ CBLIST *alist, *dlist;
+ const char *ap, *dp;
+ printfflush("<Combination Test>\n name=%s cmode=%d\n\n", name, cmode);
+ printfflush("Creating a database with VL_CMPLEX ... ");
+ omode = VL_OWRITER | VL_OCREAT | VL_OTRUNC | cmode;
+ if(!(villa = vlopen(name, omode, VL_CMPLEX))){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Setting tuning parameters with 3, 4, 16, 16 ... ");
+ vlsettuning(villa, 3, 4, 16, 16);
+ printfflush("ok\n");
+ printfflush("Adding 100 records with VL_DOVER ... ");
+ for(i = 1; i <= 100; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!vlput(villa, buf, len, buf, len, VL_DOVER)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Checking records ... ");
+ for(i = 1; i <= 100; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!(vbuf = vlget(villa, buf, len, &vsiz))){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ free(vbuf);
+ if(vsiz != 8 || vlvsiz(villa, buf, len) != 8){
+ fprintf(stderr, "%s: %s: invalid vsiz\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ if(vlvnum(villa, buf, len) != 1){
+ fprintf(stderr, "%s: %s: invalid vnum\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Deleting x1 - x5 records ... ");
+ for(i = 1; i <= 100; i++){
+ if(i % 10 < 1 || i % 10 > 5) continue;
+ len = sprintf(buf, "%08d", i);
+ if(!vlout(villa, buf, len)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Adding 100 records with VL_DOVER ... ");
+ for(i = 1; i <= 100; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!vlput(villa, buf, len, buf, len, VL_DOVER)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Deleting x1 - x5 records ... ");
+ for(i = 1; i <= 100; i++){
+ if(i % 10 < 1 || i % 10 > 5) continue;
+ len = sprintf(buf, "%08d", i);
+ if(!vlout(villa, buf, len)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Checking number of records ... ");
+ if(vlrnum(villa) != 50){
+ fprintf(stderr, "%s: %s: invalid rnum\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Adding 100 records with VL_DDUP ... ");
+ for(i = 1; i <= 100; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!vlput(villa, buf, len, buf, len, VL_DDUP)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Deleting x6 - x0 records ... ");
+ for(i = 1; i <= 100; i++){
+ if(i % 10 >= 1 && i % 10 <= 5) continue;
+ len = sprintf(buf, "%08d", i);
+ if(!vlout(villa, buf, len)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Optimizing the database ... ");
+ if(!vloptimize(villa)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Checking number of records ... ");
+ if(vlrnum(villa) != 100){
+ fprintf(stderr, "%s: %s: invalid rnum\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Checking records ... ");
+ for(i = 1; i <= 100; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!(vbuf = vlget(villa, buf, len, &vsiz))){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ free(vbuf);
+ if(vsiz != 8){
+ fprintf(stderr, "%s: %s: invalid vsiz\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ if(vlvnum(villa, buf, len) != 1){
+ fprintf(stderr, "%s: %s: invalid vnum\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Deleting x6 - x0 records ... ");
+ for(i = 1; i <= 100; i++){
+ if(i % 10 >= 1 && i % 10 <= 5) continue;
+ len = sprintf(buf, "%08d", i);
+ if(!vlout(villa, buf, len)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Scanning with the cursor in ascending order ... ");
+ if(!vlcurfirst(villa)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ i = 0;
+ do {
+ kbuf = NULL;
+ vbuf = NULL;
+ if(!(kbuf = vlcurkey(villa, &ksiz)) || !(vbuf = vlcurval(villa, &vsiz))){
+ pdperror(name);
+ free(kbuf);
+ free(vbuf);
+ vlclose(villa);
+ return 1;
+ }
+ free(kbuf);
+ free(vbuf);
+ i++;
+ } while(vlcurnext(villa));
+ if(i != 50){
+ fprintf(stderr, "%s: %s: invalid cursor\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ if(dpecode != DP_ENOITEM){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Scanning with the cursor in decending order ... ");
+ if(!vlcurlast(villa)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ i = 0;
+ do {
+ kbuf = NULL;
+ vbuf = NULL;
+ if(!(kbuf = vlcurkey(villa, &ksiz)) || !(vbuf = vlcurval(villa, &vsiz))){
+ pdperror(name);
+ free(kbuf);
+ free(vbuf);
+ vlclose(villa);
+ return 1;
+ }
+ free(kbuf);
+ free(vbuf);
+ i++;
+ } while(vlcurprev(villa));
+ if(i != 50){
+ fprintf(stderr, "%s: %s: invalid cursor\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ if(dpecode != DP_ENOITEM){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Adding 50 random records with VL_DDUPR ... ");
+ for(i = 0; i < 50; i++){
+ len = sprintf(buf, "%08d", myrand() % 100 + 1);
+ if(!vlput(villa, buf, len, buf, len, VL_DDUPR)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Deleting 80 random records ... ");
+ i = 0;
+ while(i < 80){
+ len = sprintf(buf, "%08d", myrand() % 100 + 1);
+ if(!vlout(villa, buf, len)){
+ if(dpecode == DP_ENOITEM) continue;
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ i++;
+ }
+ printfflush("ok\n");
+ alist = cblistopen();
+ dlist = cblistopen();
+ printfflush("Scanning with the cursor in ascending order ... ");
+ if(!vlcurfirst(villa)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ i = 0;
+ do {
+ kbuf = NULL;
+ vbuf = NULL;
+ if(!(kbuf = vlcurkey(villa, &ksiz)) || !(vbuf = vlcurval(villa, &vsiz))){
+ pdperror(name);
+ cblistclose(alist);
+ cblistclose(dlist);
+ free(kbuf);
+ free(vbuf);
+ vlclose(villa);
+ return 1;
+ }
+ cblistpush(alist, kbuf, ksiz);
+ free(kbuf);
+ free(vbuf);
+ i++;
+ } while(vlcurnext(villa));
+ if(i != 20){
+ fprintf(stderr, "%s: %s: invalid cursor\n", progname, name);
+ cblistclose(alist);
+ cblistclose(dlist);
+ vlclose(villa);
+ return 1;
+ }
+ if(dpecode != DP_ENOITEM){
+ pdperror(name);
+ cblistclose(alist);
+ cblistclose(dlist);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Scanning with the cursor in decending order ... ");
+ if(!vlcurlast(villa)){
+ pdperror(name);
+ cblistclose(alist);
+ cblistclose(dlist);
+ vlclose(villa);
+ return 1;
+ }
+ i = 0;
+ do {
+ kbuf = NULL;
+ vbuf = NULL;
+ if(!(kbuf = vlcurkey(villa, &ksiz)) || !(vbuf = vlcurval(villa, &vsiz))){
+ pdperror(name);
+ free(kbuf);
+ free(vbuf);
+ cblistclose(alist);
+ cblistclose(dlist);
+ vlclose(villa);
+ return 1;
+ }
+ cblistunshift(dlist, kbuf, ksiz);
+ free(kbuf);
+ free(vbuf);
+ i++;
+ } while(vlcurprev(villa));
+ if(i != 20){
+ fprintf(stderr, "%s: %s: invalid cursor\n", progname, name);
+ cblistclose(alist);
+ cblistclose(dlist);
+ vlclose(villa);
+ return 1;
+ }
+ if(dpecode != DP_ENOITEM){
+ pdperror(name);
+ cblistclose(alist);
+ cblistclose(dlist);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Matching result of ascending scan and desending scan ... ");
+ for(i = 0; i < cblistnum(alist); i++){
+ ap = cblistval(alist, i, NULL);
+ dp = cblistval(dlist, i, NULL);
+ if(strcmp(ap, dp)){
+ fprintf(stderr, "%s: %s: not match\n", progname, name);
+ cblistclose(alist);
+ cblistclose(dlist);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ cblistsort(alist);
+ for(i = 0; i < cblistnum(alist); i++){
+ ap = cblistval(alist, i, NULL);
+ dp = cblistval(dlist, i, NULL);
+ if(strcmp(ap, dp)){
+ fprintf(stderr, "%s: %s: not match\n", progname, name);
+ cblistclose(alist);
+ cblistclose(dlist);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ cblistclose(alist);
+ cblistclose(dlist);
+ printfflush("Resetting tuning parameters with 41, 80, 32, 32 ... ");
+ vlsettuning(villa, 41, 80, 32, 32);
+ printfflush("ok\n");
+ printfflush("Adding 1000 random records with VL_DDUP ... ");
+ for(i = 0; i < 1000; i++){
+ len = sprintf(buf, "%08d", myrand() % 1000 + 1);
+ if(!vlput(villa, buf, len, buf, len, VL_DDUP)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Resetting tuning parameters with 8, 5, 16, 16 ... ");
+ vlsettuning(villa, 8, 5, 16, 16);
+ printfflush("ok\n");
+ printfflush("Adding 1000 random records with VL_DDUP ... ");
+ for(i = 0; i < 1000; i++){
+ len = sprintf(buf, "%08d", myrand() % 1000 + 1);
+ if(!vlput(villa, buf, len, buf, len, VL_DDUP)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Beginning the transaction ... ");
+ if(!vltranbegin(villa)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Adding 100 random records with VL_DDUP ... ");
+ for(i = 0; i < 100; i++){
+ len = sprintf(buf, "%08d", myrand() % 1000 + 1);
+ if(!vlput(villa, buf, len, buf, len, VL_DDUP)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Scanning and checking ... ");
+ i = 0;
+ for(vlcurlast(villa); (kbuf = vlcurkey(villa, &ksiz)) != NULL; vlcurprev(villa)){
+ if(vlvnum(villa, kbuf, ksiz) < 1 || !(vbuf = vlcurval(villa, NULL))){
+ pdperror(name);
+ free(kbuf);
+ vlclose(villa);
+ return 1;
+ }
+ free(vbuf);
+ free(kbuf);
+ i++;
+ }
+ if(i != vlrnum(villa)){
+ fprintf(stderr, "%s: %s: invalid\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Committing the transaction ... ");
+ if(!vltrancommit(villa)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Scanning and checking ... ");
+ i = 0;
+ for(vlcurlast(villa); (kbuf = vlcurkey(villa, &ksiz)) != NULL; vlcurprev(villa)){
+ if(vlvnum(villa, kbuf, ksiz) < 1 || !(vbuf = vlcurval(villa, NULL))){
+ pdperror(name);
+ free(kbuf);
+ vlclose(villa);
+ return 1;
+ }
+ free(vbuf);
+ free(kbuf);
+ i++;
+ }
+ if(i != vlrnum(villa)){
+ fprintf(stderr, "%s: %s: invalid\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ lnum = vllnum(villa);
+ nnum = vlnnum(villa);
+ rnum = vlrnum(villa);
+ fsiz = vlfsiz(villa);
+ printfflush("Beginning the transaction ... ");
+ if(!vltranbegin(villa)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Adding 100 random records with VL_DDUP ... ");
+ for(i = 0; i < 100; i++){
+ len = sprintf(buf, "%08d", myrand() % 1000 + 1);
+ if(!vlput(villa, buf, len, buf, len, VL_DDUP)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Aborting the transaction ... ");
+ if(!vltranabort(villa)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Checking rollback ... ");
+ if(vlfsiz(villa) != fsiz || vllnum(villa) != lnum ||
+ vlnnum(villa) != nnum || vlrnum(villa) != rnum){
+ fprintf(stderr, "%s: %s: invalid\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Scanning and checking ... ");
+ i = 0;
+ for(vlcurlast(villa); (kbuf = vlcurkey(villa, &ksiz)) != NULL; vlcurprev(villa)){
+ if(vlvnum(villa, kbuf, ksiz) < 1 || !(vbuf = vlcurval(villa, NULL))){
+ pdperror(name);
+ free(kbuf);
+ vlclose(villa);
+ return 1;
+ }
+ free(vbuf);
+ free(kbuf);
+ i++;
+ }
+ if(i != vlrnum(villa)){
+ fprintf(stderr, "%s: %s: invalid\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Closing the database ... ");
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Creating a database with VL_CMPLEX ... ");
+ omode = VL_OWRITER | VL_OCREAT | VL_OTRUNC | cmode;
+ if(!(villa = vlopen(name, omode, VL_CMPLEX))){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Setting tuning parameters with 5, 6, 16, 16 ... ");
+ vlsettuning(villa, 5, 6, 16, 16);
+ printfflush("ok\n");
+ printfflush("Adding 3 * 3 records with VL_DDUP ... ");
+ for(i = 0; i < 3; i++){
+ for(j = 0; j < 3; j++){
+ len = sprintf(buf, "%08d", j);
+ if(!vlput(villa, buf, len, buf, -1, VL_DDUP)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Inserting records with the cursor ... ");
+ if(!vlcurjump(villa, "00000001", -1, VL_JFORWARD) ||
+ !vlcurput(villa, "first", -1, VL_CPAFTER) || !vlcurput(villa, "second", -1, VL_CPAFTER) ||
+ !vlcurnext(villa) ||
+ !vlcurput(villa, "third", -1, VL_CPAFTER) ||
+ strcmp(vlcurvalcache(villa, NULL), "third") ||
+ !vlcurput(villa, "fourth", -1, VL_CPCURRENT) ||
+ strcmp(vlcurvalcache(villa, NULL), "fourth") ||
+ !vlcurjump(villa, "00000001", -1, VL_JFORWARD) ||
+ strcmp(vlcurvalcache(villa, NULL), "00000001") ||
+ !vlcurput(villa, "one", -1, VL_CPBEFORE) || !vlcurput(villa, "two", -1, VL_CPBEFORE) ||
+ !vlcurput(villa, "three", -1, VL_CPBEFORE) || !vlcurput(villa, "five", -1, VL_CPBEFORE) ||
+ !vlcurnext(villa) ||
+ !vlcurput(villa, "four", -1, VL_CPBEFORE) ||
+ strcmp(vlcurvalcache(villa, NULL), "four") ||
+ !vlcurjump(villa, "00000001*", -1, VL_JBACKWARD) ||
+ strcmp(vlcurvalcache(villa, NULL), "00000001") ||
+ !vlcurput(villa, "omega", -1, VL_CPAFTER) ||
+ strcmp(vlcurkeycache(villa, NULL), "00000001") ||
+ strcmp(vlcurvalcache(villa, NULL), "omega") ||
+ !vlcurjump(villa, "00000000*", -1, VL_JFORWARD) ||
+ !vlcurput(villa, "alpha", -1, VL_CPBEFORE) ||
+ strcmp(vlcurvalcache(villa, NULL), "alpha") ||
+ !vlcurprev(villa) ||
+ strcmp(vlcurkeycache(villa, NULL), "00000000") ||
+ strcmp(vlcurvalcache(villa, NULL), "00000000") ||
+ !vlcurput(villa, "before", -1, VL_CPAFTER) ||
+ strcmp(vlcurvalcache(villa, NULL), "before") ||
+ !vlcurjump(villa, "00000001*", -1, VL_JFORWARD) ||
+ !vlcurput(villa, "after", -1, VL_CPBEFORE) ||
+ strcmp(vlcurvalcache(villa, NULL), "after") ||
+ !vlcurfirst(villa) ||
+ strcmp(vlcurvalcache(villa, NULL), "00000000") ||
+ !vlcurput(villa, "top", -1, VL_CPBEFORE) ||
+ strcmp(vlcurvalcache(villa, NULL), "top") ||
+ !vlcurlast(villa) ||
+ !vlcurput(villa, "bottom", -1, VL_CPAFTER) ||
+ strcmp(vlcurvalcache(villa, NULL), "bottom")){
+ fprintf(stderr, "%s: %s: invalid\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Deleting records with the cursor ... ");
+ if(!vlcurjump(villa, "00000000*", -1, VL_JBACKWARD) ||
+ strcmp(vlcurvalcache(villa, NULL), "before") ||
+ !vlcurout(villa) ||
+ strcmp(vlcurvalcache(villa, NULL), "alpha") ||
+ !vlcurout(villa) ||
+ strcmp(vlcurvalcache(villa, NULL), "five") ||
+ !vlcurfirst(villa) || !vlcurnext(villa) ||
+ !vlcurout(villa) || !vlcurout(villa) || !vlcurout(villa) ||
+ strcmp(vlcurvalcache(villa, NULL), "five") ||
+ !vlcurprev(villa) ||
+ strcmp(vlcurvalcache(villa, NULL), "top") ||
+ !vlcurout(villa) ||
+ strcmp(vlcurvalcache(villa, NULL), "five") ||
+ !vlcurjump(villa, "00000002", -1, VL_JBACKWARD) ||
+ strcmp(vlcurvalcache(villa, NULL), "bottom") ||
+ !vlcurout(villa) ||
+ !vlcurjump(villa, "00000001", -1, VL_JBACKWARD) ||
+ !vlcurout(villa) ||
+ !vlcurout(villa) || !vlcurout(villa) || !vlcurout(villa) ||
+ strcmp(vlcurkeycache(villa, NULL), "00000002") ||
+ strcmp(vlcurvalcache(villa, NULL), "00000002") ||
+ !vlcurout(villa) || vlcurout(villa) ||
+ !vlcurfirst(villa) ||
+ strcmp(vlcurvalcache(villa, NULL), "five")){
+ fprintf(stderr, "%s: %s: invalid\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ vlcurfirst(villa);
+ while(vlcurout(villa)){
+ free(vlcurval(villa, NULL));
+ }
+ if(vlrnum(villa) != 0){
+ printf("%d\n", vlrnum(villa));
+ fprintf(stderr, "%s: %s: invalid\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ for(i = 0; i < 1000; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!vlput(villa, buf, len, buf, -1, VL_DKEEP)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ for(i = 200; i < 800; i++){
+ len = sprintf(buf, "%08d", i);
+ if(!vlout(villa, buf, len)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ vlcurfirst(villa);
+ while(vlcurout(villa)){
+ free(vlcurval(villa, NULL));
+ }
+ if(vlrnum(villa) != 0){
+ printf("%d\n", vlrnum(villa));
+ fprintf(stderr, "%s: %s: invalid\n", progname, name);
+ vlclose(villa);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Adding 3 * 100 records with VL_DDUP ... ");
+ for(i = 1; i <= 100; i++){
+ len = sprintf(buf, "%08d", i);
+ for(j = 0; j < 3; j++){
+ if(!vlput(villa, buf, len, buf, len, VL_DDUP)){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Closing the database ... ");
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Opening the database as a reader ... ");
+ if(!(villa = vlopen(name, VL_OREADER, VL_CMPLEX))){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("Opening multiple cursors ... ");
+ mulcurs = cbmalloc(sizeof(VLMULCUR *) * 8);
+ for(i = 0; i < 8; i++){
+ if(!(mulcurs[i] = vlmulcuropen(villa))){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Scanning multiple cursors ... ");
+ for(i = 0; i < 8; i++){
+ if(i % 2 == 0){
+ vlmulcurfirst(mulcurs[i]);
+ } else {
+ vlmulcurlast(mulcurs[i]);
+ }
+ }
+ for(i = 0; i < 300; i++){
+ for(j = 0; j < 8; j++){
+ if(j % 2 == 0){
+ if(!(vbuf = vlmulcurkey(mulcurs[j], &vsiz))){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ free(vbuf);
+ vlmulcurnext(mulcurs[j]);
+ } else {
+ if(!(vbuf = vlmulcurval(mulcurs[j], &vsiz))){
+ pdperror(name);
+ vlclose(villa);
+ return 1;
+ }
+ free(vbuf);
+ vlmulcurprev(mulcurs[j]);
+ }
+ }
+ }
+ printfflush("ok\n");
+ printfflush("Closing multiple cursors ... ");
+ for(i = 0; i < 8; i++){
+ vlmulcurclose(mulcurs[i]);
+ }
+ free(mulcurs);
+ printfflush("ok\n");
+ printfflush("Closing the database ... ");
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ printfflush("ok\n");
+ printfflush("all ok\n\n");
+ return 0;
+}
+
+
+/* perform wicked command */
+int dowicked(const char *name, int rnum, int cb, int cmode){
+ VILLA *villa;
+ CBMAP *map;
+ int i, j, omode, len, err, ksiz, vsiz, tran, mksiz, mvsiz, rsiz;
+ const char *mkbuf, *mvbuf;
+ char buf[32], *kbuf, *vbuf;
+ CBLIST *list;
+ printfflush("<Wicked Writing Test>\n name=%s rnum=%d\n\n", name, rnum);
+ omode = VL_OWRITER | VL_OCREAT | VL_OTRUNC | cmode;
+ if(!(villa = vlopen(name, omode, VL_CMPLEX))){
+ pdperror(name);
+ return 1;
+ }
+ err = FALSE;
+ tran = FALSE;
+ vlsettuning(villa, 5, 10, 64, 64);
+ map = NULL;
+ if(cb) map = cbmapopen();
+ for(i = 1; i <= rnum; i++){
+ len = sprintf(buf, "%08d", myrand() % rnum + 1);
+ switch(cb ? (myrand() % 5) : myrand() % 16){
+ case 0:
+ putchar('O');
+ if(!vlput(villa, buf, len, buf, len, VL_DOVER)) err = TRUE;
+ if(map) cbmapput(map, buf, len, buf, len, TRUE);
+ break;
+ case 1:
+ putchar('K');
+ if(!vlput(villa, buf, len, buf, len, VL_DKEEP) && dpecode != DP_EKEEP) err = TRUE;
+ if(map) cbmapput(map, buf, len, buf, len, FALSE);
+ break;
+ case 2:
+ putchar('C');
+ if(!vlput(villa, buf, len, buf, len, VL_DCAT)) err = TRUE;
+ if(map) cbmapputcat(map, buf, len, buf, len);
+ break;
+ case 3:
+ putchar('D');
+ if(!vlout(villa, buf, len) && dpecode != DP_ENOITEM) err = TRUE;
+ if(map) cbmapout(map, buf, len);
+ break;
+ case 4:
+ putchar('G');
+ if((vbuf = vlget(villa, buf, len, NULL)) != NULL){
+ free(vbuf);
+ } else if(dpecode != DP_ENOITEM){
+ err = TRUE;
+ }
+ break;
+ case 5:
+ putchar('V');
+ if(vlvsiz(villa, buf, len) < 0 && dpecode != DP_ENOITEM) err = TRUE;
+ if(!vlvnum(villa, buf, len) && dpecode != DP_ENOITEM) err = TRUE;
+ break;
+ case 6:
+ putchar('X');
+ list = cblistopen();
+ cblistpush(list, buf, len);
+ cblistpush(list, buf, len);
+ if(!vlputlist(villa, buf, len, list)) err = TRUE;
+ cblistclose(list);
+ break;
+ case 7:
+ putchar('Y');
+ if(!vloutlist(villa, buf, len) && dpecode != DP_ENOITEM) err = TRUE;
+ break;
+ case 8:
+ putchar('Z');
+ if((list = vlgetlist(villa, buf, len)) != NULL){
+ cblistclose(list);
+ } else if(dpecode != DP_ENOITEM){
+ err = TRUE;
+ }
+ if((vbuf = vlgetcat(villa, buf, len, NULL)) != NULL){
+ free(vbuf);
+ } else if(dpecode != DP_ENOITEM){
+ err = TRUE;
+ }
+ break;
+ case 9:
+ putchar('Q');
+ if(vlcurjump(villa, buf, len, VL_JFORWARD)){
+ for(j = 0; j < 3 && (kbuf = vlcurkey(villa, &ksiz)); j++){
+ if(VL_CMPLEX(buf, len, kbuf, ksiz) > 0) err = TRUE;
+ if(strcmp(vlcurkeycache(villa, NULL), kbuf)) err = TRUE;
+ if((vbuf = vlcurval(villa, &vsiz)) != NULL){
+ if(strcmp(vlcurvalcache(villa, NULL), vbuf)) err = TRUE;
+ free(vbuf);
+ } else {
+ err = TRUE;
+ }
+ free(kbuf);
+ if(!vlcurnext(villa) && dpecode != DP_ENOITEM) err = TRUE;
+ }
+ } else {
+ if(dpecode != DP_ENOITEM) err = TRUE;
+ }
+ break;
+ case 10:
+ putchar('W');
+ if(vlcurjump(villa, buf, len, VL_JBACKWARD)){
+ for(j = 0; j < 3 && (kbuf = vlcurkey(villa, &ksiz)); j++){
+ if(VL_CMPLEX(buf, len, kbuf, ksiz) < 0) err = TRUE;
+ if(strcmp(vlcurkeycache(villa, NULL), kbuf)) err = TRUE;
+ if((vbuf = vlcurval(villa, &vsiz)) != NULL){
+ if(strcmp(vlcurvalcache(villa, NULL), vbuf)) err = TRUE;
+ free(vbuf);
+ } else {
+ err = TRUE;
+ }
+ free(kbuf);
+ if(!vlcurprev(villa) && dpecode != DP_ENOITEM) err = TRUE;
+ }
+ } else {
+ if(dpecode != DP_ENOITEM) err = TRUE;
+ }
+ break;
+ case 11:
+ putchar('L');
+ if(myrand() % 3 == 0 &&
+ !vlcurjump(villa, buf, len, i % 3 == 0 ? VL_JFORWARD : VL_JBACKWARD) &&
+ dpecode != DP_ENOITEM) err = TRUE;
+ for(j = myrand() % 5; j >= 0; j--){
+ switch(myrand() % 6){
+ case 0:
+ if(!vlcurput(villa, buf, len, VL_CPAFTER) && dpecode != DP_ENOITEM) err = TRUE;
+ break;
+ case 1:
+ if(!vlcurput(villa, buf, len, VL_CPBEFORE) && dpecode != DP_ENOITEM) err = TRUE;
+ break;
+ case 2:
+ if(!vlcurput(villa, buf, len, VL_CPCURRENT) && dpecode != DP_ENOITEM) err = TRUE;
+ break;
+ default:
+ if(!vlcurout(villa)){
+ if(dpecode != DP_ENOITEM) err = TRUE;
+ break;
+ }
+ break;
+ }
+ }
+ break;
+ case 12:
+ if(tran ? myrand() % 32 != 0 : myrand() % 1024 != 0){
+ putchar('N');
+ break;
+ }
+ putchar('T');
+ if(tran){
+ if(myrand() % 5 == 0){
+ if(!vltranabort(villa)) err = TRUE;
+ } else {
+ if(!vltrancommit(villa)) err = TRUE;
+ }
+ tran = FALSE;
+ } else {
+ if(!vltranbegin(villa)) err = TRUE;
+ tran = TRUE;
+ }
+ break;
+ default:
+ putchar('P');
+ if(!vlput(villa, buf, len, buf, len, myrand() % 3 == 0 ? VL_DDUPR : VL_DDUP)) err = TRUE;
+ break;
+ }
+ if(i % 50 == 0) printfflush(" (%08d)\n", i);
+ if(err){
+ pdperror(name);
+ break;
+ }
+ }
+ if(tran){
+ if(!vltranabort(villa)) err = TRUE;
+ }
+ if(!vloptimize(villa)){
+ pdperror(name);
+ err = TRUE;
+ }
+ if((rnum = vlrnum(villa)) == -1){
+ pdperror(name);
+ err = TRUE;
+ }
+ if(!vlcurfirst(villa)){
+ pdperror(name);
+ err = TRUE;
+ }
+ i = 0;
+ do {
+ kbuf = NULL;
+ vbuf = NULL;
+ if(!(kbuf = vlcurkey(villa, &ksiz)) || !(vbuf = vlcurval(villa, &vsiz)) ||
+ ksiz != 8 || vsiz % 8 != 0 || vlvnum(villa, kbuf, ksiz) < 1){
+ pdperror(name);
+ free(kbuf);
+ free(vbuf);
+ err = TRUE;
+ break;
+ }
+ free(kbuf);
+ free(vbuf);
+ i++;
+ } while(vlcurnext(villa));
+ if(i != rnum){
+ fprintf(stderr, "%s: %s: invalid cursor\n", progname, name);
+ err = TRUE;
+ }
+ if(dpecode != DP_ENOITEM){
+ pdperror(name);
+ err = TRUE;
+ }
+ if(!vlcurlast(villa)){
+ pdperror(name);
+ err = TRUE;
+ }
+ i = 0;
+ do {
+ kbuf = NULL;
+ vbuf = NULL;
+ if(!(kbuf = vlcurkey(villa, &ksiz)) || !(vbuf = vlcurval(villa, &vsiz)) ||
+ ksiz != 8 || vsiz % 8 != 0 || vlvnum(villa, kbuf, ksiz) < 1){
+ pdperror(name);
+ free(kbuf);
+ free(vbuf);
+ err = TRUE;
+ break;
+ }
+ free(kbuf);
+ free(vbuf);
+ i++;
+ } while(vlcurprev(villa));
+ if(i != rnum){
+ fprintf(stderr, "%s: %s: invalid cursor\n", progname, name);
+ err = TRUE;
+ }
+ if(dpecode != DP_ENOITEM){
+ pdperror(name);
+ err = TRUE;
+ }
+ if(map){
+ printfflush("Matching records ... ");
+ cbmapiterinit(map);
+ while((mkbuf = cbmapiternext(map, &mksiz)) != NULL){
+ mvbuf = cbmapget(map, mkbuf, mksiz, &mvsiz);
+ if(!(vbuf = vlget(villa, mkbuf, mksiz, &rsiz))){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ if(rsiz != mvsiz || memcmp(vbuf, mvbuf, rsiz)){
+ fprintf(stderr, "%s: %s: unmatched record\n", progname, name);
+ free(vbuf);
+ err = TRUE;
+ break;
+ }
+ free(vbuf);
+ }
+ cbmapclose(map);
+ if(!err) printfflush("ok\n");
+ }
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ if(!err) printfflush("ok\n\n");
+ return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/qdbm/vltsv.c b/qdbm/vltsv.c
new file mode 100644
index 00000000..acca6070
--- /dev/null
+++ b/qdbm/vltsv.c
@@ -0,0 +1,261 @@
+/*************************************************************************************************
+ * Mutual converter between a database of Villa and a TSV text
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <cabin.h>
+#include <villa.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE 1 /* boolean true */
+#undef FALSE
+#define FALSE 0 /* boolean false */
+
+
+/* for RISC OS */
+#if defined(__riscos__) || defined(__riscos)
+#include <unixlib/local.h>
+int __riscosify_control = __RISCOSIFY_NO_PROCESS;
+#endif
+
+
+/* global variables */
+const char *progname; /* program name */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runimport(int argc, char **argv);
+int runexport(int argc, char **argv);
+void pdperror(const char *name);
+char *getl(void);
+int doimport(const char *name, int bin);
+int doexport(const char *name, int bin);
+
+
+/* main routine */
+int main(int argc, char **argv){
+ int rv;
+ cbstdiobin();
+ progname = argv[0];
+ if(argc < 2) usage();
+ rv = 0;
+ if(!strcmp(argv[1], "import")){
+ rv = runimport(argc, argv);
+ } else if(!strcmp(argv[1], "export")){
+ rv = runexport(argc, argv);
+ } else {
+ usage();
+ }
+ return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+ fprintf(stderr, "%s: mutual converter between TSV and Villa database\n", progname);
+ fprintf(stderr, "\n");
+ fprintf(stderr, "usage:\n");
+ fprintf(stderr, " %s import [-bin] name\n", progname);
+ fprintf(stderr, " %s export [-bin] name\n", progname);
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+
+/* parse arguments of import command */
+int runimport(int argc, char **argv){
+ char *name;
+ int i, bin, rv;
+ name = NULL;
+ bin = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-bin")){
+ bin = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doimport(name, bin);
+ return rv;
+}
+
+
+/* parse arguments of export command */
+int runexport(int argc, char **argv){
+ char *name;
+ int i, bin, rv;
+ name = NULL;
+ bin = FALSE;
+ for(i = 2; i < argc; i++){
+ if(!name && argv[i][0] == '-'){
+ if(!strcmp(argv[i], "-bin")){
+ bin = TRUE;
+ } else {
+ usage();
+ }
+ } else if(!name){
+ name = argv[i];
+ } else {
+ usage();
+ }
+ }
+ if(!name) usage();
+ rv = doexport(name, bin);
+ return rv;
+}
+
+
+/* print an error message */
+void pdperror(const char *name){
+ fprintf(stderr, "%s: %s: %s\n", progname, name, dperrmsg(dpecode));
+}
+
+
+/* read a line */
+char *getl(void){
+ char *buf;
+ int c, len, blen;
+ buf = NULL;
+ len = 0;
+ blen = 256;
+ while((c = getchar()) != EOF){
+ if(blen <= len) blen *= 2;
+ buf = cbrealloc(buf, blen + 1);
+ if(c == '\n') c = '\0';
+ buf[len++] = c;
+ if(c == '\0') break;
+ }
+ if(!buf) return NULL;
+ buf[len] = '\0';
+ return buf;
+}
+
+
+/* perform import command */
+int doimport(const char *name, int bin){
+ VILLA *villa;
+ char *buf, *kbuf, *vbuf, *ktmp, *vtmp;
+ int i, err, ktsiz, vtsiz;
+ /* open a database */
+ if(!(villa = vlopen(name, VL_OWRITER | VL_OCREAT, VL_CMPLEX))){
+ pdperror(name);
+ return 1;
+ }
+ /* loop for each line */
+ err = FALSE;
+ for(i = 1; (buf = getl()) != NULL; i++){
+ kbuf = buf;
+ if((vbuf = strchr(buf, '\t')) != NULL){
+ *vbuf = '\0';
+ vbuf++;
+ /* store a record */
+ if(bin){
+ ktmp = cbbasedecode(kbuf, &ktsiz);
+ vtmp = cbbasedecode(vbuf, &vtsiz);
+ if(!vlput(villa, ktmp, ktsiz, vtmp, vtsiz, VL_DDUP)){
+ pdperror(name);
+ err = TRUE;
+ }
+ free(vtmp);
+ free(ktmp);
+ } else {
+ if(!vlput(villa, kbuf, -1, vbuf, -1, VL_DDUP)){
+ pdperror(name);
+ err = TRUE;
+ break;
+ }
+ }
+ } else {
+ fprintf(stderr, "%s: %s: invalid format in line %d\n", progname, name, i);
+ }
+ free(buf);
+ if(err) break;
+ }
+ /* close the database */
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ return err ? 1 : 0;
+}
+
+
+/* perform export command */
+int doexport(const char *name, int bin){
+ VILLA *villa;
+ char *kbuf, *vbuf, *tmp;
+ int err, ksiz, vsiz;
+ /* open a database */
+ if(!(villa = vlopen(name, VL_OREADER, VL_CMPLEX))){
+ pdperror(name);
+ return 1;
+ }
+ /* initialize the cursor */
+ vlcurfirst(villa);
+ /* loop for each key */
+ err = FALSE;
+ while((kbuf = vlcurkey(villa, &ksiz)) != NULL){
+ /* retrieve a value with a key */
+ if(!(vbuf = vlcurval(villa, &vsiz))){
+ pdperror(name);
+ free(kbuf);
+ err = TRUE;
+ break;
+ }
+ /* output data */
+ if(bin){
+ tmp = cbbaseencode(kbuf, ksiz);
+ printf("%s\t", tmp);
+ free(tmp);
+ tmp = cbbaseencode(vbuf, vsiz);
+ printf("%s\n", tmp);
+ free(tmp);
+ } else {
+ printf("%s\t%s\n", kbuf, vbuf);
+ }
+ /* free resources */
+ free(vbuf);
+ free(kbuf);
+ /* step the cursor */
+ vlcurnext(villa);
+ }
+ /* check whether all records were retrieved */
+ if(dpecode != DP_ENOITEM){
+ pdperror(name);
+ err = TRUE;
+ }
+ /* close the database */
+ if(!vlclose(villa)){
+ pdperror(name);
+ return 1;
+ }
+ return 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/runtest.pl.in b/runtest.pl.in
index 42407378..27b94763 100755
--- a/runtest.pl.in
+++ b/runtest.pl.in
@@ -99,7 +99,11 @@ sub runtest
# attempt to make this test
my $flag = ($test_mode eq 'release')?(BoxPlatform::make_flag('RELEASE')):'';
+<<<<<<< HEAD
my $make_res = system("cd test/$t ; $make_command $flag");
+=======
+ my $make_res = system("cd test/$t && $make_command $flag");
+>>>>>>> 0.12
if($make_res != 0)
{
push @results,"$t: make failed";
diff --git a/test/backupstore/Makefile.extra b/test/backupstore/Makefile.extra
index e2e2d27c..2ce40389 100644
--- a/test/backupstore/Makefile.extra
+++ b/test/backupstore/Makefile.extra
@@ -1 +1,5 @@
+<<<<<<< HEAD
link-extra: ../../bin/bbstored/HousekeepStoreAccount.o
+=======
+link-extra: ../../lib/backupstore/HousekeepStoreAccount.o
+>>>>>>> 0.12
diff --git a/test/backupstore/testbackupstore.cpp b/test/backupstore/testbackupstore.cpp
index 646e6b45..cc708da0 100644
--- a/test/backupstore/testbackupstore.cpp
+++ b/test/backupstore/testbackupstore.cpp
@@ -12,6 +12,7 @@
#include <stdlib.h>
#include <string.h>
+<<<<<<< HEAD
#include "Test.h"
#include "autogen_BackupProtocolClient.h"
#include "SSLLib.h"
@@ -38,6 +39,36 @@
#include "BackupStoreRefCountDatabase.h"
#include "BackupStoreAccounts.h"
#include "HousekeepStoreAccount.h"
+=======
+#include "Archive.h"
+#include "BackupClientCryptoKeys.h"
+#include "BackupClientFileAttributes.h"
+#include "BackupStoreAccountDatabase.h"
+#include "BackupStoreAccounts.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreDirectory.h"
+#include "BackupStoreException.h"
+#include "BackupStoreInfo.h"
+#include "BackupStoreFilenameClear.h"
+#include "BackupStoreRefCountDatabase.h"
+#include "BackupStoreFile.h"
+#include "BoxPortsAndFiles.h"
+#include "CollectInBufferStream.h"
+#include "FileStream.h"
+#include "HousekeepStoreAccount.h"
+#include "MemBlockStream.h"
+#include "RaidFileController.h"
+#include "RaidFileException.h"
+#include "RaidFileRead.h"
+#include "RaidFileWrite.h"
+#include "SSLLib.h"
+#include "ServerControl.h"
+#include "Socket.h"
+#include "SocketStreamTLS.h"
+#include "TLSContext.h"
+#include "Test.h"
+#include "autogen_BackupProtocol.h"
+>>>>>>> 0.12
#include "MemLeakFindOn.h"
@@ -221,10 +252,13 @@ void CheckEntries(BackupStoreDirectory &rDir, int16_t FlagsMustBeSet, int16_t Fl
int test1(int argc, const char *argv[])
{
+<<<<<<< HEAD
// Initialise the raid file controller
RaidFileController &rcontroller = RaidFileController::GetController();
rcontroller.Initialise("testfiles/raidfile.conf");
+=======
+>>>>>>> 0.12
// test some basics -- encoding and decoding filenames
{
// Make some filenames in various ways
@@ -437,6 +471,7 @@ void test_test_file(int t, IOStream &rStream)
void test_everything_deleted(BackupProtocolClient &protocol, int64_t DirID)
{
+<<<<<<< HEAD
printf("Test for del: %llx\n", DirID);
// Command
@@ -444,6 +479,15 @@ void test_everything_deleted(BackupProtocolClient &protocol, int64_t DirID)
DirID,
BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+=======
+ printf("Test for del: %llx\n", (unsigned long long)DirID);
+
+ // Command
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocol.QueryListDirectory(
+ DirID,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+>>>>>>> 0.12
// Stream
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
@@ -455,7 +499,11 @@ void test_everything_deleted(BackupProtocolClient &protocol, int64_t DirID)
int dirs = 0;
while((en = i.Next()) != 0)
{
+<<<<<<< HEAD
if(en->GetFlags() & BackupProtocolClientListDirectory::Flags_Dir)
+=======
+ if(en->GetFlags() & BackupProtocolListDirectory::Flags_Dir)
+>>>>>>> 0.12
{
dirs++;
// Recurse
@@ -466,7 +514,11 @@ void test_everything_deleted(BackupProtocolClient &protocol, int64_t DirID)
files++;
}
// Check it's deleted
+<<<<<<< HEAD
TEST_THAT(en->GetFlags() & BackupProtocolClientListDirectory::Flags_Deleted);
+=======
+ TEST_THAT(en->GetFlags() & BackupProtocolListDirectory::Flags_Deleted);
+>>>>>>> 0.12
}
// Check there were the right number of files and directories
@@ -478,7 +530,11 @@ std::vector<uint32_t> ExpectedRefCounts;
void set_refcount(int64_t ObjectID, uint32_t RefCount = 1)
{
+<<<<<<< HEAD
if (ExpectedRefCounts.size() <= ObjectID);
+=======
+ if ((int64_t)ExpectedRefCounts.size() <= ObjectID);
+>>>>>>> 0.12
{
ExpectedRefCounts.resize(ObjectID + 1, 0);
}
@@ -491,7 +547,11 @@ void create_file_in_dir(std::string name, std::string source, int64_t parentId,
BackupStoreFilenameClear name_encoded("file_One");
std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile(
source.c_str(), parentId, name_encoded));
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> stored(
+=======
+ std::auto_ptr<BackupProtocolSuccess> stored(
+>>>>>>> 0.12
protocol.QueryStoreFile(
parentId,
0x123456789abcdefLL, /* modification time */
@@ -515,13 +575,22 @@ int64_t create_test_data_subdirs(BackupProtocolClient &protocol, int64_t indir,
// Create with dummy attributes
int attrS = 0;
MemBlockStream attr(&attrS, sizeof(attrS));
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirCreate(protocol.QueryCreateDirectory(
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirCreate(protocol.QueryCreateDirectory(
+>>>>>>> 0.12
indir,
9837429842987984LL, dirname, attr));
subdirid = dirCreate->GetObjectID();
}
+<<<<<<< HEAD
printf("Create subdirs, depth = %d, dirid = %llx\n", depth, subdirid);
+=======
+ printf("Create subdirs, depth = %d, dirid = %llx\n", depth,
+ (unsigned long long)subdirid);
+>>>>>>> 0.12
TEST_EQUAL(subdirid, rRefCount.GetLastObjectIDUsed());
TEST_EQUAL(1, rRefCount.GetRefCount(subdirid))
@@ -550,11 +619,19 @@ int64_t create_test_data_subdirs(BackupProtocolClient &protocol, int64_t indir,
void check_dir_after_uploads(BackupProtocolClient &protocol, const StreamableMemBlock &Attributes)
{
// Command
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocol.QueryListDirectory(
BackupProtocolClientListDirectory::RootDirectory,
BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
TEST_THAT(dirreply->GetObjectID() == BackupProtocolClientListDirectory::RootDirectory);
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocol.QueryListDirectory(
+ BackupProtocolListDirectory::RootDirectory,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+ TEST_THAT(dirreply->GetObjectID() == BackupProtocolListDirectory::RootDirectory);
+>>>>>>> 0.12
// Stream
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
@@ -575,9 +652,15 @@ void check_dir_after_uploads(BackupProtocolClient &protocol, const StreamableMem
TEST_THAT(en->GetName() == uploads[t].name);
TEST_THAT(en->GetObjectID() == uploads[t].allocated_objid);
TEST_THAT(en->GetModificationTime() == uploads[t].mod_time);
+<<<<<<< HEAD
int correct_flags = BackupProtocolClientListDirectory::Flags_File;
if(uploads[t].should_be_old_version) correct_flags |= BackupProtocolClientListDirectory::Flags_OldVersion;
if(uploads[t].delete_file) correct_flags |= BackupProtocolClientListDirectory::Flags_Deleted;
+=======
+ int correct_flags = BackupProtocolListDirectory::Flags_File;
+ if(uploads[t].should_be_old_version) correct_flags |= BackupProtocolListDirectory::Flags_OldVersion;
+ if(uploads[t].delete_file) correct_flags |= BackupProtocolListDirectory::Flags_Deleted;
+>>>>>>> 0.12
TEST_THAT(en->GetFlags() == correct_flags);
if(t == UPLOAD_ATTRS_EN)
{
@@ -606,10 +689,17 @@ typedef struct
void recursive_count_objects_r(BackupProtocolClient &protocol, int64_t id, recursive_count_objects_results &results)
{
// Command
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocol.QueryListDirectory(
id,
BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocol.QueryListDirectory(
+ id,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+>>>>>>> 0.12
// Stream
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
@@ -649,9 +739,15 @@ void recursive_count_objects(const char *hostname, int64_t id, recursive_count_o
BackupProtocolClient protocolReadOnly(connReadOnly);
{
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientVersion> serverVersion(protocolReadOnly.QueryVersion(BACKUP_STORE_SERVER_VERSION));
TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocolReadOnly.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly));
+=======
+ std::auto_ptr<BackupProtocolVersion> serverVersion(protocolReadOnly.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+ std::auto_ptr<BackupProtocolLoginConfirmed> loginConf(protocolReadOnly.QueryLogin(0x01234567, BackupProtocolLogin::Flags_ReadOnly));
+>>>>>>> 0.12
}
// Count objects
@@ -758,10 +854,17 @@ void test_server_1(BackupProtocolClient &protocol, BackupProtocolClient &protoco
for(int l = 0; l < 3; ++l)
{
// Command
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocol.QueryListDirectory(
BackupProtocolClientListDirectory::RootDirectory,
BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocol.QueryListDirectory(
+ BackupProtocolListDirectory::RootDirectory,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+>>>>>>> 0.12
// Stream
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
@@ -772,10 +875,17 @@ void test_server_1(BackupProtocolClient &protocol, BackupProtocolClient &protoco
// Read the dir from the readonly connection (make sure it gets in the cache)
{
// Command
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
BackupProtocolClientListDirectory::RootDirectory,
BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ BackupProtocolListDirectory::RootDirectory,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+>>>>>>> 0.12
// Stream
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
@@ -787,7 +897,11 @@ void test_server_1(BackupProtocolClient &protocol, BackupProtocolClient &protoco
BackupStoreFilenameClear store1name("testfiles/file1");
{
FileStream out("testfiles/file1_upload1", O_WRONLY | O_CREAT | O_EXCL);
+<<<<<<< HEAD
std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/file1", BackupProtocolClientListDirectory::RootDirectory, store1name));
+=======
+ std::auto_ptr<IOStream> encoded(BackupStoreFile::EncodeFile("testfiles/file1", BackupProtocolListDirectory::RootDirectory, store1name));
+>>>>>>> 0.12
encoded->CopyStreamTo(out);
}
@@ -797,8 +911,13 @@ void test_server_1(BackupProtocolClient &protocol, BackupProtocolClient &protoco
int64_t store1objid = 0;
{
FileStream upload("testfiles/file1_upload1");
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
BackupProtocolClientListDirectory::RootDirectory,
+=======
+ std::auto_ptr<BackupProtocolSuccess> stored(protocol.QueryStoreFile(
+ BackupProtocolListDirectory::RootDirectory,
+>>>>>>> 0.12
0x123456789abcdefLL, /* modification time */
0x7362383249872dfLL, /* attr hash */
0, /* diff from ID */
@@ -811,7 +930,11 @@ void test_server_1(BackupProtocolClient &protocol, BackupProtocolClient &protoco
// And retrieve it
{
// Retrieve as object
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> getfile(protocol.QueryGetObject(store1objid));
+=======
+ std::auto_ptr<BackupProtocolSuccess> getfile(protocol.QueryGetObject(store1objid));
+>>>>>>> 0.12
TEST_THAT(getfile->GetObjectID() == store1objid);
// BLOCK
{
@@ -826,7 +949,11 @@ void test_server_1(BackupProtocolClient &protocol, BackupProtocolClient &protoco
}
// Retrieve as file
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> getobj(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, store1objid));
+=======
+ std::auto_ptr<BackupProtocolSuccess> getobj(protocol.QueryGetFile(BackupProtocolListDirectory::RootDirectory, store1objid));
+>>>>>>> 0.12
TEST_THAT(getobj->GetObjectID() == store1objid);
// BLOCK
{
@@ -852,7 +979,11 @@ void test_server_1(BackupProtocolClient &protocol, BackupProtocolClient &protoco
// Retrieve the block index, by ID
{
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> getblockindex(protocol.QueryGetBlockIndexByID(store1objid));
+=======
+ std::auto_ptr<BackupProtocolSuccess> getblockindex(protocol.QueryGetBlockIndexByID(store1objid));
+>>>>>>> 0.12
TEST_THAT(getblockindex->GetObjectID() == store1objid);
std::auto_ptr<IOStream> blockIndexStream(protocol.ReceiveStream());
// Check against uploaded file
@@ -860,7 +991,11 @@ void test_server_1(BackupProtocolClient &protocol, BackupProtocolClient &protoco
}
// and again, by name
{
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> getblockindex(protocol.QueryGetBlockIndexByName(BackupProtocolClientListDirectory::RootDirectory, store1name));
+=======
+ std::auto_ptr<BackupProtocolSuccess> getblockindex(protocol.QueryGetBlockIndexByName(BackupProtocolListDirectory::RootDirectory, store1name));
+>>>>>>> 0.12
TEST_THAT(getblockindex->GetObjectID() == store1objid);
std::auto_ptr<IOStream> blockIndexStream(protocol.ReceiveStream());
// Check against uploaded file
@@ -870,10 +1005,17 @@ void test_server_1(BackupProtocolClient &protocol, BackupProtocolClient &protoco
// Get the directory again, and see if the entry is in it
{
// Command
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocol.QueryListDirectory(
BackupProtocolClientListDirectory::RootDirectory,
BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocol.QueryListDirectory(
+ BackupProtocolListDirectory::RootDirectory,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+>>>>>>> 0.12
// Stream
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
@@ -896,7 +1038,11 @@ void test_server_1(BackupProtocolClient &protocol, BackupProtocolClient &protoco
// Try using GetFile on a directory
{
+<<<<<<< HEAD
TEST_CHECK_THROWS(std::auto_ptr<BackupProtocolClientSuccess> getFile(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, BackupProtocolClientListDirectory::RootDirectory)),
+=======
+ TEST_CHECK_THROWS(std::auto_ptr<BackupProtocolSuccess> getFile(protocol.QueryGetFile(BackupProtocolListDirectory::RootDirectory, BackupProtocolListDirectory::RootDirectory)),
+>>>>>>> 0.12
ConnectionException, Conn_Protocol_UnexpectedReply);
}
}
@@ -919,6 +1065,7 @@ std::auto_ptr<SocketStreamTLS> open_conn(const char *hostname,
return conn;
}
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClient> test_server_login(SocketStreamTLS& rConn)
{
// Make a protocol
@@ -927,11 +1074,28 @@ std::auto_ptr<BackupProtocolClient> test_server_login(SocketStreamTLS& rConn)
// Check the version
std::auto_ptr<BackupProtocolClientVersion> serverVersion(
+=======
+std::auto_ptr<BackupProtocolClient> test_server_login(const char *hostname,
+ TLSContext& rContext, std::auto_ptr<SocketStreamTLS>& rapConn)
+{
+ rapConn = open_conn(hostname, rContext);
+
+ // Make a protocol
+ std::auto_ptr<BackupProtocolClient> protocol(new
+ BackupProtocolClient(*rapConn));
+
+ // Check the version
+ std::auto_ptr<BackupProtocolVersion> serverVersion(
+>>>>>>> 0.12
protocol->QueryVersion(BACKUP_STORE_SERVER_VERSION));
TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
// Login
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(
+=======
+ std::auto_ptr<BackupProtocolLoginConfirmed> loginConf(
+>>>>>>> 0.12
protocol->QueryLogin(0x01234567, 0));
return protocol;
@@ -948,6 +1112,7 @@ void run_housekeeping(BackupStoreAccountDatabase::Entry& rAccount)
housekeeping.DoHousekeeping(true /* keep trying forever */);
}
+<<<<<<< HEAD
int test_server(const char *hostname)
{
TLSContext context;
@@ -955,6 +1120,37 @@ int test_server(const char *hostname)
std::auto_ptr<BackupProtocolClient> apProtocol(
test_server_login(*conn));
BackupProtocolClient& protocol(*apProtocol);
+=======
+// Run housekeeping (for which we need to disconnect ourselves) and check
+// that it doesn't change the numbers of files.
+//
+// Also check that bbstoreaccounts doesn't change anything
+
+void run_housekeeping_and_check_account(const char *hostname,
+ TLSContext& rContext, std::auto_ptr<SocketStreamTLS>& rapConn,
+ std::auto_ptr<BackupProtocolClient>& rapProtocol)
+{
+ rapProtocol->QueryFinished();
+ std::auto_ptr<BackupStoreAccountDatabase> apAccounts(
+ BackupStoreAccountDatabase::Read("testfiles/accounts.txt"));
+ BackupStoreAccountDatabase::Entry account =
+ apAccounts->GetEntry(0x1234567);
+ run_housekeeping(account);
+
+ TEST_THAT(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf check 01234567 fix") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ rapProtocol = test_server_login(hostname, rContext, rapConn);
+}
+
+int test_server(const char *hostname)
+{
+ TLSContext context;
+ std::auto_ptr<SocketStreamTLS> conn;
+ std::auto_ptr<BackupProtocolClient> apProtocol =
+ test_server_login(hostname, context, conn);
+>>>>>>> 0.12
// Make some test attributes
#define ATTR1_SIZE 245
@@ -975,7 +1171,11 @@ int test_server(const char *hostname)
// Get it logging
FILE *protocolLog = ::fopen("testfiles/protocol.log", "w");
TEST_THAT(protocolLog != 0);
+<<<<<<< HEAD
protocol.SetLogToFile(protocolLog);
+=======
+ apProtocol->SetLogToFile(protocolLog);
+>>>>>>> 0.12
#ifndef WIN32
// Check that we can't open a new connection which requests write permissions
@@ -984,16 +1184,26 @@ int test_server(const char *hostname)
conn.Open(context, Socket::TypeINET, hostname,
BOX_PORT_BBSTORED_TEST);
BackupProtocolClient protocol(conn);
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
TEST_CHECK_THROWS(std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, 0)),
+=======
+ std::auto_ptr<BackupProtocolVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+ TEST_CHECK_THROWS(std::auto_ptr<BackupProtocolLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, 0)),
+>>>>>>> 0.12
ConnectionException, Conn_Protocol_UnexpectedReply);
protocol.QueryFinished();
}
#endif
// Set the client store marker
+<<<<<<< HEAD
protocol.QuerySetClientStoreMarker(0x8732523ab23aLL);
+=======
+ apProtocol->QuerySetClientStoreMarker(0x8732523ab23aLL);
+>>>>>>> 0.12
#ifndef WIN32
// Open a new connection which is read only
@@ -1008,18 +1218,51 @@ int test_server(const char *hostname)
protocolReadOnly.SetLogToFile(protocolReadOnlyLog);
{
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientVersion> serverVersion(protocolReadOnly.QueryVersion(BACKUP_STORE_SERVER_VERSION));
TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocolReadOnly.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly));
+=======
+ std::auto_ptr<BackupProtocolVersion> serverVersion(protocolReadOnly.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+ std::auto_ptr<BackupProtocolLoginConfirmed> loginConf(protocolReadOnly.QueryLogin(0x01234567, BackupProtocolLogin::Flags_ReadOnly));
+>>>>>>> 0.12
// Check client store marker
TEST_THAT(loginConf->GetClientStoreMarker() == 0x8732523ab23aLL);
}
#else // WIN32
+<<<<<<< HEAD
BackupProtocolClient& protocolReadOnly(protocol);
#endif
test_server_1(protocol, protocolReadOnly);
+=======
+ #define protocolReadOnly (*apProtocol)
+#endif
+
+ test_server_1(*apProtocol, protocolReadOnly);
+
+ #define TEST_NUM_FILES(files, old, deleted, dirs) \
+ { \
+ std::auto_ptr<BackupStoreInfo> apInfo = \
+ BackupStoreInfo::Load(0x1234567, \
+ "backup/01234567/", 0, true); \
+ TEST_EQUAL_LINE(files, apInfo->GetNumFiles(), \
+ "num files"); \
+ TEST_EQUAL_LINE(old, apInfo->GetNumOldFiles(), \
+ "old files"); \
+ TEST_EQUAL_LINE(deleted, apInfo->GetNumDeletedFiles(), \
+ "deleted files"); \
+ TEST_EQUAL_LINE(dirs, apInfo->GetNumDirectories(), \
+ "directories"); \
+ }
+
+ TEST_NUM_FILES(1, 0, 0, 1);
+ run_housekeeping_and_check_account(hostname, context,
+ conn, apProtocol);
+ TEST_NUM_FILES(1, 0, 0, 1);
+>>>>>>> 0.12
// sleep to ensure that the timestamp on the file will change
::safe_sleep(1);
@@ -1033,6 +1276,7 @@ int test_server(const char *hostname)
std::string filename("testfiles/test");
filename += uploads[t].fnextra;
int64_t modtime = 0;
+<<<<<<< HEAD
std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile(filename.c_str(), BackupProtocolClientListDirectory::RootDirectory, uploads[t].name, &modtime));
TEST_THAT(modtime != 0);
@@ -1042,6 +1286,17 @@ int test_server(const char *hostname)
modtime,
modtime, /* use it for attr hash too */
0, /* diff from ID */
+=======
+
+ std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile(filename.c_str(), BackupProtocolListDirectory::RootDirectory, uploads[t].name, &modtime));
+ TEST_THAT(modtime != 0);
+
+ std::auto_ptr<BackupProtocolSuccess> stored(apProtocol->QueryStoreFile(
+ BackupProtocolListDirectory::RootDirectory,
+ modtime,
+ modtime, /* use it for attr hash too */
+ 0, /* diff from ID */
+>>>>>>> 0.12
uploads[t].name,
*upload));
uploads[t].allocated_objid = stored->GetObjectID();
@@ -1051,31 +1306,61 @@ int test_server(const char *hostname)
BOX_TRACE("wrote file " << filename << " to server "
"as object " <<
BOX_FORMAT_OBJECTID(stored->GetObjectID()));
+<<<<<<< HEAD
+=======
+ TEST_NUM_FILES(t + 2, 0, 0, 1);
+
+ run_housekeeping_and_check_account(hostname, context,
+ conn, apProtocol);
+ TEST_NUM_FILES(t + 2, 0, 0, 1);
+>>>>>>> 0.12
}
// Add some attributes onto one of them
{
+<<<<<<< HEAD
MemBlockStream attrnew(attr3, sizeof(attr3));
std::auto_ptr<BackupProtocolClientSuccess> set(protocol.QuerySetReplacementFileAttributes(
BackupProtocolClientListDirectory::RootDirectory,
+=======
+ TEST_NUM_FILES(UPLOAD_NUM + 1, 0, 0, 1);
+ MemBlockStream attrnew(attr3, sizeof(attr3));
+ std::auto_ptr<BackupProtocolSuccess> set(apProtocol->QuerySetReplacementFileAttributes(
+ BackupProtocolListDirectory::RootDirectory,
+>>>>>>> 0.12
32498749832475LL,
uploads[UPLOAD_ATTRS_EN].name,
attrnew));
TEST_THAT(set->GetObjectID() == uploads[UPLOAD_ATTRS_EN].allocated_objid);
+<<<<<<< HEAD
+=======
+ TEST_NUM_FILES(UPLOAD_NUM + 1, 0, 0, 1);
+>>>>>>> 0.12
}
// Delete one of them (will implicitly delete an old version)
{
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> del(protocol.QueryDeleteFile(
BackupProtocolClientListDirectory::RootDirectory,
uploads[UPLOAD_DELETE_EN].name));
TEST_THAT(del->GetObjectID() == uploads[UPLOAD_DELETE_EN].allocated_objid);
}
+=======
+ std::auto_ptr<BackupProtocolSuccess> del(apProtocol->QueryDeleteFile(
+ BackupProtocolListDirectory::RootDirectory,
+ uploads[UPLOAD_DELETE_EN].name));
+ TEST_THAT(del->GetObjectID() == uploads[UPLOAD_DELETE_EN].allocated_objid);
+ TEST_NUM_FILES(UPLOAD_NUM, 0, 1, 1);
+ }
+
+>>>>>>> 0.12
// Check that the block index can be obtained by name even though it's been deleted
{
// Fetch the raw object
{
FileStream out("testfiles/downloaddelobj", O_WRONLY | O_CREAT);
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> getobj(protocol.QueryGetObject(uploads[UPLOAD_DELETE_EN].allocated_objid));
std::auto_ptr<IOStream> objstream(protocol.ReceiveStream());
objstream->CopyStreamTo(out);
@@ -1085,6 +1370,17 @@ int test_server(const char *hostname)
BackupProtocolClientListDirectory::RootDirectory, uploads[UPLOAD_DELETE_EN].name));
TEST_THAT(getblockindex->GetObjectID() == uploads[UPLOAD_DELETE_EN].allocated_objid);
std::auto_ptr<IOStream> blockIndexStream(protocol.ReceiveStream());
+=======
+ std::auto_ptr<BackupProtocolSuccess> getobj(apProtocol->QueryGetObject(uploads[UPLOAD_DELETE_EN].allocated_objid));
+ std::auto_ptr<IOStream> objstream(apProtocol->ReceiveStream());
+ objstream->CopyStreamTo(out);
+ }
+ // query index and test
+ std::auto_ptr<BackupProtocolSuccess> getblockindex(apProtocol->QueryGetBlockIndexByName(
+ BackupProtocolListDirectory::RootDirectory, uploads[UPLOAD_DELETE_EN].name));
+ TEST_THAT(getblockindex->GetObjectID() == uploads[UPLOAD_DELETE_EN].allocated_objid);
+ std::auto_ptr<IOStream> blockIndexStream(apProtocol->ReceiveStream());
+>>>>>>> 0.12
TEST_THAT(check_block_index("testfiles/downloaddelobj", *blockIndexStream));
}
@@ -1092,9 +1388,15 @@ int test_server(const char *hostname)
for(int t = 0; t < UPLOAD_NUM; ++t)
{
printf("%d\n", t);
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> getFile(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, uploads[t].allocated_objid));
TEST_THAT(getFile->GetObjectID() == uploads[t].allocated_objid);
std::auto_ptr<IOStream> filestream(protocol.ReceiveStream());
+=======
+ std::auto_ptr<BackupProtocolSuccess> getFile(apProtocol->QueryGetFile(BackupProtocolListDirectory::RootDirectory, uploads[t].allocated_objid));
+ TEST_THAT(getFile->GetObjectID() == uploads[t].allocated_objid);
+ std::auto_ptr<IOStream> filestream(apProtocol->ReceiveStream());
+>>>>>>> 0.12
test_test_file(t, *filestream);
}
@@ -1106,7 +1408,11 @@ int test_server(const char *hostname)
check_dir_after_uploads(protocolReadOnly, attrtest);
printf("done.\n\n");
// And on the read/write one
+<<<<<<< HEAD
check_dir_after_uploads(protocol, attrtest);
+=======
+ check_dir_after_uploads(*apProtocol, attrtest);
+>>>>>>> 0.12
}
// sleep to ensure that the timestamp on the file will change
@@ -1128,12 +1434,43 @@ int test_server(const char *hostname)
out.Write(buf, TEST_FILE_FOR_PATCHING_SIZE - TEST_FILE_FOR_PATCHING_PATCH_AT);
::free(buf);
}
+<<<<<<< HEAD
{
// Fetch the block index for this one
std::auto_ptr<BackupProtocolClientSuccess> getblockindex(protocol.QueryGetBlockIndexByName(
BackupProtocolClientListDirectory::RootDirectory, uploads[UPLOAD_PATCH_EN].name));
TEST_THAT(getblockindex->GetObjectID() == uploads[UPLOAD_PATCH_EN].allocated_objid);
std::auto_ptr<IOStream> blockIndexStream(protocol.ReceiveStream());
+=======
+
+ TEST_NUM_FILES(UPLOAD_NUM, 0, 1, 1);
+
+ // Run housekeeping (for which we need to disconnect
+ // ourselves) and check that it doesn't change the numbers
+ // of files
+ apProtocol->QueryFinished();
+ std::auto_ptr<BackupStoreAccountDatabase> apAccounts(
+ BackupStoreAccountDatabase::Read("testfiles/accounts.txt"));
+ BackupStoreAccountDatabase::Entry account =
+ apAccounts->GetEntry(0x1234567);
+ run_housekeeping(account);
+
+ // Also check that bbstoreaccounts doesn't change anything
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf check 01234567 fix") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ apProtocol = test_server_login(hostname, context, conn);
+
+ TEST_NUM_FILES(UPLOAD_NUM, 0, 1, 1);
+
+ {
+ // Fetch the block index for this one
+ std::auto_ptr<BackupProtocolSuccess> getblockindex(apProtocol->QueryGetBlockIndexByName(
+ BackupProtocolListDirectory::RootDirectory, uploads[UPLOAD_PATCH_EN].name));
+ TEST_THAT(getblockindex->GetObjectID() == uploads[UPLOAD_PATCH_EN].allocated_objid);
+ std::auto_ptr<IOStream> blockIndexStream(apProtocol->ReceiveStream());
+>>>>>>> 0.12
// Do the patching
bool isCompletelyDifferent = false;
@@ -1141,7 +1478,11 @@ int test_server(const char *hostname)
std::auto_ptr<IOStream> patchstream(
BackupStoreFile::EncodeFileDiff(
TEST_FILE_FOR_PATCHING ".mod",
+<<<<<<< HEAD
BackupProtocolClientListDirectory::RootDirectory,
+=======
+ BackupProtocolListDirectory::RootDirectory,
+>>>>>>> 0.12
uploads[UPLOAD_PATCH_EN].name,
uploads[UPLOAD_PATCH_EN].allocated_objid,
*blockIndexStream,
@@ -1160,8 +1501,13 @@ int test_server(const char *hostname)
int64_t patchedID = 0;
{
FileStream uploadpatch(TEST_FILE_FOR_PATCHING ".patch");
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
BackupProtocolClientListDirectory::RootDirectory,
+=======
+ std::auto_ptr<BackupProtocolSuccess> stored(apProtocol->QueryStoreFile(
+ BackupProtocolListDirectory::RootDirectory,
+>>>>>>> 0.12
modtime,
modtime, /* use it for attr hash too */
uploads[UPLOAD_PATCH_EN].allocated_objid, /* diff from ID */
@@ -1175,12 +1521,23 @@ int test_server(const char *hostname)
set_refcount(patchedID, 1);
// Then download it to check it's OK
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> getFile(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, patchedID));
TEST_THAT(getFile->GetObjectID() == patchedID);
std::auto_ptr<IOStream> filestream(protocol.ReceiveStream());
BackupStoreFile::DecodeFile(*filestream, TEST_FILE_FOR_PATCHING ".downloaded", IOStream::TimeOutInfinite);
// Check it's the same
TEST_THAT(check_files_same(TEST_FILE_FOR_PATCHING ".downloaded", TEST_FILE_FOR_PATCHING ".mod"));
+=======
+ std::auto_ptr<BackupProtocolSuccess> getFile(apProtocol->QueryGetFile(BackupProtocolListDirectory::RootDirectory, patchedID));
+ TEST_THAT(getFile->GetObjectID() == patchedID);
+ std::auto_ptr<IOStream> filestream(apProtocol->ReceiveStream());
+ BackupStoreFile::DecodeFile(*filestream, TEST_FILE_FOR_PATCHING ".downloaded", IOStream::TimeOutInfinite);
+ // Check it's the same
+ TEST_THAT(check_files_same(TEST_FILE_FOR_PATCHING ".downloaded", TEST_FILE_FOR_PATCHING ".mod"));
+
+ TEST_NUM_FILES(UPLOAD_NUM, 1, 1, 1);
+>>>>>>> 0.12
}
// Create a directory
@@ -1189,11 +1546,21 @@ int test_server(const char *hostname)
{
// Attributes
MemBlockStream attr(attr1, sizeof(attr1));
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirCreate(protocol.QueryCreateDirectory(
BackupProtocolClientListDirectory::RootDirectory,
9837429842987984LL, dirname, attr));
subdirid = dirCreate->GetObjectID();
TEST_THAT(subdirid == maxID + 1);
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirCreate(apProtocol->QueryCreateDirectory(
+ BackupProtocolListDirectory::RootDirectory,
+ 9837429842987984LL, dirname, attr));
+ subdirid = dirCreate->GetObjectID();
+ TEST_THAT(subdirid == maxID + 1);
+
+ TEST_NUM_FILES(UPLOAD_NUM, 1, 1, 2);
+>>>>>>> 0.12
}
set_refcount(subdirid, 1);
@@ -1205,7 +1572,11 @@ int test_server(const char *hostname)
int64_t modtime;
std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile(filename.c_str(), subdirid, uploads[0].name, &modtime));
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
+=======
+ std::auto_ptr<BackupProtocolSuccess> stored(apProtocol->QueryStoreFile(
+>>>>>>> 0.12
subdirid,
modtime,
modtime, /* use for attr hash too */
@@ -1213,6 +1584,11 @@ int test_server(const char *hostname)
uploads[0].name,
*upload));
subdirfileid = stored->GetObjectID();
+<<<<<<< HEAD
+=======
+
+ TEST_NUM_FILES(UPLOAD_NUM + 1, 1, 1, 2);
+>>>>>>> 0.12
}
set_refcount(subdirfileid, 1);
@@ -1221,10 +1597,17 @@ int test_server(const char *hostname)
// Check the directories on the read only connection
{
// Command
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
BackupProtocolClientListDirectory::RootDirectory,
BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes! */)); // Stream
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ BackupProtocolListDirectory::RootDirectory,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes! */)); // Stream
+>>>>>>> 0.12
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite);
@@ -1246,17 +1629,28 @@ int test_server(const char *hostname)
}
// Does it look right?
TEST_THAT(en->GetName() == dirname);
+<<<<<<< HEAD
TEST_THAT(en->GetFlags() == BackupProtocolClientListDirectory::Flags_Dir);
+=======
+ TEST_THAT(en->GetFlags() == BackupProtocolListDirectory::Flags_Dir);
+>>>>>>> 0.12
TEST_THAT(en->GetObjectID() == subdirid);
TEST_THAT(en->GetModificationTime() == 0); // dirs don't have modification times.
}
{
// Command
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
subdirid,
BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, true /* get attributes */));
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ subdirid,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, true /* get attributes */));
+>>>>>>> 0.12
TEST_THAT(dirreply->GetObjectID() == subdirid);
// Stream
BackupStoreDirectory dir;
@@ -1271,7 +1665,11 @@ int test_server(const char *hostname)
TEST_THAT(en != 0);
// Does it look right?
TEST_THAT(en->GetName() == uploads[0].name);
+<<<<<<< HEAD
TEST_THAT(en->GetFlags() == BackupProtocolClientListDirectory::Flags_File);
+=======
+ TEST_THAT(en->GetFlags() == BackupProtocolListDirectory::Flags_File);
+>>>>>>> 0.12
TEST_THAT(en->GetObjectID() == subdirfileid);
TEST_THAT(en->GetModificationTime() != 0);
@@ -1286,10 +1684,17 @@ int test_server(const char *hostname)
// Check that we don't get attributes if we don't ask for them
{
// Command
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
subdirid,
BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes! */));
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ subdirid,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes! */));
+>>>>>>> 0.12
// Stream
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
@@ -1303,7 +1708,11 @@ int test_server(const char *hostname)
// Change attributes on the directory
{
MemBlockStream attrnew(attr2, sizeof(attr2));
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> changereply(protocol.QueryChangeDirAttributes(
+=======
+ std::auto_ptr<BackupProtocolSuccess> changereply(apProtocol->QueryChangeDirAttributes(
+>>>>>>> 0.12
subdirid,
329483209443598LL,
attrnew));
@@ -1312,10 +1721,17 @@ int test_server(const char *hostname)
// Check the new attributes
{
// Command
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
subdirid,
0, // no flags
BackupProtocolClientListDirectory::Flags_EXCLUDE_EVERYTHING, true /* get attributes */));
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ subdirid,
+ 0, // no flags
+ BackupProtocolListDirectory::Flags_EXCLUDE_EVERYTHING, true /* get attributes */));
+>>>>>>> 0.12
// Stream
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
@@ -1336,15 +1752,22 @@ int test_server(const char *hostname)
{
BackupStoreFilenameClear newName("moved-files");
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> rep(protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid,
BackupProtocolClientListDirectory::RootDirectory,
subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName));
+=======
+ std::auto_ptr<BackupProtocolSuccess> rep(apProtocol->QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid,
+ BackupProtocolListDirectory::RootDirectory,
+ subdirid, BackupProtocolMoveObject::Flags_MoveAllWithSameName, newName));
+>>>>>>> 0.12
TEST_THAT(rep->GetObjectID() == uploads[UPLOAD_FILE_TO_MOVE].allocated_objid);
}
// Try some dodgy renames
{
BackupStoreFilenameClear newName("moved-files");
+<<<<<<< HEAD
TEST_CHECK_THROWS(protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid,
BackupProtocolClientListDirectory::RootDirectory,
subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName),
@@ -1352,24 +1775,46 @@ int test_server(const char *hostname)
TEST_CHECK_THROWS(protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid,
subdirid,
subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName),
+=======
+ TEST_CHECK_THROWS(apProtocol->QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid,
+ BackupProtocolListDirectory::RootDirectory,
+ subdirid, BackupProtocolMoveObject::Flags_MoveAllWithSameName, newName),
+ ConnectionException, Conn_Protocol_UnexpectedReply);
+ TEST_CHECK_THROWS(apProtocol->QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid,
+ subdirid,
+ subdirid, BackupProtocolMoveObject::Flags_MoveAllWithSameName, newName),
+>>>>>>> 0.12
ConnectionException, Conn_Protocol_UnexpectedReply);
}
// Rename within a directory
{
BackupStoreFilenameClear newName("moved-files-x");
+<<<<<<< HEAD
protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid,
subdirid,
subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName);
+=======
+ apProtocol->QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid,
+ subdirid,
+ subdirid, BackupProtocolMoveObject::Flags_MoveAllWithSameName, newName);
+>>>>>>> 0.12
}
// Check it's all gone from the root directory...
{
// Command
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
BackupProtocolClientListDirectory::RootDirectory,
BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ BackupProtocolListDirectory::RootDirectory,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+>>>>>>> 0.12
// Stream
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
@@ -1388,10 +1833,17 @@ int test_server(const char *hostname)
BackupStoreFilenameClear lookFor("moved-files-x");
// Command
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
subdirid,
BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ subdirid,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+>>>>>>> 0.12
// Stream
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
@@ -1423,14 +1875,22 @@ int test_server(const char *hostname)
BackupStoreFilenameClear nd("sub2");
// Attributes
MemBlockStream attr(attr1, sizeof(attr1));
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirCreate(protocol.QueryCreateDirectory(
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirCreate(apProtocol->QueryCreateDirectory(
+>>>>>>> 0.12
subdirid,
9837429842987984LL, nd, attr));
subsubdirid = dirCreate->GetObjectID();
FileStream upload("testfiles/file1_upload1");
BackupStoreFilenameClear nf("file2");
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
+=======
+ std::auto_ptr<BackupProtocolSuccess> stored(apProtocol->QueryStoreFile(
+>>>>>>> 0.12
subsubdirid,
0x123456789abcdefLL, /* modification time */
0x7362383249872dfLL, /* attr hash */
@@ -1445,6 +1905,7 @@ int test_server(const char *hostname)
// Query names -- test that invalid stuff returns not found OK
{
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientObjectName> nameRep(protocol.QueryGetObjectName(3248972347823478927LL, subsubdirid));
TEST_THAT(nameRep->GetNumNameElements() == 0);
}
@@ -1458,16 +1919,39 @@ int test_server(const char *hostname)
}
{
std::auto_ptr<BackupProtocolClientObjectName> nameRep(protocol.QueryGetObjectName(BackupProtocolClientGetObjectName::ObjectID_DirectoryOnly, 2234342378424LL));
+=======
+ std::auto_ptr<BackupProtocolObjectName> nameRep(apProtocol->QueryGetObjectName(3248972347823478927LL, subsubdirid));
+ TEST_THAT(nameRep->GetNumNameElements() == 0);
+ }
+ {
+ std::auto_ptr<BackupProtocolObjectName> nameRep(apProtocol->QueryGetObjectName(subsubfileid, 2342378424LL));
+ TEST_THAT(nameRep->GetNumNameElements() == 0);
+ }
+ {
+ std::auto_ptr<BackupProtocolObjectName> nameRep(apProtocol->QueryGetObjectName(38947234789LL, 2342378424LL));
+ TEST_THAT(nameRep->GetNumNameElements() == 0);
+ }
+ {
+ std::auto_ptr<BackupProtocolObjectName> nameRep(apProtocol->QueryGetObjectName(BackupProtocolGetObjectName::ObjectID_DirectoryOnly, 2234342378424LL));
+>>>>>>> 0.12
TEST_THAT(nameRep->GetNumNameElements() == 0);
}
// Query names... first, get info for the file
{
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientObjectName> nameRep(protocol.QueryGetObjectName(subsubfileid, subsubdirid));
std::auto_ptr<IOStream> namestream(protocol.ReceiveStream());
TEST_THAT(nameRep->GetNumNameElements() == 3);
TEST_THAT(nameRep->GetFlags() == BackupProtocolClientListDirectory::Flags_File);
+=======
+ std::auto_ptr<BackupProtocolObjectName> nameRep(apProtocol->QueryGetObjectName(subsubfileid, subsubdirid));
+ std::auto_ptr<IOStream> namestream(apProtocol->ReceiveStream());
+
+ TEST_THAT(nameRep->GetNumNameElements() == 3);
+ TEST_THAT(nameRep->GetFlags() == BackupProtocolListDirectory::Flags_File);
+>>>>>>> 0.12
TEST_THAT(nameRep->GetModificationTime() == 0x123456789abcdefLL);
TEST_THAT(nameRep->GetAttributesHash() == 0x7362383249872dfLL);
static const char *testnames[] = {"file2","sub2","lovely_directory"};
@@ -1481,11 +1965,19 @@ int test_server(const char *hostname)
// Query names... secondly, for the directory
{
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientObjectName> nameRep(protocol.QueryGetObjectName(BackupProtocolClientGetObjectName::ObjectID_DirectoryOnly, subsubdirid));
std::auto_ptr<IOStream> namestream(protocol.ReceiveStream());
TEST_THAT(nameRep->GetNumNameElements() == 2);
TEST_THAT(nameRep->GetFlags() == BackupProtocolClientListDirectory::Flags_Dir);
+=======
+ std::auto_ptr<BackupProtocolObjectName> nameRep(apProtocol->QueryGetObjectName(BackupProtocolGetObjectName::ObjectID_DirectoryOnly, subsubdirid));
+ std::auto_ptr<IOStream> namestream(apProtocol->ReceiveStream());
+
+ TEST_THAT(nameRep->GetNumNameElements() == 2);
+ TEST_THAT(nameRep->GetFlags() == BackupProtocolListDirectory::Flags_Dir);
+>>>>>>> 0.12
static const char *testnames[] = {"sub2","lovely_directory"};
for(int l = 0; l < nameRep->GetNumNameElements(); ++l)
{
@@ -1497,21 +1989,33 @@ int test_server(const char *hostname)
//} skip:
+<<<<<<< HEAD
std::auto_ptr<BackupStoreAccountDatabase> apAccounts(
BackupStoreAccountDatabase::Read(
"testfiles/accounts.txt"));
+=======
+>>>>>>> 0.12
std::auto_ptr<BackupStoreRefCountDatabase> apRefCount(
BackupStoreRefCountDatabase::Load(
apAccounts->GetEntry(0x1234567), true));
// Create some nice recursive directories
+<<<<<<< HEAD
int64_t dirtodelete = create_test_data_subdirs(protocol,
BackupProtocolClientListDirectory::RootDirectory,
+=======
+ int64_t dirtodelete = create_test_data_subdirs(*apProtocol,
+ BackupProtocolListDirectory::RootDirectory,
+>>>>>>> 0.12
"test_delete", 6 /* depth */, *apRefCount);
// And delete them
{
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirdel(protocol.QueryDeleteDirectory(
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirdel(apProtocol->QueryDeleteDirectory(
+>>>>>>> 0.12
dirtodelete));
TEST_THAT(dirdel->GetObjectID() == dirtodelete);
}
@@ -1519,10 +2023,17 @@ int test_server(const char *hostname)
// Get the root dir, checking for deleted items
{
// Command
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocolReadOnly.QueryListDirectory(
BackupProtocolClientListDirectory::RootDirectory,
BackupProtocolClientListDirectory::Flags_Dir | BackupProtocolClientListDirectory::Flags_Deleted,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocolReadOnly.QueryListDirectory(
+ BackupProtocolListDirectory::RootDirectory,
+ BackupProtocolListDirectory::Flags_Dir | BackupProtocolListDirectory::Flags_Deleted,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+>>>>>>> 0.12
// Stream
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(protocolReadOnly.ReceiveStream());
@@ -1549,7 +2060,11 @@ int test_server(const char *hostname)
#ifndef WIN32
protocolReadOnly.QueryFinished();
#endif
+<<<<<<< HEAD
protocol.QueryFinished();
+=======
+ apProtocol->QueryFinished();
+>>>>>>> 0.12
// Close logs
#ifndef WIN32
@@ -1742,6 +2257,10 @@ int test3(int argc, const char *argv[])
TEST_CHECK_THROWS(info->ChangeBlocksInDeletedFiles(1), BackupStoreException, StoreInfoIsReadOnly);
TEST_CHECK_THROWS(info->RemovedDeletedDirectory(2), BackupStoreException, StoreInfoIsReadOnly);
TEST_CHECK_THROWS(info->AddDeletedDirectory(2), BackupStoreException, StoreInfoIsReadOnly);
+<<<<<<< HEAD
+=======
+ TEST_CHECK_THROWS(info->SetAccountName("hello"), BackupStoreException, StoreInfoIsReadOnly);
+>>>>>>> 0.12
}
{
std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(76, "test-info/", 0, false));
@@ -1758,6 +2277,10 @@ int test3(int argc, const char *argv[])
info->AddDeletedDirectory(3);
info->AddDeletedDirectory(4);
info->RemovedDeletedDirectory(3);
+<<<<<<< HEAD
+=======
+ info->SetAccountName("whee");
+>>>>>>> 0.12
TEST_CHECK_THROWS(info->RemovedDeletedDirectory(9), BackupStoreException, StoreInfoDirNotInList);
info->Save();
}
@@ -1768,6 +2291,10 @@ int test3(int argc, const char *argv[])
TEST_THAT(info->GetBlocksInDeletedFiles() == 1);
TEST_THAT(info->GetBlocksSoftLimit() == 3461231233455433LL);
TEST_THAT(info->GetBlocksHardLimit() == 2934852487LL);
+<<<<<<< HEAD
+=======
+ TEST_THAT(info->GetAccountName() == "whee");
+>>>>>>> 0.12
const std::vector<int64_t> &delfiles(info->GetDeletedDirectories());
TEST_THAT(delfiles.size() == 2);
TEST_THAT(delfiles[0] == 2);
@@ -1791,6 +2318,7 @@ int test3(int argc, const char *argv[])
int pid = LaunchServer(cmd.c_str(), "testfiles/bbstored.pid");
TEST_THAT(pid != -1 && pid != 0);
+<<<<<<< HEAD
if(pid > 0)
{
::sleep(1);
@@ -2021,6 +2549,276 @@ int test3(int argc, const char *argv[])
TestRemoteProcessMemLeaks("bbstored.memleaks");
#endif
}
+=======
+ if(pid <= 0)
+ {
+ return 1;
+ }
+
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+
+ // BLOCK
+ {
+ // Open a connection to the server
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, "localhost",
+ BOX_PORT_BBSTORED_TEST);
+
+ // Make a protocol
+ BackupProtocolClient protocol(conn);
+
+ // Check the version
+ std::auto_ptr<BackupProtocolVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+
+ // Login
+ TEST_CHECK_THROWS(std::auto_ptr<BackupProtocolLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, 0)),
+ ConnectionException, Conn_Protocol_UnexpectedReply);
+
+ // Finish the connection
+ protocol.QueryFinished();
+ }
+
+ // Create an account for the test client
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf create 01234567 0 "
+ "10000B 20000B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ TEST_THAT(TestDirExists("testfiles/0_0/backup/01234567"));
+ TEST_THAT(TestDirExists("testfiles/0_1/backup/01234567"));
+ TEST_THAT(TestDirExists("testfiles/0_2/backup/01234567"));
+ TEST_THAT(TestGetFileSize("testfiles/accounts.txt") > 8);
+ // make sure something is written to it
+
+ std::auto_ptr<BackupStoreAccountDatabase> apAccounts(
+ BackupStoreAccountDatabase::Read("testfiles/accounts.txt"));
+
+ std::auto_ptr<BackupStoreRefCountDatabase> apReferences(
+ BackupStoreRefCountDatabase::Load(
+ apAccounts->GetEntry(0x1234567), true));
+ TEST_EQUAL(BACKUPSTORE_ROOT_DIRECTORY_ID,
+ apReferences->GetLastObjectIDUsed());
+ TEST_EQUAL(1, apReferences->GetRefCount(BACKUPSTORE_ROOT_DIRECTORY_ID))
+ apReferences.reset();
+
+ // Test that login fails on a disabled account
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf enabled 01234567 no") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+ // BLOCK
+ {
+ // Open a connection to the server
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, "localhost",
+ BOX_PORT_BBSTORED_TEST);
+
+ // Make a protocol
+ BackupProtocolClient protocol(conn);
+
+ // Check the version
+ std::auto_ptr<BackupProtocolVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+
+ // Login
+ TEST_CHECK_THROWS(std::auto_ptr<BackupProtocolLoginConfirmed>
+ loginConf(protocol.QueryLogin(0x01234567, 0)),
+ ConnectionException, Conn_Protocol_UnexpectedReply);
+ int type, subType;
+ TEST_EQUAL_LINE(true, protocol.GetLastError(type, subType),
+ "expected a protocol error");
+ TEST_EQUAL_LINE(BackupProtocolError::ErrorType, type,
+ "expected a BackupProtocolError");
+ TEST_EQUAL_LINE(BackupProtocolError::Err_DisabledAccount, subType,
+ "expected an Err_DisabledAccount");
+
+ // Finish the connection
+ protocol.QueryFinished();
+ }
+
+ // Re-enable the account so that subsequent logins should succeed
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf enabled 01234567 yes") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ // Delete the refcount database and log in again,
+ // check that it is recreated automatically but with
+ // no objects in it, to ensure seamless upgrade.
+ TEST_EQUAL(0, ::unlink("testfiles/0_0/backup/01234567/refcount.db.rfw"));
+
+ std::auto_ptr<SocketStreamTLS> conn;
+ test_server_login("localhost", context, conn)->QueryFinished();
+
+ BackupStoreAccountDatabase::Entry account =
+ apAccounts->GetEntry(0x1234567);
+ apReferences = BackupStoreRefCountDatabase::Load(account, true);
+ TEST_EQUAL(0, apReferences->GetLastObjectIDUsed());
+
+ TEST_THAT(ServerIsAlive(pid));
+
+ run_housekeeping(account);
+
+ // Check that housekeeping fixed the ref counts
+ TEST_EQUAL(BACKUPSTORE_ROOT_DIRECTORY_ID,
+ apReferences->GetLastObjectIDUsed());
+ TEST_EQUAL(1, apReferences->GetRefCount(BACKUPSTORE_ROOT_DIRECTORY_ID))
+
+ TEST_THAT(ServerIsAlive(pid));
+
+ set_refcount(BACKUPSTORE_ROOT_DIRECTORY_ID, 1);
+
+ TEST_THAT(test_server("localhost") == 0);
+
+ // test that all object reference counts have the
+ // expected values
+ TEST_EQUAL(ExpectedRefCounts.size() - 1,
+ apReferences->GetLastObjectIDUsed());
+ for (unsigned int i = BACKUPSTORE_ROOT_DIRECTORY_ID;
+ i < ExpectedRefCounts.size(); i++)
+ {
+ TEST_EQUAL_LINE(ExpectedRefCounts[i],
+ apReferences->GetRefCount(i),
+ "object " << BOX_FORMAT_OBJECTID(i));
+ }
+
+ // Delete the refcount database again, and let
+ // housekeeping recreate it and fix the ref counts.
+ // This could also happen after upgrade, if a housekeeping
+ // runs before the user logs in.
+ apReferences.reset();
+ TEST_EQUAL(0, ::unlink("testfiles/0_0/backup/01234567/refcount.db.rfw"));
+ run_housekeeping(account);
+ apReferences = BackupStoreRefCountDatabase::Load(account, true);
+
+ TEST_EQUAL(ExpectedRefCounts.size() - 1,
+ apReferences->GetLastObjectIDUsed());
+ for (unsigned int i = BACKUPSTORE_ROOT_DIRECTORY_ID;
+ i < ExpectedRefCounts.size(); i++)
+ {
+ TEST_EQUAL_LINE(ExpectedRefCounts[i],
+ apReferences->GetRefCount(i),
+ "object " << BOX_FORMAT_OBJECTID(i));
+ }
+
+ // Test the deletion of objects by the housekeeping system
+ // First, things as they are now.
+ recursive_count_objects_results before = {0,0,0};
+
+ recursive_count_objects("localhost", BackupProtocolListDirectory::RootDirectory, before);
+
+ TEST_THAT(before.objectsNotDel != 0);
+ TEST_THAT(before.deleted != 0);
+ TEST_THAT(before.old != 0);
+
+ // Kill it
+ TEST_THAT(KillServer(pid));
+ ::sleep(1);
+ TEST_THAT(!ServerIsAlive(pid));
+
+ #ifdef WIN32
+ TEST_THAT(unlink("testfiles/bbstored.pid") == 0);
+ #else
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+ #endif
+
+ // Set a new limit on the account -- leave the hard limit
+ // high to make sure the target for freeing space is the
+ // soft limit.
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf setlimit 01234567 "
+ "10B 20000B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ // Start things up
+ pid = LaunchServer(BBSTORED " testfiles/bbstored.conf",
+ "testfiles/bbstored.pid");
+
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(pid));
+
+ // wait for housekeeping to happen
+ printf("waiting for housekeeping:\n");
+ for(int l = 0; l < 30; ++l)
+ {
+ ::sleep(1);
+ printf(".");
+ fflush(stdout);
+ }
+ printf("\n");
+
+ // Count the objects again
+ recursive_count_objects_results after = {0,0,0};
+ recursive_count_objects("localhost",
+ BackupProtocolListDirectory::RootDirectory,
+ after);
+
+ // If these tests fail then try increasing the timeout above
+ TEST_THAT(after.objectsNotDel == before.objectsNotDel);
+ TEST_THAT(after.deleted == 0);
+ TEST_THAT(after.old == 0);
+
+ // Set a really small hard limit
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf setlimit 01234567 "
+ "10B 20B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ // Try to upload a file and create a directory, and check an error is generated
+ {
+ // Open a connection to the server
+ SocketStreamTLS conn;
+ conn.Open(context, Socket::TypeINET, "localhost",
+ BOX_PORT_BBSTORED_TEST);
+
+ // Make a protocol
+ BackupProtocolClient protocol(conn);
+
+ // Check the version
+ std::auto_ptr<BackupProtocolVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+ TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
+
+ // Login
+ std::auto_ptr<BackupProtocolLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, 0));
+
+ int64_t modtime = 0;
+
+ BackupStoreFilenameClear fnx("exceed-limit");
+ std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile("testfiles/test3", BackupProtocolListDirectory::RootDirectory, fnx, &modtime));
+ TEST_THAT(modtime != 0);
+
+ TEST_CHECK_THROWS(std::auto_ptr<BackupProtocolSuccess> stored(protocol.QueryStoreFile(
+ BackupProtocolListDirectory::RootDirectory,
+ modtime,
+ modtime, /* use it for attr hash too */
+ 0, /* diff from ID */
+ fnx,
+ *upload)),
+ ConnectionException, Conn_Protocol_UnexpectedReply);
+
+ MemBlockStream attr(&modtime, sizeof(modtime));
+ BackupStoreFilenameClear fnxd("exceed-limit-dir");
+ TEST_CHECK_THROWS(std::auto_ptr<BackupProtocolSuccess> dirCreate(protocol.QueryCreateDirectory(
+ BackupProtocolListDirectory::RootDirectory,
+ 9837429842987984LL, fnxd, attr)),
+ ConnectionException, Conn_Protocol_UnexpectedReply);
+
+
+ // Finish the connection
+ protocol.QueryFinished();
+ }
+
+ // Kill it again
+ TEST_THAT(KillServer(pid));
+ ::sleep(1);
+ TEST_THAT(!ServerIsAlive(pid));
+
+ #ifdef WIN32
+ TEST_THAT(unlink("testfiles/bbstored.pid") == 0);
+ #else
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+ #endif
+>>>>>>> 0.12
return 0;
}
@@ -2068,25 +2866,36 @@ int multi_server()
return 0;
}
+<<<<<<< HEAD
#ifdef WIN32
WCHAR* ConvertUtf8ToWideString(const char* pString);
std::string ConvertPathToAbsoluteUnicode(const char *pFileName);
#endif
int test(int argc, const char *argv[])
+=======
+void test_open_files_with_limited_win32_permissions()
+>>>>>>> 0.12
{
#ifdef WIN32
// this had better work, or bbstored will die when combining diffs
char* file = "foo";
+<<<<<<< HEAD
std::string abs = ConvertPathToAbsoluteUnicode(file);
WCHAR* wfile = ConvertUtf8ToWideString(abs.c_str());
+=======
+>>>>>>> 0.12
DWORD accessRights = FILE_READ_ATTRIBUTES |
FILE_LIST_DIRECTORY | FILE_READ_EA | FILE_WRITE_ATTRIBUTES |
FILE_WRITE_DATA | FILE_WRITE_EA /*| FILE_ALL_ACCESS*/;
DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+<<<<<<< HEAD
HANDLE h1 = CreateFileW(wfile, accessRights, shareMode,
+=======
+ HANDLE h1 = CreateFileA(file, accessRights, shareMode,
+>>>>>>> 0.12
NULL, OPEN_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, NULL);
assert(h1 != INVALID_HANDLE_VALUE);
TEST_THAT(h1 != INVALID_HANDLE_VALUE);
@@ -2094,23 +2903,328 @@ int test(int argc, const char *argv[])
accessRights = FILE_READ_ATTRIBUTES |
FILE_LIST_DIRECTORY | FILE_READ_EA;
+<<<<<<< HEAD
HANDLE h2 = CreateFileW(wfile, accessRights, shareMode,
+=======
+ HANDLE h2 = CreateFileA(file, accessRights, shareMode,
+>>>>>>> 0.12
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
assert(h2 != INVALID_HANDLE_VALUE);
TEST_THAT(h2 != INVALID_HANDLE_VALUE);
CloseHandle(h2);
CloseHandle(h1);
+<<<<<<< HEAD
delete [] wfile;
h1 = openfile("foo", O_CREAT | O_RDWR, 0);
TEST_THAT(h1 != INVALID_HANDLE_VALUE);
h2 = openfile("foo", O_RDWR, 0);
+=======
+
+ h1 = openfile(file, O_CREAT | O_RDWR, 0);
+ TEST_THAT(h1 != INVALID_HANDLE_VALUE);
+ h2 = openfile(file, O_RDWR, 0);
+>>>>>>> 0.12
TEST_THAT(h2 != INVALID_HANDLE_VALUE);
CloseHandle(h2);
CloseHandle(h1);
#endif
+<<<<<<< HEAD
+
+=======
+}
+
+void compare_backupstoreinfo_values_to_expected
+(
+ const std::string& test_phase,
+ const info_StreamFormat_1& expected,
+ const BackupStoreInfo& actual,
+ const std::string& expected_account_name,
+ bool expected_account_enabled,
+ const MemBlockStream& extra_data = MemBlockStream(/* empty */)
+)
+{
+ TEST_EQUAL_LINE(ntohl(expected.mAccountID), actual.GetAccountID(),
+ test_phase << " AccountID");
+ #define TEST_INFO_EQUAL(property) \
+ TEST_EQUAL_LINE(box_ntoh64(expected.m ## property), \
+ actual.Get ## property (), test_phase << " " #property)
+ TEST_INFO_EQUAL(ClientStoreMarker);
+ TEST_INFO_EQUAL(LastObjectIDUsed);
+ TEST_INFO_EQUAL(BlocksUsed);
+ TEST_INFO_EQUAL(BlocksInOldFiles);
+ TEST_INFO_EQUAL(BlocksInDeletedFiles);
+ TEST_INFO_EQUAL(BlocksInDirectories);
+ TEST_INFO_EQUAL(BlocksSoftLimit);
+ TEST_INFO_EQUAL(BlocksHardLimit);
+ #undef TEST_INFO_EQUAL
+
+ // These attributes of the v2 structure are not supported by v1,
+ // so they should all be initialised to 0
+ TEST_EQUAL_LINE(0, actual.GetBlocksInCurrentFiles(),
+ test_phase << " BlocksInCurrentFiles");
+ TEST_EQUAL_LINE(0, actual.GetNumOldFiles(),
+ test_phase << " NumOldFiles");
+ TEST_EQUAL_LINE(0, actual.GetNumDeletedFiles(),
+ test_phase << " NumDeletedFiles");
+ TEST_EQUAL_LINE(0, actual.GetNumDirectories(),
+ test_phase << " NumDirectories");
+
+ // These attributes of the old v1 structure are not actually loaded
+ // or used:
+ // TEST_INFO_EQUAL(CurrentMarkNumber);
+ // TEST_INFO_EQUAL(OptionsPresent);
+
+ TEST_EQUAL_LINE(box_ntoh64(expected.mNumberDeletedDirectories),
+ actual.GetDeletedDirectories().size(),
+ test_phase << " number of deleted directories");
+
+ for (size_t i = 0; i < box_ntoh64(expected.mNumberDeletedDirectories) &&
+ i < actual.GetDeletedDirectories().size(); i++)
+ {
+ TEST_EQUAL_LINE(13 + i, actual.GetDeletedDirectories()[i],
+ test_phase << " deleted directory " << (i+1));
+ }
+
+ TEST_EQUAL_LINE(expected_account_name, actual.GetAccountName(),
+ test_phase << " AccountName");
+ TEST_EQUAL_LINE(expected_account_enabled, actual.IsAccountEnabled(),
+ test_phase << " AccountEnabled");
+
+ TEST_EQUAL_LINE(extra_data.GetSize(), actual.GetExtraData().GetSize(),
+ test_phase << " extra data has wrong size");
+ TEST_EQUAL_LINE(0, memcmp(extra_data.GetBuffer(),
+ actual.GetExtraData().GetBuffer(), extra_data.GetSize()),
+ test_phase << " extra data has wrong contents");
+}
+
+int test_read_old_backupstoreinfo_files()
+{
+ // Create an account for the test client
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf create 01234567 0 "
+ "10000B 20000B") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+ std::auto_ptr<BackupStoreInfo> apInfo = BackupStoreInfo::Load(0x1234567,
+ "backup/01234567/", 0, /* ReadOnly */ false);
+ TEST_EQUAL_LINE(true, apInfo->IsAccountEnabled(),
+ "'bbstoreaccounts create' should have set AccountEnabled flag");
+
+ info_StreamFormat_1 info_v1;
+ info_v1.mMagicValue = htonl(INFO_MAGIC_VALUE_1);
+ info_v1.mAccountID = htonl(0x01234567);
+ info_v1.mClientStoreMarker = box_hton64(3);
+ info_v1.mLastObjectIDUsed = box_hton64(4);
+ info_v1.mBlocksUsed = box_hton64(5);
+ info_v1.mBlocksInOldFiles = box_hton64(6);
+ info_v1.mBlocksInDeletedFiles = box_hton64(7);
+ info_v1.mBlocksInDirectories = box_hton64(8);
+ info_v1.mBlocksSoftLimit = box_hton64(9);
+ info_v1.mBlocksHardLimit = box_hton64(10);
+ info_v1.mCurrentMarkNumber = htonl(11);
+ info_v1.mOptionsPresent = htonl(12);
+ info_v1.mNumberDeletedDirectories = box_hton64(2);
+ // Then mNumberDeletedDirectories * int64_t IDs for the deleted directories
+
+ // Generate the filename
+ std::string info_filename("backup/01234567/" INFO_FILENAME);
+ std::auto_ptr<RaidFileWrite> rfw(new RaidFileWrite(0, info_filename));
+ rfw->Open(/* AllowOverwrite = */ true);
+ rfw->Write(&info_v1, sizeof(info_v1));
+ // Write mNumberDeletedDirectories * int64_t IDs for the deleted directories
+ std::auto_ptr<Archive> apArchive(new Archive(*rfw, IOStream::TimeOutInfinite));
+ apArchive->Write((int64_t) 13);
+ apArchive->Write((int64_t) 14);
+ rfw->Commit(/* ConvertToRaidNow */ true);
+ rfw.reset();
+
+ apInfo = BackupStoreInfo::Load(0x1234567, "backup/01234567/", 0,
+ /* ReadOnly */ false);
+ compare_backupstoreinfo_values_to_expected("loaded from v1", info_v1,
+ *apInfo, "" /* no name by default */,
+ true /* enabled by default */);
+ apInfo->SetAccountName("bonk");
+
+ // Save the info again
+ apInfo->Save(/* allowOverwrite */ true);
+
+ // Check that it was saved in the new Archive format
+ std::auto_ptr<RaidFileRead> rfr(RaidFileRead::Open(0, info_filename, 0));
+ int32_t magic;
+ if(!rfr->ReadFullBuffer(&magic, sizeof(magic), 0))
+ {
+ THROW_FILE_ERROR("Failed to read store info file: "
+ "short read of magic number", info_filename,
+ BackupStoreException, CouldNotLoadStoreInfo);
+ }
+ TEST_EQUAL_LINE(INFO_MAGIC_VALUE_2, ntohl(magic),
+ "format version in newly saved BackupStoreInfo");
+ rfr.reset();
+
+ // load it, and check that all values are loaded properly
+ apInfo = BackupStoreInfo::Load(0x1234567, "backup/01234567/", 0,
+ /* ReadOnly */ false);
+ compare_backupstoreinfo_values_to_expected("loaded in v1, resaved in v2",
+ info_v1, *apInfo, "bonk", true);
+
+ // Check that the new AccountEnabled flag is saved properly
+ apInfo->SetAccountEnabled(false);
+ apInfo->Save(/* allowOverwrite */ true);
+ apInfo = BackupStoreInfo::Load(0x1234567, "backup/01234567/", 0,
+ /* ReadOnly */ false);
+ compare_backupstoreinfo_values_to_expected("saved in v2, loaded in v2",
+ info_v1, *apInfo, "bonk", false /* as modified above */);
+ apInfo->SetAccountEnabled(true);
+ apInfo->Save(/* allowOverwrite */ true);
+ apInfo = BackupStoreInfo::Load(0x1234567, "backup/01234567/", 0,
+ /* ReadOnly */ true);
+ compare_backupstoreinfo_values_to_expected("resaved in v2 with "
+ "account enabled", info_v1, *apInfo, "bonk",
+ true /* as modified above */);
+
+ // Now save the info in v2 format without the AccountEnabled flag
+ // (boxbackup 0.11 format) and check that the flag is set to true
+ // for backwards compatibility
+
+ rfw.reset(new RaidFileWrite(0, info_filename));
+ rfw->Open(/* allowOverwrite */ true);
+ magic = htonl(INFO_MAGIC_VALUE_2);
+ apArchive.reset(new Archive(*rfw, IOStream::TimeOutInfinite));
+ rfw->Write(&magic, sizeof(magic));
+ apArchive->Write(apInfo->GetAccountID());
+ apArchive->Write(std::string("test"));
+ apArchive->Write(apInfo->GetClientStoreMarker());
+ apArchive->Write(apInfo->GetLastObjectIDUsed());
+ apArchive->Write(apInfo->GetBlocksUsed());
+ apArchive->Write(apInfo->GetBlocksInCurrentFiles());
+ apArchive->Write(apInfo->GetBlocksInOldFiles());
+ apArchive->Write(apInfo->GetBlocksInDeletedFiles());
+ apArchive->Write(apInfo->GetBlocksInDirectories());
+ apArchive->Write(apInfo->GetBlocksSoftLimit());
+ apArchive->Write(apInfo->GetBlocksHardLimit());
+ apArchive->Write(apInfo->GetNumFiles());
+ apArchive->Write(apInfo->GetNumOldFiles());
+ apArchive->Write(apInfo->GetNumDeletedFiles());
+ apArchive->Write(apInfo->GetNumDirectories());
+ apArchive->Write((int64_t) apInfo->GetDeletedDirectories().size());
+ apArchive->Write(apInfo->GetDeletedDirectories()[0]);
+ apArchive->Write(apInfo->GetDeletedDirectories()[1]);
+ rfw->Commit(/* ConvertToRaidNow */ true);
+ rfw.reset();
+
+ apInfo = BackupStoreInfo::Load(0x1234567, "backup/01234567/", 0,
+ /* ReadOnly */ false);
+ compare_backupstoreinfo_values_to_expected("saved in v2 without "
+ "AccountEnabled", info_v1, *apInfo, "test", true);
+ // Default for missing AccountEnabled should be true
+
+ // Rewrite using full length, so that the first 4 bytes of extra data
+ // doesn't get swallowed by "extra data".
+ apInfo->Save(/* allowOverwrite */ true);
+
+ // Append some extra data after the known account values, to simulate a
+ // new addition to the store format. Check that this extra data is loaded
+ // and resaved with the info file. We made the mistake of deleting it in
+ // 0.11, let's not make the same mistake again.
+ CollectInBufferStream info_data;
+ rfr = RaidFileRead::Open(0, info_filename, 0);
+ rfr->CopyStreamTo(info_data);
+ rfr.reset();
+ info_data.SetForReading();
+ rfw.reset(new RaidFileWrite(0, info_filename));
+ rfw->Open(/* allowOverwrite */ true);
+ info_data.CopyStreamTo(*rfw);
+ char extra_string[] = "hello!";
+ MemBlockStream extra_data(extra_string, strlen(extra_string));
+ extra_data.CopyStreamTo(*rfw);
+ rfw->Commit(/* ConvertToRaidNow */ true);
+ rfw.reset();
+ apInfo = BackupStoreInfo::Load(0x1234567, "backup/01234567/", 0,
+ /* ReadOnly */ false);
+ TEST_EQUAL_LINE(extra_data.GetSize(), apInfo->GetExtraData().GetSize(),
+ "wrong amount of extra data loaded from info file");
+ TEST_EQUAL_LINE(0, memcmp(extra_data.GetBuffer(),
+ apInfo->GetExtraData().GetBuffer(), extra_data.GetSize()),
+ "extra data loaded from info file has wrong contents");
+ // Save the file and load again, check that the extra data is still there
+ apInfo->Save(/* allowOverwrite */ true);
+ apInfo = BackupStoreInfo::Load(0x1234567, "backup/01234567/", 0, true);
+ compare_backupstoreinfo_values_to_expected("saved in future format "
+ "with extra_data", info_v1, *apInfo, "test", true, extra_data);
+
+ // Check that the new bbstoreaccounts command sets the flag properly
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf enabled 01234567 no") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+ apInfo = BackupStoreInfo::Load(0x1234567, "backup/01234567/", 0, true);
+ TEST_EQUAL_LINE(false, apInfo->IsAccountEnabled(),
+ "'bbstoreaccounts disabled no' should have reset AccountEnabled flag");
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf enabled 01234567 yes") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+ apInfo = BackupStoreInfo::Load(0x1234567, "backup/01234567/", 0, true);
+ TEST_EQUAL_LINE(true, apInfo->IsAccountEnabled(),
+ "'bbstoreaccounts disabled yes' should have set AccountEnabled flag");
+
+ // Check that BackupStoreInfo::CreateForRegeneration saves all the
+ // expected properties, including any extra data for forward
+ // compatibility
+ extra_data.Seek(0, IOStream::SeekType_Absolute);
+ apInfo = BackupStoreInfo::CreateForRegeneration(
+ apInfo->GetAccountID(), "spurtle" /* rAccountName */,
+ "backup/01234567/" /* rRootDir */, 0 /* DiscSet */,
+ apInfo->GetLastObjectIDUsed(),
+ apInfo->GetBlocksUsed(),
+ apInfo->GetBlocksInCurrentFiles(),
+ apInfo->GetBlocksInOldFiles(),
+ apInfo->GetBlocksInDeletedFiles(),
+ apInfo->GetBlocksInDirectories(),
+ apInfo->GetBlocksSoftLimit(),
+ apInfo->GetBlocksHardLimit(),
+ false /* AccountEnabled */,
+ extra_data);
+ // CreateForRegeneration always sets the ClientStoreMarker to 0
+ info_v1.mClientStoreMarker = 0;
+ // CreateForRegeneration does not store any deleted directories
+ info_v1.mNumberDeletedDirectories = 0;
+
+ // check that the store info has the correct values in memory
+ compare_backupstoreinfo_values_to_expected("stored by "
+ "BackupStoreInfo::CreateForRegeneration", info_v1, *apInfo,
+ "spurtle", false /* AccountEnabled */, extra_data);
+ // Save the file and load again, check that the extra data is still there
+ apInfo->Save(/* allowOverwrite */ true);
+ apInfo = BackupStoreInfo::Load(0x1234567, "backup/01234567/", 0, true);
+ compare_backupstoreinfo_values_to_expected("saved by "
+ "BackupStoreInfo::CreateForRegeneration and reloaded", info_v1,
+ *apInfo, "spurtle", false /* AccountEnabled */, extra_data);
+
+ // Delete the account to leave the store in the same state as before
+ apInfo.reset();
+ TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
+ " -c testfiles/bbstored.conf delete 01234567 yes") == 0);
+ TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+
+ return 0;
+}
+
+int test(int argc, const char *argv[])
+{
+ test_open_files_with_limited_win32_permissions();
+
+ // Initialise the raid file controller
+ RaidFileController &rcontroller = RaidFileController::GetController();
+ rcontroller.Initialise("testfiles/raidfile.conf");
+
+ int ret = test_read_old_backupstoreinfo_files();
+ if (ret != 0)
+ {
+ return ret;
+ }
+
+>>>>>>> 0.12
// SSL library
SSLLib::Initialise();
@@ -2159,7 +3273,11 @@ int test(int argc, const char *argv[])
/* {
int64_t modtime = 0;
std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile("/Users/ben/temp/large.tar",
+<<<<<<< HEAD
BackupProtocolClientListDirectory::RootDirectory, uploads[0].name, &modtime));
+=======
+ BackupProtocolListDirectory::RootDirectory, uploads[0].name, &modtime));
+>>>>>>> 0.12
TEST_THAT(modtime != 0);
FileStream write("testfiles/large.enc", O_WRONLY | O_CREAT);
upload->CopyStreamTo(write);
diff --git a/test/backupstorefix/testbackupstorefix.cpp b/test/backupstorefix/testbackupstorefix.cpp
index 2d4ce052..a93ffb8f 100644
--- a/test/backupstorefix/testbackupstorefix.cpp
+++ b/test/backupstorefix/testbackupstorefix.cpp
@@ -17,6 +17,7 @@
#include <map>
#include "Test.h"
+<<<<<<< HEAD
#include "BackupStoreConstants.h"
#include "BackupStoreDirectory.h"
#include "BackupStoreFile.h"
@@ -30,6 +31,28 @@
#include "StoreStructure.h"
#include "BackupStoreFileWire.h"
#include "ServerControl.h"
+=======
+#include "BackupClientCryptoKeys.h"
+#include "BackupStoreCheck.h"
+#include "BackupStoreConstants.h"
+#include "BackupStoreContext.h"
+#include "BackupStoreDirectory.h"
+#include "BackupStoreException.h"
+#include "BackupStoreFile.h"
+#include "BackupStoreFileWire.h"
+#include "BackupStoreFileEncodeStream.h"
+#include "BackupStoreInfo.h"
+#include "BufferedWriteStream.h"
+#include "FileStream.h"
+#include "RaidFileController.h"
+#include "RaidFileException.h"
+#include "RaidFileRead.h"
+#include "RaidFileUtil.h"
+#include "RaidFileWrite.h"
+#include "ServerControl.h"
+#include "StoreStructure.h"
+#include "ZeroStream.h"
+>>>>>>> 0.12
#include "MemLeakFindOn.h"
@@ -69,6 +92,29 @@ std::map<int32_t, bool> objectIsDir;
::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf check 01234567"); \
::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf check 01234567 fix");
+<<<<<<< HEAD
+=======
+bool check_fix_internal(int expected_num_errors)
+{
+ BackupStoreCheck checker(storeRoot, discSetNum,
+ 0x01234567, true /* FixErrors */, false /* Quiet */);
+ checker.Check();
+ if (expected_num_errors == -1)
+ {
+ TEST_THAT(checker.ErrorsFound());
+ return checker.ErrorsFound();
+ }
+ else
+ {
+ TEST_EQUAL(expected_num_errors, checker.GetNumErrorsFound());
+ return checker.GetNumErrorsFound() == expected_num_errors;
+ }
+}
+
+#define RUN_CHECK_INTERNAL(expected_num_errors) \
+ TEST_THAT(check_fix_internal(expected_num_errors))
+
+>>>>>>> 0.12
// Get ID of an object given a filename
int32_t getID(const char *name)
{
@@ -146,7 +192,13 @@ void check_dir(BackupStoreDirectory &dir, dir_en_check *ck)
while((en = i.Next()) != 0)
{
+<<<<<<< HEAD
TEST_THAT(ck->name != -1);
+=======
+ BackupStoreFilenameClear clear(en->GetName());
+ TEST_LINE(ck->name != -1, "Unexpected entry found in "
+ "directory: " << clear.GetClearFilename());
+>>>>>>> 0.12
if(ck->name == -1)
{
break;
@@ -178,9 +230,18 @@ void check_dir_dep(BackupStoreDirectory &dir, checkdepinfoen *ck)
{
break;
}
+<<<<<<< HEAD
TEST_THAT(en->GetObjectID() == ck->id);
TEST_THAT(en->GetDependsNewer() == ck->depNewer);
TEST_THAT(en->GetDependsOlder() == ck->depOlder);
+=======
+ TEST_EQUAL_LINE(ck->id, en->GetObjectID(), "Wrong object ID "
+ "for " << BOX_FORMAT_OBJECTID(ck->id));
+ TEST_EQUAL_LINE(ck->depNewer, en->GetDependsNewer(),
+ "Wrong Newer dependency for " << BOX_FORMAT_OBJECTID(ck->id));
+ TEST_EQUAL_LINE(ck->depOlder, en->GetDependsOlder(),
+ "Wrong Older dependency for " << BOX_FORMAT_OBJECTID(ck->id));
+>>>>>>> 0.12
++ck;
}
@@ -190,19 +251,50 @@ void check_dir_dep(BackupStoreDirectory &dir, checkdepinfoen *ck)
void test_dir_fixing()
{
+<<<<<<< HEAD
{
MEMLEAKFINDER_NO_LEAKS;
fnames[0].SetAsClearFilename("x1");
fnames[1].SetAsClearFilename("x2");
fnames[2].SetAsClearFilename("x3");
+=======
+ // Test that entries pointing to nonexistent entries are removed
+ {
+ BackupStoreDirectory dir;
+ BackupStoreDirectory::Entry* e = dir.AddEntry(fnames[0], 12,
+ 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File |
+ BackupStoreDirectory::Entry::Flags_OldVersion, 2);
+ e->SetDependsNewer(3);
+
+ TEST_THAT(dir.CheckAndFix() == true);
+ TEST_THAT(dir.CheckAndFix() == false);
+
+ dir_en_check ck[] = {
+ {-1, 0, 0}
+ };
+
+ check_dir(dir, ck);
+>>>>>>> 0.12
}
{
BackupStoreDirectory dir;
+<<<<<<< HEAD
dir.AddEntry(fnames[0], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2);
dir.AddEntry(fnames[1], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2);
dir.AddEntry(fnames[0], 12, 3 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2);
dir.AddEntry(fnames[0], 12, 5 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2);
+=======
+ dir.AddEntry(fnames[0], 12, 2 /* id */, 1,
+ BackupStoreDirectory::Entry::Flags_File, 2);
+ dir.AddEntry(fnames[1], 12, 2 /* id */, 1,
+ BackupStoreDirectory::Entry::Flags_File, 2);
+ dir.AddEntry(fnames[0], 12, 3 /* id */, 1,
+ BackupStoreDirectory::Entry::Flags_File, 2);
+ dir.AddEntry(fnames[0], 12, 5 /* id */, 1,
+ BackupStoreDirectory::Entry::Flags_File |
+ BackupStoreDirectory::Entry::Flags_OldVersion, 2);
+>>>>>>> 0.12
dir_en_check ck[] = {
{1, 2, BackupStoreDirectory::Entry::Flags_File},
@@ -215,6 +307,10 @@ void test_dir_fixing()
TEST_THAT(dir.CheckAndFix() == false);
check_dir(dir, ck);
}
+<<<<<<< HEAD
+=======
+
+>>>>>>> 0.12
{
BackupStoreDirectory dir;
dir.AddEntry(fnames[0], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2);
@@ -238,6 +334,7 @@ void test_dir_fixing()
// Test dependency fixing
{
BackupStoreDirectory dir;
+<<<<<<< HEAD
BackupStoreDirectory::Entry *e2
= dir.AddEntry(fnames[0], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2);
TEST_THAT(e2 != 0);
@@ -252,6 +349,28 @@ void test_dir_fixing()
e4->SetDependsNewer(5); e4->SetDependsOlder(3);
BackupStoreDirectory::Entry *e5
= dir.AddEntry(fnames[0], 12, 5 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2);
+=======
+ BackupStoreDirectory::Entry *e2 = dir.AddEntry(fnames[0], 12,
+ 2 /* id */, 1,
+ BackupStoreDirectory::Entry::Flags_File |
+ BackupStoreDirectory::Entry::Flags_OldVersion, 2);
+ TEST_THAT(e2 != 0);
+ e2->SetDependsNewer(3);
+ BackupStoreDirectory::Entry *e3 = dir.AddEntry(fnames[0], 12,
+ 3 /* id */, 1,
+ BackupStoreDirectory::Entry::Flags_File |
+ BackupStoreDirectory::Entry::Flags_OldVersion, 2);
+ TEST_THAT(e3 != 0);
+ e3->SetDependsNewer(4); e3->SetDependsOlder(2);
+ BackupStoreDirectory::Entry *e4 = dir.AddEntry(fnames[0], 12,
+ 4 /* id */, 1,
+ BackupStoreDirectory::Entry::Flags_File |
+ BackupStoreDirectory::Entry::Flags_OldVersion, 2);
+ TEST_THAT(e4 != 0);
+ e4->SetDependsNewer(5); e4->SetDependsOlder(3);
+ BackupStoreDirectory::Entry *e5 = dir.AddEntry(fnames[0], 12,
+ 5 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2);
+>>>>>>> 0.12
TEST_THAT(e5 != 0);
e5->SetDependsOlder(4);
@@ -281,14 +400,135 @@ void test_dir_fixing()
}
}
+<<<<<<< HEAD
int test(int argc, const char *argv[])
{
// Test the backupstore directory fixing
test_dir_fixing();
+=======
+int64_t fake_upload(BackupProtocolLocal& client, const std::string& file_path,
+ int64_t diff_from_id)
+{
+ std::auto_ptr<IOStream> upload;
+ if(diff_from_id)
+ {
+ std::auto_ptr<BackupProtocolSuccess> getBlockIndex(
+ client.QueryGetBlockIndexByName(
+ BACKUPSTORE_ROOT_DIRECTORY_ID, fnames[0]));
+ std::auto_ptr<IOStream> blockIndexStream(client.ReceiveStream());
+ upload = BackupStoreFile::EncodeFileDiff(
+ file_path,
+ BACKUPSTORE_ROOT_DIRECTORY_ID, fnames[0],
+ diff_from_id, *blockIndexStream,
+ IOStream::TimeOutInfinite,
+ NULL, // DiffTimer implementation
+ 0 /* not interested in the modification time */,
+ NULL // isCompletelyDifferent
+ );
+ }
+ else
+ {
+ upload = BackupStoreFile::EncodeFile(
+ file_path,
+ BACKUPSTORE_ROOT_DIRECTORY_ID, fnames[0],
+ NULL,
+ NULL, // pLogger
+ NULL // pRunStatusProvider
+ );
+ }
+
+ return client.QueryStoreFile(BACKUPSTORE_ROOT_DIRECTORY_ID,
+ 1, // ModificationTime
+ 2, // AttributesHash
+ diff_from_id, // DiffFromFileID
+ fnames[0], // rFilename
+ upload)->GetObjectID();
+}
+
+void read_bb_dir(int64_t objectId, BackupStoreDirectory& dir)
+{
+ std::string fn;
+ StoreStructure::MakeObjectFilename(1 /* root */, storeRoot,
+ discSetNum, fn, true /* EnsureDirectoryExists */);
+
+ std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(discSetNum,
+ fn));
+ dir.ReadFromStream(*file, IOStream::TimeOutInfinite);
+}
+
+void login_client_and_check_empty(BackupProtocolCallable& client)
+{
+ // Check that the initial situation matches our expectations.
+ BackupStoreDirectory dir;
+ read_bb_dir(1 /* root */, dir);
+
+ dir_en_check start_entries[] = {{-1, 0, 0}};
+ check_dir(dir, start_entries);
+ static checkdepinfoen start_deps[] = {{-1, 0, 0}};
+ check_dir_dep(dir, start_deps);
+
+ client.QueryVersion(BACKUP_STORE_SERVER_VERSION);
+ client.QueryLogin(0x01234567, 0 /* read/write */);
+
+ read_bb_dir(1 /* root */, dir);
+
+ // Everything should be OK at the moment
+ TEST_THAT(dir.CheckAndFix() == false);
+
+ // Check that we've ended up with the right preconditions
+ // for the tests below.
+ dir_en_check before_entries[] = {
+ {-1, 0, 0}
+ };
+ check_dir(dir, before_entries);
+ static checkdepinfoen before_deps[] = {{-1, 0, 0}};
+ check_dir_dep(dir, before_deps);
+}
+
+void check_root_dir_ok(dir_en_check after_entries[],
+ checkdepinfoen after_deps[])
+{
+ // Check the store, check that the error is detected and
+ // repaired, by removing x1 from the directory.
+ RUN_CHECK_INTERNAL(0);
+
+ // Read the directory back in, check that it's empty
+ BackupStoreDirectory dir;
+ read_bb_dir(1 /* root */, dir);
+
+ check_dir(dir, after_entries);
+ check_dir_dep(dir, after_deps);
+}
+
+void check_and_fix_root_dir(dir_en_check after_entries[],
+ checkdepinfoen after_deps[])
+{
+ // Check the store, check that the error is detected and
+ // repaired.
+ RUN_CHECK_INTERNAL(-1);
+ check_root_dir_ok(after_entries, after_deps);
+}
+
+int test(int argc, const char *argv[])
+{
+ {
+ MEMLEAKFINDER_NO_LEAKS;
+ fnames[0].SetAsClearFilename("x1");
+ fnames[1].SetAsClearFilename("x2");
+ fnames[2].SetAsClearFilename("x3");
+ }
+
+ // Test the backupstore directory fixing
+ // FIXME reenable: test_dir_fixing();
+>>>>>>> 0.12
// Initialise the raidfile controller
RaidFileController &rcontroller = RaidFileController::GetController();
rcontroller.Initialise("testfiles/raidfile.conf");
+<<<<<<< HEAD
+=======
+ BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys");
+>>>>>>> 0.12
// Create an account
TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS
@@ -296,6 +536,7 @@ int test(int argc, const char *argv[])
"create 01234567 0 10000B 20000B") == 0);
TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks");
+<<<<<<< HEAD
// Start the bbstored server
int pid = LaunchServer(BBSTORED " testfiles/bbstored.conf",
"testfiles/bbstored.pid");
@@ -608,6 +849,511 @@ int test(int argc, const char *argv[])
TestRemoteProcessMemLeaks("bbstored.memleaks");
#endif
}
+=======
+ // Run the perl script to create the initial directories
+ TEST_THAT_ABORTONFAIL(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl init") == 0);
+
+ BOX_INFO(" === Test that an entry pointing to a file that doesn't "
+ "exist is really deleted");
+
+ {
+ BackupStoreContext ctx(0x01234567,
+ *(HousekeepingInterface *)NULL,
+ "test" /* rConnectionDetails */);
+ ctx.SetClientHasAccount(storeRoot, discSetNum);
+ BackupProtocolLocal client(ctx);
+ login_client_and_check_empty(client);
+
+ std::string file_path = "testfiles/TestDir1/cannes/ict/metegoguered/oats";
+ int x1id = fake_upload(client, file_path, 0);
+
+ // Now break the reverse dependency by deleting x1 (the file,
+ // not the directory entry)
+ std::string x1FileName;
+ StoreStructure::MakeObjectFilename(x1id, storeRoot, discSetNum,
+ x1FileName, true /* EnsureDirectoryExists */);
+ RaidFileWrite deleteX1(discSetNum, x1FileName);
+ deleteX1.Delete();
+
+ dir_en_check after_entries[] = {{-1, 0, 0}};
+ static checkdepinfoen after_deps[] = {{-1, 0, 0}};
+ check_and_fix_root_dir(after_entries, after_deps);
+ }
+
+ BOX_INFO(" === Test that an entry pointing to another that doesn't "
+ "exist is really deleted");
+
+ {
+ BackupStoreContext ctx(0x01234567,
+ *(HousekeepingInterface *)NULL,
+ "test" /* rConnectionDetails */);
+ ctx.SetClientHasAccount(storeRoot, discSetNum);
+ BackupProtocolLocal client(ctx);
+ login_client_and_check_empty(client);
+
+ std::string file_path = "testfiles/TestDir1/cannes/ict/metegoguered/oats";
+ int x1id = fake_upload(client, file_path, 0);
+
+ // Make a small change to the file
+ FileStream fs(file_path, O_WRONLY | O_APPEND);
+ const char* more = " and more oats!";
+ fs.Write(more, strlen(more));
+ fs.Close();
+
+ int x1aid = fake_upload(client, file_path, x1id);
+
+ // Check that we've ended up with the right preconditions
+ // for the tests below.
+ dir_en_check before_entries[] = {
+ {0, x1id, BackupStoreDirectory::Entry::Flags_File |
+ BackupStoreDirectory::Entry::Flags_OldVersion},
+ {0, x1aid, BackupStoreDirectory::Entry::Flags_File},
+ {-1, 0, 0}
+ };
+ static checkdepinfoen before_deps[] = {{x1id, x1aid, 0},
+ {x1aid, 0, x1id}, {-1, 0, 0}};
+ check_root_dir_ok(before_entries, before_deps);
+
+ // Now break the reverse dependency by deleting x1a (the file,
+ // not the directory entry)
+ std::string x1aFileName;
+ StoreStructure::MakeObjectFilename(x1aid, storeRoot, discSetNum,
+ x1aFileName, true /* EnsureDirectoryExists */);
+ RaidFileWrite deleteX1a(discSetNum, x1aFileName);
+ deleteX1a.Delete();
+
+ // Check and fix the directory, and check that it's left empty
+ dir_en_check after_entries[] = {{-1, 0, 0}};
+ static checkdepinfoen after_deps[] = {{-1, 0, 0}};
+ check_and_fix_root_dir(after_entries, after_deps);
+ }
+
+ BOX_INFO(" === Test that an entry pointing to a directory whose "
+ "raidfile is corrupted doesn't crash");
+
+ // Start the bbstored server
+ BOX_TRACE(" === Starting bbstored server: " BBSTORED
+ " testfiles/bbstored.conf");
+ int bbstored_pid = LaunchServer(BBSTORED " testfiles/bbstored.conf",
+ "testfiles/bbstored.pid");
+ TEST_THAT(bbstored_pid > 0);
+ if (bbstored_pid <= 0) return 1;
+
+ ::sleep(1);
+ TEST_THAT(ServerIsAlive(bbstored_pid));
+
+ std::string cmd = BBACKUPD " " + bbackupd_args +
+ " testfiles/bbackupd.conf";
+ int bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");
+ TEST_THAT(bbackupd_pid > 0);
+ if (bbackupd_pid <= 0) return 1;
+
+ ::safe_sleep(1);
+ TEST_THAT(ServerIsAlive(bbackupd_pid));
+
+ // Wait 4 more seconds for the files to be old enough
+ // to upload
+ ::safe_sleep(4);
+
+ // Upload files to create a nice store directory
+ ::sync_and_wait();
+
+ // Stop bbackupd
+ #ifdef WIN32
+ terminate_bbackupd(bbackupd_pid);
+ // implicit check for memory leaks
+ #else
+ TEST_THAT(KillServer(bbackupd_pid));
+ TestRemoteProcessMemLeaks("bbackupd.memleaks");
+ #endif
+
+ BOX_INFO(" === Add a reference to a file that doesn't exist, check "
+ "that it's removed");
+ {
+ BackupStoreDirectory dir;
+ read_bb_dir(1 /* root */, dir);
+
+ dir.AddEntry(fnames[0], 12, 0x1234567890123456LL /* id */, 1,
+ BackupStoreDirectory::Entry::Flags_File, 2);
+
+ std::string fn;
+ StoreStructure::MakeObjectFilename(1 /* root */, storeRoot,
+ discSetNum, fn, true /* EnsureDirectoryExists */);
+
+ std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(discSetNum,
+ fn));
+ RaidFileWrite d(discSetNum, fn);
+ d.Open(true /* allow overwrite */);
+ dir.WriteToStream(d);
+ d.Commit(true /* write now! */);
+
+ read_bb_dir(1 /* root */, dir);
+ TEST_THAT(dir.FindEntryByID(0x1234567890123456LL) != 0);
+
+ // Check it
+ BackupStoreCheck checker(storeRoot, discSetNum,
+ 0x01234567, true /* FixErrors */, false /* Quiet */);
+ checker.Check();
+
+ // Should just be greater than 1 really, we don't know quite
+ // how good the checker is (or will become) at spotting errors!
+ // But this will help us catch changes in checker behaviour,
+ // so it's not a bad thing to test.
+ TEST_EQUAL(2, checker.GetNumErrorsFound());
+
+ file = RaidFileRead::Open(discSetNum, fn);
+ dir.ReadFromStream(*file, IOStream::TimeOutInfinite);
+ TEST_THAT(dir.FindEntryByID(0x1234567890123456LL) == 0);
+ }
+
+ if (failures > 0) return 1;
+
+ // Generate a list of all the object IDs
+ TEST_THAT_ABORTONFAIL(::system(BBACKUPQUERY " -Wwarning "
+ "-c testfiles/bbackupd.conf \"list -r\" quit "
+ "> testfiles/initial-listing.txt") == 0);
+
+ // And load it in
+ {
+ FILE *f = ::fopen("testfiles/initial-listing.txt", "r");
+ TEST_THAT_ABORTONFAIL(f != 0);
+ char line[512];
+ int32_t id;
+ char flags[32];
+ char name[256];
+ while(::fgets(line, sizeof(line), f) != 0)
+ {
+ TEST_THAT(::sscanf(line, "%x %s %s", &id,
+ flags, name) == 3);
+ bool isDir = (::strcmp(flags, "-d---") == 0);
+ //TRACE3("%x,%d,%s\n", id, isDir, name);
+ MEMLEAKFINDER_NO_LEAKS;
+ nameToID[std::string(name)] = id;
+ objectIsDir[id] = isDir;
+ }
+ ::fclose(f);
+ }
+
+ // ------------------------------------------------------------------------------------------------
+ BOX_INFO(" === Delete store info, add random file");
+ {
+ // Delete store info
+ RaidFileWrite del(discSetNum, storeRoot + "info");
+ del.Delete();
+ }
+ {
+ // Add a spurious file
+ RaidFileWrite random(discSetNum,
+ storeRoot + "randomfile");
+ random.Open();
+ random.Write("test", 4);
+ random.Commit(true);
+ }
+
+ // Fix it
+ RUN_CHECK
+
+ // Check everything is as it was
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl check 0") == 0);
+ // Check the random file doesn't exist
+ {
+ TEST_THAT(!RaidFileRead::FileExists(discSetNum,
+ storeRoot + "01/randomfile"));
+ }
+
+ // ------------------------------------------------------------------------------------------------
+ BOX_INFO(" === Delete an entry for an object from dir, change that "
+ "object to be a patch, check it's deleted");
+ {
+ // Open dir and find entry
+ int64_t delID = getID("Test1/cannes/ict/metegoguered/oats");
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/cannes/ict/metegoguered", dir);
+ TEST_THAT(dir.FindEntryByID(delID) != 0);
+ dir.DeleteEntry(delID);
+ SaveDirectory("Test1/cannes/ict/metegoguered", dir);
+ }
+
+ // Adjust that entry
+ //
+ // IMPORTANT NOTE: There's a special hack in testbackupstorefix.pl to make sure that
+ // the file we're modifiying has at least two blocks so we can modify it and produce a valid file
+ // which will pass the verify checks.
+ //
+ std::string fn(getObjectName(delID));
+ {
+ std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(discSetNum, fn));
+ RaidFileWrite f(discSetNum, fn);
+ f.Open(true /* allow overwrite */);
+ // Make a copy of the original
+ file->CopyStreamTo(f);
+ // Move to header in both
+ file->Seek(0, IOStream::SeekType_Absolute);
+ BackupStoreFile::MoveStreamPositionToBlockIndex(*file);
+ f.Seek(file->GetPosition(), IOStream::SeekType_Absolute);
+ // Read header
+ struct
+ {
+ file_BlockIndexHeader hdr;
+ file_BlockIndexEntry e[2];
+ } h;
+ TEST_THAT(file->Read(&h, sizeof(h)) == sizeof(h));
+ file->Close();
+
+ // Modify
+ TEST_THAT(box_ntoh64(h.hdr.mOtherFileID) == 0);
+ TEST_THAT(box_ntoh64(h.hdr.mNumBlocks) >= 2);
+ h.hdr.mOtherFileID = box_hton64(2345); // don't worry about endianness
+ h.e[0].mEncodedSize = box_hton64((box_ntoh64(h.e[0].mEncodedSize)) + (box_ntoh64(h.e[1].mEncodedSize)));
+ h.e[1].mOtherBlockIndex = box_hton64(static_cast<uint64_t>(-2));
+ // Write to modified file
+ f.Write(&h, sizeof(h));
+ // Commit new version
+ f.Commit(true /* write now! */);
+ }
+
+#ifndef BOX_RELEASE_BUILD
+ // Delete two of the three raidfiles and their parent
+ // directories. This used to crash bbstoreaccounts check.
+ // We can only do this, without destroying the entire store,
+ // in debug mode, where the store has a far deeper
+ // structure.
+ // This will destroy or damage objects 18-1b and 58-5b,
+ // some repairably.
+ #define RUN(x) TEST_THAT(system(x) == 0);
+ RUN("mv testfiles/0_0/backup/01234567/02/01/o00.rf "
+ "testfiles/0_0/backup/01234567/02/01/o00.rfw"); // 0x18
+ RUN("mv testfiles/0_1/backup/01234567/02/01/o01.rf "
+ "testfiles/0_1/backup/01234567/02/01/o01.rfw"); // 0x19
+ //RUN("mv testfiles/0_2/backup/01234567/02/01/o02.rf "
+ // "testfiles/0_0/backup/01234567/02/01/o02.rfw"); // 0x1a
+ RUN("mv testfiles/0_0/backup/01234567/02/01/o03.rf "
+ "testfiles/0_0/backup/01234567/02/01/o03.rfw"); // 0x1b
+ RUN("mv testfiles/0_0/backup/01234567/02/01/01/o00.rf "
+ "testfiles/0_0/backup/01234567/02/01/01/o00.rfw"); // 0x58
+ RUN("mv testfiles/0_1/backup/01234567/02/01/01/o01.rf "
+ "testfiles/0_1/backup/01234567/02/01/01/o01.rfw"); // 0x59
+ //RUN("mv testfiles/0_2/backup/01234567/02/01/01/o02.rf "
+ // "testfiles/0_0/backup/01234567/02/01/01/o02.rfw"); // 0x5a
+ RUN("mv testfiles/0_0/backup/01234567/02/01/01/o03.rf "
+ "testfiles/0_0/backup/01234567/02/01/01/o03.rfw"); // 0x5b
+ // RUN("rm -r testfiles/0_1/backup/01234567/02/01");
+ RUN("rm -r testfiles/0_2/backup/01234567/02/01");
+ #undef RUN
+#endif // BOX_RELEASE_BUILD
+
+ // Fix it
+ RUN_CHECK_INTERNAL(3);
+
+ // Check
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl check 1")
+ == 0);
+
+ // Check the modified file doesn't exist
+ TEST_THAT(!RaidFileRead::FileExists(discSetNum, fn));
+
+ // Check that the missing RaidFiles were regenerated and
+ // committed. FileExists returns NonRaid if it find a .rfw
+ // file, so checking for AsRaid excludes this possibility.
+ RaidFileController &rcontroller(RaidFileController::GetController());
+ RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(discSetNum));
+ TEST_EQUAL(RaidFileUtil::AsRaid, RaidFileUtil::RaidFileExists(
+ rdiscSet, "backup/01234567/02/01/o00"));
+ TEST_EQUAL(RaidFileUtil::AsRaid, RaidFileUtil::RaidFileExists(
+ rdiscSet, "backup/01234567/02/01/o01"));
+ TEST_EQUAL(RaidFileUtil::AsRaid, RaidFileUtil::RaidFileExists(
+ rdiscSet, "backup/01234567/02/01/o02"));
+ TEST_EQUAL(RaidFileUtil::AsRaid, RaidFileUtil::RaidFileExists(
+ rdiscSet, "backup/01234567/02/01/o03"));
+ TEST_EQUAL(RaidFileUtil::AsRaid, RaidFileUtil::RaidFileExists(
+ rdiscSet, "backup/01234567/02/01/01/o00"));
+ TEST_EQUAL(RaidFileUtil::AsRaid, RaidFileUtil::RaidFileExists(
+ rdiscSet, "backup/01234567/02/01/01/o01"));
+ TEST_EQUAL(RaidFileUtil::AsRaid, RaidFileUtil::RaidFileExists(
+ rdiscSet, "backup/01234567/02/01/01/o02"));
+ TEST_EQUAL(RaidFileUtil::AsRaid, RaidFileUtil::RaidFileExists(
+ rdiscSet, "backup/01234567/02/01/01/o03"));
+ }
+
+ // ------------------------------------------------------------------------------------------------
+ BOX_INFO(" === Delete directory, change container ID of another, "
+ "duplicate entry in dir, spurious file size, delete file");
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/foreomizes/stemptinevidate/ict", dir);
+ dir.SetContainerID(73773);
+ SaveDirectory("Test1/foreomizes/stemptinevidate/ict", dir);
+ }
+ int64_t duplicatedID = 0;
+ int64_t notSpuriousFileSize = 0;
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/cannes/ict/peep", dir);
+ // Duplicate the second entry
+ {
+ BackupStoreDirectory::Iterator i(dir);
+ i.Next();
+ BackupStoreDirectory::Entry *en = i.Next();
+ TEST_THAT(en != 0);
+ duplicatedID = en->GetObjectID();
+ dir.AddEntry(*en);
+ }
+ // Adjust file size of first file
+ {
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = i.Next(BackupStoreDirectory::Entry::Flags_File);
+ TEST_THAT(en != 0);
+ notSpuriousFileSize = en->GetSizeInBlocks();
+ en->SetSizeInBlocks(3473874);
+ TEST_THAT(en->GetSizeInBlocks() == 3473874);
+ }
+ SaveDirectory("Test1/cannes/ict/peep", dir);
+ }
+ // Delete a directory
+ DeleteObject("Test1/pass/cacted/ming");
+ // Delete a file
+ DeleteObject("Test1/cannes/ict/scely");
+
+ // We don't know quite how good the checker is (or will become) at
+ // spotting errors! But asserting an exact number will help us catch
+ // changes in checker behaviour, so it's not a bad thing to test.
+
+ // The 11 errors are:
+ // ERROR: Directory ID 0xb references object 0x3e which does not exist.
+ // ERROR: Removing directory entry 0x3e from directory 0xb
+ // ERROR: Directory ID 0xc had invalid entries, fixed
+ // ERROR: Directory ID 0xc has wrong size for object 0x40
+ // ERROR: Directory ID 0x17 has wrong container ID.
+ // ERROR: Object 0x51 is unattached.
+ // ERROR: Object 0x52 is unattached.
+ // ERROR: BlocksUsed changed from 282 to 278
+ // ERROR: BlocksInCurrentFiles changed from 226 to 220
+ // ERROR: BlocksInDirectories changed from 56 to 54
+ // ERROR: NumFiles changed from 113 to 110
+
+ RUN_CHECK_INTERNAL(11);
+
+ // Check everything is as it should be
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl check 2") == 0);
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/foreomizes/stemptinevidate/ict", dir);
+ TEST_THAT(dir.GetContainerID() == getID("Test1/foreomizes/stemptinevidate"));
+ }
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/cannes/ict/peep", dir);
+ BackupStoreDirectory::Iterator i(dir);
+ // Count the number of entries with the ID which was duplicated
+ int count = 0;
+ BackupStoreDirectory::Entry *en = 0;
+ while((en = i.Next()) != 0)
+ {
+ if(en->GetObjectID() == duplicatedID)
+ {
+ ++count;
+ }
+ }
+ TEST_THAT(count == 1);
+ // Check file size has changed
+ {
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = i.Next(BackupStoreDirectory::Entry::Flags_File);
+ TEST_THAT(en != 0);
+ TEST_THAT(en->GetSizeInBlocks() == notSpuriousFileSize);
+ }
+ }
+
+ // ------------------------------------------------------------------------------------------------
+ BOX_INFO(" === Modify the obj ID of dir, delete dir with no members, "
+ "add extra reference to a file");
+ // Set bad object ID
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/foreomizes/stemptinevidate/ict", dir);
+ dir.TESTONLY_SetObjectID(73773);
+ SaveDirectory("Test1/foreomizes/stemptinevidate/ict", dir);
+ }
+ // Delete dir with no members
+ DeleteObject("Test1/dir-no-members");
+ // Add extra reference
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/divel", dir);
+ BackupStoreDirectory::Iterator i(dir);
+ BackupStoreDirectory::Entry *en = i.Next(BackupStoreDirectory::Entry::Flags_File);
+ TEST_THAT(en != 0);
+ BackupStoreDirectory dir2;
+ LoadDirectory("Test1/divel/torsines/cruishery", dir2);
+ dir2.AddEntry(*en);
+ SaveDirectory("Test1/divel/torsines/cruishery", dir2);
+ }
+ // Fix it
+ RUN_CHECK
+ // Check everything is as it should be
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl check 3") == 0);
+ {
+ BackupStoreDirectory dir;
+ LoadDirectory("Test1/foreomizes/stemptinevidate/ict", dir);
+ TEST_THAT(dir.GetObjectID() == getID("Test1/foreomizes/stemptinevidate/ict"));
+ }
+
+ // ------------------------------------------------------------------------------------------------
+ BOX_INFO(" === Orphan files and dirs without being recoverable");
+ DeleteObject("Test1/dir1");
+ DeleteObject("Test1/dir1/dir2");
+ // Fix it
+ RUN_CHECK
+ // Check everything is where it is predicted to be
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl check 4") == 0);
+
+ // ------------------------------------------------------------------------------------------------
+ BOX_INFO(" === Corrupt file and dir");
+ // File
+ CorruptObject("Test1/foreomizes/stemptinevidate/algoughtnerge",
+ 33, "34i729834298349283479233472983sdfhasgs");
+ // Dir
+ CorruptObject("Test1/cannes/imulatrougge/foreomizes",23,
+ "dsf32489sdnadf897fd2hjkesdfmnbsdfcsfoisufio2iofe2hdfkjhsf");
+ // Fix it
+ RUN_CHECK
+ // Check everything is where it should be
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl check 5") == 0);
+
+ // ------------------------------------------------------------------------------------------------
+ BOX_INFO(" === Overwrite root with a file");
+ {
+ std::auto_ptr<RaidFileRead> r(RaidFileRead::Open(discSetNum, getObjectName(getID("Test1/pass/shuted/brightinats/milamptimaskates"))));
+ RaidFileWrite w(discSetNum, getObjectName(1 /* root */));
+ w.Open(true /* allow overwrite */);
+ r->CopyStreamTo(w);
+ w.Commit(true /* convert now */);
+ }
+ // Fix it
+ RUN_CHECK
+ // Check everything is where it should be
+ TEST_THAT(::system(PERL_EXECUTABLE
+ " testfiles/testbackupstorefix.pl reroot 6") == 0);
+
+
+ // ---------------------------------------------------------
+ // Stop server
+ TEST_THAT(KillServer(bbstored_pid));
+
+ #ifdef WIN32
+ TEST_THAT(unlink("testfiles/bbstored.pid") == 0);
+ #else
+ TestRemoteProcessMemLeaks("bbstored.memleaks");
+ #endif
+>>>>>>> 0.12
return 0;
}
diff --git a/test/backupstorefix/testfiles/testbackupstorefix.pl.in b/test/backupstorefix/testfiles/testbackupstorefix.pl.in
index d27c1106..b58414af 100755
--- a/test/backupstorefix/testfiles/testbackupstorefix.pl.in
+++ b/test/backupstorefix/testfiles/testbackupstorefix.pl.in
@@ -104,7 +104,11 @@ elsif($ARGV[0] eq 'check')
{
print LISTING_COPY;
chomp; s/\r//;
+<<<<<<< HEAD
s/\[FILENAME NOT ENCRYPTED\]//;
+=======
+ s/ \[FILENAME NOT ENCRYPTED\]//;
+>>>>>>> 0.12
if(exists $expected{$_})
{
delete $expected{$_}
diff --git a/test/backupstorepatch/testbackupstorepatch.cpp b/test/backupstorepatch/testbackupstorepatch.cpp
index 2510da30..fe0e0531 100644
--- a/test/backupstorepatch/testbackupstorepatch.cpp
+++ b/test/backupstorepatch/testbackupstorepatch.cpp
@@ -13,7 +13,11 @@
#include <string.h>
#include <signal.h>
+<<<<<<< HEAD
#include "autogen_BackupProtocolClient.h"
+=======
+#include "autogen_BackupProtocol.h"
+>>>>>>> 0.12
#include "BackupClientCryptoKeys.h"
#include "BackupClientFileAttributes.h"
#include "BackupStoreAccountDatabase.h"
@@ -354,7 +358,11 @@ int test(int argc, const char *argv[])
// Login
{
// Check the version
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+=======
+ std::auto_ptr<BackupProtocolVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+>>>>>>> 0.12
TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
// Login
@@ -367,9 +375,15 @@ int test(int argc, const char *argv[])
// Upload the first file
{
std::auto_ptr<IOStream> upload(BackupStoreFile::EncodeFile("testfiles/0.test",
+<<<<<<< HEAD
BackupProtocolClientListDirectory::RootDirectory, storeFilename));
std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
BackupProtocolClientListDirectory::RootDirectory, ModificationTime,
+=======
+ BackupProtocolListDirectory::RootDirectory, storeFilename));
+ std::auto_ptr<BackupProtocolSuccess> stored(protocol.QueryStoreFile(
+ BackupProtocolListDirectory::RootDirectory, ModificationTime,
+>>>>>>> 0.12
ModificationTime, 0 /* no diff from file ID */, storeFilename, *upload));
test_files[0].IDOnServer = stored->GetObjectID();
test_files[0].IsCompletelyDifferent = true;
@@ -380,8 +394,13 @@ int test(int argc, const char *argv[])
for(unsigned int f = 1; f < NUMBER_FILES; ++f)
{
// Get an index for the previous version
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> getBlockIndex(protocol.QueryGetBlockIndexByName(
BackupProtocolClientListDirectory::RootDirectory, storeFilename));
+=======
+ std::auto_ptr<BackupProtocolSuccess> getBlockIndex(protocol.QueryGetBlockIndexByName(
+ BackupProtocolListDirectory::RootDirectory, storeFilename));
+>>>>>>> 0.12
int64_t diffFromID = getBlockIndex->GetObjectID();
TEST_THAT(diffFromID != 0);
@@ -397,7 +416,11 @@ int test(int argc, const char *argv[])
std::auto_ptr<IOStream> patchStream(
BackupStoreFile::EncodeFileDiff(
filename,
+<<<<<<< HEAD
BackupProtocolClientListDirectory::RootDirectory, /* containing directory */
+=======
+ BackupProtocolListDirectory::RootDirectory, /* containing directory */
+>>>>>>> 0.12
storeFilename,
diffFromID,
*blockIndexStream,
@@ -407,8 +430,13 @@ int test(int argc, const char *argv[])
&isCompletelyDifferent));
// Upload the patch to the store
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> stored(protocol.QueryStoreFile(
BackupProtocolClientListDirectory::RootDirectory, ModificationTime,
+=======
+ std::auto_ptr<BackupProtocolSuccess> stored(protocol.QueryStoreFile(
+ BackupProtocolListDirectory::RootDirectory, ModificationTime,
+>>>>>>> 0.12
ModificationTime, isCompletelyDifferent?(0):(diffFromID), storeFilename, *patchStream));
ModificationTime += MODIFICATION_TIME_INC;
@@ -432,10 +460,17 @@ int test(int argc, const char *argv[])
// List the directory from the server, and check that no dependency info is sent -- waste of bytes
{
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> dirreply(protocol.QueryListDirectory(
BackupProtocolClientListDirectory::RootDirectory,
BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+=======
+ std::auto_ptr<BackupProtocolSuccess> dirreply(protocol.QueryListDirectory(
+ BackupProtocolListDirectory::RootDirectory,
+ BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */));
+>>>>>>> 0.12
// Stream
BackupStoreDirectory dir;
std::auto_ptr<IOStream> dirstream(protocol.ReceiveStream());
@@ -531,7 +566,11 @@ int test(int argc, const char *argv[])
BOX_PORT_BBSTORED_TEST);
BackupProtocolClient protocol(conn);
{
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+=======
+ std::auto_ptr<BackupProtocolVersion> serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION));
+>>>>>>> 0.12
TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION);
protocol.QueryLogin(0x01234567, 0);
}
@@ -555,8 +594,13 @@ int test(int argc, const char *argv[])
// Fetch the file
{
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> getobj(protocol.QueryGetFile(
BackupProtocolClientListDirectory::RootDirectory,
+=======
+ std::auto_ptr<BackupProtocolSuccess> getobj(protocol.QueryGetFile(
+ BackupProtocolListDirectory::RootDirectory,
+>>>>>>> 0.12
test_files[f].IDOnServer));
TEST_THAT(getobj->GetObjectID() == test_files[f].IDOnServer);
// BLOCK
@@ -572,7 +616,11 @@ int test(int argc, const char *argv[])
// Download the index, and check it looks OK
{
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientSuccess> getblockindex(protocol.QueryGetBlockIndexByID(test_files[f].IDOnServer));
+=======
+ std::auto_ptr<BackupProtocolSuccess> getblockindex(protocol.QueryGetBlockIndexByID(test_files[f].IDOnServer));
+>>>>>>> 0.12
TEST_THAT(getblockindex->GetObjectID() == test_files[f].IDOnServer);
std::auto_ptr<IOStream> blockIndexStream(protocol.ReceiveStream());
TEST_THAT(BackupStoreFile::CompareFileContentsAgainstBlockIndex(filename, *blockIndexStream, IOStream::TimeOutInfinite));
diff --git a/test/basicserver/Makefile.extra b/test/basicserver/Makefile.extra
index 50120136..cfafbdb2 100644
--- a/test/basicserver/Makefile.extra
+++ b/test/basicserver/Makefile.extra
@@ -1,6 +1,7 @@
MAKEPROTOCOL = ../../lib/server/makeprotocol.pl
+<<<<<<< HEAD
GEN_CMD_SRV = $(MAKEPROTOCOL) Server testprotocol.txt
GEN_CMD_CLI = $(MAKEPROTOCOL) Client testprotocol.txt
@@ -18,4 +19,14 @@ autogen_TestProtocolClient.cpp: $(MAKEPROTOCOL) testprotocol.txt
autogen_TestProtocolClient.h: $(MAKEPROTOCOL) testprotocol.txt
$(_PERL) $(GEN_CMD_CLI)
+=======
+GEN_CMD = $(MAKEPROTOCOL) testprotocol.txt
+
+# AUTOGEN SEEDING
+autogen_TestProtocol.cpp: $(MAKEPROTOCOL) testprotocol.txt
+ $(_PERL) $(GEN_CMD)
+
+autogen_TestProtocolServer.h: $(MAKEPROTOCOL) testprotocol.txt
+ $(_PERL) $(GEN_CMD)
+>>>>>>> 0.12
diff --git a/test/basicserver/TestCommands.cpp b/test/basicserver/TestCommands.cpp
index 657e79ea..e193458e 100644
--- a/test/basicserver/TestCommands.cpp
+++ b/test/basicserver/TestCommands.cpp
@@ -5,12 +5,17 @@
#include <syslog.h>
#endif
+<<<<<<< HEAD
#include "autogen_TestProtocolServer.h"
+=======
+#include "autogen_TestProtocol.h"
+>>>>>>> 0.12
#include "CollectInBufferStream.h"
#include "MemLeakFindOn.h"
+<<<<<<< HEAD
std::auto_ptr<ProtocolObject> TestProtocolServerHello::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext)
{
if(mNumber32 != 41 || mNumber16 != 87 || mNumber8 != 11 || mText != "pingu")
@@ -33,6 +38,30 @@ std::auto_ptr<ProtocolObject> TestProtocolServerQuit::DoCommand(TestProtocolServ
std::auto_ptr<ProtocolObject> TestProtocolServerSimple::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext)
{
return std::auto_ptr<ProtocolObject>(new TestProtocolServerSimpleReply(mValue+1));
+=======
+std::auto_ptr<TestProtocolMessage> TestProtocolHello::DoCommand(TestProtocolReplyable &rProtocol, TestContext &rContext) const
+{
+ if(mNumber32 != 41 || mNumber16 != 87 || mNumber8 != 11 || mText != "pingu")
+ {
+ return std::auto_ptr<TestProtocolMessage>(new TestProtocolError(0, 0));
+ }
+ return std::auto_ptr<TestProtocolMessage>(new TestProtocolHello(12,89,22,std::string("Hello world!")));
+}
+
+std::auto_ptr<TestProtocolMessage> TestProtocolLists::DoCommand(TestProtocolReplyable &rProtocol, TestContext &rContext) const
+{
+ return std::auto_ptr<TestProtocolMessage>(new TestProtocolListsReply(mLotsOfText.size()));
+}
+
+std::auto_ptr<TestProtocolMessage> TestProtocolQuit::DoCommand(TestProtocolReplyable &rProtocol, TestContext &rContext) const
+{
+ return std::auto_ptr<TestProtocolMessage>(new TestProtocolQuit);
+}
+
+std::auto_ptr<TestProtocolMessage> TestProtocolSimple::DoCommand(TestProtocolReplyable &rProtocol, TestContext &rContext) const
+{
+ return std::auto_ptr<TestProtocolMessage>(new TestProtocolSimpleReply(mValue+1));
+>>>>>>> 0.12
}
class UncertainBufferStream : public CollectInBufferStream
@@ -45,7 +74,11 @@ public:
}
};
+<<<<<<< HEAD
std::auto_ptr<ProtocolObject> TestProtocolServerGetStream::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext)
+=======
+std::auto_ptr<TestProtocolMessage> TestProtocolGetStream::DoCommand(TestProtocolReplyable &rProtocol, TestContext &rContext) const
+>>>>>>> 0.12
{
// make a new stream object
CollectInBufferStream *pstream = mUncertainSize?(new UncertainBufferStream):(new CollectInBufferStream);
@@ -68,6 +101,7 @@ std::auto_ptr<ProtocolObject> TestProtocolServerGetStream::DoCommand(TestProtoco
// Get it to be sent
rProtocol.SendStreamAfterCommand(pstream);
+<<<<<<< HEAD
return std::auto_ptr<ProtocolObject>(new TestProtocolServerGetStream(mStartingValue, mUncertainSize));
}
@@ -76,6 +110,16 @@ std::auto_ptr<ProtocolObject> TestProtocolServerSendStream::DoCommand(TestProtoc
if(mValue != 0x73654353298ffLL)
{
return std::auto_ptr<ProtocolObject>(new TestProtocolServerError(0, 0));
+=======
+ return std::auto_ptr<TestProtocolMessage>(new TestProtocolGetStream(mStartingValue, mUncertainSize));
+}
+
+std::auto_ptr<TestProtocolMessage> TestProtocolSendStream::DoCommand(TestProtocolReplyable &rProtocol, TestContext &rContext) const
+{
+ if(mValue != 0x73654353298ffLL)
+ {
+ return std::auto_ptr<TestProtocolMessage>(new TestProtocolError(0, 0));
+>>>>>>> 0.12
}
// Get a stream
@@ -91,11 +135,20 @@ std::auto_ptr<ProtocolObject> TestProtocolServerSendStream::DoCommand(TestProtoc
}
// tell the caller how many bytes there were
+<<<<<<< HEAD
return std::auto_ptr<ProtocolObject>(new TestProtocolServerGetStream(bytes, uncertain));
}
std::auto_ptr<ProtocolObject> TestProtocolServerString::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext)
{
return std::auto_ptr<ProtocolObject>(new TestProtocolServerString(mTest));
+=======
+ return std::auto_ptr<TestProtocolMessage>(new TestProtocolGetStream(bytes, uncertain));
+}
+
+std::auto_ptr<TestProtocolMessage> TestProtocolString::DoCommand(TestProtocolReplyable &rProtocol, TestContext &rContext) const
+{
+ return std::auto_ptr<TestProtocolMessage>(new TestProtocolString(mTest));
+>>>>>>> 0.12
}
diff --git a/test/basicserver/testbasicserver.cpp b/test/basicserver/testbasicserver.cpp
index 18bc0aa8..ebf77979 100644
--- a/test/basicserver/testbasicserver.cpp
+++ b/test/basicserver/testbasicserver.cpp
@@ -26,8 +26,12 @@
#include "CollectInBufferStream.h"
#include "TestContext.h"
+<<<<<<< HEAD
#include "autogen_TestProtocolClient.h"
#include "autogen_TestProtocolServer.h"
+=======
+#include "autogen_TestProtocol.h"
+>>>>>>> 0.12
#include "ServerControl.h"
#include "MemLeakFindOn.h"
@@ -331,8 +335,13 @@ void Srv2TestConversations(const std::vector<IOStream *> &conns)
bool hadTimeout = false;
while(!getline[c]->GetLine(rep, false, COMMS_READ_TIMEOUT))
hadTimeout = true;
+<<<<<<< HEAD
TEST_THAT(rep == recieve[q]);
TEST_THAT(hadTimeout)
+=======
+ TEST_EQUAL_LINE(rep, recieve[q], "Line " << q);
+ TEST_LINE(hadTimeout, "Line " << q)
+>>>>>>> 0.12
}
}
for(unsigned int c = 0; c < conns.size(); ++c)
@@ -393,7 +402,11 @@ void Srv2TestConversations(const std::vector<IOStream *> &conns)
void TestStreamReceive(TestProtocolClient &protocol, int value, bool uncertainstream)
{
+<<<<<<< HEAD
std::auto_ptr<TestProtocolClientGetStream> reply(protocol.QueryGetStream(value, uncertainstream));
+=======
+ std::auto_ptr<TestProtocolGetStream> reply(protocol.QueryGetStream(value, uncertainstream));
+>>>>>>> 0.12
TEST_THAT(reply->GetStartingValue() == value);
// Get a stream
@@ -704,11 +717,19 @@ int test(int argc, const char *argv[])
// Simple query
{
+<<<<<<< HEAD
std::auto_ptr<TestProtocolClientSimpleReply> reply(protocol.QuerySimple(41));
TEST_THAT(reply->GetValuePlusOne() == 42);
}
{
std::auto_ptr<TestProtocolClientSimpleReply> reply(protocol.QuerySimple(809));
+=======
+ std::auto_ptr<TestProtocolSimpleReply> reply(protocol.QuerySimple(41));
+ TEST_THAT(reply->GetValuePlusOne() == 42);
+ }
+ {
+ std::auto_ptr<TestProtocolSimpleReply> reply(protocol.QuerySimple(809));
+>>>>>>> 0.12
TEST_THAT(reply->GetValuePlusOne() == 810);
}
@@ -724,14 +745,22 @@ int test(int argc, const char *argv[])
char buf[1663];
s.Write(buf, sizeof(buf));
s.SetForReading();
+<<<<<<< HEAD
std::auto_ptr<TestProtocolClientGetStream> reply(protocol.QuerySendStream(0x73654353298ffLL, s));
+=======
+ std::auto_ptr<TestProtocolGetStream> reply(protocol.QuerySendStream(0x73654353298ffLL, s));
+>>>>>>> 0.12
TEST_THAT(reply->GetStartingValue() == sizeof(buf));
}
// Lots of simple queries
for(int q = 0; q < 514; q++)
{
+<<<<<<< HEAD
std::auto_ptr<TestProtocolClientSimpleReply> reply(protocol.QuerySimple(q));
+=======
+ std::auto_ptr<TestProtocolSimpleReply> reply(protocol.QuerySimple(q));
+>>>>>>> 0.12
TEST_THAT(reply->GetValuePlusOne() == (q+1));
}
// Send a list of strings to it
@@ -740,13 +769,21 @@ int test(int argc, const char *argv[])
strings.push_back(std::string("test1"));
strings.push_back(std::string("test2"));
strings.push_back(std::string("test3"));
+<<<<<<< HEAD
std::auto_ptr<TestProtocolClientListsReply> reply(protocol.QueryLists(strings));
+=======
+ std::auto_ptr<TestProtocolListsReply> reply(protocol.QueryLists(strings));
+>>>>>>> 0.12
TEST_THAT(reply->GetNumberOfStrings() == 3);
}
// And another
{
+<<<<<<< HEAD
std::auto_ptr<TestProtocolClientHello> reply(protocol.QueryHello(41,87,11,std::string("pingu")));
+=======
+ std::auto_ptr<TestProtocolHello> reply(protocol.QueryHello(41,87,11,std::string("pingu")));
+>>>>>>> 0.12
TEST_THAT(reply->GetNumber32() == 12);
TEST_THAT(reply->GetNumber16() == 89);
TEST_THAT(reply->GetNumber8() == 22);
diff --git a/test/bbackupd/Makefile.extra b/test/bbackupd/Makefile.extra
index 0ae56bd1..1bd897bd 100644
--- a/test/bbackupd/Makefile.extra
+++ b/test/bbackupd/Makefile.extra
@@ -6,9 +6,20 @@ link-extra: ../../bin/bbackupd/autogen_ClientException.o \
../../bin/bbackupd/BackupClientInodeToIDMap.o \
../../bin/bbackupd/Win32ServiceFunctions.o \
../../bin/bbackupd/BackupDaemon.o \
+<<<<<<< HEAD
../../bin/bbstored/BackupStoreContext.o \
../../bin/bbstored/BBStoreDHousekeeping.o \
../../bin/bbstored/HousekeepStoreAccount.o \
../../bin/bbstored/autogen_BackupProtocolServer.o \
../../bin/bbstored/BackupCommands.o \
../../bin/bbstored/BackupStoreDaemon.o
+=======
+ ../../bin/bbstored/BBStoreDHousekeeping.o \
+ ../../lib/backupstore/HousekeepStoreAccount.o \
+ ../../lib/backupstore/autogen_BackupProtocol.o \
+ ../../lib/backupstore/BackupStoreContext.o \
+ ../../lib/backupstore/BackupCommands.o \
+ ../../bin/bbstored/BackupStoreDaemon.o \
+ ../../bin/bbackupquery/BackupQueries.o \
+ ../../bin/bbackupquery/autogen_Documentation.o
+>>>>>>> 0.12
diff --git a/test/bbackupd/testbbackupd.cpp b/test/bbackupd/testbbackupd.cpp
index 77c463ba..71778ca6 100644
--- a/test/bbackupd/testbbackupd.cpp
+++ b/test/bbackupd/testbbackupd.cpp
@@ -42,18 +42,30 @@
#include <sys/syscall.h>
#endif
+<<<<<<< HEAD
#include "autogen_BackupProtocolServer.h"
+=======
+#include "autogen_BackupProtocol.h"
+>>>>>>> 0.12
#include "BackupClientCryptoKeys.h"
#include "BackupClientFileAttributes.h"
#include "BackupClientRestore.h"
#include "BackupDaemon.h"
#include "BackupDaemonConfigVerify.h"
#include "BackupQueries.h"
+<<<<<<< HEAD
+=======
+#include "BackupStoreAccounts.h"
+>>>>>>> 0.12
#include "BackupStoreConstants.h"
#include "BackupStoreContext.h"
#include "BackupStoreDaemon.h"
#include "BackupStoreDirectory.h"
#include "BackupStoreException.h"
+<<<<<<< HEAD
+=======
+#include "BackupStoreConfigVerify.h"
+>>>>>>> 0.12
#include "BoxPortsAndFiles.h"
#include "BoxTime.h"
#include "BoxTimeToUnix.h"
@@ -62,8 +74,15 @@
#include "Configuration.h"
#include "FileModificationTime.h"
#include "FileStream.h"
+<<<<<<< HEAD
#include "IOStreamGetLine.h"
#include "LocalProcessStream.h"
+=======
+#include "intercept.h"
+#include "IOStreamGetLine.h"
+#include "LocalProcessStream.h"
+#include "RaidFileController.h"
+>>>>>>> 0.12
#include "SSLLib.h"
#include "ServerControl.h"
#include "Socket.h"
@@ -73,10 +92,13 @@
#include "Timer.h"
#include "Utils.h"
+<<<<<<< HEAD
#include "autogen_BackupProtocolClient.h"
#include "intercept.h"
#include "ServerControl.h"
+=======
+>>>>>>> 0.12
#include "MemLeakFindOn.h"
// ENOATTR may be defined in a separate header file which we may not have
@@ -475,8 +497,13 @@ int64_t GetDirID(BackupProtocolClient &protocol, const char *name, int64_t InDir
{
protocol.QueryListDirectory(
InDirectory,
+<<<<<<< HEAD
BackupProtocolClientListDirectory::Flags_Dir,
BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING,
+=======
+ BackupProtocolListDirectory::Flags_Dir,
+ BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING,
+>>>>>>> 0.12
true /* want attributes */);
// Retrieve the directory from the stream following
@@ -518,6 +545,7 @@ void do_interrupted_restore(const TLSContext &context, int64_t restoredirid)
22011);
BackupProtocolClient protocol(conn);
protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION);
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly));
// Test the restoration
@@ -525,6 +553,21 @@ void do_interrupted_restore(const TLSContext &context, int64_t restoredirid)
"Test1", "testfiles/restore-interrupt",
true /* print progress dots */)
== Restore_Complete);
+=======
+ std::auto_ptr<BackupProtocolLoginConfirmed>
+ loginConf(protocol.QueryLogin(0x01234567,
+ BackupProtocolLogin::Flags_ReadOnly));
+
+ // Test the restoration
+ TEST_THAT(BackupClientRestore(protocol, restoredirid,
+ "testfiles/restore-interrupt", /* remote */
+ "testfiles/restore-interrupt", /* local */
+ true /* print progress dots */,
+ false /* restore deleted */,
+ false /* undelete after */,
+ false /* resume */,
+ false /* keep going */) == Restore_Complete);
+>>>>>>> 0.12
// Log out
protocol.QueryFinished();
@@ -602,7 +645,11 @@ int64_t SearchDir(BackupStoreDirectory& rDir,
if (en == 0) return 0;
int64_t id = en->GetObjectID();
TEST_THAT(id > 0);
+<<<<<<< HEAD
TEST_THAT(id != BackupProtocolClientListDirectory::RootDirectory);
+=======
+ TEST_THAT(id != BackupProtocolListDirectory::RootDirectory);
+>>>>>>> 0.12
return id;
}
@@ -615,7 +662,11 @@ std::auto_ptr<BackupProtocolClient> Connect(TLSContext& rContext)
std::auto_ptr<BackupProtocolClient> connection;
connection.reset(new BackupProtocolClient(sSocket));
connection->Handshake();
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientVersion>
+=======
+ std::auto_ptr<BackupProtocolVersion>
+>>>>>>> 0.12
serverVersion(connection->QueryVersion(
BACKUP_STORE_SERVER_VERSION));
if(serverVersion->GetVersion() !=
@@ -638,10 +689,17 @@ std::auto_ptr<BackupProtocolClient> ConnectAndLogin(TLSContext& rContext,
std::auto_ptr<BackupStoreDirectory> ReadDirectory
(
BackupProtocolClient& rClient,
+<<<<<<< HEAD
int64_t id
)
{
std::auto_ptr<BackupProtocolClientSuccess> dirreply(
+=======
+ int64_t id = BackupProtocolListDirectory::RootDirectory
+)
+{
+ std::auto_ptr<BackupProtocolSuccess> dirreply(
+>>>>>>> 0.12
rClient.QueryListDirectory(id, false, 0, false));
std::auto_ptr<IOStream> dirstream(rClient.ReceiveStream());
std::auto_ptr<BackupStoreDirectory> apDir(new BackupStoreDirectory());
@@ -649,6 +707,11 @@ std::auto_ptr<BackupStoreDirectory> ReadDirectory
return apDir;
}
+<<<<<<< HEAD
+=======
+Daemon* spDaemon = NULL;
+
+>>>>>>> 0.12
int start_internal_daemon()
{
// ensure that no child processes end up running tests!
@@ -662,6 +725,10 @@ int start_internal_daemon()
};
BackupDaemon daemon;
+<<<<<<< HEAD
+=======
+ spDaemon = &daemon; // to propagate into child
+>>>>>>> 0.12
int result;
if (bbackupd_args.size() > 0)
@@ -672,6 +739,11 @@ int start_internal_daemon()
{
result = daemon.Main("testfiles/bbackupd.conf", 1, argv);
}
+<<<<<<< HEAD
+=======
+
+ spDaemon = NULL; // to ensure not used by parent
+>>>>>>> 0.12
TEST_EQUAL_LINE(0, result, "Daemon exit code");
@@ -709,6 +781,10 @@ int start_internal_daemon()
fflush(stdout);
TEST_THAT(pid > 0);
+<<<<<<< HEAD
+=======
+ spDaemon = &daemon;
+>>>>>>> 0.12
return pid;
}
@@ -749,6 +825,7 @@ extern "C" struct dirent *readdir_test_hook_1(DIR *dir)
extern "C" struct dirent *readdir_test_hook_2(DIR *dir)
{
+<<<<<<< HEAD
if (time(NULL) >= readdir_stop_time)
{
#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE
@@ -757,6 +834,32 @@ extern "C" struct dirent *readdir_test_hook_2(DIR *dir)
// we will not be called again.
#endif
}
+=======
+ if (spDaemon->IsTerminateWanted())
+ {
+ // force daemon to crash, right now
+ return NULL;
+ }
+
+ time_t time_now = time(NULL);
+
+ if (time_now >= readdir_stop_time)
+ {
+#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE
+ BOX_NOTICE("Cancelling readdir hook at " << time_now);
+ intercept_setup_readdir_hook(NULL, NULL);
+ intercept_setup_lstat_hook (NULL, NULL);
+ // we will not be called again.
+#else
+ BOX_NOTICE("Failed to cancel readdir hook at " << time_now);
+#endif
+ }
+ else
+ {
+ BOX_TRACE("readdir hook still active at " << time_now << ", "
+ "waiting for " << readdir_stop_time);
+ }
+>>>>>>> 0.12
// fill in the struct dirent appropriately
memset(&readdir_test_dirent, 0, sizeof(readdir_test_dirent));
@@ -768,6 +871,10 @@ extern "C" struct dirent *readdir_test_hook_2(DIR *dir)
snprintf(readdir_test_dirent.d_name,
sizeof(readdir_test_dirent.d_name),
"test.%d", readdir_test_counter);
+<<<<<<< HEAD
+=======
+ BOX_TRACE("readdir hook returning " << readdir_test_dirent.d_name);
+>>>>>>> 0.12
// ensure that when bbackupd stats the file, it gets the
// right answer
@@ -779,6 +886,12 @@ extern "C" struct dirent *readdir_test_hook_2(DIR *dir)
intercept_setup_lstat_hook(stat_hook_filename, lstat_test_hook);
#endif
+<<<<<<< HEAD
+=======
+ // sleep a bit to reduce the number of dirents returned
+ ::safe_sleep(1);
+
+>>>>>>> 0.12
return &readdir_test_dirent;
}
@@ -822,6 +935,36 @@ bool test_entry_deleted(BackupStoreDirectory& rDir,
return flags && BackupStoreDirectory::Entry::Flags_Deleted;
}
+<<<<<<< HEAD
+=======
+bool compare_all(BackupQueries::ReturnCode::Type expected_status,
+ std::string config_file = "testfiles/bbackupd.conf")
+{
+ std::string cmd = BBACKUPQUERY;
+ cmd += " ";
+ cmd += (expected_status == BackupQueries::ReturnCode::Compare_Same)
+ ? "-Werror" : "-Wwarning";
+ cmd += " -c ";
+ cmd += config_file;
+ cmd += " \"compare -acQ\" quit";
+
+ int returnValue = ::system(cmd.c_str());
+
+ int expected_system_result = (int) expected_status;
+
+ #ifndef WIN32
+ expected_system_result <<= 8;
+ #endif
+
+ TEST_EQUAL_LINE(expected_system_result, returnValue, "compare return value");
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+ return (returnValue == expected_system_result);
+}
+
+#define TEST_COMPARE(...) \
+ TEST_THAT(compare_all(BackupQueries::ReturnCode::__VA_ARGS__));
+
+>>>>>>> 0.12
int test_bbackupd()
{
// First, wait for a normal period to make sure the last changes
@@ -964,6 +1107,7 @@ int test_bbackupd()
comp2.size() + 1, comp2.size());
TEST_LINE(comp2 != sub, line);
}
+<<<<<<< HEAD
if (failures > 0)
{
@@ -971,6 +1115,13 @@ int test_bbackupd()
Timers::Init();
return 1;
}
+=======
+
+ Timers::Init();
+
+ // stop early to make debugging easier
+ if (failures) return 1;
+>>>>>>> 0.12
// four-second delay on first read() of f1
// should mean that no keepalives were sent,
@@ -978,6 +1129,7 @@ int test_bbackupd()
// before any matching blocks could be found.
intercept_setup_delay("testfiles/TestDir1/spacetest/f1",
0, 4000, SYS_read, 1);
+<<<<<<< HEAD
pid = start_internal_daemon();
intercept_clear_setup();
@@ -995,6 +1147,29 @@ int test_bbackupd()
// it's in a different process.
// TEST_THAT(intercept_triggered());
TEST_THAT(stop_internal_daemon(pid));
+=======
+
+ {
+ BackupDaemon bbackupd;
+ bbackupd.Configure("testfiles/bbackupd.conf");
+ bbackupd.InitCrypto();
+
+ fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY);
+ TEST_THAT(fd > 0);
+ // write again, to update the file's timestamp
+ TEST_EQUAL_LINE(1, write(fd, "z", 1), "Buffer write");
+ TEST_THAT(close(fd) == 0);
+
+ // wait long enough to put file into sync window
+ wait_for_operation(5, "locally modified file to "
+ "mature for sync");
+
+ bbackupd.RunSyncNow();
+ TEST_THAT(intercept_triggered());
+ intercept_clear_setup();
+ Timers::Cleanup();
+ }
+>>>>>>> 0.12
// check that the diff was aborted, i.e. upload was not a diff
found1 = false;
@@ -1118,6 +1293,12 @@ int test_bbackupd()
TEST_LINE(comp2 != sub, line);
}
+<<<<<<< HEAD
+=======
+ // Check that no read error has been reported yet
+ TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));
+
+>>>>>>> 0.12
if (failures > 0)
{
// stop early to make debugging easier
@@ -1137,7 +1318,11 @@ int test_bbackupd()
std::string touchfile =
"testfiles/TestDir1/spacetest/d1/touch-me";
+<<<<<<< HEAD
fd = open(touchfile.c_str(), O_CREAT | O_WRONLY);
+=======
+ fd = open(touchfile.c_str(), O_CREAT | O_WRONLY, 0700);
+>>>>>>> 0.12
TEST_THAT(fd > 0);
// write again, to update the file's timestamp
TEST_EQUAL_LINE(sizeof(buffer),
@@ -1229,6 +1414,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
if(bbackupd_pid > 0)
{
@@ -1297,6 +1486,7 @@ int test_bbackupd()
BOX_TRACE("Sync finished.");
BOX_TRACE("Compare to check that there are differences");
+<<<<<<< HEAD
int compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query0a.log "
@@ -1304,6 +1494,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Different);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Different);
+>>>>>>> 0.12
BOX_TRACE("Compare finished.");
// Check that the notify script was run
@@ -1325,7 +1518,11 @@ int test_bbackupd()
std::auto_ptr<BackupProtocolClient> client =
ConnectAndLogin(context, 0 /* read-write */);
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientAccountUsage> usage(
+=======
+ std::auto_ptr<BackupProtocolAccountUsage> usage(
+>>>>>>> 0.12
client->QueryGetAccountUsage());
TEST_EQUAL_LINE(24, usage->GetBlocksUsed(),
"blocks used");
@@ -1338,11 +1535,15 @@ int test_bbackupd()
sSocket.Close();
}
+<<<<<<< HEAD
if (failures > 0)
{
// stop early to make debugging easier
return 1;
}
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// ensure time is different to refresh the cache
::safe_sleep(1);
@@ -1365,6 +1566,17 @@ int test_bbackupd()
*/
BackupDaemon bbackupd;
+<<<<<<< HEAD
+=======
+
+ {
+ const char* argv[] = { "bbackupd",
+ bbackupd_args.c_str() };
+ TEST_EQUAL_LINE(0, bbackupd.ProcessOptions(2, argv),
+ "processing command-line options");
+ }
+
+>>>>>>> 0.12
bbackupd.Configure("testfiles/bbackupd-exclude.conf");
bbackupd.InitCrypto();
BOX_TRACE("done.");
@@ -1408,8 +1620,12 @@ int test_bbackupd()
ConnectAndLogin(context, 0 /* read-write */);
std::auto_ptr<BackupStoreDirectory> rootDir =
+<<<<<<< HEAD
ReadDirectory(*client,
BackupProtocolClientListDirectory::RootDirectory);
+=======
+ ReadDirectory(*client);
+>>>>>>> 0.12
int64_t testDirId = SearchDir(*rootDir, "Test1");
TEST_THAT(testDirId != 0);
@@ -1451,7 +1667,11 @@ int test_bbackupd()
ReadDirectory(*client, d4_id);
TEST_THAT(test_entry_deleted(*d4_dir, "f5"));
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientAccountUsage> usage(
+=======
+ std::auto_ptr<BackupProtocolAccountUsage> usage(
+>>>>>>> 0.12
client->QueryGetAccountUsage());
TEST_EQUAL_LINE(24, usage->GetBlocksUsed(),
"blocks used");
@@ -1470,11 +1690,15 @@ int test_bbackupd()
}
BOX_TRACE("done.");
+<<<<<<< HEAD
if (failures > 0)
{
// stop early to make debugging easier
return 1;
}
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
wait_for_operation(5, "housekeeping to remove the "
"deleted files");
@@ -1485,8 +1709,12 @@ int test_bbackupd()
ConnectAndLogin(context, 0 /* read-write */);
std::auto_ptr<BackupStoreDirectory> rootDir =
+<<<<<<< HEAD
ReadDirectory(*client,
BackupProtocolClientListDirectory::RootDirectory);
+=======
+ ReadDirectory(*client);
+>>>>>>> 0.12
int64_t testDirId = SearchDir(*rootDir, "Test1");
TEST_THAT(testDirId != 0);
@@ -1506,7 +1734,11 @@ int test_bbackupd()
TEST_THAT(SearchDir(*spacetest_dir, "d3") == 0);
TEST_THAT(SearchDir(*spacetest_dir, "d7") == 0);
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientAccountUsage> usage(
+=======
+ std::auto_ptr<BackupProtocolAccountUsage> usage(
+>>>>>>> 0.12
client->QueryGetAccountUsage());
TEST_EQUAL_LINE(16, usage->GetBlocksUsed(),
"blocks used");
@@ -1523,11 +1755,15 @@ int test_bbackupd()
sSocket.Close();
}
+<<<<<<< HEAD
if (failures > 0)
{
// stop early to make debugging easier
return 1;
}
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// Need 22 blocks free to upload everything
TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c "
@@ -1537,14 +1773,19 @@ int test_bbackupd()
// Run another backup, now there should be enough space
// for everything we want to upload.
+<<<<<<< HEAD
{
Logging::Guard guard(Log::ERROR);
bbackupd.RunSyncNow();
}
+=======
+ bbackupd.RunSyncNow();
+>>>>>>> 0.12
TEST_THAT(!bbackupd.StorageLimitExceeded());
// Check that the contents of the store are the same
// as the contents of the disc
+<<<<<<< HEAD
// (-a = all, -c = give result in return code)
BOX_TRACE("Check that all files were uploaded successfully");
compareReturnValue = ::system(BBACKUPQUERY " "
@@ -1554,6 +1795,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same, "testfiles/bbackupd-exclude.conf");
+>>>>>>> 0.12
BOX_TRACE("done.");
// BLOCK
@@ -1561,7 +1805,11 @@ int test_bbackupd()
std::auto_ptr<BackupProtocolClient> client =
ConnectAndLogin(context, 0 /* read-write */);
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientAccountUsage> usage(
+=======
+ std::auto_ptr<BackupProtocolAccountUsage> usage(
+>>>>>>> 0.12
client->QueryGetAccountUsage());
TEST_EQUAL_LINE(22, usage->GetBlocksUsed(),
"blocks used");
@@ -1576,11 +1824,15 @@ int test_bbackupd()
sSocket.Close();
}
+<<<<<<< HEAD
if (failures > 0)
{
// stop early to make debugging easier
return 1;
}
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// Put the limit back
TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c "
@@ -1600,6 +1852,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
BOX_TRACE("done.");
// unpack the initial files again
@@ -1617,6 +1873,7 @@ int test_bbackupd()
// as the contents of the disc
// (-a = all, -c = give result in return code)
BOX_TRACE("Check that all files were uploaded successfully");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query1.log "
@@ -1624,18 +1881,25 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
BOX_TRACE("done.");
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
if (failures > 0)
{
// stop early to make debugging easier
return 1;
}
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
}
// Check that no read error has been reported yet
@@ -1672,17 +1936,29 @@ int test_bbackupd()
class MyHook : public BackupStoreContext::TestHook
{
+<<<<<<< HEAD
virtual std::auto_ptr<ProtocolObject> StartCommand(
BackupProtocolObject& rCommand)
{
if (rCommand.GetType() ==
BackupProtocolServerStoreFile::TypeID)
+=======
+ virtual std::auto_ptr<BackupProtocolMessage> StartCommand(
+ const BackupProtocolMessage& rCommand)
+ {
+ if (rCommand.GetType() ==
+ BackupProtocolStoreFile::TypeID)
+>>>>>>> 0.12
{
// terminate badly
THROW_EXCEPTION(CommonException,
Internal);
}
+<<<<<<< HEAD
return std::auto_ptr<ProtocolObject>();
+=======
+ return std::auto_ptr<BackupProtocolMessage>();
+>>>>>>> 0.12
}
};
MyHook hook;
@@ -1769,6 +2045,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
}
#endif // !WIN32
@@ -1810,6 +2090,7 @@ int test_bbackupd()
sync_and_wait();
// Check that the backup was successful, i.e. no differences
+<<<<<<< HEAD
int compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query1.log "
@@ -1817,6 +2098,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
// now stop bbackupd and update the test file,
// make the original directory unreadable
@@ -1832,12 +2116,23 @@ int test_bbackupd()
TEST_THAT(chmod(SYM_DIR, 0) == 0);
// check that we can restore it
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-Wwarning \"restore Test1 testfiles/restore-symlink\" "
"quit");
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Command_OK);
+=======
+ {
+ int returnValue = ::system(BBACKUPQUERY " "
+ "-c testfiles/bbackupd.conf "
+ "-Wwarning \"restore Test1 testfiles/restore-symlink\" "
+ "quit");
+ TEST_RETURN(returnValue,
+ BackupQueries::ReturnCode::Command_OK);
+ }
+>>>>>>> 0.12
// make it accessible again
TEST_THAT(chmod(SYM_DIR, 0755) == 0);
@@ -1902,6 +2197,7 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
}
#endif // !WIN32
@@ -1921,25 +2217,48 @@ int test_bbackupd()
TEST_THAT(::system("gzip -d < testfiles/spacetest1.tgz "
"| ( cd testfiles/TestDir2 && tar xf - )") == 0);
#endif
+=======
+ if (failures) return 1;
+ }
+ #endif // !WIN32
+
+ printf("\n==== Testing that nonexistent locations are backed up "
+ "if they are created later\n");
+
+ // ensure that the directory does not exist at the start
+ TEST_THAT(::system("rm -rf testfiles/TestDir2") == 0);
+>>>>>>> 0.12
// BLOCK
{
// Kill the daemon
terminate_bbackupd(bbackupd_pid);
+<<<<<<< HEAD
// Start it with a config that has a temporary location
// that will be created on the server
+=======
+ // Delete any old result marker files
+ TEST_RETURN(0, system("rm -f testfiles/notifyran.*"));
+
+ // Start it with a config that has a temporary location
+ // whose path does not exist yet
+>>>>>>> 0.12
std::string cmd = BBACKUPD " " + bbackupd_args +
" testfiles/bbackupd-temploc.conf";
bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");
TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);
+<<<<<<< HEAD
::safe_sleep(1);
+=======
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
sync_and_wait();
@@ -1958,12 +2277,96 @@ int test_bbackupd()
sSocket.Close();
}
+=======
+ if (failures) return 1;
+
+ TEST_THAT(!TestFileExists("testfiles/notifyran.backup-start.1"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.backup-start.2"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.backup-ok.1"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.backup-finish.1"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.backup-finish.2"));
+
+ sync_and_wait();
+ TEST_COMPARE(Compare_Same);
+
+ TEST_THAT( TestFileExists("testfiles/notifyran.backup-start.1"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.backup-start.2"));
+ TEST_THAT( TestFileExists("testfiles/notifyran.read-error.1"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.backup-ok.1"));
+ TEST_THAT( TestFileExists("testfiles/notifyran.backup-finish.1"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.backup-finish.2"));
+
+ // Did it actually get created? Should not have been!
+ std::auto_ptr<BackupProtocolClient> client =
+ ConnectAndLogin(context,
+ BackupProtocolLogin::Flags_ReadOnly);
+
+ std::auto_ptr<BackupStoreDirectory> dir =
+ ReadDirectory(*client);
+ int64_t testDirId = SearchDir(*dir, "Test2");
+ TEST_THAT(testDirId == 0);
+ client->QueryFinished();
+ sSocket.Close();
+ }
+
+ // create the location directory and unpack some files into it
+ TEST_THAT(::mkdir("testfiles/TestDir2", 0777) == 0);
+
+ #ifdef WIN32
+ TEST_THAT(::system("tar xzvf testfiles/spacetest1.tgz "
+ "-C testfiles/TestDir2") == 0);
+ #else
+ TEST_THAT(::system("gzip -d < testfiles/spacetest1.tgz "
+ "| ( cd testfiles/TestDir2 && tar xf - )") == 0);
+ #endif
+
+ // check that the files are backed up now
+ sync_and_wait();
+ TEST_COMPARE(Compare_Same);
+
+ TEST_THAT( TestFileExists("testfiles/notifyran.backup-start.2"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.backup-start.3"));
+ TEST_THAT( TestFileExists("testfiles/notifyran.read-error.1"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2"));
+ TEST_THAT( TestFileExists("testfiles/notifyran.backup-ok.1"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.backup-ok.2"));
+ TEST_THAT( TestFileExists("testfiles/notifyran.backup-finish.2"));
+ TEST_THAT(!TestFileExists("testfiles/notifyran.backup-finish.3"));
+
+ // BLOCK
+ {
+ std::auto_ptr<BackupProtocolClient> client =
+ ConnectAndLogin(context,
+ BackupProtocolLogin::Flags_ReadOnly);
+
+ std::auto_ptr<BackupStoreDirectory> dir =
+ ReadDirectory(*client,
+ BackupProtocolListDirectory::RootDirectory);
+ int64_t testDirId = SearchDir(*dir, "Test2");
+ TEST_THAT(testDirId != 0);
+
+ client->QueryFinished();
+ sSocket.Close();
+ }
+
+ printf("\n==== Testing that redundant locations are deleted on time\n");
+
+ // BLOCK
+ {
+>>>>>>> 0.12
// Kill the daemon
terminate_bbackupd(bbackupd_pid);
// Start it again with the normal config (no Test2)
+<<<<<<< HEAD
cmd = BBACKUPD " " + bbackupd_args +
" testfiles/bbackupd.conf";
+=======
+ cmd = BBACKUPD " " + bbackupd_args + " testfiles/bbackupd.conf";
+>>>>>>> 0.12
bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid");
TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0);
@@ -1974,6 +2377,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// Test2 should be deleted after 10 seconds (4 runs)
wait_for_sync_end();
@@ -1985,11 +2392,18 @@ int test_bbackupd()
{
std::auto_ptr<BackupProtocolClient> client =
ConnectAndLogin(context,
+<<<<<<< HEAD
BackupProtocolClientLogin::Flags_ReadOnly);
std::auto_ptr<BackupStoreDirectory> dir =
ReadDirectory(*client,
BackupProtocolClientListDirectory::RootDirectory);
+=======
+ BackupProtocolLogin::Flags_ReadOnly);
+
+ std::auto_ptr<BackupStoreDirectory> dir =
+ ReadDirectory(*client);
+>>>>>>> 0.12
int64_t testDirId = SearchDir(*dir, "Test2");
TEST_THAT(testDirId != 0);
@@ -2004,11 +2418,18 @@ int test_bbackupd()
{
std::auto_ptr<BackupProtocolClient> client =
ConnectAndLogin(context,
+<<<<<<< HEAD
BackupProtocolClientLogin::Flags_ReadOnly);
std::auto_ptr<BackupStoreDirectory> root_dir =
ReadDirectory(*client,
BackupProtocolClientListDirectory::RootDirectory);
+=======
+ BackupProtocolLogin::Flags_ReadOnly);
+
+ std::auto_ptr<BackupStoreDirectory> root_dir =
+ ReadDirectory(*client);
+>>>>>>> 0.12
TEST_THAT(test_entry_deleted(*root_dir, "Test2"));
@@ -2021,11 +2442,20 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
if(bbackupd_pid > 0)
{
// Check that no read error has been reported yet
TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));
+=======
+ if (failures) return 1;
+
+ if(bbackupd_pid > 0)
+ {
+ // Delete any old result marker files
+ TEST_RETURN(0, system("rm -f testfiles/notifyran.*"));
+>>>>>>> 0.12
printf("\n==== Check that read-only directories and "
"their contents can be restored.\n");
@@ -2073,6 +2503,35 @@ int test_bbackupd()
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+<<<<<<< HEAD
+=======
+ // Try a restore with just the remote directory name,
+ // check that it uses the same name in the local
+ // directory.
+ TEST_THAT(::mkdir("testfiles/restore-test", 0700) == 0);
+
+ compareReturnValue = ::system(BBACKUPQUERY " "
+ "-Wwarning "
+ "-c testfiles/bbackupd.conf "
+ "\"lcd testfiles/restore-test\" "
+ "\"restore Test1\" "
+ "quit");
+ TEST_RETURN(compareReturnValue,
+ BackupQueries::ReturnCode::Command_OK);
+ TestRemoteProcessMemLeaks("testfiles/restore-test/"
+ "bbackupquery.memleaks");
+
+ // check that it restored properly
+ compareReturnValue = ::system(BBACKUPQUERY " "
+ "-Wwarning "
+ "-c testfiles/bbackupd.conf "
+ "\"compare -cEQ Test1 testfiles/restore-test/Test1\" "
+ "quit");
+ TEST_RETURN(compareReturnValue,
+ BackupQueries::ReturnCode::Compare_Same);
+ TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+
+>>>>>>> 0.12
// put the permissions back to sensible values
#ifdef WIN32
TEST_THAT(::system("chmod 0755 testfiles/"
@@ -2085,7 +2544,10 @@ int test_bbackupd()
TEST_THAT(chmod("testfiles/restore1/x1",
0755) == 0);
#endif
+<<<<<<< HEAD
+=======
+>>>>>>> 0.12
}
#ifdef WIN32
@@ -2213,11 +2675,15 @@ int test_bbackupd()
wait_for_backup_operation("upload of file with unicode name");
// Compare to check that the file was uploaded
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " -Wwarning "
"-c testfiles/bbackupd.conf \"compare -acQ\" quit");
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
// Check that we can find it in directory listing
{
@@ -2225,8 +2691,12 @@ int test_bbackupd()
ConnectAndLogin(context, 0);
std::auto_ptr<BackupStoreDirectory> dir = ReadDirectory(
+<<<<<<< HEAD
*client,
BackupProtocolClientListDirectory::RootDirectory);
+=======
+ *client);
+>>>>>>> 0.12
int64_t baseDirId = SearchDir(*dir, "Test1");
TEST_THAT(baseDirId != 0);
@@ -2400,6 +2870,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
printf("\n==== Check that SyncAllowScript is executed and can "
"pause backup\n");
@@ -2470,6 +2944,7 @@ int test_bbackupd()
long start_time = time(NULL);
// check that no backup has run (compare fails)
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-Werror "
"-c testfiles/bbackupd.conf "
@@ -2478,6 +2953,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Different);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Different);
+>>>>>>> 0.12
TEST_THAT(unlink(sync_control_file) == 0);
wait_for_sync_start();
@@ -2496,6 +2974,7 @@ int test_bbackupd()
TEST_THAT(wait_time <= 12);
wait_for_sync_end();
+<<<<<<< HEAD
// check that backup has run (compare succeeds)
compareReturnValue = ::system(BBACKUPQUERY " "
"-Wwarning "
@@ -2511,6 +2990,13 @@ int test_bbackupd()
// stop early to make debugging easier
return 1;
}
+=======
+
+ // check that backup has run (compare succeeds)
+ TEST_COMPARE(Compare_Same);
+
+ if (failures) return 1;
+>>>>>>> 0.12
}
// Check that no read error has been reported yet
@@ -2520,6 +3006,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
printf("\n==== Delete file and update another, "
"create symlink.\n");
@@ -2556,6 +3046,7 @@ int test_bbackupd()
sync_and_wait();
// compare to make sure that it worked
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " -Wwarning "
"-c testfiles/bbackupd.conf "
"-l testfiles/query2.log "
@@ -2563,6 +3054,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
// Try a quick compare, just for fun
compareReturnValue = ::system(BBACKUPQUERY " "
@@ -2577,12 +3071,17 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// Check that store errors are reported neatly
printf("\n==== Create store error\n");
TEST_THAT(system("rm -f testfiles/notifyran.backup-error.*")
== 0);
+<<<<<<< HEAD
// break the store
TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf",
"testfiles/0_0/backup/01234567/info.rf.bak") == 0);
@@ -2590,6 +3089,40 @@ int test_bbackupd()
"testfiles/0_1/backup/01234567/info.rf.bak") == 0);
TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf",
"testfiles/0_2/backup/01234567/info.rf.bak") == 0);
+=======
+ // Break the store. We need a write lock on the account
+ // while we do this, otherwise housekeeping might be running
+ // and might rewrite the info files when it finishes,
+ // undoing our breakage.
+ std::string errs;
+ std::auto_ptr<Configuration> config(
+ Configuration::LoadAndVerify
+ ("testfiles/bbstored.conf", &BackupConfigFileVerify, errs));
+ TEST_EQUAL_LINE(0, errs.size(), "Loading configuration file "
+ "reported errors: " << errs);
+ TEST_THAT(config.get() != 0);
+ // Initialise the raid file controller
+ RaidFileController &rcontroller(RaidFileController::GetController());
+ rcontroller.Initialise(config->GetKeyValue("RaidFileConf").c_str());
+ std::auto_ptr<BackupStoreAccountDatabase> db(
+ BackupStoreAccountDatabase::Read(
+ config->GetKeyValue("AccountDatabase")));
+
+ BackupStoreAccounts acc(*db);
+
+ // Lock scope
+ {
+ NamedLock writeLock;
+ acc.LockAccount(0x01234567, writeLock);
+
+ TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf",
+ "testfiles/0_0/backup/01234567/info.rf.bak") == 0);
+ TEST_THAT(::rename("testfiles/0_1/backup/01234567/info.rf",
+ "testfiles/0_1/backup/01234567/info.rf.bak") == 0);
+ TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf",
+ "testfiles/0_2/backup/01234567/info.rf.bak") == 0);
+ }
+>>>>>>> 0.12
// Create a file to trigger an upload
{
@@ -2618,6 +3151,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
sync_and_wait();
@@ -2637,6 +3174,7 @@ int test_bbackupd()
// Check that we DO get errors on compare (cannot do this
// until after we fix the store, which creates a race)
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3b.log "
@@ -2644,6 +3182,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Different);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Different);
+>>>>>>> 0.12
// Test initial state
TEST_THAT(!TestFileExists("testfiles/"
@@ -2668,6 +3209,7 @@ int test_bbackupd()
"notifyran.backup-start.wait-snapshot.1"));
// Should not have backed up, should still get errors
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3b.log "
@@ -2675,6 +3217,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Different);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Different);
+>>>>>>> 0.12
// wait another 10 seconds, bbackup should have run
wait_for_operation(10, "bbackupd to recover");
@@ -2682,6 +3227,7 @@ int test_bbackupd()
"notifyran.backup-start.wait-snapshot.1"));
// Check that it did get uploaded, and we have no more errors
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3b.log "
@@ -2689,6 +3235,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(::unlink("testfiles/notifyscript.tag") == 0);
@@ -2722,6 +3271,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
sync_and_wait();
@@ -2737,6 +3290,7 @@ int test_bbackupd()
// Check that we DO get errors on compare (cannot do this
// until after we fix the store, which creates a race)
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3b.log "
@@ -2744,12 +3298,19 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Different);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Different);
+>>>>>>> 0.12
// Test initial state
TEST_THAT(!TestFileExists("testfiles/"
"notifyran.backup-start.wait-automatic.1"));
+<<<<<<< HEAD
// Set a tag for the notify script to distinguist from
+=======
+ // Set a tag for the notify script to distinguish from
+>>>>>>> 0.12
// previous runs.
{
int fd1 = open("testfiles/notifyscript.tag",
@@ -2768,6 +3329,7 @@ int test_bbackupd()
"notifyran.backup-start.wait-automatic.1"));
// Should not have backed up, should still get errors
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3b.log "
@@ -2775,6 +3337,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Different);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Different);
+>>>>>>> 0.12
// wait another 10 seconds, bbackup should have run
wait_for_operation(10, "bbackupd to recover");
@@ -2782,6 +3347,7 @@ int test_bbackupd()
"notifyran.backup-start.wait-automatic.1"));
// Check that it did get uploaded, and we have no more errors
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3b.log "
@@ -2789,6 +3355,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(::unlink("testfiles/notifyscript.tag") == 0);
@@ -2796,6 +3365,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// Bad case: delete a file/symlink, replace it with a directory
printf("\n==== Replace symlink with directory, "
@@ -2821,6 +3394,7 @@ int test_bbackupd()
#endif
wait_for_backup_operation("bbackupd to sync the changes");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3c.log "
@@ -2828,11 +3402,18 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// And the inverse, replace a directory with a file/symlink
printf("\n==== Replace directory with symlink\n");
@@ -2851,6 +3432,7 @@ int test_bbackupd()
wait_for_backup_operation("bbackupd to sync the changes");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3d.log "
@@ -2858,11 +3440,18 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// And then, put it back to how it was before.
printf("\n==== Replace symlink with directory "
@@ -2884,6 +3473,7 @@ int test_bbackupd()
wait_for_backup_operation("bbackupd to sync the changes");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3e.log "
@@ -2891,11 +3481,18 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// And finally, put it back to how it was before
// it was put back to how it was before
@@ -2917,6 +3514,7 @@ int test_bbackupd()
wait_for_backup_operation("bbackupd to sync the changes");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3f.log "
@@ -2924,11 +3522,18 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// rename an untracked file over an
// existing untracked file
@@ -2952,6 +3557,7 @@ int test_bbackupd()
wait_for_backup_operation("bbackupd to sync the "
"untracked files");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3g.log "
@@ -2959,6 +3565,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
#ifdef WIN32
TEST_THAT(::unlink("testfiles/TestDir1/untracked-2")
@@ -2973,6 +3582,7 @@ int test_bbackupd()
wait_for_backup_operation("bbackupd to sync the untracked "
"files again");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3g.log "
@@ -2980,11 +3590,18 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// case which went wrong: rename a tracked file over an
// existing tracked file
@@ -3013,6 +3630,7 @@ int test_bbackupd()
sync_and_wait();
// compare to make sure that it worked
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3h.log "
@@ -3020,6 +3638,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
#ifdef WIN32
TEST_THAT(::unlink("testfiles/TestDir1/tracked-2")
@@ -3034,6 +3655,7 @@ int test_bbackupd()
wait_for_backup_operation("bbackupd to sync the tracked "
"files again");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3i.log "
@@ -3041,11 +3663,18 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// case which went wrong: rename a tracked file
// over a deleted file
@@ -3056,6 +3685,7 @@ int test_bbackupd()
wait_for_backup_operation("bbackupd to sync");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3j.log "
@@ -3063,6 +3693,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
// Check that no read error has been reported yet
TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));
@@ -3071,6 +3704,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
printf("\n==== Add files with old times, update "
"attributes of one to latest time\n");
@@ -3095,6 +3732,7 @@ int test_bbackupd()
// Wait and test
wait_for_backup_operation("bbackupd to sync old files");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3k.log "
@@ -3102,11 +3740,18 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// Check that no read error has been reported yet
TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));
@@ -3157,6 +3802,7 @@ int test_bbackupd()
wait_for_sync_end(); // files too new
wait_for_sync_end(); // should (not) be backed up this time
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3l.log "
@@ -3164,11 +3810,18 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// Check that no read error has been reported yet
TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1"));
@@ -3189,6 +3842,7 @@ int test_bbackupd()
wait_for_sync_end();
// compare with exclusions, should not find differences
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3m.log "
@@ -3196,6 +3850,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
// compare without exclusions, should find differences
compareReturnValue = ::system(BBACKUPQUERY " "
@@ -3210,6 +3867,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// check that the excluded files did not make it
// into the store, and the included files did
@@ -3219,11 +3880,18 @@ int test_bbackupd()
{
std::auto_ptr<BackupProtocolClient> client =
ConnectAndLogin(context,
+<<<<<<< HEAD
BackupProtocolClientLogin::Flags_ReadOnly);
std::auto_ptr<BackupStoreDirectory> dir = ReadDirectory(
*client,
BackupProtocolClientListDirectory::RootDirectory);
+=======
+ BackupProtocolLogin::Flags_ReadOnly);
+
+ std::auto_ptr<BackupStoreDirectory> dir =
+ ReadDirectory(*client);
+>>>>>>> 0.12
int64_t testDirId = SearchDir(*dir, "Test1");
TEST_THAT(testDirId != 0);
@@ -3254,6 +3922,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
#ifndef WIN32
// These tests only work as non-root users.
@@ -3279,6 +3951,7 @@ int test_bbackupd()
// Wait and test...
wait_for_backup_operation("bbackupd to try to sync "
"unreadable file");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3o.log "
@@ -3288,6 +3961,11 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Error);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+
+ // should fail with an error due to unreadable file
+ TEST_COMPARE(Compare_Error);
+>>>>>>> 0.12
// Check that it was reported correctly
TEST_THAT(TestFileExists("testfiles/notifyran.read-error.1"));
@@ -3308,6 +3986,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
printf("\n==== Continuously update file, "
"check isn't uploaded\n");
@@ -3373,6 +4055,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
printf("\n==== Delete directory, change attributes\n");
@@ -3385,6 +4071,7 @@ int test_bbackupd()
wait_for_backup_operation("bbackupd to sync deletion "
"of directory");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query4.log "
@@ -3392,6 +4079,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
printf("\n==== Restore files and directories\n");
int64_t deldirid = 0;
@@ -3400,17 +4090,35 @@ int test_bbackupd()
// connect and log in
std::auto_ptr<BackupProtocolClient> client =
ConnectAndLogin(context,
+<<<<<<< HEAD
BackupProtocolClientLogin::Flags_ReadOnly);
// Find the ID of the Test1 directory
restoredirid = GetDirID(*client, "Test1",
BackupProtocolClientListDirectory::RootDirectory);
+=======
+ BackupProtocolLogin::Flags_ReadOnly);
+
+ // Find the ID of the Test1 directory
+ restoredirid = GetDirID(*client, "Test1",
+ BackupProtocolListDirectory::RootDirectory);
+>>>>>>> 0.12
TEST_THAT(restoredirid != 0);
// Test the restoration
TEST_THAT(BackupClientRestore(*client, restoredirid,
+<<<<<<< HEAD
"Test1", "testfiles/restore-Test1",
true /* print progress dots */)
+=======
+ "Test1" /* remote */,
+ "testfiles/restore-Test1" /* local */,
+ true /* print progress dots */,
+ false /* restore deleted */,
+ false /* undelete after */,
+ false /* resume */,
+ false /* keep going */)
+>>>>>>> 0.12
== Restore_Complete);
// On Win32 we can't open another connection
@@ -3419,7 +4127,15 @@ int test_bbackupd()
// Make sure you can't restore a restored directory
TEST_THAT(BackupClientRestore(*client, restoredirid,
"Test1", "testfiles/restore-Test1",
+<<<<<<< HEAD
true /* print progress dots */)
+=======
+ true /* print progress dots */,
+ false /* restore deleted */,
+ false /* undelete after */,
+ false /* resume */,
+ false /* keep going */)
+>>>>>>> 0.12
== Restore_TargetExists);
// Find ID of the deleted directory
@@ -3429,9 +4145,18 @@ int test_bbackupd()
// Just check it doesn't bomb out -- will check this
// properly later (when bbackupd is stopped)
TEST_THAT(BackupClientRestore(*client, deldirid,
+<<<<<<< HEAD
"Test1", "testfiles/restore-Test1-x1",
true /* print progress dots */,
true /* deleted files */)
+=======
+ "Test1", "testfiles/restore-Test1-x1",
+ true /* print progress dots */,
+ true /* restore deleted */,
+ false /* undelete after */,
+ false /* resume */,
+ false /* keep going */)
+>>>>>>> 0.12
== Restore_Complete);
// Make sure you can't restore to a nonexistant path
@@ -3444,7 +4169,15 @@ int test_bbackupd()
TEST_THAT(BackupClientRestore(*client,
restoredirid, "Test1",
"testfiles/no-such-path/subdir",
+<<<<<<< HEAD
true /* print progress dots */)
+=======
+ true /* print progress dots */,
+ true /* restore deleted */,
+ false /* undelete after */,
+ false /* resume */,
+ false /* keep going */)
+>>>>>>> 0.12
== Restore_TargetPathNotFound);
}
@@ -3454,6 +4187,7 @@ int test_bbackupd()
}
// Compare the restored files
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query10.log "
@@ -3463,11 +4197,18 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
#ifdef WIN32
// make one of the files read-only, expect a compare failure
@@ -3475,6 +4216,7 @@ int test_bbackupd()
"testfiles\\restore-Test1\\f1.dat");
TEST_RETURN(compareReturnValue, 0);
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query10a.log "
@@ -3484,12 +4226,16 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Different);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Different);
+>>>>>>> 0.12
// set it back, expect no failures
compareReturnValue = ::system("attrib -r "
"testfiles\\restore-Test1\\f1.dat");
TEST_RETURN(compareReturnValue, 0);
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf -l testfiles/query10a.log "
"-Wwarning "
@@ -3498,6 +4244,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
// change the timestamp on a file, expect a compare failure
char* testfile = "testfiles\\restore-Test1\\f1.dat";
@@ -3516,6 +4265,7 @@ int test_bbackupd()
// a compare failure
TEST_THAT(set_file_time(testfile, dummyTime, lastModTime,
lastAccessTime));
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query10a.log "
@@ -3525,10 +4275,15 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Different);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+
+ TEST_COMPARE(Compare_Different);
+>>>>>>> 0.12
// last access time is not backed up, so it cannot be compared
TEST_THAT(set_file_time(testfile, creationTime, lastModTime,
dummyTime));
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query10a.log "
@@ -3538,11 +4293,15 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
// last write time is backed up, so changing it should cause
// a compare failure
TEST_THAT(set_file_time(testfile, creationTime, dummyTime,
lastAccessTime));
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query10a.log "
@@ -3552,10 +4311,14 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Different);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Different);
+>>>>>>> 0.12
// set back to original values, check that compare succeeds
TEST_THAT(set_file_time(testfile, creationTime, lastModTime,
lastAccessTime));
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query10a.log "
@@ -3565,12 +4328,19 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
#endif // WIN32
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
printf("\n==== Add files with current time\n");
@@ -3587,6 +4357,7 @@ int test_bbackupd()
// Wait and test
wait_for_backup_operation("bbackupd to sync new files");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query5.log "
@@ -3594,11 +4365,18 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// Rename directory
printf("\n==== Rename directory\n");
@@ -3607,6 +4385,7 @@ int test_bbackupd()
wait_for_backup_operation("bbackupd to sync renamed directory");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query6.log "
@@ -3614,6 +4393,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
// and again, but with quick flag
compareReturnValue = ::system(BBACKUPQUERY " "
@@ -3635,6 +4417,7 @@ int test_bbackupd()
wait_for_backup_operation("bbackupd to sync renamed files");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query6.log "
@@ -3642,11 +4425,18 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
// Check that modifying files with madly in the future
// timestamps still get added
@@ -3678,6 +4468,7 @@ int test_bbackupd()
// Wait and test
wait_for_backup_operation("bbackup to sync future file");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query3e.log "
@@ -3685,11 +4476,18 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
printf("\n==== Change client store marker\n");
@@ -3707,7 +4505,11 @@ int test_bbackupd()
// Make sure the marker isn't zero,
// because that's the default, and
// it should have changed
+<<<<<<< HEAD
std::auto_ptr<BackupProtocolClientLoginConfirmed> loginConf(protocol->QueryLogin(0x01234567, 0));
+=======
+ std::auto_ptr<BackupProtocolLoginConfirmed> loginConf(protocol->QueryLogin(0x01234567, 0));
+>>>>>>> 0.12
TEST_THAT(loginConf->GetClientStoreMarker() != 0);
// Change it to something else
@@ -3732,6 +4534,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
printf("\n==== Check change of store marker pauses daemon\n");
@@ -3749,6 +4555,7 @@ int test_bbackupd()
3) / 2, "bbackupd to detect changed store marker");
// Test that there *are* differences
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query6.log "
@@ -3756,19 +4563,35 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Different);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Different);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
// 100 seconds - (12*3/2)
wait_for_operation(82, "bbackupd to recover");
+=======
+ if (failures) return 1;
+
+ wait_for_operation(100, "bbackupd to recover");
+
+ // Then check it has backed up successfully.
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
#ifndef WIN32
printf("\n==== Interrupted restore\n");
@@ -3785,28 +4608,48 @@ int test_bbackupd()
std::auto_ptr<BackupProtocolClient> client =
ConnectAndLogin(context,
+<<<<<<< HEAD
BackupProtocolClientLogin::Flags_ReadOnly);
+=======
+ BackupProtocolLogin::Flags_ReadOnly);
+>>>>>>> 0.12
// Check that the restore fn returns resume possible,
// rather than doing anything
TEST_THAT(BackupClientRestore(*client, restoredirid,
"Test1", "testfiles/restore-interrupt",
+<<<<<<< HEAD
true /* print progress dots */)
+=======
+ true /* print progress dots */,
+ false /* restore deleted */,
+ false /* undelete after */,
+ false /* resume */,
+ false /* keep going */)
+>>>>>>> 0.12
== Restore_ResumePossible);
// Then resume it
TEST_THAT(BackupClientRestore(*client, restoredirid,
"Test1", "testfiles/restore-interrupt",
true /* print progress dots */,
+<<<<<<< HEAD
false /* deleted files */,
false /* undelete server */,
true /* resume */)
+=======
+ false /* restore deleted */,
+ false /* undelete after */,
+ true /* resume */,
+ false /* keep going */)
+>>>>>>> 0.12
== Restore_Complete);
client->QueryFinished();
sSocket.Close();
// Then check it has restored the correct stuff
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query14.log "
@@ -3815,6 +4658,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
}
#endif // !WIN32
@@ -3822,6 +4668,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
printf("\n==== Check restore deleted files\n");
@@ -3834,7 +4684,13 @@ int test_bbackupd()
"Test1", "testfiles/restore-Test1-x1-2",
true /* print progress dots */,
true /* deleted files */,
+<<<<<<< HEAD
true /* undelete on server */)
+=======
+ true /* undelete after */,
+ false /* resume */,
+ false /* keep going */)
+>>>>>>> 0.12
== Restore_Complete);
client->QueryFinished();
@@ -3860,6 +4716,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
#ifdef WIN32
printf("\n==== Testing locked file behaviour:\n");
@@ -3886,6 +4746,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
if (handle != 0)
{
@@ -3902,6 +4766,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
if (handle != 0)
{
@@ -3919,11 +4787,16 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
if (handle != 0)
{
// compare, and check that it works
// reports the correct error message (and finishes)
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query15a.log "
@@ -3931,12 +4804,19 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_THAT(compare_all(false));
+>>>>>>> 0.12
}
TEST_THAT(ServerIsAlive(bbackupd_pid));
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
if (handle != 0)
{
@@ -3946,6 +4826,7 @@ int test_bbackupd()
O_LOCK, 0);
TEST_THAT(handle != INVALID_HANDLE_VALUE);
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query15.log "
@@ -3953,6 +4834,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Error);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Error);
+>>>>>>> 0.12
// close the file again, check that compare
// works again
@@ -3963,6 +4847,7 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
if (handle != 0)
{
@@ -3973,6 +4858,13 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ if (failures) return 1;
+
+ if (handle != 0)
+ {
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
}
#endif
@@ -3990,6 +4882,10 @@ int test_bbackupd()
TEST_THAT(ServerIsAlive(bbstored_pid));
if (!ServerIsAlive(bbackupd_pid)) return 1;
if (!ServerIsAlive(bbstored_pid)) return 1;
+<<<<<<< HEAD
+=======
+ if (failures) return 1;
+>>>>>>> 0.12
if(bbackupd_pid != -1 && bbackupd_pid != 0)
{
@@ -3997,6 +4893,7 @@ int test_bbackupd()
wait_for_operation(
(TIME_TO_WAIT_FOR_BACKUP_OPERATION*3) / 2,
"bbackupd to sync everything");
+<<<<<<< HEAD
compareReturnValue = ::system(BBACKUPQUERY " "
"-c testfiles/bbackupd.conf "
"-l testfiles/query4a.log "
@@ -4004,6 +4901,9 @@ int test_bbackupd()
TEST_RETURN(compareReturnValue,
BackupQueries::ReturnCode::Compare_Same);
TestRemoteProcessMemLeaks("bbackupquery.memleaks");
+=======
+ TEST_COMPARE(Compare_Same);
+>>>>>>> 0.12
// Kill it again
terminate_bbackupd(bbackupd_pid);
@@ -4030,6 +4930,22 @@ int test_bbackupd()
int test(int argc, const char *argv[])
{
+<<<<<<< HEAD
+=======
+ {
+ BackupDaemon daemon;
+
+ TEST_EQUAL(1234, daemon.ParseSyncAllowScriptOutput("test", "1234"));
+ TEST_EQUAL(0, daemon.GetMaxBandwidthFromSyncAllowScript());
+
+ TEST_EQUAL(1234, daemon.ParseSyncAllowScriptOutput("test", "1234 5"));
+ TEST_EQUAL(5, daemon.GetMaxBandwidthFromSyncAllowScript());
+
+ TEST_EQUAL(-1, daemon.ParseSyncAllowScriptOutput("test", "now"));
+ TEST_EQUAL(0, daemon.GetMaxBandwidthFromSyncAllowScript());
+ }
+
+>>>>>>> 0.12
// SSL library
SSLLib::Initialise();
diff --git a/test/bbackupd/testfiles/bbackupd-temploc.conf.in b/test/bbackupd/testfiles/bbackupd-temploc.conf.in
new file mode 100644
index 00000000..d4e0d1b3
--- /dev/null
+++ b/test/bbackupd/testfiles/bbackupd-temploc.conf.in
@@ -0,0 +1,58 @@
+
+CertificateFile = testfiles/clientCerts.pem
+PrivateKeyFile = testfiles/clientPrivKey.pem
+TrustedCAsFile = testfiles/clientTrustedCAs.pem
+
+KeysFile = testfiles/bbackupd.keys
+
+DataDirectory = testfiles/bbackupd-data
+
+StoreHostname = localhost
+StorePort = 22011
+AccountNumber = 0x01234567
+
+AutomaticBackup = no
+UpdateStoreInterval = 3
+MinimumFileAge = 4
+MaxUploadWait = 24
+
+FileTrackingSizeThreshold = 1024
+DiffingUploadSizeThreshold = 1024
+
+MaximumDiffingTime = 3
+KeepAliveTime = 1
+
+ExtendedLogging = no
+ExtendedLogFile = testfiles/bbackupd.log
+
+CommandSocket = testfiles/bbackupd.sock
+
+NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl
+
+Server
+{
+ PidFile = testfiles/bbackupd.pid
+}
+
+BackupLocations
+{
+ Test1
+ {
+ Path = testfiles/TestDir1
+
+ ExcludeFile = testfiles/TestDir1/excluded_1
+ ExcludeFile = testfiles/TestDir1/excluded_2
+ ExcludeFilesRegex = \.excludethis$
+ ExcludeFilesRegex = EXCLUDE
+ AlwaysIncludeFile = testfiles/TestDir1/dont.excludethis
+ ExcludeDir = testfiles/TestDir1/exclude_dir
+ ExcludeDir = testfiles/TestDir1/exclude_dir_2
+ ExcludeDirsRegex = not_this_dir
+ AlwaysIncludeDirsRegex = ALWAYSINCLUDE
+ }
+ Test2
+ {
+ Path = testfiles/TestDir2
+ }
+}
+
diff --git a/test/common/testcommon.cpp b/test/common/testcommon.cpp
index e633969b..2d4b7431 100644
--- a/test/common/testcommon.cpp
+++ b/test/common/testcommon.cpp
@@ -332,9 +332,15 @@ int test(int argc, const char *argv[])
Timers::Init();
Timer t0(0, "t0"); // should never expire
+<<<<<<< HEAD
Timer t1(1, "t1");
Timer t2(2, "t2");
Timer t3(3, "t3");
+=======
+ Timer t1(1000, "t1");
+ Timer t2(2000, "t2");
+ Timer t3(3000, "t3");
+>>>>>>> 0.12
TEST_THAT(!t0.HasExpired());
TEST_THAT(!t1.HasExpired());
@@ -352,12 +358,23 @@ int test(int argc, const char *argv[])
TEST_THAT(t1.HasExpired());
TEST_THAT(t2.HasExpired());
TEST_THAT(!t3.HasExpired());
+<<<<<<< HEAD
t1 = Timer(1, "t1a");
t2 = Timer(2, "t2a");
TEST_THAT(!t0.HasExpired());
TEST_THAT(!t1.HasExpired());
TEST_THAT(!t2.HasExpired());
+=======
+
+ // Try both ways of resetting an existing timer.
+ t1 = Timer(1000, "t1a");
+ t2.Reset(2000);
+ TEST_THAT(!t0.HasExpired());
+ TEST_THAT(!t1.HasExpired());
+ TEST_THAT(!t2.HasExpired());
+ TEST_THAT(!t3.HasExpired());
+>>>>>>> 0.12
safe_sleep(1);
TEST_THAT(!t0.HasExpired());
@@ -365,6 +382,15 @@ int test(int argc, const char *argv[])
TEST_THAT(!t2.HasExpired());
TEST_THAT(t3.HasExpired());
+<<<<<<< HEAD
+=======
+ safe_sleep(1);
+ TEST_THAT(!t0.HasExpired());
+ TEST_THAT(t1.HasExpired());
+ TEST_THAT(t2.HasExpired());
+ TEST_THAT(t3.HasExpired());
+
+>>>>>>> 0.12
// Leave timers initialised for rest of test.
// Test main() will cleanup after test finishes.
@@ -404,8 +430,15 @@ int test(int argc, const char *argv[])
{
TEST_THAT(!getline.IsEOF());
std::string line = getline.GetLine(true);
+<<<<<<< HEAD
//printf("expected |%s| got |%s|\n", lines[l], line.c_str());
TEST_THAT(strcmp(testfilelines[l], line.c_str()) == 0);
+=======
+ printf("expected |%s| got |%s|\n", testfilelines[l],
+ line.c_str());
+ TEST_LINE(strcmp(testfilelines[l], line.c_str()) == 0,
+ line);
+>>>>>>> 0.12
l++;
}
TEST_THAT(getline.IsEOF());
@@ -455,8 +488,15 @@ int test(int argc, const char *argv[])
std::string line;
while(!getline.GetLine(line, true))
;
+<<<<<<< HEAD
//printf("expected |%s| got |%s|\n", lines[l], line.c_str());
TEST_THAT(strcmp(testfilelines[l], line.c_str()) == 0);
+=======
+ printf("expected |%s| got |%s|\n", testfilelines[l],
+ line.c_str());
+ TEST_LINE(strcmp(testfilelines[l], line.c_str()) == 0,
+ line);
+>>>>>>> 0.12
l++;
}
TEST_THAT(getline.IsEOF());
diff --git a/win32.bat b/win32.bat
index 744c31b3..f0cd6405 100644
--- a/win32.bat
+++ b/win32.bat
@@ -4,22 +4,40 @@ echo quick and dirty to get up and running by generating the required files
echo using Cygwin and Perl
copy .\infrastructure\BoxPlatform.pm.in .\infrastructure\BoxPlatform.pm
+<<<<<<< HEAD
+=======
+copy .\lib\common\BoxPortsAndFiles.h.in .\lib\common\BoxPortsAndFiles.h
+copy .\lib\common\BoxConfig-MSVC.h .\lib\common\BoxConfig.h
+>>>>>>> 0.12
cd .\bin\bbackupquery\ & perl ./../../bin/bbackupquery/makedocumentation.pl.in
cd ..\..\
+<<<<<<< HEAD
cd .\lib\backupclient & perl ./../../lib/common/makeexception.pl.in BackupStoreException.txt & perl ./../../lib/server/makeprotocol.pl.in Client ./../../bin/bbstored/backupprotocol.txt
+=======
+cd .\lib\backupstore & perl ./../../lib/common/makeexception.pl.in BackupStoreException.txt & perl ./../../lib/server/makeprotocol.pl.in backupprotocol.txt
+>>>>>>> 0.12
cd ..\..\
cd .\lib\compress & perl ./../../lib/common/makeexception.pl.in CompressException.txt
cd ..\..\
cd .\lib\common & perl ./../../lib/common/makeexception.pl.in CommonException.txt & perl ./../../lib/common/makeexception.pl.in ConversionException.txt
+<<<<<<< HEAD
cd ..\..\
cd .\bin\bbackupd & perl ./../../lib/common/makeexception.pl.in ClientException.txt
+=======
+cd ..\..\
+
+cd .\lib\raidfile & perl ./../../lib/common/makeexception.pl.in RaidFileException.txt
+cd ..\..\
+
+cd .\bin\bbackupd & perl ./../../lib/common/makeexception.pl.in ClientException.txt
+>>>>>>> 0.12
cd ..\..\
cd .\lib\crypto & perl ./../../lib/common/makeexception.pl.in CipherException.txt
@@ -31,3 +49,9 @@ cd .\lib\server & perl ./../../lib/common/makeexception.pl.in ServerException.tx
cd ..\..\
perl -pe 's/@PERL@/perl/' ./test/bbackupd/testfiles/bbackupd.conf.in > .\test\bbackupd\testfiles\bbackupd.conf
+<<<<<<< HEAD
+=======
+
+echo Generating InstallJammer configuration file
+perl infrastructure/msvc/fake-config.sub.pl ./contrib/windows/installer/boxbackup.mpi.in > ./contrib/windows/installer/boxbackup.mpi
+>>>>>>> 0.12